Embedded Linux Device Driver Development, Part 3: Coding Your First Driver
https://www.pinterest.com/pin/313774299020000464/

Embedded Linux Device Driver Development, Part 3: Coding Your First Driver

This is part 3 of the series 'Weekend Hacking, Embedded Linux Device Driver (LDD) Development from the Ground Up'.

In Part 2 (The Linux Device Model) we saw how devices, buses, and drivers are neatly laid out in a hierarchy that has several viewports via the sysfs virtual filesystem. User space utilities like udev use these viewports to populate the /dev directory. One viewport shows devices grouped into classes via /sys/class/. Another viewport, /sys/dev/, shows the top of the hierarchy where devices are grouped into either block or char devices (there are also network devices). Going forward we will be exclusively dealing with char devices.

In this part, we will take our first baby steps developing a simple device driver (a hello world driver if you will) as a loadable kernel module. We will compile and deploy this module on our RPi board. No need to rebuild the kernel or reboot the device to load the driver.

All the code in this series is posted on GitHub (https://github.com/neerajkc/weekend_ldd.git). Feel free to play around.

So, what exactly is a kernel module? It is a piece of code, written following certain convention, that extends the functionality of the kernel. Note it has nothing to do with drivers. Drivers can be either statically built into the kernel so that they are available immediately after bootup, or we can write them as kernel modules. These modules can be loaded to and unloaded dynamically from the virtual memory of the kernel as we wish, without rebooting. As we saw earlier, the statically built modules are located in /lib/modules/ directory on the root filesystem, as shown below on my RPi.

No alt text provided for this image

Alright, time to code our first module. Following is the code listing for the module (first_module.c):

No alt text provided for this image

The MODULE_* macros are self-explanatory. Basically, there are a few license options, but don’t expect debugging help from the kernel community for anything other than GPL (or dual MIT/GPL). Loading a non-GPL module marks the kernel as tainted. That also precludes certain kernel optimization and debugging features.

The module_param() macro (defined in linux/moduleparam.h) lets us pass parameters while loading the module. It takes the parameter name, its type, and read/write permissions as arguments. The __init and __exit macros in the function signatures are for the linker to invoke memory optimization, with __init attribute marking text and data sections in the init function for one time use and then freed.?

Now, every module must define init and exit functions. The module_init() and module_exit() macros export the symbols for the functions we provide for this purpose, thus enabling the kernel code to identify and invoke these. Basically, init is where memory allocations (device structures) happen and exit is where the teardown happens.

Here, pr_info() macro (defined in include/linux/printk.h) is a wrapper around printk(), the kernel space equivalent of the familiar user-space printf() function we all know. Here’s the definition:

#define pr_info(fmt,...) printk(KERN_INFO pr_fmt(fmt),##__VA_ARGS__)        

KERN_INFO is one among the eight different log severity levels defined in include/linux/kern_levels.h.

Great! Now let’s build this. Use the following Makefile to build the module and transfer to the board.

No alt text provided for this image

Replace the IP address of the RPi with yours. Do a make and make deploy at the terminal.

No alt text provided for this image

Of course, you can transfer the module to RPi by other means, I just find scp more convenient.

On the RPi, use insmod to load the module and rmmod to unload. Remember to do this as root as all kernel activities require root priviledges. Note, we can pass any string as parameter to the module while loading. You can check the kernel log with dmesg to see what the module prints out.?

No alt text provided for this image

We can also check that the module is listed in the /sys/module/ directory:

No alt text provided for this image

Perfect! Now that we know the basics of building and loading a module, let’s see how we can leverage it to build a char driver. We have already seen in part 2 that a struct device is the lowest level structure in the device model and is usually embedded inside a more meaningful and functional structure such as a char device (or a platform device, etc). Moreover, a struct file_operations is also associated with such structures, defining what we could do with such devices.?

The structures struct cdev and struct file_operations are defined in linux/cdev.h and linux/fs.h, respectively, so we include them and extend our code (the file first_char_driver.c in the repo). The following code snippet shows declaration of a dummy char device and associated file operations on it.

No alt text provided for this image

The init function needs to be extended to allocate device identifiers (major:minor numbers that we saw in part 2) for the number of devices the driver expects to handle. This is done by the register_chrdev_region() function below. Here we allocate only one device (denoted as 100:0). We then allocate the cdev structure and register it to the kernel space, following which any read/write operations on the device will be handled by file operation callbacks of the driver.

No alt text provided for this image

Let’s test this. Add first_char_driver.o to the obj-m field of the Makefile, recompile and deploy as earlier. You should be able to load and remove the the new module just like we did earlier. You should also see the driver allocating dummy_char_device with major number 100 in /proc/devices but the device doesn’t exist yet (in /dev directory)

No alt text provided for this image

We can create a dummy device in the /dev directory using the mknod command:

No alt text provided for this image

We might now be tempted to read from this device. If we do a cat on this device (it’s a virtual file anyway), we observe that, despite the error, it tries to open and close the device with our dummy_dev_open() and dummy_dev_close() callback functions:

No alt text provided for this image

Phew! We finally have reached a stage where we can create virtual devices and drivers that handle operations on them. There is a problem though. We chose the major number for the char device as 100. But Linux folks have already allocated that to a telephony device:?

No alt text provided for this image

Going forward we will see how to avoid such programming errors and cut down upon the laborious, yet diligent, allocation and release of resources in the driver. The kernel provides several device managed APIs to do just this.

See you soon!

Other parts of this series:

Part 0: Preliminaries

Part 1: Tinkering with the Kernel

Part 2: The Linux Device Model

Part 4: Platform Device Drivers

Part 5: Interfacing Sensors via the IIO Framework

要查看或添加评论,请登录

Neeraj Kumar, PhD的更多文章

社区洞察

其他会员也浏览了