Book Image

Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization

By : Kaiwan N. Billimoria
Book Image

Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization

By: Kaiwan N. Billimoria

Overview of this book

Linux Kernel Programming Part 2 - Char Device Drivers and Kernel Synchronization is an ideal companion guide to the Linux Kernel Programming book. This book provides a comprehensive introduction for those new to Linux device driver development and will have you up and running with writing misc class character device driver code (on the 5.4 LTS Linux kernel) in next to no time. You'll begin by learning how to write a simple and complete misc class character driver before interfacing your driver with user-mode processes via procfs, sysfs, debugfs, netlink sockets, and ioctl. You'll then find out how to work with hardware I/O memory. The book covers working with hardware interrupts in depth and helps you understand interrupt request (IRQ) allocation, threaded IRQ handlers, tasklets, and softirqs. You'll also explore the practical usage of useful kernel mechanisms, setting up delays, timers, kernel threads, and workqueues. Finally, you'll discover how to deal with the complexity of kernel synchronization with locking technologies (mutexes, spinlocks, and atomic/refcount operators), including more advanced topics such as cache effects, a primer on lock-free techniques, deadlock avoidance (with lockdep), and kernel lock debugging techniques. By the end of this Linux kernel book, you'll have learned the fundamentals of writing Linux character device driver code for real-world projects and products.
Table of Contents (11 chapters)
1
Section 1: Character Device Driver Basics
3
User-Kernel Communication Pathways
5
Handling Hardware Interrupts
6
Working with Kernel Timers, Threads, and Workqueues
7
Section 2: Delving Deeper

Writing the misc driver code – part 2

Armed with this knowledge, look again at the init code of ch1/miscdrv/miscdrv.c. You will see that, just as described in the previous section, we have initialized the fops member of the miscdev struct to a file_operations structure, thus setting up the functionality of the driver. The relevant code snippet (from our driver) is as follows:

static const struct file_operations llkd_misc_fops = {
.open = open_miscdrv,
.read = read_miscdrv,
.write = write_miscdrv,
.release = close_miscdrv,
};

static struct miscdevice llkd_miscdev = {
[ ... ]
.fops = &llkd_misc_fops, /* connect to this driver's 'functionality' */
};

So, now you can see it: when a user space process (or thread) that has opened our device file invokes, say, a read(2) system call, the kernel VFS layer will follow the pointers (generically, filp->f_op->foo()) and invoke the function, read_miscdrv(), in effect handing over control to the device driver! How exactly the read method is written is covered in the next section.

Continuing with the init code of our simple misc driver:

    [ ... ] 
/* Retrieve the device pointer for this device */
dev = llkd_miscdev.this_device;
pr_info("LLKD misc driver (major # 10) registered, minor# = %d,"
" dev node is /dev/%s\n", llkd_miscdev.minor, llkd_miscdev.name);
dev_info(dev, "sample dev_info(): minor# = %d\n", llkd_miscdev.minor);
return 0; /* success */
}

Our driver retrieves a pointer to the device structure – it's something required by every driver. Within the misc kernel framework, it's available within the this_device member of our miscdevice structure.

Next, pr_info() shows the minor number dynamically obtained. The dev_info() helper routine is more interesting: as a driver author, you are expected to use these dev_xxx() helpers when emitting printk; it will also prefix useful information about the device. The only difference in syntax between the dev_xxx() and pr_xxx() helpers is that the first parameter to the former is the pointer to the device structure.

Okay, let's get our hands dirty! We build the driver and insmod it into kernel space (we use our lkm helper script to do so):

Figure 1.4 – Screenshot of building and loading our miscdrv.ko skeleton misc driver on an x86_64 Ubuntu VM

(By the way, as you can see in Figure 1.4, I tried out this misc driver on a more recent distro: Ubuntu 20.04.1 LTS running the 5.4.0-58-generic kernel.) Notice the two prints toward the bottom of Figure 1.4; the first is emitted via the pr_info() (prefixed with the pr_fmt() macro content, as explained in the companion guide Linux Kernel Programming - Chapter 4, Writing Your First Kernel Module - LKMs Part 1 section Standardizing printk output via the pr_fmt macro). The second print is emitted via the dev_info() helper routine – it's prefixed with the words misc llkd_miscdrv, indicating that it originated from the kernel's misc framework, and specifically from the llkd_miscdrv device! (The dev_xxx() routines are versatile; depending on the bus they're on, they will display various details. This is useful for debugging and logging purposes. We repeat: you're recommended to use the dev_*() routines when writing drivers.) You can also see that the /dev/llkd_miscdrv device node is indeed created, with the expected type (character) and major and minor pair (10 and 56 here).