Build Linux kernel driver demo subsystem example

Original text: https://blog.csdn.net/luckyapple1028/article/details/50642906?spm=1001.2014.3001.5501
The original author is really powerful. Reprint and backup, learn to worship.

Generally writing embedded Linux In the case of kernel driver, in the simplest case, only a simple misc driver needs to be written. It only needs to be compatible with a hardware peripheral and a specific chip platform. The biggest disadvantage of this driver is poor scalability and portability. It is often necessary to change the driver source code if there are small changes on the board hardware, Some as like as two peas are added to the same hardware, sometimes a similar driver is required to be written.

A good Linux kernel driver requires that it can quickly adapt to different platforms with as few changes as possible, and can support multiple devices. Linux kernel designs a platform bus driver framework for embedded devices that are not connected to the physical bus (PCI, I2C, USB, etc.), which can well meet the requirements of general embedded peripheral drivers. However, if there are many peripherals with similar but different functions, they need to design corresponding subsystems for unified management. There are many kinds of subsystems in the Linux kernel, some of which are quite complex and some are relatively simple. They not only manage different types of peripheral drivers, but also shield their differences and provide a unified interface to the user space. For example, unified management of RTC driven RTC subsystem, unified management of frame buffer subsystem of graphic display equipment, and even very large and complex network subsystem and file system subsystem.

This paper refers to the kernel RTC subsystem and extracts a simple demo driver subsystem framework example program, which can be applied to some simple Linux device driver development.

Example environment:

Cross compilation tool chain: arm bcm2708 Linux gnueabi-
Linux kernel: linux-rpi-4.1.y

Board hardware: Raspberry pie b

Source code link: https://github.com/luckyapple1028/linux-demo-subsys-module


1, demo subsystem framework

Frame structure diagram:


Sample program list: demo_core.c,demo_dev.c,demo_interface.c,demo_proc.c,demo_sysfs.c,xxx_demo_driver.c,xxx_demo_device.c,demo-core.h,demo_dev.h,demo.h

1,demo_core

demo is the core of driver management. It provides an interface to the device driver to complete the registration and unloading of the driver to the kernel.

2,demo_dev

Responsible for the management of character device files driven by demo, including registering and unregistering character devices, and defining a series of device control interfaces including read, write, ioctl, etc.

3,demo_interface.c

The demo device iotcl control function interface is provided, which is responsible for the unified management and call of the actual driver interface of the lower layer.

4,demo_proc.c

The demo device query and control interface of proc file system is provided.

5,demo_sysfs.c

The demo device query and control interface of sys file system is provided, which depends on the standard Linux device driver model.

6,xxx_demo_driver.c

Specific peripheral drivers. Different peripherals can be implemented separately according to their own characteristics. They are really different parts, and then they are uniformly registered with the demo subsystem. As an example in this article, the driver relies on the platform driven model.

7,xxx_demo_device.c

The specific peripherals are the same as the device driver XXX in this paper_ demo_ Driver matching is responsible for registering the platform device and passing specific physical peripheral parameters to the driver. (Note: the traditional method is used here, which can be replaced by Device Tree in the new Linux).

2, Structure

1,xxx_demo

  1. struct xxx_demo {
  2. struct demo_device *demo; /* demo Subsystem general device pointer */
  3. struct timer_list xxx_demo_timer;
  4. unsigned long xxx_demo_data;
  5. /* something else */
  6. };
xxx_demo structure is a specific demo driven control structure. Different peripheral drivers are different, and can include some data and special structures they need. For example, this example structure includes a kernel timer structure and a data structure. In addition to the specific driving data, the most important thing is the demo_ The device pointer, which is the core of the demo subsystem, is responsible for the role of threading. After the driver registers with the demo subsystem, it will generate specific objects. Let's analyze this structure in detail.

2,demo_device

  1. struct demo_device
  2. {
  3. struct device dev;
  4. struct module *owner;
  5. int id;
  6. char name[DEMO_DEVICE_NAME_SIZE];
  7. const struct demo_class_ops *ops;
  8. struct mutex ops_lock;
  9. struct cdev char_dev;
  10. unsigned long flags;
  11. unsigned long irq_data;
  12. spinlock_t irq_lock;
  13. wait_queue_head_t irq_queue;
  14. struct fasync_struct *async_queue;
  15. /* some demo data */
  16. struct demo_data demo_data;
  17. };
In this structure, the struct device dev ice structure is the device structure of the Linux device driver model, which needs to be initialized when registering the device with the kernel; The struct module *owner pointer is generally set to THIS_MODULE; id is the demo device number. Since the subsystem can support multiple demo drive devices, it needs to be numbered to facilitate management; Name is the name of the device driver; struct demo_class_ops *ops is the control function set of the demo driver and the control function interface registered by the driver with the subsystem. The value will be assigned when the driver is registered. Later, the subsystem is responsible for calling the specific driver function interface; struce cdev char_dev is the character device structure of demo device; Other struct mutex ops_lock and spinlock_t irq_lock lock is used to protect the critical area data reached by iotcl operation and protection interrupt operation, irq_queue is a waiting queue, which is used to implement blocking io operations, async_queue is used for signal asynchronous io operation. The last demo_data is an example data structure managed by the demo subsystem (the above items can be deleted according to the specific needs of the driving subsystem).

3,demo_data

  1. struct demo_data
  2. {
  3. unsigned long text_data;
  4. /* something else */
  5. };

This structure is included in the demo_ The device structure contains some general parameters in the demo driver and the parameters that need unified management proposed by the demo subsystem in order to shield the underlying differences and provide users with a unified interface. Here is only an exemplary demo_data.


4,demo_class_ops

  1. struct demo_class_ops {
  2. int (*open)(struct device *);
  3. void (*release)(struct device *);
  4. int (*ioctl)(struct device *, unsigned int, unsigned long);
  5. int (*set_data)(struct device *, struct demo_ctl_data *);
  6. int (*get_data)(struct device *, struct demo_ctl_data *);
  7. int (*proc)(struct device *, struct seq_file *);
  8. int (*read_callback)(struct device *, int data);
  9. };
This structure is the registered function interface of demo driver, which is composed of specific XXX_ The demo driver is responsible for implementing and registering with the demo subsystem, but not every instance needs to be implemented. It only needs to implement the functions supported by the driver. It will be called in the demo subsystem according to the needs of users. The open, release and ioctl interfaces here are very familiar. The following set_data and get_data is only used as an example to set and obtain specific data in the driver. The proc interface is used for proc file system calls. You will see the usage later.


3, demo subsystem and driver flow analysis


1. demo subsystem initialization



Here, the initialization of the demo subsystem conforms to most Linux peripheral driver subsystem initialization schemes. Let's read the code briefly:

  1. /* demo Subsystem initialization */
  2. static int __init demo_core_init(void)
  3. {
  4. /* Create demo class */
  5. demo_class = class_create(THIS_MODULE, "demo");
  6. if (IS_ERR(demo_class)) {
  7. pr_err("couldn't create class\n");
  8. return PTR_ERR(demo_class);
  9. }
  10. /* demo Device driver initialization */
  11. demo_dev_init();
  12. /* demo proc initialization */
  13. demo_proc_init();
  14. /* demo sysfs initialization */
  15. demo_sysfs_init(demo_class);
  16. pr_info("demo subsys init success\n");
  17. return 0;
  18. }
First, a class is created. See the Linux device driver model for specific functions. It is mainly used to automatically create device files and control nodes in the sysfs directory. And then call demo_. dev_ Init() function:

  1. void __init demo_dev_init(void)
  2. {
  3. int err;
  4. err = alloc_chrdev_region(&demo_devt, 0, DEMO_DEV_MAX, "demo");
  5. if (err < 0)
  6. pr_err("failed to allocate char dev region\n");
  7. }
This function applies to the kernel for the master device number and the maximum DEMO_DEV_MAX secondary device numbers, that is, demo can be supported at most_ DEV_ Max demo character device, which can be dynamically adjusted as needed (8 bits, upper limit 255). Then initialize the function call demo_proc_init() function:

  1. void __init demo_proc_init(void)
  2. {
  3. /* Create demo proc directory */
  4. demo_proc = proc_mkdir("driver/demo", NULL);
  5. }
This function is very simple. It is to create a demo subdirectory in the / proc/driver / directory of the file system, and the proc nodes of subsequent drivers will be saved in this directory. Finally, initialize the function call demo_sysfs_init() function:

  1. void __init demo_sysfs_init(struct class *demo_class)
  2. {
  3. /* Bind the general sys node, which will be generated in turn when registering the device */
  4. demo_class->dev_groups = demo_groups;
  5. }
This function is bound to the collection of sysfs node operation functions. It will not immediately generate nodes in the / sys directory, but will be generated when the driver registers the device later. Demo here_ Group is an array of attribute function pointers, as follows:

  1. static struct attribute *demo_attrs[] = {
  2. &dev_attr_demo_name.attr,
  3. &dev_attr_demo_data.attr,
  4. NULL,
  5. };
  6. ATTRIBUTE_GROUPS(demo);
Where dev_attr_demo_name and dev_attr_demo_data is controlled by the macro DEVICE_ATTR_RO and DEVICE_ATTR_RW generation, they define read-only and read-write attribute nodes respectively, and bind the corresponding functions. For details, see< Self loading example and principle analysis of Linux device driver module>.

The demo subsystem initialization process here is just a simple framework model. For specific equipment subsystems, some other components will be initialized. After analyzing the initialization process, take a brief look at the de initialization process:

  1. static void __exit demo_core_exit(void)
  2. {
  3. demo_proc_exit();
  4. demo_dev_exit();
  5. class_destroy(demo_class);
  6. ida_destroy(&demo_ida);
  7. pr_info("demo subsys exit success\n");
  8. }

The de initialization process can be regarded as an inverse process of the initialization process, which is very straightforward. Let's analyze how a specific driver completes initialization and registers in the demo subsystem.


2. Drive the overall registration process


The initialization process of specific drivers can vary depending on the specific physical characteristics. Peripherals that depend on I2C communication can be initialized through I2C bus, and peripherals that depend on SPI communication can be initialized through SPI bus. For simplicity, this paper uses virtual platform bus for driver matching and registration to analyze the code in detail :

xxx_demo_driver:

  1. static struct platform_driver xxx_demo_driver = {
  2. .driver = {
  3. .name = "xxx_demo_device",
  4. .owner = THIS_MODULE,
  5. },
  6. .probe = xxx_demo_driver_probe,
  7. .remove = xxx_demo_driver_remove,
  8. };
demo0_device and demo1_device:

  1. static struct platform_device demo0_device = {
  2. .name = "xxx_demo_device",
  3. .id = 0,
  4. .dev = {
  5. .release = demo_device_release,
  6. }
  7. };
  8. static struct platform_device demo1_device = {
  9. .name = "xxx_demo_device",
  10. .id = 1,
  11. .dev = {
  12. .release = demo_device_release,
  13. }
  14. };
