Mutex guards in the Linux kernel

Mutex guards in the Linux kernel

I found an interresting thread [1] while searching my inbox for something completely unrelated.

Peter Zijistra has written a few cleanup functions that where introduced in v6.4 with this commit:

commit 54da6a0924311c7cf5015533991e44fb8eb12773
Author: Peter Zijlstra <peterz@infradead.org>
Date:   Fri May 26 12:23:48 2023 +0200

    locking: Introduce __cleanup() based infrastructure

    Use __attribute__((__cleanup__(func))) to build:

     - simple auto-release pointers using __free()

     - 'classes' with constructor and destructor semantics for
       scope-based resource management.

     - lock guards based on the above classes.

    Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>

It adds functionality to "guard" locks. The guard wraps the lock, takes ownership of the given mutex and release it as soon the guard leaves the scope. In other words - no more forgotten locks due to early exits.

Compare this to the std::lock_guard class we have in C++.

Although this adds valuable functionality to the core, it's currently not widely used. In fact, it only has two users in the latest (v.6.6) kernel:

	$ git grep -l "guard(mutex)" 
	drivers/gpio/gpio-sim.c
	kernel/sched/core.c

Hands on

I have adapted ( [2], [3]) two of my drivers to make use of the guard locks. The adaptation is quickly made.

The features is located in linux/cleanup.h:

+#include <linux/cleanup.h>

Then we can start to make use of the guards. What I like is that the code will be simplier in two ways:

  • All the mutex_lock-pairs in the same scope could be replaced with guard(mutex)(&your->mutex).
  • The code can now return without taking any taken locks into account.

Together with device managed ( devm ) resources, you will end up with a code that clean up itself pretty good.

A typical adaption to guarded mutexes could look likt this:

	@@ -83,31 +85,26 @@ static int pxrc_open(struct input_dev *input)
		struct pxrc *pxrc = input_get_drvdata(input);
		int retval;

	-       mutex_lock(&pxrc->pm_mutex);
	+       guard(mutex)(&pxrc->pm_mutex);
		retval = usb_submit_urb(pxrc->urb, GFP_KERNEL);
		if (retval) {
			dev_err(&pxrc->intf->dev,
				"%s - usb_submit_urb failed, error: %d\n",
				__func__, retval);
	-               retval = -EIO;
	-               goto out;
	+               return -EIO;
		}

		pxrc->is_open = true;
	-
	-out:
	-       mutex_unlock(&pxrc->pm_mutex);
	-       return retval;
	+       return 0;
	 }

What it does is:

  • Removes the mutex_lock/mutex_unlock pair
  • Simplifies the error handling to just return in case of error
  • No need for the out: label anymore so remove it

Under the hood

The implementation makes use of the __attribute__((cleanup())) attribute that is available for both LLVM [4] and GCC [5].

Here is what the GCC documentation [5] says about the cleanup_function:

cleanup (cleanup_function)
The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration.
The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored.

If -fexceptions is enabled, then cleanup_function is run during the stack unwinding that happens during the processing of the exception.
Note that the cleanup attribute does not allow the exception to be caught, only to perform an action. It's undefined what happens if cleanup_function does not return normally.

To illustrate this, consider the following example:

#include <stdio.h>

void cleanup_func (int *x)
{
	printf("Tidy up for x as it's leaving its scope\n");
}

int main(int argc, char **argv)
{
	printf("Start\n");
	{
		int x __attribute__((cleanup(cleanup_func)));
		/* Do stuff */
	}
	printf("Exit\n");
}

We create a variable, x, declared with the cleanup attribute inside of its own scope. This makes that the cleanup_func() will be called as soon x goes out of scope.

Here is the output of the example above:

$ gcc main.c -o main && ./main
Start
Tidy up for x as it's leaving its scope
Exit

As you can see, the cleanup_func() is called in between of Start and Exit - as expected.

Use b4 for kernel contributions

Use b4 for kernel contributions

There is a little tool called b4 [1] that has been part of my workflow with the Linux kernel for a while. It's developed to be a tool used to simplify the work of the maintainers, but my main use of the tool has been to fetch patch series from the mailing list and apply them to my local git repository during reviews. I recently noticed that it got a lot of handy features (experimental though) for the contributors as well, which I now want to test!

The reason I started digging into my toolbox started with a discussion I had with a friend about how I could possibly prefer an email-based workflow when working with FOSS versus new and fresh web-based tools like Github and Gitlab. In web-based tools, all you have to do is push the right button, and email is so old-fashioned, isn't it?

First of all, I never hit the right button. Never. Secondly, it disturbs my workflow.

That is also my biggest reason; these "old school" tools fits my workflow perfectly:

  • I use mutt [4] to read and send my emails.
  • I use vim [5] for all text manipulation.
  • I use git [6] to manage my code.
  • I use b4 [1] in the (kernel) review process (and in contributions from now on)

These are the tools that I use for almost every email based FOSS project that I'm involved with. The first three is rather common, but what about b4? It's not too much information out there so I think a little introduction might be good.

/media/b4.png

So, what is b4?

The project started as a tool named get-lore-mbox [2] , which later became b4. Fun fact is that the name, b4, was chosen for ease of typing and because B-4 was the precursor to Lore and Data in the Star Trek universe :-)

B4 is a tool to simplify the development workflow with distributed patches, especially mailing lists. It works with public inbox archives and aims to be a useful tool for both maintainers and developers.

Example on what b4 will do for you:

  • Retrieve patch series from a public mailing list (e.g. lore.kernel.org)
  • Compare patch series
  • Apply patch series to your git repository
  • Prepare and send in your work
  • Retrieve code-review trailers

It's a pretty competent tool.

Install b4

First we have to install b4 on our system.

B4 is propbably already available in your distribution:

Archlinux:

$ pacman -Sy b4

Ubuntu:

$ apt-get install b4

Fedora:

$ dnf install b4

Or whatever package manager you use.

It's also possible to install it with pip:

$ python3 -m pip install --user b4

And of course, run it directly from the git repository [3] :

$ git clone https://git.kernel.org/pub/scm/utils/b4/b4.git
$ cd b4
$ git submodule update --init
$ pip install --user -r requirements.txt

Review patches workflow

I use b4 shazam to fetch the latest version of a patch series and apply them to my tree. All you need to provide is the Message-ID of the thread that you find in the email header of the patch. For instance:

$ b4 shazam 20230820102610.755188-6-marcus.folkesson@gmail.com
Grabbing thread from lore.kernel.org/all/20230820102610.755188-6-marcus.folkesson@gmail.com/t.mbox.gz
Checking for newer revisions
Grabbing search results from lore.kernel.org
  Added from v8: 7 patches
Analyzing 17 messages in the thread
Will use the latest revision: v8
You can pick other revisions using the -vN flag
Checking attestation on all messages, may take a moment...
---
  [PATCH v8 1/6] dt-bindings: iio: adc: mcp3911: add support for the whole MCP39xx family
  [PATCH v8 2/6] iio: adc: mcp3911: make use of dev_err_probe()
  [PATCH v8 3/6] iio: adc: mcp3911: simplify usage of spi->dev
  [PATCH v8 4/6] iio: adc: mcp3911: fix indentation
  [PATCH v8 5/6] iio: adc: mcp3911: avoid ambiguity parameters in macros
  [PATCH v8 6/6] iio: adc: mcp3911: add support for the whole MCP39xx family
  ---
  NOTE: install dkimpy for DKIM signature verification
---
Total patches: 6
---
 Base: using specified base-commit b320441c04c9bea76cbee1196ae55c20288fd7a6
Applying: dt-bindings: iio: adc: mcp3911: add support for the whole MCP39xx family
Applying: iio: adc: mcp3911: make use of dev_err_probe()
Applying: iio: adc: mcp3911: simplify usage of spi->dev
Applying: iio: adc: mcp3911: fix indentation
Applying: iio: adc: mcp3911: avoid ambiguity parameters in macros
Applying: iio: adc: mcp3911: add support for the whole MCP39xx family

Or even b4 prep to create a branch for it. This will not fetch the latest version though.

$ b4 prep -n review -F  20230820102610.755188-6-marcus.folkesson@gmail.com
Checking attestation on all messages, may take a moment...
---
  [PATCH v7 1/6] dt-bindings: iio: adc: mcp3911: add support for the whole MCP39xx family
  [PATCH v7 2/6] iio: adc: mcp3911: make use of dev_err_probe()
  [PATCH v7 3/6] iio: adc: mcp3911: simplify usage of spi->dev
  [PATCH v7 4/6] iio: adc: mcp3911: fix indentation
  [PATCH v7 5/6] iio: adc: mcp3911: avoid ambiguity parameters in macros
  [PATCH v7 6/6] iio: adc: mcp3911: add support for the whole MCP39xx family
  ---
  NOTE: install dkimpy for DKIM signature verification
---
Created new branch b4/review
Applying 6 patches
---
Applying: dt-bindings: iio: adc: mcp3911: add support for the whole MCP39xx family
Applying: iio: adc: mcp3911: make use of dev_err_probe()
Applying: iio: adc: mcp3911: simplify usage of spi->dev
Applying: iio: adc: mcp3911: fix indentation
Applying: iio: adc: mcp3911: avoid ambiguity parameters in macros
Applying: iio: adc: mcp3911: add support for the whole MCP39xx family

Once you have the patches applied to your local repository it's easier to perform a review as it gives a better context when you can jump around in the codebase. Also, it allows you to run any scripts for sanity checks and such.

That is pretty much how I use b4 in the review process. b4 has a lot of more neat features such as fetching pull-requests or generate thank-emails when something gets merged/applied, but that is nothing I use for the moment.

Contributor's workflow

As I said, I've been unaware of that b4 can assist you even in the workflow as a contributor. I'm so excited!

The workflow

