Linux kernel MFD driver resources

In Linux Multi Function Device (MFD) driver example, we created simple MFD core and function devices drivers. This example shows how to pass memory and IRQ resources to MFD child device drivers.

In example below core driver passes one memory and one IRQ reosurces. misc device driver, gets those resources in its platform device data structure and use the same way as those are provided in its own device tree.

Example sources

drivers/mfd/example-core.c:

// SPDX-License-Identifier: GPL-2.0
/*
 * Example MFD core driver
 *
 * Rajan Vaja 
 */
#include 
#include 
#include 

static struct resource example_clk_resources[] = {
	{
		.name = "mem1_base",
		.start = 0x80000000,
		.end   = 0x8FFFFFFF,
		.flags = IORESOURCE_MEM,
	},
	{
		.name = "irq1_base",
		.start = 67,
		.end   = 67,
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
	},
};

static const struct mfd_cell example_func_devs[] = {
	{
		.name = "example-clk",
		.of_compatible = "test,example-clk",
	},
	{
		.name = "example-misc",
		.of_compatible = "test,example-misc",
		.num_resources = ARRAY_SIZE(example_clk_resources),
		.resources = example_clk_resources,
	},
};

static int example_core_probe(struct platform_device *pdev)
{
	int ret;

	dev_info(&pdev->dev, "#### %s() Adding MFD devices ####\n", __func__);
	ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE,
				   example_func_devs,
				   ARRAY_SIZE(example_func_devs),
				   NULL, 0, NULL);
	dev_info(&pdev->dev, "#### %s() MFD probed ####\n", __func__);

	return ret;
}

static int example_core_remove(struct platform_device *pdev)
{
	pr_info("#### %s() ####\n", __func__);
	return 0;
}

static const struct of_device_id example_core_of_id_table[] = {
	{ .compatible = "test,example-core" },
	{ }
};
MODULE_DEVICE_TABLE(of, example_core_of_id_table);

static struct platform_driver example_core_driver = {
	.driver = {
		.name           = "example-core-drv",
		.of_match_table = example_core_of_id_table,
	},
	.probe                  = example_core_probe,
	.remove                 = example_core_remove,
};

module_platform_driver(example_core_driver);

MODULE_AUTHOR("Rajan Vaja");
MODULE_DESCRIPTION("Example core platform driver");
MODULE_LICENSE("GPL v2");

drivers/misc/example-misc.c:

// SPDX-License-Identifier: GPL-2.0
/*
 * Example misc function driver
 *
 * Rajan Vaja 
 */
#include 
#include 
#include 

static int example_misc_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct resource *res;
	u32 val;
	int ret;

	dev_info(&pdev->dev, "#### %s() ####\n", __func__);

	ret = of_property_read_u32(np, "example-u32", &val);
	if (ret dev, "%s() sample-u32 property read fail %d\n",
			 __func__, ret);
		return ret;
	}
	dev_info(&pdev->dev, "%s() val = 0x%08x\n", __func__, val);

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem1_base");
	if (!res) {
		dev_err(&pdev->dev, "%s() get mem1 resource failed\n",
				__func__);
		return ret;
	};
	dev_info(&pdev->dev, "%s() name = %s start = 0x%08llx end = 0x%08llx\n",
		 __func__, res->name, res->start, res->end);

	res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq1_base");
	if (!res) {
		dev_err(&pdev->dev, "%s() get irq1 resource failed\n",
				__func__);
		return ret;
	};

	dev_info(&pdev->dev, "%s() name = %s start = 0x%08llx end = 0x%08llx\n",
		 __func__, res->name, res->start, res->end);

	return ret;
}

static int example_misc_remove(struct platform_device *pdev)
{
	pr_info("#### %s() ####\n", __func__);

	return 0;
}

static const struct of_device_id example_misc_of_id_table[] = {
	{ .compatible = "test,example-misc" },
	{ }
};
MODULE_DEVICE_TABLE(of, example_misc_of_id_table);

static struct platform_driver example_misc_driver = {
	.driver = {
		.name           = "example-misc-drv",
		.of_match_table = example_misc_of_id_table,
	},
	.probe                  = example_misc_probe,
	.remove                 = example_misc_remove,
};

module_platform_driver(example_misc_driver);

MODULE_AUTHOR("Rajan Vaja");
MODULE_DESCRIPTION("Example misc function driver");
MODULE_LICENSE("GPL v2");

 

Advertisements

Linux Multi Function Device (MFD) driver example

In Create a simple platform driver, we created simple platform device driver. If there is device which can provide multiple functionalities, its driver may not go in particular subsystems. For an example, lets say there is chip which can also work as codec & sensors, its full driver can neither go in sound/soc/codec or in sensors subsystem. In this case, MFD provides way to register multiple functionalities through MFD core.