Xxx_demo_driver defines the probe function xxx_demo_driver_probe, which will be called by the platform_drv_probe() function when the platform device matches the platform driver; the platform driver is initialized by the driver module, and the function xxx_demo_driver_init() calls the platform_driver_register (& xxx_demo_driver) function for registration; the platform device is initialized by the driver module, and the function demo_device_init() Call the platform_device_register (& demox_device) function to register. Two xxx_demo devices are registered here to simulate multi device registration. It is worth noting that the more traditional method is used to register the platform device here, and the more appropriate way is to use the Linux device tree to complete the device registration (which will be supplemented later). Next, analyze the probe() function:

  1. static int xxx_demo_driver_probe(struct platform_device *pdev)
  2. {
  3. struct xxx_demo *xxx_demo = NULL;
  4. int ret = 0;
  5. /* Apply for drive structure memory and save it as private data of platform */
  6. xxx_demo = devm_kzalloc(&pdev->dev, sizeof(struct xxx_demo), GFP_KERNEL);
  7. if (!xxx_demo)
  8. return -ENOMEM;
  9. platform_set_drvdata(pdev, xxx_demo);
  10. /* Get platform resources */
  11. /* do something */
  12. /* Execute driver related initialization (including peripheral hardware, lock, queue, etc.)*/
  13. xxx_demo->xxx_demo_data = 0;
  14. /* do something */
  15. init_timer(&xxx_demo->xxx_demo_timer);
  16. xxx_demo->xxx_demo_timer.function = xxx_demo_time;
  17. xxx_demo->xxx_demo_timer.data = (unsigned long)xxx_demo;
  18. xxx_demo->xxx_demo_timer.expires = jiffies + HZ;
  19. add_timer(&xxx_demo->xxx_demo_timer);
  20. /* Register device with demo subsystem */
  21. xxx_demo->demo = devm_demo_device_register(&pdev->dev, "xxx_demo",
  22. &xxx_demo_ops, THIS_MODULE);
  23. if (IS_ERR(xxx_demo->demo)) {
  24. dev_err(&pdev->dev, "unable to register the demo class device\n");
  25. ret = PTR_ERR(xxx_demo->demo);
  26. goto err;
  27. }
  28. return 0;
  29. err:
  30. del_timer_sync(&xxx_demo->xxx_demo_timer);
  31. return ret;
  32. }
This function first applies to the kernel for the memory space of the structure instance of xxx_demo, and then sets the structure as the private data of pedv (note that this step is key, and later it is used to find the corresponding xxx_demo structure instance through pdev); then it can obtain some platform resource s and some physical peripheral parameters and resources (not described in detail in this example program) Next, you can start the initialization of the driver. You can set some physical registers of the driver peripherals or initialize a kernel timer as here. The next key is to call the devm_demo_device_register function to complete the registration with the demo subsystem.

  1. /* demo Device registration (using devm mechanism) */
  2. struct demo_device *devm_demo_device_register(struct device *dev,
  3. const char *name,
  4. const struct demo_class_ops *ops,
  5. struct module *owner)
  6. {
  7. struct demo_device **ptr, *demo;
  8. ptr = devres_alloc(devm_demo_device_release, sizeof(*ptr), GFP_KERNEL);
  9. if (!ptr)
  10. return ERR_PTR(-ENOMEM);
  11. /* Register demo device */
  12. demo = demo_device_register(name, dev, ops, owner);
  13. if (!IS_ERR(demo)) {
  14. *ptr = demo;
  15. devres_add(dev, ptr);
  16. } else {
  17. devres_free(ptr);
  18. }
  19. return demo;
  20. }
This function uses the devm mechanism of the kernel. It is an error recovery mechanism. When an error occurs in a certain initialization step, it does not need to drive the programmer to call the reverse initialization one by one. The following points of attention are focused on the most critical demo_device_register() function:

  1. /* demo Device registration */
  2. struct demo_device *demo_device_register(const char *name, struct device *dev,
  3. const struct demo_class_ops *ops,
  4. struct module *owner)
  5. {
  6. struct demo_device *demo;
  7. int of_id = -1, id = -1, err;
  8. /* Get ID number */
  9. if (dev->of_node)
  10. of_id = of_alias_get_id(dev->of_node, "demo");
  11. else if (dev->parent && dev->parent->of_node)
  12. of_id = of_alias_get_id(dev->parent->of_node, "demo");
  13. if (of_id >= 0) {
  14. id = ida_simple_get(&demo_ida, of_id, of_id + 1, GFP_KERNEL);
  15. if (id < 0)
  16. dev_warn(dev, "/aliases ID %d not available\n", of_id);
  17. }
  18. if (id < 0) {
  19. id = ida_simple_get(&demo_ida, 0, 0, GFP_KERNEL);
  20. if (id < 0) {
  21. err = id;
  22. goto exit;
  23. }
  24. }
  25. /* Start allocating memory */
  26. demo = kzalloc(sizeof(struct demo_device), GFP_KERNEL);
  27. if (demo == NULL) {
  28. err = -ENOMEM;
  29. goto exit_ida;
  30. }
  31. /* demo Structure initialization */
  32. demo->id = id;
  33. demo->ops = ops;
  34. demo->owner = owner;
  35. demo->dev.parent = dev;
  36. demo->dev.class = demo_class;
  37. demo->dev.release = demo_device_release;
  38. mutex_init(&demo->ops_lock);
  39. spin_lock_init(&demo->irq_lock);
  40. init_waitqueue_head(&demo->irq_queue);
  41. strlcpy(demo->name, name, DEMO_DEVICE_NAME_SIZE);
  42. dev_set_name(&demo->dev, "demo%d", id);
  43. /* Character device initialization */
  44. demo_dev_prepare(demo);
  45. err = device_register(&demo->dev);
  46. if (err) {
  47. put_device(&demo->dev);
  48. goto exit_kfree;
  49. }
  50. /* Add character device, sysfs device and proc device registration */
  51. demo_dev_add_device(demo);
  52. demo_sysfs_add_device(demo);
  53. demo_proc_add_device(demo);
  54. dev_notice(dev, "demo core: registered %s as %s\n", demo->name, dev_name(&demo->dev));
  55. return demo;
  56. exit_kfree:
  57. kfree(demo);
  58. exit_ida:
  59. ida_simple_remove(&demo_ida, id);
  60. exit:
  61. dev_err(dev, "demo core: unable to register %s, err = %d\n", name, err);
  62. return ERR_PTR(err);
  63. }
This function first looks for and obtains the of_id number from the of nodes of dev (PEDV - > dev here) and dev - > parent, and then obtains a new ID number based on the of_id. here, because there is no of_node, it will directly obtain the idle IDA from demo_ida. For the two demo driver devices registered in the previous article, 0 and 1 will be allocated here respectively. The demo_ida here is defined by the macro: static DEFINE_IDA(demo_ida);

Next, the registration function applies to the kernel for the memory of the demo_device structure and assigns values, assigns the id number and the demo driver operation function pointer OPS respectively, initializes the demo - > dev structure, specifies the parent as PEDV - > Dev and the class as demo_class, specifies the structure release callback function, and then initializes the lock and waiting queue. Note that the function pointer OPS here points to xxx_demo_drive xxx_demo_ops implemented in R:

  1. struct demo_class_ops xxx_demo_ops = {
  2. .open = xxx_demo_open,
  3. .release = xxx_demo_release,
  4. .ioctl = xxx_demo_ioctl,
  5. .set_data = xxx_demo_set_data,
  6. .get_data = xxx_demo_get_data,
  7. .proc = xxx_demo_proc,
  8. .read_callback = xxx_demo_read,
  9. };

Then assign a value to the device name. Demo - > name is "xxx_demo", and demo - > dev.name is "demoX" , the latter name will be used as the name of / dev / directory device file, procfs node file and sysfs node directory. Similarly, other different components can be initialized according to different requirements. Next, call demo_dev_prepare function to initialize the character device structure according to the assigned id number and character device number:

  1. void demo_dev_prepare(struct demo_device *demo)
  2. {
  3. if (!demo_devt)
  4. return;
  5. if (demo->id >= DEMO_DEV_MAX) {
  6. dev_warn(&demo->dev, "%s: too many demo devices\n", demo->name);
  7. return;
  8. }
  9. /* Character device structure initialization */
  10. demo->dev.devt = MKDEV(MAJOR(demo_devt), demo->id);
  11. cdev_init(&demo->char_dev, &demo_dev_fops);
  12. demo->char_dev.owner = demo->owner;
  13. }

You can see that the character device number is composed of the primary device number and id number allocated during subsystem initialization as the secondary device, and then bind the FOPS function structure demo_dev_fops:

  1. static const struct file_operations demo_dev_fops = {
  2. .owner = THIS_MODULE,
  3. .llseek = no_llseek,
  4. .read = demo_dev_read,
  5. .poll = demo_dev_poll,
  6. .unlocked_ioctl = demo_dev_ioctl,
  7. .open = demo_dev_open,
  8. .release = demo_dev_release,
  9. .fasync = demo_dev_fasync,
  10. };

After initializing the character device structure, call the device_register() function to register the device with the Linux kernel. After registration, the corresponding directory will be generated in the / sys directory and the attr attribute files demo_data and demo_name bound in the previous text will be generated. Then call demo_dev_add_device() Function to add a character device to the kernel. After adding, the corresponding / demoX device node will be generated in the / dev directory.

  1. void demo_dev_add_device(struct demo_device *demo)
  2. {
  3. /* Register character device */
  4. if (cdev_add(&demo->char_dev, demo->dev.devt, 1))
  5. dev_warn(&demo->dev, "%s: failed to add char device %d:%d\n",
  6. demo->name, MAJOR(demo_devt), demo->id);
  7. else
  8. dev_dbg(&demo->dev, "%s: dev (%d:%d)\n", demo->name,
  9. MAJOR(demo_devt), demo->id);
  10. }

The registration function then calls the demo_sysfs_add_device function to create some special attr nodes.

  1. void demo_sysfs_add_device(struct demo_device *demo)
  2. {
  3. int err;
  4. /* Conditional judgment */
  5. /* do something */
  6. /* Create some special sys nodes for the required devices */
  7. err = device_create_file(&demo->dev, &dev_attr_demodata);
  8. if (err)
  9. dev_err(demo->dev.parent,
  10. "failed to create alarm attribute, %d\n", err);
  11. }

This function can perform some conditional judgement after entering, to determine whether or not to create the attr attribute file. In this example, a dev_attr_demodata attribute file is created and the show and set functions are demo_sysfs_show_demodata() and demo_sysfs_set_demodata(). Finally, the registration function calls demo_proc_add_device() to create "demoX" in /proc/driver/demo directory. (dev_name (& Demo - > DEV)) and bind the file operation function:

  1. void demo_proc_add_device(struct demo_device *demo)
  2. {
  3. /* Assign proc to newly registered devices */
  4. proc_create_data(dev_name(&demo->dev), 0, demo_proc, &demo_proc_fops, demo);
  5. }
  1. static const struct file_operations demo_proc_fops = {
  2. .open = demo_proc_open,
  3. .read = seq_read,
  4. .llseek = seq_lseek,
  5. .release = demo_proc_release,
  6. };
After analyzing the initialization process of xxx_demo driver, let's take a brief look at the initialization process. The de initialization process is executed in the remove interface of xxx_demo driver module:

  1. static int xxx_demo_driver_remove(struct platform_device *pdev)
  2. {
  3. struct xxx_demo *xxx_demo = platform_get_drvdata(pdev);
  4. /* Execute driver related de initialization (including peripheral hardware, lock, queue, etc.)*/
  5. del_timer_sync(&xxx_demo->xxx_demo_timer);
  6. /* do something */
  7. /* Unregister the device to the demo subsystem */
  8. devm_demo_device_unregister(&pdev->dev, xxx_demo->demo);
  9. /* Free drive structure memory */
  10. devm_kfree(&pdev->dev, xxx_demo);
  11. return 0;
  12. }
This function first executes the de initialization of the specific driver components and closes the physical hardware functions (the sample program destroys the kernel timer that was initialized in the previous version), and then calls devm_. demo_ device_ unregister()->devres_ The release () function executes the logoff process to the demo subsystem, and finally devm_kfree releases the driver structure instance.
  1. void devm_demo_device_unregister(struct device *dev, struct demo_device *demo)
  2. {
  3. int res;
  4. /* Unregister demo device */
  5. res = devres_release(dev, devm_demo_device_release,
  6. devm_demo_device_match, demo);
  7. WARN_ON(res);
  8. }
This function calls devm_ demo_device_ The match function finds the required demo_ The device structure is then called devm_. demo_device_ Release execute demo device logout
  1. static void devm_demo_device_release(struct device *dev, void *res)
  2. {
  3. struct demo_device *demo = *(struct demo_device **)res;
  4. demo_device_unregister(demo);
  5. }
  1. void demo_device_unregister(struct demo_device *demo)
  2. {
  3. if (get_device(&demo->dev) != NULL) {
  4. mutex_lock(&demo->ops_lock);
  5. demo_sysfs_del_device(demo);
  6. demo_dev_del_device(demo);
  7. demo_proc_del_device(demo);
  8. device_unregister(&demo->dev);
  9. demo->ops = NULL;
  10. mutex_unlock(&demo->ops_lock);
  11. put_device(&demo->dev);
  12. }
  13. }
The initial function here is also very intuitive. First acquire the device (increase the reference count of the device), then lock up and release the proc and sysfs attribute files, then call device_. Unregister unregisters the device and finally puts it_ Device releases the device reference count. After the device reference count drops to 0, the release function demo registered during the previous initialization will be called_ device_ release():

  1. static void demo_device_release(struct device *dev)
  2. {
  3. struct demo_device *demo = to_demo_device(dev);
  4. ida_simple_remove(&demo_ida, demo->id);
  5. kfree(demo);
  6. }
Here, the id number is recovered and the demo is released_ Device structure. After the driver is registered successfully, let's analyze how the application layer calls the mechanism in the driver.


3. Application call overall process


The application layer can interact with the kernel demo subsystem through the device file / dev/demoX device file, procfs and sys attribute file. First, let's look at the previously registered demo_ dev_ open function in FOPS function set

  1. static int demo_dev_open(struct inode *inode, struct file *file)
  2. {
  3. struct demo_device *demo = container_of(inode->i_cdev, struct demo_device, char_dev);
  4. const struct demo_class_ops *ops = demo->ops;
  5. int err;
  6. if (test_and_set_bit_lock(DEMO_DEV_BUSY, &demo->flags))
  7. return -EBUSY;
  8. file->private_data = demo;
  9. /* Call driver layer open implementation */
  10. err = ops->open ? ops->open(demo->dev.parent) : 0;
  11. if (err == 0) {
  12. spin_lock_irq(&demo->irq_lock);
  13. /* do something while open */
  14. demo->irq_data = 0;
  15. spin_unlock_irq(&demo->irq_lock);
  16. return 0;
  17. }
  18. clear_bit_unlock(DEMO_DEV_BUSY, &demo->flags);
  19. return err;
  20. }
This function first finds the corresponding demo_device structure instance, and then through this structure, you can find the ops function set registered by the driver and try to call it_ demo_ The driver driver does not implement the function interface, so it will not be called. The specific driver can be implemented as appropriate.

In addition, it is worth noting how to find the corresponding demo here_ Device structure instance? What is the impact if the driver registers multiple fabric instances?

The key is in the first statement of this function, I in inode_ The cdev pointer stores the address of the cdev structure that the user wants to open the device file, but the structure is included in the demo_ In the device structure, you can find the corresponding demo through it_ Device instance. At the same time, because the Linux process will create a file structure after each file is opened, the demo will be found here_ The device instance is bound to the private data of the file structure. In the future, other system call operations of the user space on the device node will be able to accurately find the demo_device structure, no error will occur. For example, if two demo devices are registered in the previous article, two device files demo0 and demo1 will be generated in the / dev directory. When the user program opens demo 0, it will call the open function here, find the corresponding cdev structure through the device number saved in the demo 0 file, and finally find the corresponding demo through the cedv structure_ Device structure without affecting demo1. After opening the device, the user process can call read, ioctl and other system calls. Let's take a look at them respectively.

  1. static ssize_t demo_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
  2. {
  3. struct demo_device *demo = file->private_data;
  4. DECLARE_WAITQUEUE(wait, current);
  5. unsigned long data;
  6. ssize_t ret;
  7. /* Protect the amount of data read */
  8. if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
  9. return -EINVAL;
  10. /* Waiting for data to be ready */
  11. add_wait_queue(&demo->irq_queue, &wait);
  12. do {
  13. __set_current_state(TASK_INTERRUPTIBLE);
  14. spin_lock_irq(&demo->irq_lock);
  15. data = demo->irq_data;
  16. demo->irq_data = 0;
  17. spin_unlock_irq(&demo->irq_lock);
  18. if (data != 0) {
  19. ret = 0;
  20. break;
  21. }
  22. if (file->f_flags & O_NONBLOCK) {
  23. ret = -EAGAIN;
  24. break;
  25. }
  26. if (signal_pending(current)) {
  27. ret = -ERESTARTSYS;
  28. break;
  29. }
  30. schedule();
  31. } while (1);
  32. set_current_state(TASK_RUNNING);
  33. remove_wait_queue(&demo->irq_queue, &wait);
  34. /* Read data from the domo driver layer and transfer it to the application layer */
  35. if (ret == 0) {
  36. if (demo->ops->read_callback)
  37. data = demo->ops->read_callback(demo->dev.parent,
  38. data);
  39. if (sizeof(int) != sizeof(long) &&
  40. count == sizeof(unsigned int))
  41. ret = put_user(data, (unsigned int __user *)buf) ?:
  42. sizeof(unsigned int);
  43. else
  44. ret = put_user(data, (unsigned long __user *)buf) ?:
  45. sizeof(unsigned long);
  46. }
  47. return ret;
  48. }
The read function implements both synchronous non blocking and synchronous blocking interfaces. If the user has configured O_NONBLOCK, if the data is not ready, it will directly return failure, otherwise the process will sleep and know that the data is ready. When the data is ready, try to call the driver's read_callback function to obtain data, and finally send the data back to the application layer.

  1. static unsigned int demo_dev_poll(struct file *file, poll_table *wait)
  2. {
  3. struct demo_device *demo = file->private_data;
  4. unsigned long data;
  5. /* Join the waiting queue */
  6. poll_wait(file, &demo->irq_queue, wait);
  7. /* Read the data and judge whether the conditions are met (if not, the calling process will sleep) */
  8. data = demo->irq_data;
  9. return (data != 0) ? (POLLIN | POLLRDNORM) : 0;
  10. }
This interface implements an asynchronous blocking interface. When the user looks at the space and calls the poll, select or epoll interface, it will judge whether the data is ready here. If it is ready, the above system calls will return directly. Otherwise, it will sleep in the waiting queue prepared here (not here).

  1. static long demo_dev_ioctl(struct file *file,
  2. unsigned int cmd, unsigned long arg)
  3. {
  4. struct demo_device *demo = file->private_data;
  5. struct demo_ctl_data demo_ctl;
  6. const struct demo_class_ops *ops = demo->ops;
  7. void __user *uarg = (void __user *) arg;
  8. int err = 0;
  9. err = mutex_lock_interruptible(&demo->ops_lock);
  10. if (err)
  11. return err;
  12. switch (cmd) {
  13. case DEMO_IOCTL_SET:
  14. /* Process permission limit (optional). See capability.h for details */
  15. if (!capable(CAP_SYS_RESOURCE)) {
  16. err = -EACCES;
  17. goto done;
  18. }
  19. mutex_unlock(&demo->ops_lock);
  20. if (copy_from_user(&demo_ctl, uarg, sizeof(demo_ctl)))
  21. return -EFAULT;
  22. /* demo Example setting command function */
  23. return demo_test_set(demo, &demo_ctl);
  24. case DEMO_IOCTL_GET:
  25. mutex_unlock(&demo->ops_lock);
  26. /* demo Example get command function */
  27. err = demo_test_get(demo, &demo_ctl);
  28. if (err < 0)
  29. return err;
  30. if (copy_to_user(uarg, &demo_ctl, sizeof(demo_ctl)))
  31. return -EFAULT;
  32. return err;
  33. default:
  34. /* Try using the driver's ioctl interface */
  35. if (ops->ioctl) {
  36. err = ops->ioctl(demo->dev.parent, cmd, arg);
  37. if (err == -ENOIOCTLCMD)
  38. err = -ENOTTY;
  39. } else
  40. err = -ENOTTY;
  41. break;
  42. }
  43. done:
  44. mutex_unlock(&demo->ops_lock);
  45. return err;
  46. }
The ioctl interface is locked first, and then the user process permission is determined if the write operation is performed. Finally, the demo_ is called. Interface function demo in interface. C_ test_ Set () and demo_test_get() performs specific operations.

  1. int demo_test_set(struct demo_device *demo, struct demo_ctl_data *demo_ctl)
  2. {
  3. int err = 0;
  4. err = mutex_lock_interruptible(&demo->ops_lock);
  5. if (err)
  6. return err;
  7. if (demo->ops == NULL)
  8. err = -ENODEV;
  9. else if (!demo->ops->set_data)
  10. err = -EINVAL;
  11. else {
  12. /* do somerhing */
  13. demo->demo_data.text_data = demo_ctl->data;
  14. /* Call driver layer interface */
  15. err = demo->ops->set_data(demo->dev.parent, demo_ctl);
  16. }
  17. mutex_unlock(&demo->ops_lock);
  18. return err;
  19. }
  20. int demo_test_get(struct demo_device *demo, struct demo_ctl_data *demo_ctl)
  21. {
  22. int err = 0;
  23. err = mutex_lock_interruptible(&demo->ops_lock);
  24. if (err)
  25. return err;
  26. if (demo->ops == NULL)
  27. err = -ENODEV;
  28. else if (!demo->ops->get_data)
  29. err = -EINVAL;
  30. else {
  31. /* do somerhing */
  32. demo_ctl->data = demo->demo_data.text_data;
  33. /* Call driver layer interface */
  34. err = demo->ops->get_data(demo->dev.parent, demo_ctl);
  35. }
  36. mutex_unlock(&demo->ops_lock);
  37. return err;
  38. }

The implementation of these two functions in this sample program is relatively simple, namely setting and returning demo_ Text in data_ Data value, and then if the driver implements its own get_data and set_ The data function interface will call them. The idea here is a bit similar to the derivation in object-oriented. We have seen XXX in the previous article_ demo_ These two interfaces have been implemented in the driver driver:

  1. static int xxx_demo_set_data(struct device *dev, struct demo_ctl_data *ctrl_data)
  2. {
  3. struct xxx_demo *xxx_demo = dev_get_drvdata(dev);
  4. printk(KERN_INFO "xxx demo set data\n");
  5. xxx_demo->xxx_demo_data = ctrl_data->data;
  6. return 0;
  7. }
  8. static int xxx_demo_get_data(struct device *dev, struct demo_ctl_data *ctrl_data)
  9. {
  10. struct xxx_demo *xxx_demo = dev_get_drvdata(dev);
  11. printk(KERN_INFO "xxx demo get data\n");
  12. ctrl_data->data = xxx_demo->xxx_demo_data;
  13. return 0;
  14. }
These two functions in the driver will set and get the driver's own xxx_demo_data to replace the general demo in the demo subsystem_ Data. If these two interfaces are not implemented here, the values in the demo subsystem will not be changed. Then let's take a look at the last release interface in fops:

  1. static int demo_dev_release(struct inode *inode, struct file *file)
  2. {
  3. struct demo_device *demo = file->private_data;
  4. /* do something while exit */
  5. /* Call the release implementation of the driver layer */
  6. if (demo->ops->release)
  7. demo->ops->release(demo->dev.parent);
  8. clear_bit_unlock(DEMO_DEV_BUSY, &demo->flags);
  9. return 0;
  10. }
This interface will be called and executed when the application calls close(fd). First, perform some general release operations, and then similarly, if the driver implements its own release interface, call the driver's own interface to implement the driver's close operation. Let's take a look at the process of user interaction with the demo subsystem through procfs. First, let's take a look at the interface demo of the proc file system_ proc_ fops
  1. static const struct file_operations demo_proc_fops = {
  2. .open = demo_proc_open,
  3. .read = seq_read,
  4. .llseek = seq_lseek,
  5. .release = demo_proc_release,
  6. };
The implementation of several functions here is based on seq_file subsystem, in demo_ proc_ Seq is created in open_ Operations structure and seq_ The file structure instance is bound with the show function as demo_proc_show, when the user reads / proc/driver/demo/demoX, SEQ will be called_ read()->demo_proc_show() function:

  1. static int demo_proc_show(struct seq_file *seq, void *offset)
  2. {
  3. int err = 0;
  4. struct demo_device *demo = seq->private;
  5. const struct demo_class_ops *ops = demo->ops;
  6. /* Output the required subsys proc information */
  7. seq_printf(seq, "demo_com_data\t: %ld\n", demo->demo_data.text_data);
  8. seq_printf(seq, "\n");
  9. /* Output driver layer proc information */
  10. if (ops->proc)
  11. err = ops->proc(demo->dev.parent, seq);
  12. return err;
  13. }
Here you can call SEQ in turn_ Printf outputs some general information about subsystems. If the driver also needs to output and provides a proc interface, call it:

  1. static int xxx_demo_proc(struct device *dev, struct seq_file *seq)
  2. {
  3. struct xxx_demo *xxx_demo = dev_get_drvdata(dev);
  4. seq_printf(seq, "xxx_demo_data\t: %ld\n", xxx_demo->xxx_demo_data);
  5. seq_printf(seq, "\n");
  6. return 0;
  7. }
Through the proc interface, the application program can easily obtain the information on the kernel demo subsystem and driver. Finally, let's take a look at the interaction process between users through sysfs and demo subsystem. In the previous article, dev is registered in the driver registration process_ attr_ demo_ Name and dev_attr_demo_data two general properties files and one
dev_attr_demodata special properties file. Where dev_ attr_ demo_ The name property file is read-only, and the bound show interface function is
  1. static ssize_t
  2. demo_name_show(struct device *dev, struct device_attribute *attr, char *buf)
  3. {
  4. return sprintf(buf, "%s\n", to_demo_device(dev)->name);
  5. }
  6. static DEVICE_ATTR_RO(demo_name);
This function outputs the name of the device to user space. Another dev_ attr_ demo_ The data attribute file is readable and writable, and its bound show interface and store interface are:

  1. static ssize_t
  2. demo_data_show(struct device *dev, struct device_attribute *attr, char *buf)
  3. {
  4. return sprintf(buf, "%ld\n", to_demo_device(dev)->demo_data.text_data);
  5. }
  6. static ssize_t
  7. demo_data_store(struct device *dev, struct device_attribute *attr,
  8. const char *buf, size_t n)
  9. {
  10. struct demo_device *demo = to_demo_device(dev);
  11. unsigned long val = simple_strtoul(buf, NULL, 0);
  12. if (val >= 4096 || val == 0)
  13. return -EINVAL;
  14. demo->demo_data.text_data = (unsigned long)val;
  15. return n;
  16. }

Here, the demo in the subsystem is set and output respectively_ data. Finally, let's look at dev_ attr_ store interface of demodata:

  1. static ssize_t
  2. demo_sysfs_set_demodata(struct device *dev, struct device_attribute *attr,
  3. const char *buf, size_t n)
  4. {
  5. struct demo_device *demo = to_demo_device(dev);
  6. struct demo_ctl_data demo_ctl;
  7. unsigned long val = 0;
  8. ssize_t retval;
  9. val = simple_strtoul(buf, NULL, 0);
  10. if (val >= 4096 || val == 0)
  11. retval = -EINVAL;
  12. /* Call the interface interface to write driver data */
  13. demo_ctl.data = (unsigned long)val;
  14. retval = demo_test_set(demo, &demo_ctl);
  15. return (retval < 0) ? retval : n;
  16. }
After receiving the data entered by the user, the demo in the interface is encapsulated and called_ test_ The set interface is the same as ioctl. Users can also easily interact with drivers through the sysfs interface.


3, demo driver and subsystem demonstration


First, compile with the Makefile program as follows:
  1. ifneq ($(KERNELRELEASE),)
  2. obj-m := demo.o
  3. demo-objs := demo_core.o demo_dev.o demo_interface.o demo_proc.o demo_sysfs.o
  4. obj-m += xxx_demo_driver.o
  5. obj-m += xxx_demo_device.o
  6. else
  7. KDIR := /home/apple/raspberry/build/linux-rpi-4.1.y
  8. all:prepare
  9. make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-bcm2708-linux-gnueabi-
  10. cp *.ko ./release/
  11. prepare:
  12. mkdir release
  13. modules_install:
  14. make -C $(KDIR) M=$(PWD) modules_install ARCH=arm CROSS_COMPILE=arm-bcm2708-linux-gnueabi-
  15. clean:
  16. rm -f *.ko *.o *.mod.o *.mod.c *.symvers modul*
  17. rm -f ./release/*
  18. endif
After compilation, three module Ko files demo.ko and XXX are obtained in the relseae directory_ demo_ Device.ko and xxx_demo_driver.ko, copy them into raspberry pie and load them in sequence:

root@apple:~# insmod demo.ko                                                   
root@apple:~# insmod xxx_demo_driver.ko                                        
root@apple:~# insmod xxx_demo_device.ko                                        
root@apple:~# lsmod                                                            
Module                  Size  Used by
xxx_demo_device         1604  0 
xxx_demo_driver         2935  0 

demo                    9989  1 xxx_demo_driver

After loading, you can see the generated device file in the / dev / Directory:

root@apple:/dev# ls /dev/demo*                                                 

/dev/demo0  /dev/demo1

Then you can see the generated attribute file in the / proc/driver/demo / Directory:

root@apple:/dev# ls /proc/driver/demo/demo*                                    

/proc/driver/demo/demo0  /proc/driver/demo/demo1

Reading one of them is enough to see the output:

root@apple:/dev# cat /proc/driver/demo/demo0                                   
demo_com_data   : 0

xxx_demo_data   : 206

XXX here_ demo_ Data will continue to accumulate (about 1 every 1s)

Finally, you can see the generated two directories in the / sys/class/demo directory. They point to the corresponding link files in the device Directory:

root@apple:/sys/class/demo# ls                                                 
demo0  demo1

root@apple:/sys/class/demo# ls -l                                              
total 0
lrwxrwxrwx 1 root root 0 Jan  1 01:26 demo0 -> ../../devices/platform/xxx_demo_device.0/demo/demo0
lrwxrwxrwx 1 root root 0 Jan  1 01:27 demo1 -> ../../devices/platform/xxx_demo_device.1/demo/demo1

Open one of them to see:

root@apple:/sys/class/demo/demo0# ls                                           
demo_data  demo_name  demodata  dev  device  subsystem  uevent

Where demo_data,demo_name and demodata are the property files generated by the program in the previous article. You can read and write values

root@apple:/sys/class/demo/demo0# cat demodata                                 
0

root@apple:/sys/class/demo/demo0# echo 100 > demo_data                         
root@apple:/sys/class/demo/demo0# cat demodata                                 
100


4, Summary


Finally, to sum up, the core purpose of designing the demo subsystem is to extract the similar parts of different demo drivers for normalization. There can be XXX on the bottom layer_ demo_ driver,yyy_demo_driver,zzz_demo_drive, etc. They mainly provide a specific service for Linux applications, but they may have different communication buses, different register configurations, and even slight differences in functions. However, as long as a demo subsystem similar to the above can be designed, the differences of these drive peripherals can be shielded, The subsystem requires the driver to connect with it with a set of standard interfaces (unsupported functions can not be realized), so that the subsystem can normalize the management of these drivers, with clearer structure and clearer hierarchy, greatly reduce the code repetition rate, and finally provide a set of standard interfaces for the application program, and the design of application sequence can be greatly simplified.














Posted on Tue, 30 Nov 2021 16:09:45 -0500 by amchargue