These steps is more or less copied directly from the documentation [1].

  1. Prepare your patch series by using b4 prep and queueing your commits. Use git rebase -i to arrange the commits in the right order and to write good commit messages.
  2. Prepare your cover letter using b4 prep --edit-cover. You should provide a good overview of what your series does and why you think it will improve the current code.
  3. When you are almost ready to send, use b4 prep --auto-to-cc to collect the relevant addresses from your commits. If your project uses a MAINTAINERS file, this will also perform the required query to figure out who should be included on your patch series submission.
  4. Review the list of addresses that were added to the cover letter and, if you know what you're doing, remove any that you think are unnecessary.
  5. Send your series using b4 send. This will automatically reroll your series to the next version and add changelog entries to the cover letter.
  6. Await code review and feedback from maintainers.
  7. Apply any received code-review trailers using b4 trailers -u.
  8. Use git rebase -i to make any changes to the code based on the feedback you receive. Remember to record these changes in the cover letter's changelog.
  9. Unless series is accepted upstream, GOTO 3.
  10. Clean up obsolete prep-managed branches using b4 prep --cleanup

Example of usage

A Lego loving friend of mine pointed out that a reference in kernel documentation [6] I wrote no longer points to the site it was supposed to. That is something we are going to fix!

Just follow the steps listed above.

Start by prepare the tree with b4 prep -n pxrc -f v6.3:

$ b4 prep -n pxrc -f v6.3
Created new branch b4/pxrc
Created the default cover letter, you can edit with --edit-cover.

Now we have a branch for our work. We base our work on the v6.3 tag.

Next step is to edit the coverletter by b4 prep --edit-cover.

Here is what the first patch (coverletter) looks like after editing:

$ git show HEAD
commit b97650b087d88d113b11cd1bc367c67c314d77a1 (HEAD -> b4/pxrc)
Author: Marcus Folkesson <marcus.folkesson@gmail.com>
Date:   Fri Aug 25 11:43:52 2023 +0200

    Remove reference to site that is no longer related
    
    Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>
    
    --- b4-submit-tracking ---
    # This section is used internally by b4 prep for tracking purposes.
    {
      "series": {
        "revision": 1,
        "change-id": "20230825-pxrc-8518b297cd21",
        "prefixes": []
      }
    }

Notice the meta information about this series (revision, change-id). The change-id will follow us through all the versions of the patch.

Next is to commit the changes as usual with git add and git commit:

$ git add Documentation/input/devices/pxrc.rst
$ git commit --signoff
$ git show
commit 00cf9f943529c06b36e89407aecc18d46f1b028e (HEAD -> b4/pxrc)
Author: Marcus Folkesson <marcus.folkesson@gmail.com>
Date:   Thu Aug 24 15:47:24 2023 +0200

    input: docs: pxrc: remove reference to phoenix-sim
    
    The reference undeniably points to something unrelated nowadays.
    Remove it.
    
    Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>

diff --git a/Documentation/input/devices/pxrc.rst b/Documentation/input/devices/pxrc.rst
index ca11f646bae8..5a86df4ad079 100644
--- a/Documentation/input/devices/pxrc.rst
+++ b/Documentation/input/devices/pxrc.rst
@@ -5,7 +5,7 @@ pxrc - PhoenixRC Flight Controller Adapter
 :Author: Marcus Folkesson <marcus.folkesson@gmail.com>
 
 This driver let you use your own RC controller plugged into the
-adapter that comes with PhoenixRC [1]_ or other compatible adapters.
+adapter that comes with PhoenixRC or other compatible adapters.
 
 The adapter supports 7 analog channels and 1 digital input switch.
 
@@ -41,7 +41,7 @@ Manual Testing
 ==============
 
 To test this driver's functionality you may use `input-event` which is part of
-the `input layer utilities` suite [2]_.
+the `input layer utilities` suite [1]_.
 
 For example::
 
@@ -53,5 +53,4 @@ To print all input events from input `devnr`.
 References
 ==========
 
-.. [1] http://www.phoenix-sim.com/
-.. [2] https://www.kraxel.org/cgit/input/
+.. [1] https://www.kraxel.org/cgit/input/

Verify that the patch looks good with scripts/checkpatch.pl:

$ ./scripts/checkpatch.pl --git HEAD
total: 0 errors, 0 warnings, 22 lines checked

Commit 00cf9f943529 ("input: docs: pxrc: remove reference to phoenix-sim") has no obvious style problems and is ready for submission.

Ok, the patch is ready to be sent out to the mailing list.

Collect all TO and CC from scripts/get_maintainer.pl with b4 prep --auto-to-cc:

$ b4 prep --auto-to-cc
Will collect To: addresses using get_maintainer.pl
Will collect Cc: addresses using get_maintainer.pl
Collecting To/Cc addresses
    + To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
    + To: Jonathan Corbet <corbet@lwn.net>
    + Cc: linux-input@vger.kernel.org
    + Cc: linux-doc@vger.kernel.org
    + Cc: linux-kernel@vger.kernel.org
---
You can trim/expand this list with: b4 prep --edit-cover
Invoking git-filter-repo to update the cover letter.
New history written in 0.02 seconds...
Completely finished after 0.18 seconds.

Now we are ready to send the patch to mailing list by invoking the b4 send command.

b4 send will automatically use the [sendemail] section of your git config to determine which SMTP server to use.

I've configure git to use gmail as SMTP server, here is the relevant part of my ~/.gitconfig:

[sendemail]
  smtpserver = smtp.gmail.com
  smtpuser = marcus.folkesson@gmail.com
  smtpserverport = 587
  smtpencryption = tls
  smtpssl = true
        chainreplyto = false
        confirm = auto

b4 send will send the patches to the mailing list and prepare for version 2 of the patch series by increase the version number and create a new tag.

Other features

Compare between versions

As I only have one version of the patch (and there will probably not be a version 2), I have no use for the cool compare feature. However, b4 let you compare your versions by simply :

b4 prep --compare-to v1

Trailers

Going through all mail threads and collect all trailer tags could be a painstaking work. B4 will do this for you and put the tag into the right patch magically.

Unfortunately, I forgot to mention my friend in the original patch, so I sent another mail [7] with the Suggested-by: Mark Olsson <mark@markolsson.se> tag.

Fetch the tag with b4 trailers -S -u (-S because the tag was sent by me and not Mark):

$ b4 trailers -S -u
Calculating patch-ids from commits, this may take a moment...
Checking change-id "20230824-pxrc-doc-1addbaa2250f"
Grabbing search results from lore.kernel.org
---
  input: docs: pxrc: remove reference to phoenix-sim
    + Suggested-by: Mark Olsson <mark@markolsson.se>
---
Invoking git-filter-repo to update trailers.
New history written in 0.03 seconds...
Completely finished after 0.17 seconds.
Trailers updated.

As we can see the Suggested-by tag is now applied to the patch in the right place:

commit 650264be66f0e589cf67a49f769ddc7d51e076cb (HEAD -> b4/pxrc)
Author: Marcus Folkesson <marcus.folkesson@gmail.com>
Date:   Thu Aug 24 15:47:24 2023 +0200

    input: docs: pxrc: remove reference to phoenix-sim
    
    The reference undeniably points to something unrelated nowadays.
    Remove it.
    
    Suggested-by: Mark Olsson <mark@markolsson.se>
    Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com>

Magic.

Further reading

The creator of the tool, Konstantin Ryabitsev, has an excellent youtube [8] video where he demonstrate the usage of b4.

Linux wireless regulatory domains

Linux wireless regulatory domains

I had a case where I had an embedded system that should act as a WiFi Access Point on the 5GHz band. The HW was capable and the system managed to act as a client to 5GHz networks, so everything looked good.

However, the system could not create an access point on some frequencies. How is it that? It's all about regulatory domains!

/media/tux-radio.png

Regulatory domains

Radio regulations is something that applies to all devices that make transmissions in the radio spectrum. The Linux kernel makes itself compliant to these regulations by make the regulatory restrictions as a directly part of the cfg80211 configuration API that all (new) wireless device drivers use.

Radio regulatory hasn't always been so tight integrated into the Linux kernel. This is a result to address the vendor concerns [6] about getting products based on Linux certified against all (geographically dependent) radio regulatory authorities out there.

Before that, all wireless drivers used to be a propritary blob that we load into our kernel, nowadays more and more vendors has a more FOSS driven development for such a drivers which is what we strive for.

To build trust with the chip vendors so that they could consider FOSS drivers as an real alternative, we stick to a number of principles.

Principles

There are a few principles [1] that the Linux kernel follows in order to fulfull the requirements for on use of the radio spectrum:

  • It should be reasonably impossible for a user to fail to comply with local regulations either unwittingly or by accident.
  • Default configurations should err in favor of more restrictive behavior in order to protect unwitting users.
  • Configurations that have no known compliant usage should not be part of the 'official' kernel tree.
  • Configurations that are compliant only under special circumstances (e.g. with special licenses) should not be part of the 'official' kernel tree. Any exceptions should have their legal requirements clearly marked and those options should never be configured by default.
  • Configurations that disable regulatory enforcement mechanisms should not be part of the 'official' kernel tree.
  • The kernel should rely on userland components to determine regulatory policy. Consequently, the kernel's regulatory enforcement mechanisms should be flexible enough to cover known or reasonably anticipated regulatory policies.
  • It's the moral duty of responsible distribution vendors, software developers, and community members to make every good faith effort to ensure proper compliance with applicable regulations regarding wireless communications.

The overall approach is "better safe than sorry" with respect to radio regulations. In other words, if no local configuration is setup, the system will fall back to the more restrictive world regulatory domain.

An example on such behaviour could be that the system is not allowed to initiate radio communication on certain radio frequencies that is not globally allowed.

Integration

CRDA

(Used pre Linux v4.15.)

CRDA [3], Central Regulatory Domain Agent, is a userspace agent responsible to read and intepret the regulatory.bin file and update the regulatory domains.

CRDA is intended to be trigged on uevents from the kernel (via udev) upon changes in the regulatory domain and setup the new regulations .