For this, MFD provides way to divide functionalities into core and specific function drivers. Generic functionalities(e.g. register access, generic initialisation, common functionalities, etc), can go in mfd while device specific functionalities(e.g. codec specific functionalities) can go in their specific driver.

devm_mfd_add_devices() in core, registers MFD functionalities which populates individual function devices and invokes probe for those drivers.

Functional driver may use parent device data/DT properties if needed. MFD core can pass resources(memory or IRQ) to MFD devices while registering  MFD devices.

Example

e.g. in below example, I have created three separate drivers:

  1. core driver(drivers/mfd/example-core.c) – common code & functional devices registration
  2. drivers/clk/clk-example.c – Clock function driver
  3. drivers/misc/example-misc.c – Some other miscellaneous function driver

Example source files:

drivers/mfd/example-core.c:

// SPDX-License-Identifier: GPL-2.0
/*
 * Example MFD core driver
 *
 * Rajan Vaja <rajan.vaja@gmail.com>
 */
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/platform_device.h>

static const struct mfd_cell example_func_devs[] = {
	{
		.name = "example-clk",
		.of_compatible = "test,example-clk",
	},
	{
		.name = "example-misc",
		.of_compatible = "test,example-misc",
	},
};

static int example_core_probe(struct platform_device *pdev)
{
	int ret;

	dev_info(&pdev->dev, "#### %s() Adding MFD devices ####\n", __func__);
	ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE,
				   example_func_devs,
				   ARRAY_SIZE(example_func_devs),
				   NULL, 0, NULL);
	dev_info(&pdev->dev, "#### %s() MFD probed ####\n", __func__);

	return ret;
}

static int example_core_remove(struct platform_device *pdev)
{
	pr_info("#### %s() ####\n", __func__);
	return 0;
}

static const struct of_device_id example_core_of_id_table[] = {
	{ .compatible = "test,example-core" },
	{ }
};
MODULE_DEVICE_TABLE(of, example_core_of_id_table);

static struct platform_driver example_core_driver = {
	.driver = {
		.name           = "example-core-drv",
		.of_match_table = example_core_of_id_table,
	},
	.probe                  = example_core_probe,
	.remove                 = example_core_remove,
};

module_platform_driver(example_core_driver);

MODULE_AUTHOR("Rajan Vaja");
MODULE_DESCRIPTION("Example core platform driver");
MODULE_LICENSE("GPL v2");

drivers/clk/clk-example.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Example clock function driver
 *
 * Rajan Vaja <rajan.vaja@gmail.com>
 */
#include <linux/module.h>
#include <linux/platform_device.h>

static int example_clk_probe(struct platform_device *pdev)
{
	dev_info(&pdev->dev, "#### %s() ####\n", __func__);

	return 0;
}

static int example_clk_remove(struct platform_device *pdev)
{
	pr_info("#### %s() ####\n", __func__);

	return 0;
}

static const struct of_device_id example_clk_of_id_table[] = {
	{ .compatible = "test,example-clk" },
	{ }
};
MODULE_DEVICE_TABLE(of, example_clk_of_id_table);

static struct platform_driver example_clk_driver = {
	.driver = {
		.name           = "example-clk-drv",
		.of_match_table = example_clk_of_id_table,
	},
	.probe                  = example_clk_probe,
	.remove                 = example_clk_remove,
};

module_platform_driver(example_clk_driver);

MODULE_AUTHOR("Rajan Vaja");
MODULE_DESCRIPTION("Example clock function driver");
MODULE_LICENSE("GPL v2");

drivers/misc/example-misc.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Example misc function driver
 *
 * Rajan Vaja <rajan.vaja@gmail.com>
 */
#include <linux/module.h>
#include <linux/platform_device.h>

static int example_misc_probe(struct platform_device *pdev)
{
	dev_info(&pdev->dev, "#### %s() ####\n", __func__);

	return 0;
}

static int example_misc_remove(struct platform_device *pdev)
{
	pr_info("#### %s() ####\n", __func__);

	return 0;
}

static const struct of_device_id example_misc_of_id_table[] = {
	{ .compatible = "test,example-misc" },
	{ }
};
MODULE_DEVICE_TABLE(of, example_misc_of_id_table);

static struct platform_driver example_misc_driver = {
	.driver = {
		.name           = "example-misc-drv",
		.of_match_table = example_misc_of_id_table,
	},
	.probe                  = example_misc_probe,
	.remove                 = example_misc_remove,
};

module_platform_driver(example_misc_driver);

MODULE_AUTHOR("Rajan Vaja");
MODULE_DESCRIPTION("Example misc function driver");
MODULE_LICENSE("GPL v2");

 

