Tuesday, June 25, 2019

Linux: How does the kernel invoke the correct driver for the corresponding /dev file ops?

To answer this question, I am picking snippets of texts from different blogs that I read online.
"When devfs is not being used, adding a new driver to the system means assigning a major number to it. The assignment should be made at driver (module) initialization by calling the following function, defined in <linux/fs.h>:
int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops);
The return value indicates success or failure of the operation. A negative return code signals an error; a 0 or positive return code reports successful completion. The major argument is the major number being requested, name is the name of your device, which will appear in /proc/devices, and fops is the pointer to an array of function pointers" [1]
Thus, this will result in the registration of the device driver with a major number with the kernel. 
"Once the driver has been registered in the kernel table, its operations are associated with the given major number. Whenever an operation is performed on a character device file associated with that major number, the kernel finds and invokes the proper function  from the  file_operations structure. For this reason, the pointer passed to register_chrdev should point to a global structure within the driver, not to one local to the module’s initialization function."

How to create a file in /dev with the major number?
We use the mknod command to do this:

E.g. mknod /dev/scull2 c 251 2

This will create a file scull2 with the major number 251 and minor number 2. So now every time we do an operation on this file, the kernel looks up the major number associated with this file and using the index into an array stored in the kernel finds the appropriate handlers registered by the driver with the same major number.

This is another reason why the file ops structure registered by the driver e.g. like the one below has to be GLOBAL in scope:

struct file_operations gpio_fops = {    unlocked_ioctl: gpio_ioctl_desc,    open:       gpio_open_routine,    release:    gpio_close_routine};

It is global because it is invoked directly by the kernel once there are any operations on the corresponding file in /dev.

[1] https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch03s02.html
[2] https://www.fsl.cs.sunysb.edu/kernel-api/re941.html