The udev rule to do this my look like this:

KERNEL=="regulatory*", ACTION=="change", SUBSYSTEM=="platform", RUN+="/sbin/crda"

Nowadays, CRDA is no longer needed as of kernel v.4.15 ( commit [2], "cfg80211: support loading regulatory database as firmware file"), the regulatory database is read by the Linux kernel directly as a firmware file during boot.

wireless-regdb

wireless-regdb [4] is the regulatory database used by Linux. The db.txt file in the repository contains regulatory information for each domain.

The output from this project is regulatory.db, which is loaded by the kernel as a firmware. The integrity of regulatory.db is typically ensured by the regulatory daemon by verifying the built-in RSA signature against a list of public keys in a preconfigured directory.

Although it's possible to build regulatory.db without any RSA key signature checking, it is highly recommended not to do so, as if the regulatory database is compromised in some way we could end up with a product that violates product radio compatibility.

wireless-regdb and Yocto

A side note for Yocto users.

The wireless-regdb recipe is part of oe-core [5] and should be included into your image if you intend to use any wireless LAN. wireless-regdb-static should be used with kernel >= v4.15 and wireless-regdb is intended to be used with CRDA.

In other words, add:

IMAGE_INSTALL:append = " wireless-regdb-static "

to your yocto distribution.

Hands on

iw [7] is the nl80211 based tool we use to configure wireless devices in Linux.

Here we will take a look at what the regulations may look like.

World regulatory domain

# iw reg get
global
country 00: DFS-UNSET
        (755 - 928 @ 2), (N/A, 20), (N/A), PASSIVE-SCAN
        (2402 - 2472 @ 40), (N/A, 20), (N/A)
        (2457 - 2482 @ 20), (N/A, 20), (N/A), AUTO-BW, PASSIVE-SCAN
        (2474 - 2494 @ 20), (N/A, 20), (N/A), NO-OFDM, PASSIVE-SCAN
        (5170 - 5250 @ 80), (N/A, 20), (N/A), AUTO-BW, PASSIVE-SCAN
        (5250 - 5330 @ 80), (N/A, 20), (0 ms), DFS, AUTO-BW, PASSIVE-SCAN
        (5490 - 5730 @ 160), (N/A, 20), (0 ms), DFS, PASSIVE-SCAN
        (5735 - 5835 @ 80), (N/A, 20), (N/A), PASSIVE-SCAN
        (57240 - 63720 @ 2160), (N/A, 0), (N/A)

Country 00 is the world regulatory domain. This could be a result of a system that failed to load the regulatory database.

Look into the output from dmesg for verify:

$ dmesg | grep cfg80211
[    3.268852] cfg80211: Loading compiled-in X.509 certificates for regulatory database
[    3.269107] cfg80211: failed to load regulatory.db

As a result, this is the restrictions we have on the 5GHz band:

# iw list
[...]
                Frequencies:
                        * 5040 MHz [8] (disabled)
                        * 5060 MHz [12] (disabled)
                        * 5080 MHz [16] (disabled)
                        * 5170 MHz [34] (disabled)
                        * 5190 MHz [38] (20.0 dBm) (no IR)
                        * 5210 MHz [42] (20.0 dBm) (no IR)
                        * 5230 MHz [46] (20.0 dBm) (no IR)
                        * 5180 MHz [36] (20.0 dBm) (no IR)
                        * 5200 MHz [40] (20.0 dBm) (no IR)
                        * 5220 MHz [44] (20.0 dBm) (no IR)
                        * 5240 MHz [48] (20.0 dBm) (no IR)
                        * 5260 MHz [52] (20.0 dBm) (no IR, radar detection)
                        * 5280 MHz [56] (20.0 dBm) (no IR, radar detection)
                        * 5300 MHz [60] (20.0 dBm) (no IR, radar detection)
                        * 5320 MHz [64] (20.0 dBm) (no IR, radar detection)
                        * 5500 MHz [100] (20.0 dBm) (no IR, radar detection)
                        * 5520 MHz [104] (20.0 dBm) (no IR, radar detection)
                        * 5540 MHz [108] (20.0 dBm) (no IR, radar detection)
                        * 5560 MHz [112] (20.0 dBm) (no IR, radar detection)
                        * 5580 MHz [116] (20.0 dBm) (no IR, radar detection)
                        * 5600 MHz [120] (20.0 dBm) (no IR, radar detection)
                        * 5620 MHz [124] (20.0 dBm) (no IR, radar detection)
                        * 5640 MHz [128] (20.0 dBm) (no IR, radar detection)
                        * 5660 MHz [132] (20.0 dBm) (no IR, radar detection)
                        * 5680 MHz [136] (20.0 dBm) (no IR, radar detection)
                        * 5700 MHz [140] (20.0 dBm) (no IR, radar detection)
                        * 5745 MHz [149] (20.0 dBm) (no IR)
                        * 5765 MHz [153] (20.0 dBm) (no IR)
                        * 5785 MHz [157] (20.0 dBm) (no IR)
                        * 5805 MHz [161] (20.0 dBm) (no IR)
                        * 5825 MHz [165] (20.0 dBm) (no IR)
[...]

We can see the no IR flag is set for almost all frequencies on the 5GHz band. Please note that NO-IR is not the same as disabled, it simply means that we cannot initiate radiation on those frequencies.

Initiate radiation simply includes all modes of operations that require us to initiate radiation first. Think of acting as an Access Point, IBSS, Mesh or P2P master.

We can still use the frequency though, there is no problem to connect to an Access Point (we are not the one who initiate the radiation) on these frequencies.

Local regulatory domain

When a proper regulatory database is loaded into the system, we can setup the local regulatory domain instead of the globally one.

Set Swedish (SE) as our local regulatory domain:

# iw reg set SE
# iw reg get
global
country SE: DFS-ETSI
        (2400 - 2483 @ 40), (N/A, 20), (N/A)
        (5150 - 5250 @ 80), (N/A, 23), (N/A), NO-OUTDOOR, AUTO-BW
        (5250 - 5350 @ 80), (N/A, 20), (0 ms), NO-OUTDOOR, DFS, AUTO-BW
        (5470 - 5725 @ 160), (N/A, 26), (0 ms), DFS
        (5725 - 5875 @ 80), (N/A, 13), (N/A)
        (5945 - 6425 @ 160), (N/A, 23), (N/A), NO-OUTDOOR
        (57000 - 71000 @ 2160), (N/A, 40), (N/A)

And we are now allowed to use the 5GHz band with other restrictions:

#iw list
[...]
                Frequencies:
                        * 5040 MHz [8] (disabled)
                        * 5060 MHz [12] (disabled)
                        * 5080 MHz [16] (disabled)
                        * 5170 MHz [34] (23.0 dBm)
                        * 5190 MHz [38] (23.0 dBm)
                        * 5210 MHz [42] (23.0 dBm)
                        * 5230 MHz [46] (23.0 dBm)
                        * 5180 MHz [36] (23.0 dBm)
                        * 5200 MHz [40] (23.0 dBm)
                        * 5220 MHz [44] (23.0 dBm)
                        * 5240 MHz [48] (23.0 dBm)
                        * 5260 MHz [52] (20.0 dBm) (no IR, radar detection)
                        * 5280 MHz [56] (20.0 dBm) (no IR, radar detection)
                        * 5300 MHz [60] (20.0 dBm) (no IR, radar detection)
                        * 5320 MHz [64] (20.0 dBm) (no IR, radar detection)
                        * 5500 MHz [100] (26.0 dBm) (no IR, radar detection)
                        * 5520 MHz [104] (26.0 dBm) (no IR, radar detection)
                        * 5540 MHz [108] (26.0 dBm) (no IR, radar detection)
                        * 5560 MHz [112] (26.0 dBm) (no IR, radar detection)
                        * 5580 MHz [116] (26.0 dBm) (no IR, radar detection)
                        * 5600 MHz [120] (26.0 dBm) (no IR, radar detection)
                        * 5620 MHz [124] (26.0 dBm) (no IR, radar detection)
                        * 5640 MHz [128] (26.0 dBm) (no IR, radar detection)
                        * 5660 MHz [132] (26.0 dBm) (no IR, radar detection)
                        * 5680 MHz [136] (26.0 dBm) (no IR, radar detection)
                        * 5700 MHz [140] (26.0 dBm) (no IR, radar detection)
                        * 5745 MHz [149] (13.0 dBm)
                        * 5765 MHz [153] (13.0 dBm)
                        * 5785 MHz [157] (13.0 dBm)
                        * 5805 MHz [161] (13.0 dBm)
                        * 5825 MHz [165] (13.0 dBm)
[...]

Regulatory flags

Some of the flags reported by iw may not be obvious at a first glance. Here is an explaination for some of them:

Flag Meaning
  Can be used without restrictions.
disabled Disabled
NO-OUTDOOR MUST be used indoor only.
DFS MUST be used with DFS regardless indoor or outdoor.
SRD MUST comply with SRD requirements regardless indoor or outdoor.
NO-OUTDOOR/DFS MUST be used with DFS and indoor only.
NO-OUTDOOR/TPC MUST be used with TPC and indoor only.
DFS/TPC MUST be used with DFS and TPC.
DFS/TPC + SRD MUST be used with DFS, TPC and comply with SRD requirements.
  • DFS: stands for Dynamic Frequency Selection and is a channel allocation scheme used to prevent electromagnetic interference with systems that predates Wi-Fi.
  • TPC: stands for Transmit Power Control which is a mechanism to automatically reduce the used transmission output power when other networks are within range.
  • SRD: stands for Short-Range Device and cover devices that are low-power transmitters typically limited to the range of 24-100mW ERP.

Add support for MCP39XX in Linux kernel

Add support for MCP39XX in Linux kernel

I've maintained the MCP3911 driver in the Linux kernel for some time and continuously add support for new features [1] upon requests from people and companies.