Linux kernel debug print custom identifier

Sometimes it is easier to have our own identifier in print messages to easily filter/grep messages from the dmesg. It is also useful when we want to have some fixed data (e.g. __func__ or __LINE__). This also helps in debugging.

We can format pr_messages by defining pr_fmt macro.

#define pr_fmt(fmt) "<custom-fmt>" fmt, <custom-param-for-custom-fmt>

e.g. you can print kernel module name using below:

    #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

Example

I generally define it like  below format for my debug messages. It prints out “rmv” and function name before any pr_* messages.

#define pr_fmt(fmt) " rmv: %s() %d " fmt, __func__, __LINE__

Below is print statement at line 25 in my_func()

pr_info("Hello World\n");

Its output would like below after timestamp:

rmv: my_func()  25 Hello World

 

 

Passing Commandline arguments to Kernel module

Just like a C program, we can also pass command line arguments to kernel module. However, mechanisms are different in both the case.

In C, commandline arguments are stored in argv[] array while in linux kernel module, we set value of parameter from commandline.

In C, we pass arguments below way:

# ./prog 1 2 3 4  Hello

Here char * argv[] is set as {“prog”, 1″ , “2”, “3”, “4”, “Five”}.

In kernel module, to pass arguments to module, variables that need to take values from command line arguments are declared as global and then module_param() is used to setup command line mechanism.

# insmod kernel_module.ko mychar='a' myint=2345 mystring="Hello"

This will set intial values of variables mychar = ‘a’, myint = 2345 and mystring = “Hello”.

module_param(value, type, perm) macro takes three paramters:

* @value: the variable to alter, and exposed parameter name.
* @type: the type of the parameter
* @perm: visibility in sysfs.

Example:

static mychar = 'A';
module_param(mychar, unsigned char, 0);
MODULE_PARM_DESC(mychar,"this is the unsigned char variable");

Linux kernel: Print Device tree nodes

To print node name, there are different specifiers in Linux kernel. You can pass device node pointer (struct device_node *) to print function to print device node in different formats.

Lets say I have node named “my-node” in device tree:

/{
...
    my-node {
        my-sub-node@0x12345678 {
            compatible = "test,compatible-string";
        };
    };
...
};


Printing node (lets say sub-node) using device node pointer:

%pOF => /my-node/my-sub-node@0x12345678
%pOFf => /my-node/my-sub-node@0x12345678
%pOFfp => /my-node/my-sub-node@0x12345678:52
(52 is phandle)
%pOFfcF => /my-node/my-sub-node@0x12345678:test,compatible-string:–P-
Last are flags as below:
D – dynamic
d – detached
P – Populated
B – Populated bus

reference: Documentation/printk-formats.txt

Tested on: linux-4.14

Linux Kernel: Print Callee and Caller

Print the callback function name

If you cannot trace which function gets called when you call from core driver using function pointer, you can print function name using pf format specifier.
Linux maintains symbol table so based on function address, it can identify function name based on address pointed by function pointer.
e.g. Core driver calls hardware specific enable/disable functions:
core->ops->enable(). If you are not sure, which function is executed as a result of this call, you can print them using %pf or %pF:
pr_info("callee function name is: %pf,  core->ops->enable)
%pf prints without offset (e.g. foo) while %pF prints with offset (e.g. foo+0x00/0x80)
Reference: https://www.kernel.org/doc/Documentation/printk-formats.txt.
Thanks Bhargav(br13patel) for sharing this!

Print caller name

There are few ways to trace caller in Linux kernel.
  • Print stack backtrace
    • dump_stack();
      • To enable the dump_stack() function in the kernel config the following options must be set.
        1. Kernel hacking -> Kernel debugging
        2. Kernel hacking -> Verbose kernel error messages
    • WARN_ON(1);
  • Print caller name
    • pr_info(“caller function name is: %pf,  __builtin_return_address(0))

Device Tree Debugging

DTB-DTS conversion (Linux kernel)

  • Convert DTS file to DTB
  • ./scripts/dtc/dtc -I dts -O dtb -o .dtb .dts
  • Convert DTB file to DTS

  • ./scripts/dtc/dtc -I dtb -O dts -o .dts .dtb

 U-boot FDT commands

  • print fdt address
  • print $fdt_addr_r (or $fdt_addr)
  • Set FDT node property
  • fdt addr $fdt_addr
    fdt set /testnode testprop testvalue
  • remove FDT node property
  • fdt addr $fdt_addr
    fdt rm /testnode testprop
  • Remove DT node
  • fdt rm /testnode
  • List FDT nodes
  • fdt list                         /* All nodes */
    fdt list /cpus                   /* All nodes under cups node */
  • Create node
  • fdt mknode / testnode

References: