Create a simple platform driver

This post explains how to create simple platform driver and get its probe called. The driver can be compiled as module or inbuilt in kernel. When driver’s compatible string matches with any device’s compatible string, driver’s probe is called. Creating simple platform driver would help to:

  • Get into Linux kernel world
  • Device tree basic concept

There are things needs to be done for this:

  • Add Kconfig option for driver option in kernel menuconfig
  • Add Makefile entry to build driver file(s)
  • Add driver source file
  • Add device node in DTS with compatible string matching with driver

As our driver doesn’t fall into any category, I have created as misc driver(drivers/misc/simple_misc_drv.c).

Add Kconfig option

Add below lines in drivers/misc/Kconfig:

config SIMPLE_MISC_DRIVER
	tristate "Simple misc driver"
	default n
	help
	  Simple misc platform driver. This driver is designed to demonstrate
	  platform driver and DTS compatible string scenarios.

Add Makefile entry to build driver file(s)

Add below lines in drivers/misc/Makefile:

obj-$(CONFIG_SIMPLE_MISC_DRIVER)	+= simple_misc_drv.o

Add driver source file

#include <linux/module.h>
#include <linux/platform_device.h>

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

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

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

static struct platform_driver simple_misc_driver = {
.driver = {
.name = "simple-misc-drv",
.of_match_table = simple_misc_of_id_table,
},
.probe = simple_misc_probe,
.remove = simple_misc_remove,
};

module_platform_driver(simple_misc_driver);

MODULE_AUTHOR("Rajan Vaja");
MODULE_DESCRIPTION("Simple misc platform driver");
MODULE_LICENSE("GPL v2");

 

Add device node in DTS

Add below node in device tree.

	simple_misc_device: simple-misc-device {
		compatible = "test,simple-misc-dev";
	};

 

Debugging

What to do if your probe is not called. Here is some checklist which I know:

  1. Driver is not getting compiled
  2. Driver registration is failing. This can be due to:
    1. Driver name duplication with any other driver
  3. Device is not registered due to:
    1. Device node “status” property is set to “disabled” in DTS. If so change it to “okay”.
Advertisements

Linux kernel module with multiple source files

lets say we have two source files from which we want to create modules. Lets say we have two source files from which we want to create modules.

Correct method

Correct syntax to be used in Makefile is as below:

obj-m += my_module.o
my_module-objs := my_module_file1.o my_module_file2.o

Incorrect method

Below syntax will try to create two separate independent modules and hence will not work:

obj-m += my_module_file1.o my_module_file2.o

Thanks Tejas Patel for this!

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))

Array Initialise selected indexes

#include <stdio.h>

#define ARRAY_SIZE(n) (sizeof(n)/sizeof(n[0]))

int main()
{
	int i;
	int arr[] = {
		[3] = 3,
		[5] = 5,
	};

	for (i = 0; i < ARRAY_SIZE(arr); i++)
		printf("arr[%d] = %d\n", i, arr[i]);

	return 0;
}

Output:

arr[0] = 0
arr[1] = 0
arr[2] = 0
arr[3] = 3
arr[4] = 0
arr[5] = 5