Microchip has several IC:s in this series of ADC:s that works similar to MCP3911. Actually, all other IC:s are register compatible but MCP3911. The IC:s I've extended support for is MCP3910, MCP3912, MCP3913, MCP3914, MCP3918 and MCP3919.

The main difference between these IC:s from the driver perspective is the number of channels ranging from 1 to 8 channels and that the register map is not the same for all devices.

/media/mcp39xx.png

Implementation

This is a rather small patch without any fanciness, but just to show how to do this without the macro-magic you find in Zephyr [2].

Add compatible strings

The Linux driver infrastructure binds a certain device to a driver by a string (or other unique identifies such as VID/PID for USB). When, for example, the compatible property of a device tree node matches a device driver, a device is instantiated and the probe function is called.

As a single device driver could handle multiple similar IC:s where some of their properties may differ, we have to differentiate those somehow. This is done by provide device specific data to each instance of the device. This data is called "driver data" or "private data" and is part of the device lookup table.

E.g. the driver_data field of the struct spi_device_id:

struct spi_device_id {
	char name[SPI_NAME_SIZE];
	kernel_ulong_t driver_data;	/* Data private to the driver */
};

Or the data field of the struct of_device_id:

/*
 * Struct used for matching a device
 */
struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};

For this driver, the driver data to these ID tables looks as follows:

static const struct of_device_id mcp3911_dt_ids[] = {
-       { .compatible = "microchip,mcp3911" },
+       { .compatible = "microchip,mcp3910", .data = &mcp3911_chip_info[MCP3910] },
+       { .compatible = "microchip,mcp3911", .data = &mcp3911_chip_info[MCP3911] },
+       { .compatible = "microchip,mcp3912", .data = &mcp3911_chip_info[MCP3912] },
+       { .compatible = "microchip,mcp3913", .data = &mcp3911_chip_info[MCP3913] },
+       { .compatible = "microchip,mcp3914", .data = &mcp3911_chip_info[MCP3914] },
+       { .compatible = "microchip,mcp3918", .data = &mcp3911_chip_info[MCP3918] },
+       { .compatible = "microchip,mcp3919", .data = &mcp3911_chip_info[MCP3919] },
    { }
};
MODULE_DEVICE_TABLE(of, mcp3911_dt_ids);

static const struct spi_device_id mcp3911_id[] = {
-       { "mcp3911", 0 },
+       { "mcp3910", (kernel_ulong_t)&mcp3911_chip_info[MCP3910] },
+       { "mcp3911", (kernel_ulong_t)&mcp3911_chip_info[MCP3911] },
+       { "mcp3912", (kernel_ulong_t)&mcp3911_chip_info[MCP3912] },
+       { "mcp3913", (kernel_ulong_t)&mcp3911_chip_info[MCP3913] },
+       { "mcp3914", (kernel_ulong_t)&mcp3911_chip_info[MCP3914] },
+       { "mcp3918", (kernel_ulong_t)&mcp3911_chip_info[MCP3918] },
+       { "mcp3919", (kernel_ulong_t)&mcp3911_chip_info[MCP3919] },
    { }

The driver data is then reachable in the probe function via spi_get_device_match_data():

    adc->chip = spi_get_device_match_data(spi);

Driver data

The driver data is used to distinguish between different devices and provide enough information to make it possible for the driver to handle all differences between the IC:s in a common way.

The driver data for these devices looks as follows:

+struct mcp3911_chip_info {
+       const struct iio_chan_spec *channels;
+       unsigned int num_channels;
+
+       int (*config)(struct mcp3911 *adc);
+       int (*get_osr)(struct mcp3911 *adc, int *val);
+       int (*set_osr)(struct mcp3911 *adc, int val);
+       int (*get_offset)(struct mcp3911 *adc, int channel, int *val);
+       int (*set_offset)(struct mcp3911 *adc, int channel, int val);
+       int (*set_scale)(struct mcp3911 *adc, int channel, int val);
+};
+

Description of the structure members:

  • .channels is a a pointer to struct iio_chan_spec where all ADC and timestamp channels are specified.
  • .num_channels is the number of channels
  • .config is a function pointer to configure the device
  • .get_* and .set_* is function pointers used to get/set certain registers

A struct mcp3911_chip_info is created for each type of supported IC:

+static const struct mcp3911_chip_info mcp3911_chip_info[] = {
+       [MCP3910] = {
+               .channels = mcp3910_channels,
+               .num_channels = ARRAY_SIZE(mcp3910_channels),
+               .config = mcp3910_config,
+               .get_osr = mcp3910_get_osr,
+               .set_osr = mcp3910_set_osr,
+               .get_offset = mcp3910_get_offset,
+               .set_offset = mcp3910_set_offset,
+               .set_scale = mcp3910_set_scale,
+       },
+       [MCP3911] = {
+               .channels = mcp3911_channels,
+               .num_channels = ARRAY_SIZE(mcp3911_channels),
+               .config = mcp3911_config,
+               .get_osr = mcp3911_get_osr,
+               .set_osr = mcp3911_set_osr,
+               .get_offset = mcp3911_get_offset,
+               .set_offset = mcp3911_set_offset,
+               .set_scale = mcp3911_set_scale,
+       },
+       [MCP3912] = {
+               .channels = mcp3912_channels,
+               .num_channels = ARRAY_SIZE(mcp3912_channels),
+               .config = mcp3910_config,
+               .get_osr = mcp3910_get_osr,
+               .set_osr = mcp3910_set_osr,
+               .get_offset = mcp3910_get_offset,
+               .set_offset = mcp3910_set_offset,
+               .set_scale = mcp3910_set_scale,
+       },
+       [MCP3913] = {
+               .channels = mcp3913_channels,
+               .num_channels = ARRAY_SIZE(mcp3913_channels),
+               .config = mcp3910_config,
+               .get_osr = mcp3910_get_osr,
+               .set_osr = mcp3910_set_osr,
+               .get_offset = mcp3910_get_offset,
+               .set_offset = mcp3910_set_offset,
+               .set_scale = mcp3910_set_scale,
+       },
+       [MCP3914] = {
+               .channels = mcp3914_channels,
+               .num_channels = ARRAY_SIZE(mcp3914_channels),
+               .config = mcp3910_config,
+               .get_osr = mcp3910_get_osr,
+               .set_osr = mcp3910_set_osr,
+               .get_offset = mcp3910_get_offset,
+               .set_offset = mcp3910_set_offset,
+               .set_scale = mcp3910_set_scale,
+       },
+       [MCP3918] = {
+               .channels = mcp3918_channels,
+               .num_channels = ARRAY_SIZE(mcp3918_channels),
+               .config = mcp3910_config,
+               .get_osr = mcp3910_get_osr,
+               .set_osr = mcp3910_set_osr,
+               .get_offset = mcp3910_get_offset,
+               .set_offset = mcp3910_set_offset,
+               .set_scale = mcp3910_set_scale,
+       },
+       [MCP3919] = {
+               .channels = mcp3919_channels,
+               .num_channels = ARRAY_SIZE(mcp3919_channels),
+               .config = mcp3910_config,
+               .get_osr = mcp3910_get_osr,
+               .set_osr = mcp3910_set_osr,
+               .get_offset = mcp3910_get_offset,
+               .set_offset = mcp3910_set_offset,
+               .set_scale = mcp3910_set_scale,
+       },
+};

Thanks to this, all differences between the IC:s is in one place and the driver code is common for all devices. See the code below how oversampling ration is set. The differences between IC:s is handled by the callback function:

        case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
                for (int i = 0; i < ARRAY_SIZE(mcp3911_osr_table); i++) {
                        if (val == mcp3911_osr_table[i]) {
-                               val = FIELD_PREP(MCP3911_CONFIG_OSR, i);
-                               ret = mcp3911_update(adc, MCP3911_REG_CONFIG, MCP3911_CONFIG_OSR,
-                                               val, 2);
+                               ret = adc->chip->set_osr(adc, i);
                                break;
                        }
                }

Contiguous Memory Allocator

Contiguous Memory Allocator

Introduction

I do find memory management as one of the most fascinating subsystem in the Linux kernel, and I take every chance I see to talk about it. This post is inspired by a project I'm currently working on; an embedded Linux platform with a camera connected to the CSI-2 bus.

Before we dig into which problems we could trip over, lets talk briefly about how the kernel handles memory.

Memory subsystem

The memory management subsystem handles a wide spectrum of operations which all have impact on the system performance. The subsystem is therefor divided into several parts to sustain operational efficiency and optimized resource handling for different use cases.

Such parts includes:

  • Page allocator
  • Buddy system
  • Kmalloc allocator
  • Slab caches
  • Vmalloc allocator
  • Contiguous memory allocator
  • ...

The smallest allocation unit of memory is a page frame. The Memory Management Unit (MMU) does a terrific job to arrange and map these page frames of the available physical memory into a virtual address space. Most allocations in the kernel are only virtually contiguous which is fine for the most use cases.

Some hardware/IP-blocks requires physically contiguous memory to work though. Direct Memory Access (DMA) transfers are one such case where memory (often) needs to be physically contiguous. Many DMA controllers now supports scatter-gather, which let you hand-pick addresses to make it appear to be contiguous and then let the (IO)MMU do the rest.

To make it works, it requires that the hardware/IP-blocks actually do its memory accesses through the MMU, which is not always the case.

Multimedia devices such as GPU or VPU does often requires huge blocks of physically contiguous memory and do (with exceptions, see Raspberry Pi 4 below) not make use of the (IO)MMU.

Contiguous memory

In order to meet this requirement on big chunks of physically contiguous memory we have to reserve it from the main memory during system boot.

Before CMA, we had to use the mem kernel parameter to limit how much of the system memory that should be available for allocators in the Linux system.

The memory outside this mem-region is not touched by the system and could be remapped into linear address space by the driver.

Here is the documentation for the mem kernel parameter [1]:

mem=nn[KMG]     [KNL,BOOT] Force usage of a specific amount of memory
                Amount of memory to be used in cases as follows:

                1 for test;
                2 when the kernel is not able to see the whole
                system memory;
                3 memory that lies after 'mem=' boundary is
                excluded from the hypervisor, then
                assigned to KVM guests.
                4 to limit the memory available for kdump kernel.

                [ARC,MICROBLAZE] - the limit applies only to low memory,
                high memory is not affected.

                [ARM64] - only limits memory covered by the linear
                mapping. The NOMAP regions are not affected.

                [X86] Work as limiting max address. Use together
                with memmap= to avoid physical address space collisions.
                Without memmap= PCI devices could be placed at addresses
                belonging to unused RAM.

                Note that this only takes effects during boot time since
                in above case 3, memory may need be hot added after boot
                if system memory of hypervisor is not sufficient.

The mem parameter has a few drawbacks. The driver needs details about where to get the reserved memory and the memory lie momentarily unused when the driver is not initiating any access operations.

Therefor the Contiguous Memory Allocator (CMA) was introduced to manage these reserved memory areas.

The benefits by using CMA is that this area is handled by the allocator algorithms instead of the device driver itself. This let both devices and systems to allocate and use memory from this CMA area through the page allocator for regular needs and through the DMA allocation routines when DMA capabilities is needed.

A few words about Raspberry Pi

Raspberry Pi uses a configuration (config.txt) file that is read by the GPU to initialize the system. The configuration file has many tweakable parameters and one of those are gpu_mem.

This parameter specifies how much memory (in megabytes) to reserve exclusively for the GPU. This works pretty much like the mem kernel commandline parameter described above, with the very same drawbacks. The memory reserved for GPU is not available for the ARM CPU and should be kept as low as possible that your application could work with.

One big difference between the variants of the Raspberry Pi modules is that the Raspberry Pi 4 has a GPU with its own MMU, which allows the GPU to use memory that is dynamically allocated within Linux. The gpu_mem could therfor be kept small on that platform.

The GPU is normally used for displays, 3D calculations, codecs and cameras. One important thing regarding the camera is that the default camera stack (libcamera) does use CMA memory to allocate buffers instead of the reserved GPU memory. In cases that the GPU is only for camera purposes, the gpu_mem could be kept small.

How much CMA is already reserved?

The easiest way to determine how much memory that is reserved for CMA is to consult meminfo:

# grep Cma /proc/meminfo
CmaTotal:         983040 kB
CmaFree:          612068 kB

or look at the boot log:

# dmesg | grep CMA
[    0.000000] Reserved memory: created CMA memory pool at 0x0000000056000000, size 960 MiB

Reserve memory with CMA

/media/reserved.jpg

The CMA area is reserved during boot and there are a few ways to do this.

By device tree

This is the preferred way to define CMA areas.

This example is taken from the device tree bindings documentation [2]:

reserved-memory {
    #address-cells = <1>;
    #size-cells = <1>;
    ranges;

    /* global autoconfigured region for contiguous allocations */
    linux,cma {
        compatible = "shared-dma-pool";
        reusable;
        size = <0x4000000>;
        alignment = <0x2000>;
        linux,cma-default;
    };
};

By kernel command line

The CMA area size could also be specified by the kernel command line. There are tons of references out there that states that the command line parameter is overridden by the device tree, but I thought it sounded weird so I looked it up, and the kernel command line overrides device tree, not the other way around.

At least nowadays:

static int __init rmem_cma_setup(struct reserved_mem *rmem)
{
    ...
    if (size_cmdline != -1 && default_cma) {
        pr_info("Reserved memory: bypass %s node, using cmdline CMA params instead\n",
            rmem->name);
        return -EBUSY;
    }
    ...
}

Here is the documentation for the cma kernel parameter [1]:

cma=nn[MG]@[start[MG][-end[MG]]]
                [KNL,CMA]
                Sets the size of kernel global memory area for
                contiguous memory allocations and optionally the
                placement constraint by the physical address range of
                memory allocations. A value of 0 disables CMA
                altogether. For more information, see
                kernel/dma/contiguous.c

By kernel configuration

The kernel configuration could be used to set min/max and even a percentage of how much of the available memory that should be reserved for the CMA area:

CONFIG_CMA
CONFIG_CMA_AREAS
CONFIG_DMA_CMA
CONFIG_DMA_PERNUMA_CMA
CONFIG_CMA_SIZE_MBYTES
CONFIG_CMA_SIZE_SEL_MBYTES
CONFIG_CMA_SIZE_SEL_PERCENTAGE
CONFIG_CMA_SIZE_SEL_MIN
CONFIG_CMA_SIZE_SEL_MAX
CONFIG_CMA_ALIGNMENT

Conclusion

As soon we are using camera devices with higher resolution and do the image manipulation in the VPU/GPU, we almost always have to increase the CMA area size. Otherwise we will end up with errors like this:

cma_alloc: alloc failed, req-size: 8192 pages, ret: -12

Audio and Embedded Linux

Audio and Embedded Linux

Brief

Last time I used wrote kernel drivers for the ASoC (ALSA System on Chip) subsystem, the functionality was split up into these parts:

  • Platform class driver that defines the SoC audio interface for the actual CPU itself. This includes both the DAI (Digital Audio Interface) and any potential audio muxes (e.g. i.MX6 has its AUDMUX).
  • CODEC class driver that controls the actual CODEC.
  • Machine drivers that is the magic glue between the SoC and the CODEC which connect the both interfaces. Such driver had to be written for each SoC-CODEC combination, and that does not scale very well.

Nowadays, most of the CODEC class drivers is now adapted to be described with the simple-audio-card [1] in a device tree, which will completely replace the machine drivers.

The goal with this post is to describe my work to setup a a 20W mono class-D audio amplifier to work with a i.MX8MM board.

General

The configuration of the CODEC is usually done on a I2C bus, even if other simple busses like SPI could be used as well. When configuration is sent over this simple bus, the audio data is sent over a complete different bus.

Audio data could be transferred in many different formats such as AC97, PCM or I2S.

To abstract this bus and handle it in a common way, we will just call it DAI, for Digital Audio Interface.

Different SoCs have of course different names on this as well. For example, Texas Instruments has its McASP, NXP uses SSI, Atmel SSC and so on.. We call it DAI all over.

Serial audio formats

AC97

AC97 is a commonly found interface on many PC cards, it's not that popular in embedded devices though. It's a five wire interface with:

  • A reset line
  • DATA_OUT for playback
  • SDATA_IN for capture
  • BCLK as bit clock, which is always driven by the CODEC

See the specification [4] for further reading.

I2S

I2S is a common 5 wire DAI often used in embedded systems. The TX (SDOUT) and Rx (SDIN) lines are used for audio transmission while the bit and frame clock are used for synchronization.

The signals are:

  • Master clock or system clock, often referred to as MCLK, is the clock which the other clocks is derived from. This also clock the CODEC.
  • Bit clock, often referred to as BCK or BCLK, varies depending on the sample rate.
  • Frame clock, often referred to ass LRCLK (Left-Right Clock), FCLK (Frame clock) or WCLK (Word clock).
  • Audio out, SDOUT
  • Audio In, SDIN

The relationship between BCLK and LRCLK is

bclk = (sample rate) * Nchannels * (bit depth)

Some CODECs are able to use BCLK as their only clock, which leaving MCLK as optional. The CODEC we will use does supports this and is something we have to use due to HW constraints in number of available signals that should fit in a connector.

This is an illustration of the timing on the I2S bus with 64 BCLKs per LRCLK. Borrowed from the datasheet [5]:

/media/i2s.jpg

I2S could be used with TDM format timing to support more audio channels on the same I2S bus. The timing will then look like this [5] :

/media/i2s-tdm.jpg

PCM

PCM is a 4 wire interface that is quite similar to I2S. Same same but different.

Clocks

We have several clocks such as bit clock, frame clock and master clock. It's not written in stone which endpoint of the DAI that should generate these clocks, it's up to us to decide.

Either the SoC or CODEC generates some or all of the clocks, called clock master (e.g. bit clock master or frame clock master).

It's often easiest to let the CODEC generate all clocks, but some SoCs has specialized audio PLLs for this. In our case, the SoC will be clock master.

The Hardware

The SoC

The board we are going to use is an evaluation board for a i.MX8MM module [2]. The CPU module supports two I2S busses and we are going to use one of them.

/media/sm2simx8m.jpg

The CODEC

The CODEC we will use is is the TAS5720L [3] from Texas Instruments which has been supported in mainline since v4.6.

/media/tas5720l.jpg

The TAS5720L device Serial Audio Interface (SAIF) supports a variety of audio formats including I2S, left-justified and Right justified. It also supports the time division multiplexed (TDM) format that is capable of transporting up to 8 channels of audio data on a single bus.

It uses I2C as configuration interface.

We will use I2S with TDM as DAI and I2C as configuration interface.

The Software

As we mostly got rid of the machine drivers and can describe the CODEC bindings using device tree, the setup is mostly a exercise in device tree writing rather than C.

The device tree node to setup the sound card is simple-audio-card [6].

SAI node

The Synchronous Audio Interface (SAI) module is the HW part of the i.MX8 SoC that are used to generate the digital audio.

We are going to use the SAI5 interface as it's routed from the sm2s-imx8mm module. The node is properly configured in an interface (.dtsi)-file, so we only have to enable it:

&sai5 {
    status = "okay";
};

CODEC node

The TAS5720L is connected to the I2C3 bus and respond to the slave address 0x6c. Besides the compatible and reg properties, the node also requires phandle for a 3V3 supply that supplies the digital circuitry and a phandle for the Class-D amp and analog part.

The hardware does not have such controllable supplies so we have to create fixed regulators for that:

/ {
    reg_audio_p: regulator-audio {
        compatible = "regulator-fixed";
        regulator-name = "audio power";
        pinctrl-names = "default";
        regulator-min-microvolt = <12000000>;
        regulator-max-microvolt = <12000000>;
    };

    reg_audio_d: regulator-audio {
        compatible = "regulator-fixed";
        regulator-name = "audio digital";
        pinctrl-names = "default";
        regulator-min-microvolt = <3300000>;
        regulator-max-microvolt = <3300000>;
    };
};

And the device node for the CODEC itself:

&i2c3 {

    tas5720: tas5720@6c {
            #sound-dai-cells = <0>;
            reg = <0x6c>;
            compatible = "ti,tas5720";

            dvdd-supply = <&reg_audio_d>;
            pvdd-supply = <&reg_audio_p>;
    };
};

Sound node

Now it's time to setup the sound node!

First we have to specify which audio format we intend to use by setting simple-audio-card,format to i2s.

We also have to setup the two DAIs (CPU & CODEC) that we are going to use.

This is done by creating sub nodes and refer to the SAI module node and CODEC node as sound-dai respectively.

These sub nodes are referred to when assign frame-master and bitclock-master in the sound node. As we want the SoC to generate both frame- and bit-clock, set cpudai as clock master for both.

/ {
    sound-tas5720 {
        compatible = "simple-audio-card";
        simple-audio-card,name = "tas5720-audio";
        simple-audio-card,format = "i2s";
        simple-audio-card,frame-master = <&cpudai>;
        simple-audio-card,bitclock-master = <&cpudai>;

        cpudai: simple-audio-card,cpu {
            sound-dai = <&sai5>;
            clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;

        };

        simple-audio-card,codec {
            sound-dai = <&tas5720>;
            clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
        };
    };
};

Sound test

Now we should have everything in place!

Lets use speaker-test, which is part of alsa-utils [8] to test our setup.

root@imx8board:~# speaker-test

speaker-test 1.2.5.1

Playback device is default
Stream parameters are 44000Hz, S16_LE, 1 channels
Using 16 octav es of pink noise
[   12.257438] fsl-sai 30050000.sai: failed to derive required Tx rate: 1411200

That did not turn out well.

Debug clock signals

Lets look what our clock tree looks like:

root@imx8board:~# cat /sys/kernel/debug/clk/clk_summary
    ...
    audio_pll2_ref_sel                0        0        0    24000000          0     0  50000
       audio_pll2                     0        0        0   361267200          0     0  50000
          audio_pll2_bypass           0        0        0   361267200          0     0  50000
             audio_pll2_out           0        0        0   361267200          0     0  50000
    audio_pll1_ref_sel                0        0        0    24000000          0     0  50000
       audio_pll1                     0        0        0   393216000          0     0  50000
          audio_pll1_bypass           0        0        0   393216000          0     0  50000
             audio_pll1_out           0        0        0   393216000          0     0  50000
                sai5                  0        0        0    24576000          0     0  50000
                   sai5_root_clk       0        0        0    24576000          0     0  50000
    ...

The sai5 clock is running at 24576000Hz, and indeed, it's hard to find a working clock divider to get 1411200Hz.

audio_pll2 @ 361267200 looks better. 361267200/1411200=256, allmost perfect!

Then we need to reparent the sai5 module, this is done in the device tree as well:

&sai5 {
    status = "okay";
    assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL2_OUT>;
    assigned-clock-rates = <11289600>;
};

Here is our new clock tree:

root@imx8board:~# cat /sys/kernel/debug/clk/clk_summary
    ...
    audio_pll2_ref_sel                0        0        0    24000000          0     0  50000
       audio_pll2                     0        0        0   361267200          0     0  50000
          audio_pll2_bypass           0        0        0   361267200          0     0  50000
             audio_pll2_out           0        0        0   361267200          0     0  50000
                sai5                  0        0        0    11289600          0     0  50000
                   sai5_root_clk       0        0        0    11289600          0     0  50000
    ...

We can see that the frequency is right and also that we now derive our clock from audio_pll2_out instead of audio_pll1.

The speaker-test software is also happier:

root@imx8board:~# speaker-test

speaker-test 1.2.5.1

Playback device is default
Stream parameters are 44000Hz, S16_LE, 1 channels
Using 16 octaves of pink noise
Rate set to 44000Hz (requested 44000Hz)
Buffer size range from 3840 to 5760
Period size range from 1920 to 1920
Using max buffer size 5760
Periods = 4
was set period_size = 1920
was set buffer_size = 5760
 0 - Front Left

Great!

Use BCLK as MCLK

Due to my hardware constraints, I need to use the bit clock as master clock. If we look in the datasheet [5] :

/media/tas5720-1.png

If the BCLK to LRCLK ratio is 64, we could tie MCLK directly to our BCLK!

We already know our BCLK, it's 1411200Hz, and the frame clock (LRCLK) is the same as the sample rate (44kHz). We could verify that with the oscilloscope.

Bitclock:

/media/bitclock1.png

Frameclock:

/media/frameclock.png

That is not a ratio of 64.

There is not much to do about the frame clock, it will stick to the sample rate. If we make use of TDM though, we can make the bit clock running faster with the same frame clock!

Lets add 2 TDM slots @ 32bit width:

/ {
    sound-tas5720 {
        compatible = "simple-audio-card";
        simple-audio-card,name = "tas5720-audio";
        simple-audio-card,format = "i2s";
        simple-audio-card,frame-master = <&cpudai>;
        simple-audio-card,bitclock-master = <&cpudai>;

        cpudai: simple-audio-card,cpu {
            sound-dai = <&sai5>;
            clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
            dai-tdm-slot-num = <2>;
            dai-tdm-slot-width = <32>;
        };

        simple-audio-card,codec {
            sound-dai = <&tas5720>;
            clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
        };
    };
};

Verify the bitclock:

/media/bitclock1.png

Lets calculate: 2820000/44000 ~= 64! We have reached our goal!

Final device tree setup

This is what the final device tree looks like:

/ {
    sound-tas5720 {
        compatible = "simple-audio-card";
        simple-audio-card,name = "tas5720-audio";
        simple-audio-card,format = "i2s";
        simple-audio-card,frame-master = <&cpudai>;
        simple-audio-card,bitclock-master = <&cpudai>;

        cpudai: simple-audio-card,cpu {
            sound-dai = <&sai5>;
            clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
            dai-tdm-slot-num = <2>;
            dai-tdm-slot-width = <32>;
        };

        simple-audio-card,codec {
            sound-dai = <&tas5720>;
            clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
        };
    };

    reg_audio_p: regulator-audio {
        compatible = "regulator-fixed";
        regulator-name = "audio power";
        pinctrl-names = "default";
        regulator-min-microvolt = <12000000>;
        regulator-max-microvolt = <12000000>;
    };

    reg_audio_d: regulator-audio {
        compatible = "regulator-fixed";
        regulator-name = "audio digital";
        pinctrl-names = "default";
        regulator-min-microvolt = <3300000>;
        regulator-max-microvolt = <3300000>;
    };

};

&i2c3 {

    tas5720: tas5720@6c {
            #sound-dai-cells = <0>;
            reg = <0x6c>;
            compatible = "ti,tas5720";

            dvdd-supply = <&reg_audio_d>;
            pvdd-supply = <&reg_audio_p>;
    };
};

&sai5 {
    status = "okay";
    assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL2_OUT>;
    assigned-clock-rates = <11289600>;
};

Conclusion

simple-audio-card is a flexible way to describe the audio routing and I strongly prefer this way over write a machine driver for each SoC-CODEC setup.

My example here is kept to a minimum, you probably want to add widgets and routing as well.

simple-audio-card does support rather complex setup with multiple DAI links, amplifier and such. See the device tree bindings [6] for further reading.

Debug kernel with KGDB

Debug kernel with KGDB

What is KGDB?

KGDB intend to be used as a source code level debugger on a running Linux kernel. It works with GDB and allows the user to inspect memory, variables, setup breakpoints, step lines and instructions. Pretty much the same that all application developers are used to, but for the kernel itself.

Almost every embedded Linux system does have a serial port available, and that is all that you need to connect GDB to your kernel.

One thing to keep in mind, as with all debugging, is that everything that is called timing will be messed up. It will be pretty obvious when you actually pause a running kernel that keeps up the communicates with all hardware. Especially if you have any hardware watchdogs enabled...

Compile the kernel with support for KGDB

There are a few kernel options that you have to enable in order to use KGDB:

  • CONFIG_KGDB to enable remote debugging.
  • CONFIG_KGDB_SERIAL_CONSOLE let you share a serial console with GDB.
  • CONFIG_FRAME_POINTER is used to produce more reliable stack backtraces by inserting code to preserve the frame information in registers or on the stack.
  • CONFIG_KALLSYMS_ALL to make sure that all symbols are loaded into the kernel image (i.e. symbols from all sections).
  • CONFIG_MAGIC_SYSRQ to be able to make sysrq. More about this below.

KGDBOC

KGDB over console, or kgdboc, let you use a console port as the debugging port. If we only have one serial port available, we could split the console and gdb communication using agent-proxy [2] .

Agent-proxy

To split a serial port into console and GDB communication we could use agent-proxy. Download and compile agent-proxy

git clone http://git.kernel.org/pub/scm/utils/kernel/kgdb/agent-proxy.git
cd agent-proxy
make

Launch agent-proxy

agent-proxy 4440^4441 0 /dev/ttyS0,115200

If your hardware does not support the line break sequence you have to add the -s003 option. You will find out pretty soon if it's needed - if your target continues to run after sending a break, then you should try to add it, in other words:

agent-proxy 4440^4441 0 /dev/ttyS0,115200 -s003

Where ttyS0 is the serial port on your host.

This will create two TCP sockets, one for serial console and with for GDB, which is listening to port 4440 and 4441 respectively.

Connect to the serial console with your favorite client (socat, netcat, telnet...)

telnet localhost 4440

Setup kgdboc with kernel arguments

kgdboc could be used early in the boot process if it's compiled into the kernel as a built-in (not as module), by providing the kgdboc arguments.

Add kgdboc=<tty-device>,[baud] to your command line argument, e.g.

kgdboc=ttyS0,11500

Where ttyS0 is the serial port on the target.

The kgdbwait argument stops the kernel execution and enter the kernel debugger as earliest as possible. This let you connect to the running kernel with GDB.

See kernel parameters [1] for more information.

Setup kgdboc with kernel module

If the kgdb is not compiled to be built-in but as a module, you provide the same arguments while loading the kernel

modprobe kgdboc=ttyS0,115200

Setup kgdboc in runtime using sysfs

It's also possible to enable kgdboc by echoing parameters into sysfs entries

echo ttyS0 > /sys/module/kgdboc/parameters/kgdboc

Connect GDB to a running kernel

Stop execution and wait for debugger

We have to stop the execution of the kernel in order to connect with gdb.

If gdbwait is provided as boot argument, the kernel will stop its execution and wait.

Otherwise we have to trigger this manually by using SysRq-G. This requires that the CONFIG_MAGIC_SYSRQ is enabled in your kernel config.

Your favorite serial application does probably have some keyboard combination to send SYSRQ requests (GNU Screen has "CTRL+A b" for example), otherwise you can use procfs to send the trigger

echo g > /proc/sysrq-trigger

Connect with GDB

Start GDB and provide the vmlinux from your kernel root directory, remember that you have to use the GDB that came with your toolchain. I do always use the -tui flag to start with a nice terminal user interface

aarch64-linux-gnu-gdb -tui ./vmlinux

Now, if you have a separate serial port for gdb, you could connect to it directly

(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0

If you are using agent-proxy, then we should connct to port 4441

(gdb) target remote localhost:4441

Now you are able to set breakpoints, watch variables and use gdb as you used to.

/media/kgdb.jpg

One tip is to set a breakpoint at ksys_sync

(gdb) b ksys_sync

This let you run the sync command as trig to go-to-debug-mode.

What is libcamera and why should you use it?

What is libcamera and why should you use it

Read out a picture from camera

Once in a time, video devices was not that complex. To use a camera back then, your application software could iterated through /dev/video* devices and pick the camera that you want and then immediately start using it. You could query which pixel formats, frame rates, resolutions and all other properties that are supported by the camera. You could even easily change it if you want.

This still works for some cameras, basically every USB camera and most laptop cameras still works that way.

The problem, especially in embedded systems, is that there is no such thing as "the camera" anymore. The camera system is rather a complex pipeline of different image processing nodes that the image data traverse through to be shaped as you want. Even if the result of this pipeline will end up in a video device, you cannot configure things like cropping, resolution etc. directly on that device as you used to. Instead, you have to use the media controller API to configure and link each of these nodes to build up your pipeline.

To show how it may look like; this is a graph that I had in a previous post [3]:

/media/media-ctl-graph.png

What is libcamera?

/media/libcamera-banner.png

This is how libcamera is described on their website [1]

libcamera is an open source camera stack for many platforms with a core userspace library, and support from the Linux kernel APIs and drivers already in place.
It aims to control the complexity of embedded camera hardware by providing an intuitive API and method of separating untrusted vendor code from the open source core.

libcamera aims to encourage the development of new embedded camera applications by limiting the complexity that developers have to deal with.
The interface is designed around the way that modern embedded camera hardware works.

First time I heard about libcamera was on the Embedded Linux Conference 2019 where Jacopo Mondi had a talk [2] about the Public API for the first stable libcamera release. I have been working with cameras in several embedded Linux products and know for sure how complex [3] these little beast could be. The configuration also differ depending on which platform or camera you are using as there is no common way to setup the image pipe. You will soon have special cases for all your platform variants in your application. Which is not what we strive for.

libcamera is trying to solve this by provide one library that takes care of all that complexity for you.

For example, if you want to adjust a simple thing, say contrast, of a IMX219 camera module connected to a Raspberry Pi. To do that without libcamera, you first have to setup a proper image pipeline that takes the camera module, connect it to the several ISP (Image Signal Processing) blocks that your processor offers in order to get the right image format, resolution and so on. Somewhere between all these configuring, you realise that the camera module nor the ISPs have support for adjust the contrast. Too bad. To achieve this you have to take the image, pass it to a self-written contrast algorithm, create a gamma curve that the IPA (Image Processing Algorithm) understands and actually set gamma. Yes, the contrast is adjusted with a gamma curve for that particular camera on Raspberry Pi. ( Have a look at the implementation of that IPA block [7] for Raspberry Pi )

This is exactly the stuff libcamera understands and abstract for the user. libcamera will figure out what graph it has to build depending on what you want do to and which processing operations that are available at your various nodes. The application that is using libcamera for the video device will set contrast for all cameras and platforms. After all, that is what you wanted.

Camera Stack

As the libcamera library is fully implemented in userspace and use already existing kernel interfaces for communication with hardware, you will need no extra underlying support in terms of separate drivers or kernel support.

libcamera itself exposes several API's depending on how the application want to interface the camera. It even have a V4L2 compatiblity layer to emulate a high-level V4L2 camera device to make a smooth transition for all those V4L2 applications out there.

/media/libcamera-layer.png

Read more about the camera stack in the libcamera documentation [4].

Conclusion

I really like this project and I think we need an open-source stack that supports many platforms. This vendor-specific drivers/libraries/IPAs-situation we are in right now is not sustainable at all. It takes too much effort to evaluate a few cameras of different vendors just because all vendors has their own way to control the camera with their own closed-source and platform specific layers. Been there done that.

For those vendors that do not want to open-source their secret image processing algorithms, libcamera uses a plugin system for IPA modules which let vendors keep their secrets but still be compatible with libcamera. All open-source modules are identified based on digital signatures, while closed-source modules are instead isolated inside a sandbox environment with restricted access to the system. A Win-Win concept.

The project itself is still quite young and need more work to support more platforms and cameras, but the ground is stable. Raspberry Pi is now a common used platform, both in commercial and hobby, and the fact that Raspberry Pi Foundation has chosen libcamera as their primary camera system [8] must tell us something.

HID report descriptors and Linux

HID report descriptors and Linux

HID Devices

USB HID (Human Interface Device) device class is the type of computer peripherals that human interacts with, such as keyboards, mice, game controllers and touchscreens. The protocol is probably one of the most simple protocols in the USB specification. Even if HID was originally written for USB in mind, it works with several other transport layers. Your mouse and keyboard do probably use HID over USB, the touchscreen in your smartphone could use HID over I2C. Even Bluetooth/BLE make use of the same protocol for this type of devices.

The protocol is popular as it's so simple. The HID descriptor could be stored in ROM and the peripheral could be implemented using only a small 8-bit MCU.

Despite how simple and well defined [1] the HID specification is, implementers will still get it wrong as we will see later on. Those non compliant errors has to be fixed up in order to use the device. It's quite sad as it requires a separate driver for this even if the HID should be able to be handled in a generic way.

We will continue to focus on the HID implementation over USB.

HID Descriptors

The device itself has to identify itself and that information is stored in segments of its ROM (Read Only Memory). This segments, or descriptors as they're called, describe what type of device it's and which interface (Interface Descriptor) it exposes. Such interfaces are called classes and all devices belongs to one USB class. Such class [2] could be Printer, Video, or the one we will focus on - the HID class.

/media/hid-1.png

The HID class device descriptor defines and identifies which other descriptors present. Such other descriptors could be report descriptors and physical descriptors.

Report Descriptors

The report descriptor describes how the data that the device generates should be interpreted. For example, the report descriptor describes how to determine the button state of your mouse or the position of the touchscreen click on your smartphone.

Physical Descriptors

The physical descriptor on the other hand provides information about the physical layout of the device, e.g. what and how your body interact with the device to activate certain functions.

The big picture

There are of cause more descriptors, but most of them are part of the USB specification rather than specific for the HID devices.

/media/hid-2.png

There are much to say about the HID class, sub classes, interfaces and protocols and we will not cover them all. But just to give you a hint of what it's:

HID class

USB devices are grouped into USB classes depending on what type of device and transport requirement it's. For example an Audio device requires isohcronous data pipes which HID devices does not. HID Devices has different and much simpler data transport requirements.

Subclasses

The only subclass for the HID class is the Boot Interface Subclass. It's a small subset of the report descriptor that is easier to parse for code that does not want (or have resource to) parse a full report descriptor. BIOS is one example of such code that want to keep the complexity and footprint as small as possible, but still want to be able to use a keyboard.

Protocol

The HID protocol only has a meaning if subclass is a Boot Interface, then it's used to determine if the device is a mouse or keyboard. Otherwise the Protocol is not used.

Interfaces

The interface is a way for the device and host to communicate.HID devices use either the Control pipe or the Interrupt pipe.

Control pipes are used for:

  • Receive and respond to requests for USB control and class data
  • Transmit data when asked by the HID class driver
  • Receive data from the host

The interrupt pipe are used for:

  • Receiving asynchronous data from the device
  • Transmitting low-latency

Report descriptors

The information passed to and from the device is encapsulated in reports. These reports are organized data where the data layout is described in a report descripor. This report descriptor is one of the first items that the host requests from the device and describes how the data should be interpreted. For example, a mouse could have several buttons represented by one bit each and a wheel represented from -127 to +128. The report descriptor will give you details about which bits are mapped to which button and also which 8 bits should be used for the wheel.

All report descriptors are available to read out from sysfs. This is the report from my keyboard

[15:15:02]marcus@goliat:~$ od -t x1 -Anone  /sys/bus/usb/devices/3-12.3.2.1/3-12.3.2.1:1.0/0003:045E:00DB.0009/report_descriptor
 05 01 09 06 a1 01 05 08 19 01 29 03 15 00 25 01
 75 01 95 03 91 02 09 4b 95 01 91 02 95 04 91 01
 05 07 19 e0 29 e7 95 08 81 02 75 08 95 01 81 01
 19 00 29 91 26 ff 00 95 06 81 00 c0

Here is a another (parsed) example of what the report descriptor may look like

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x04,        // Usage (Joystick)
0xA1, 0x01,        // Collection (Application)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x07,  //     Logical Maximum (2047)
0x35, 0x00,        //     Physical Minimum (0)
0x46, 0xFF, 0x00,  //     Physical Maximum (255)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x75, 0x08,        //   Report Size (8)
0x95, 0x03,        //   Report Count (3)
0x81, 0x03,        //   Input (Cnst,Var,Abs)
0xC0,              // End Collection

The descriptor describes a 2 axis (X/Y) joystick controller where each axis could have an absolute value between 0 and 2047.

If we walk through the descriptor step by step.

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)

As there are a many different types of devices, these are grouped into pages. This to know how the following (Usage) entry should be intepreted.

There are a plenty of groups as seen in the reference manual [3] :

/media/hid-page.png

Our joystick belongs to the Generic Desktop Ctrls group.

The next line is the usage:

0x09, 0x04,        // Usage (Joystick)

Even mouse, keyboard and gamepads are example of Generic Desktop Ctrls as seen in the table below:

/media/hid-page.png

The next entry is the application collection:

0xA1, 0x01,        // Collection (Application)

The application collection is to make a meaningful grouping of Input, Output and Feature items. Each such grouping has at least Report Size and Report Count defined to determine how big in terms of bytes the collections is.

The next entry is the physical collection:

0xA1, 0x00,        //   Collection (Physical)

This provides information about the part or parts of the human body used to activate the controls on the device. In other words - button and knobs.

Now to the more fun part:

0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x07,  //     Logical Maximum (2047)
0x35, 0x00,        //     Physical Minimum (0)
0x46, 0xFF, 0x00,  //     Physical Maximum (255)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

Here we can see that there are two axis, X/Y, that is represented by a value between 0 and 2047 (11 bits). A single axis does occupy 16 bits and is of the type Input. That is pretty much all what we need to know in order to parse this information.

What is so hard then?

These report descriptors is not that hard to follow and there is no black magic around them. Despite that, many vendors does not get these report descriptors right and keep deliver a custom driver along with their product.

I used to build and fly tri- and quadcopters, and on way to be better at flying is to use your radio transmitter connected to a simulator as training. The crashes is not that fatal nor costly that way...

I've never seen such a flight controller that actually follow the HID specification, and that makes them useless without a custom driver that can parse the mess. It's not uncommon that the actual reports from the device looks good, it's just that the report descriptor is messed up.

In that case we can write a pretty small Linux Kernel Driver that only fixup the report descriptor and then let the HID layer create and manage the device in a generic way. This is what I did for the VRC2 and HID-PXRC driver [4] which will be available in Linux 6.1.

Such driver could be as simple as (cut out from my VRC2-driver):

static __u8 vrc2_rdesc_fixed[] = {
    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
    0x09, 0x04,        // Usage (Joystick)
    0xA1, 0x01,        // Collection (Application)
    0x09, 0x01,        //   Usage (Pointer)
    0xA1, 0x00,        //   Collection (Physical)
    0x09, 0x30,        //     Usage (X)
    0x09, 0x31,        //     Usage (Y)
    0x15, 0x00,        //     Logical Minimum (0)
    0x26, 0xFF, 0x07,  //     Logical Maximum (2047)
    0x35, 0x00,        //     Physical Minimum (0)
    0x46, 0xFF, 0x00,  //     Physical Maximum (255)
    0x75, 0x10,        //     Report Size (16)
    0x95, 0x02,        //     Report Count (2)
    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0xC0,              //   End Collection
    0x75, 0x08,        //   Report Size (8)
    0x95, 0x03,        //   Report Count (3)
    0x81, 0x03,        //   Input (Cnst,Var,Abs)
    0xC0,              // End Collection
};

static __u8 *vrc2_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                unsigned int *rsize)
{
    hid_info(hdev, "fixing up VRC-2 report descriptor\n");
    *rsize = sizeof(vrc2_rdesc_fixed);
    return vrc2_rdesc_fixed;
}

