Built-In Kernel Driver Still Need Device Tree

Built-in kernel driver still need device tree?

Built-in kernel driver still need device tree? Yes. Concept of device tree is orthogonal to whether drivers are built-in or compiled as modules. Device tree contains information about the hardware. Platform bus passes that info to relevant drivers.

This is a good brief article about platform bus, and how things used to be before device tree: https://www.codeproject.com/tips/1080177/linux-platform-device-driver. Device tree factors out harware info otherwiese hard-coded in kernel code, making the code more portable.

Device Tree Vs .Config file | Are they different?

Device Tree Vs .Config file | Are they different?

Yes, they are different, and serve different purposes at different times.

The .config file is an integral component of the kernel's build procedure.
The .config file is data to control the building of the kernel, that is, its contents is used by makefiles.

A .config file could be customized to build a kernel specifically for just one specific version of a SBC.

Or the .config file could specify a kernel with a plethora of features, subsystems, and drivers for a variety of capabilities and support families of SBCs. Such a kernel image could be booted by any compatible board.


A .config file consists of lines of configuration symbols, e.g. CONFIG_xxx=....

Assignments of strings or numeric constants are rare, but do exist, e.g. CONFIG_LOCALVERSION="my_version" and CONFIG_INIT_ENV_ARG_LIMIT=32.

Typically the assignment is y to affirm that the configuration item is enabled.

A configuration item that is disabled is not asigned n, but rather commented out, e.g. # CONFIG_xxx is not set.

A configuration item that is "tristate" can be assigned m to affirm that the configuration item is enabled but built as a loadable module (rather than statically linked, aka built-in).

Note that although kernel source code may contain preprocessor directives (e.g. #ifdef for conditional compilation) utilizing CONFIG_xxx symbols that appear identical to the .config symbols, these symbols are not equivalent.

Kernel source code does not read or use the .config file.

Rather the .config file is converted into a C header file of preprocessor statements with a #define for each enabled CONFIG_xxx line, and stored in include/generated/autoconf.h (the exact path has changed for older versions).

It is the autoconf.h file that defines the CONFIG_xxx symbols used in kernel source code.

Note that a tristate configuration item that is assigned m in the .config becomes #define CONFIG_xxx_MODULE 1 in the autoconf.h file, rather than just #define CONFIG_xxx 1.


Device Tree is used by only a handful of CPU architectures in the Linux kernel.

The Device Tree (blob) is data to inform the executing kernel of its hardware environment (i.e. the target board). It also informs the kernel which device drivers to initialize (using the compatible property string).

Regardless of how many device drivers are statically linked into the kernel or available as loadable modules, a device driver is only initialized if there is a DT device node that references that device driver (e.g. see Driver code in kernel module doesn't execute?).

(The exception to that are buses that have self-identifying devices such as USB and PCI/PCIe, which have different procedures for initializing their device drivers.)


The Device Tree goes through several transformations before it is utilized by an executing kernel.

The DT source file(s) for a specific board exist as one .dts and optional .dtsi (included) files that are compiled into a .dtb file, aka DT blob.

It is the DT blob that is loaded with the kernel during boot, and then converted from its "flat" organization into a tree structure in kernel memory.

Kernel routines, e.g. device drivers, access the DT using methods provided by of_*() routines (where the "of" is for Open Foundation).

Device node and driver without actual device

If your device is actually not present in the board, and you want to keep the device node in the device tree structure. you should use status = "disabled" in your device node. This property tells that device is not currently available. In future whenever your device is available you can make this status = "ok".

And yes driver will be built always, only bindings won't happen, as there is no corresponding device exists.

Device Tree and manual registration

Dynamically modifying the loaded device tree is not something we usually do, although it's possible.

I understand you don't really care about the device tree for this new device.

I suggest you create a new module to add your device and, once it's loaded (after insmoding it), insmod your driver module. In fact, the order doesn't matter. When you add a device, all drivers will be checked and the ones that match will be probed, and when you add a driver, all devices are checked against it.

To create the device, you first allocate it:

struct platform_device *pdev;
int inst_id = 1; /* instance unique ID: base address would be a good choice */
pdev = platform_device_alloc("unique_name_here", inst_id);

Then, you will want to create resources, at least one for the memory mapped range. For this, create and fill an array of struct resource. A struct resource is pretty simple. Here's an example on how to fill a memory resource:

struct resource res = {
.start = 0x50000000,
.end = 0x50001fff,
.name = "some_name",
.flags = IORESOURCE_MEM,
};

Once you have that, add it to the platform device you're building:

platform_device_add_resources(pdev, &res, 1);

Make sure res is not on the stack, though (make it global, or kzalloc it and kfree when unloading the module).

You're now ready to add the platform device:

platform_device_add(pdev);

Device tree aside, platform devices are matched to platform drivers by the "platform bus" (not a real actual physical bus) by name. So your platform driver will need to provide an equivalent name (unique_name_here hereabove). Your platform driver will have something like:

static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "unique_name_here",
.owner = THIS_MODULE,
},
};