static int vrc2_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
    int ret;

    /*
     * The device gives us 2 separate USB endpoints.
     * One of those (the one with report descriptor size of 23) is just bogus so ignore it
     */
    if (hdev->dev_rsize == 23)
        return -ENODEV;

    ret = hid_parse(hdev);
    if (ret) {
        hid_err(hdev, "parse failed\n");
        return ret;
    }

    ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
    if (ret) {
        hid_err(hdev, "hw start failed\n");
        return ret;
    }

    return 0;
}

static const struct hid_device_id vrc2_devices[] = {
    { HID_USB_DEVICE(USB_VENDOR_ID_VRC2, USB_DEVICE_ID_VRC2) },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(hid, vrc2_devices);

static struct hid_driver vrc2_driver = {
    .name = "vrc2",
    .id_table = vrc2_devices,
    .report_fixup = vrc2_report_fixup,
    .probe = vrc2_probe,
};
module_hid_driver(vrc2_driver);

MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>");
MODULE_DESCRIPTION("HID driver for VRC-2 2-axis Car controller");
MODULE_LICENSE("GPL");

BPF for HID drivers

Benjamin Tissoires, One of the maintiners for the HID core layer, has posted his work [6] to introduce eBPF (extended Berkely Packet Filter) support for HID devics which is a really cool thing. As many devices just lack a proper report descriptor, the eBPF let you write such fixup in userspace and simply load the program into the kernel. There is still some parts missing before we can see full support for this feature, but the main part is merged and will be available in 6.1.

See the LWN article [5] for further reading.

Conclusion

HID report descriptors has been a fun subject to dig into. It's still hard to see why different vendors has so hard to follow the specification though.

I also have to thank Benjamin Tissoires for great help and support in understanding how the HID layer and HID devices works.

Industrial I/O and triggers

Industrial I/O and triggers

I've maintained a couple of IIO-drivers (MCP3911 [4] and LTC1660 [5]) for some time now and it's time to give at least the MCP3911 a face-lift.

This time the face lift includes support for:

  • Buffers
  • Triggers
  • Make the driver interrupt driven
  • Add support for setting Oversampling Ratio
  • Add support for setting PGA (Pre Gain Amplifier)

Also clean it up a bit by only using device managed resources.

What is Industrial I/O?

Industrial I/O, or IIO [1], is a subsystem that exposes sensors and actuators in a common way to userspace. The subsystem supports a range of different sensors including ADCs, IMUs, pressure sensors, light sensors, accelerometers and more. Even actuators such as DACs and amplifiers has its place in the IIO subsystem.

The hwmon [2] subsystem provides an interface for a few types of sensors as well, but the framework lack support to cover some use cases that IIO tries to solve, such as:

  • High speed sensors
  • Triggered sampling
  • Data buffering

In short, use hwmon for slow sensors and actuators, otherwise use IIO (preferred for new devices).

The IIO subsystem also provides a stable ABI for various userspace HALs which hwmon does not. libiio [3] is the official and preferred one for the IIO.

/media/iio.png

Sysfs

All IIO devices is exported by sysfs where those can be configured and read single shot values. For example, the raw value of the first channel of an ADC can be read out by:

cat /sys/bus/devices/iio:device/in_voltage0_raw

All other parameters such as oversampling ratio and scaling value is also exposed here.

Scaling value

The value you get from in_voltageX_raw is the raw value, it means that it has to be converted in order to get something meaningful out of it.

To get the value in mV you have to take the scale and offset value into account:

Value in mV = (raw + offset) * scale

All these values are exposed by sysfs in in_voltage_scale and in_voltage_offset respectively.

Triggers

Triggers can be both hardware and software based.

Example on hardware based triggers are:

  • GPIO-based interrupts

Example on software based triggers are:

  • sysfs - you can trig a data poll from userspace
  • hrtimer - let you specify the period and a High Resolution timer will be created and trig a data poll at a given frequency

CONFIG_IIO_SYSFS_TRIGGER

By enable CONFIG_IIO_SYSFS_TRIGGER you can make use of the sysfs trigger

# echo 0 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
# cat /sys/bus/iio/devices/iio_sysfs_trigger/trigger0/name
sysfstrig0

CONFIG_IIO_HRTIMER_TRIGGER

By enable CONFIG_IIO_HRTIMER_TRIGGER you can make use of a timer based trigger

# mount -t configfs none /sys/kernel/config
# mkdir /config/iio/triggers/hrtimer/my_50ms_trigger
# echo 2000 > /sys/bus/iio/devices/trigger0/sampling_frequency

Make use of a trigger

As long as the device supports triggers, there will be an /sys/bus/iio/devices/iio:device0/trigger/current_trigger entry. All available triggers, both hardware and software based, are located in /sys/bus/iio/devices/triggerX.

One nice feature is that one trigger can be used for multiple devices.

In order to activate a trigger for a certain device, simply write the trigger name to the current_trigger entry:

# cat /sys/bus/iio/devices/trigger0/name > /sys/bus/iio/devices/iio:device0/trigger/current_trigger

The next step is to decide and enable those channels you want to scan

# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_en
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage1_en
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_timestamp_en

And finally, start the sampling process

# echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable

Now you will get the raw values for voltage0, voltage1 and the timestamp by reading from the /dev/iio:device0 device.

You will read out a stream of data from the device. Before applying the scale value to the raw data, the buffer data may be processed somehow depending on its format. The format for each channel is available as a sysfs entry as well:

# cat /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_type
be:s24/32>>0

The buffer format for voltage0 means that each sample is 32 bits wide, does not need any shifting and should be intepreted as a signed 24-bit value.

Conclusion

The IIO subsystem is rather complex. The framework also supports events which makes it possible to trig on specific threshold values. As the subsystem is optimized for performance and the triggers makes it possible to read values at a given frequency or event, this makes a lot more use cases possible than the older hwmon interface.

FYI, the patches for MCP3911 is currently up to be merged into mainline.