module_platform_driver(my_platform_driver);

and voilà. Your driver should be probed if a platform device with the same name was added.

Drivers using the device tree add another member to .driver, which is .of_match_table. A match table (array of strings) is given there. The match is then using the compatible property of device tree nodes.

Replace kernel builtin module with a loadable one

What I don't understand is why the platform device is not beeing found when the device-tree hasn't been changed except for status field value.

You seem to misunderstand what the status = "disable" attribute actually means.

Besides it meaning that the kernel should "not having it inserted at boot", a disabled node means that the device is not part of the current hardware configuration at all.

The driver, whether built-in or loadable module, is simply not to be probed because it has been disabled for the current configuration.

If you want your driver, whether built-in or loadable module, to be in the current configuration, then have a status = "okay" attribute in its Device Tree node.

IOW the Device Tree is for describing the current hardware configuration to the kernel.

Do not try to use the Device Tree to control loadable modules (since it cannot).

Here I'm in the situation where the module is loaded at boot by kernel as it's builtin and it appears into the device tree.

This statement makes no sense, as you seem to be describing your driver simultaneously as a built-in module as well as a loadable module.

A built-in driver does not have to be "loaded" in order to invoke its probe routine.

Because a driver can be either built-in or loadable, "loading" and "probing" are two distinct phases, and should not be conflated.

So I would like to keep the driver's code integrated into linux kernel but not having it inserted at boot.

You seem to be conflating the concept of the Linux kernel with the source code tree.

"Driver's code integrated into linux kernel" would typically be interpreted to mean a built-in driver, that is the driver is linked in and part of the kernel image that is loaded at boot.

Whereas driver code that is stored in the kernel source tree confers no designation has to whether it is a built-in or a loadable module. Many drivers (and other types of modules) can be built as either, and it is the build configuration that specifies which.

If you want your driver to be a loadable module, then (instead of changing the Device Tree):

a. you need to code your driver as a proper module;

b. you need to modify the Kconfig file to choose between a built-in or loadable module (i.e. a tristate versus bool selection specification).

c. configure the kernel to build your driver as a loadable module.


A device driver that is a loadable module and defined in the Device Tree could still be automatically loaded and probed during boot.
You may have to use module blacklisting to prevent that.


** ADDENDUM **

How to replace a builtin module with a loadable one without recompiling kernel to disable the builtin module?

You can't. That's why Kconfig forces you to choose between a loadable module (m) or built-in (y) if you want that driver to be built.

... then, once everything has been done, add those modifications to the builtin moduleusing a patch.

This makes no sense, since you only need a single copy of the driver source to build either a loadable module or a built-in version of the driver.

With this way of working I can just rebuild the .ko and insert it on my target platform instead of recompiling a kernel for each modification.

Seems like how you integrated your driver into the kernel source is questionable.

What have you actually done to integrate your driver into the kernel source tree?

Which Kconfig and Makefile did you modify for your driver?

What new CONFIG_* symbol(s) did you create for your driver?

Yes, you have to "recompile for each modification", but make is smart enough to rebuild only what is necessary. You could further shorten the kernel rebuild time by using make modules when you know that only your loadable driver has been modified.


TO CONCLUDE

  • It's not possible to use out-of-tree loadable module without recompiling the kernel to disable the builtin one.

BUT

  • Recompiling kernel only once with tristate at M and blacklisting the module on kernel boot line succeed.
  • Recompiling kernel only once with tristate at n succeed.

So the kernel has to be recompiled at least once, but then it's possible to work with an out-of-tree driver compiled as loadable module without having to remove the code integrated into linux sources.



Related Topics



Leave a reply



Submit