The Linux character device driver framework is described in detail

  • 2020-05-14 05:52:08
  • OfStack

Linux character device driver framework

Character device is Linux3 big equipment 1 (the other two is a piece of equipment, network equipment), character device is I/O byte streams form the communication equipment, most of the equipment are all characters, common character device including the mouse, keyboard, display, serial port and so on, when we implement ls - l/dev, can see a lot of equipment files, c is character device, b is a piece of equipment, network equipment, there is no corresponding device files. To write a character device driver for an external module, in addition to the code needed to write a module, you also need to write the code as a character device.

Driver model

Linux1 cut all files, so as a device file, its operation method interface encapsulation in struct file_operations, when we write a driver, 1 set to implement the corresponding interface, so that the driver is available, the kernel Linux extensive use of "registered + callback mechanism in the preparation of the driver, the so-called register callback, simple to understand, is when we open1 a device file, is to find corresponding VFS inode, And execute the open function registered in inode when creating this device file, as well as other functions. Therefore, in order for the driver we wrote to be normally operated by the application, the first thing we need to do is to implement the corresponding method, and then create the corresponding device file.


#include <linux/cdev.h> //for struct cdev
#include <linux/fs.h>  //for struct file
#include <asm-generic/uaccess.h>  //for copy_to_user
#include <linux/errno.h>      //for error number


/*  Prepare the set of operation methods  */
/* 
struct file_operations {
  struct module *owner;  //THIS_MODULE
  
  // Read the device 
  ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  // Write equipment 
  ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

  // Map kernel space to user space 
  int (*mmap) (struct file *, struct vm_area_struct *);

  // Read and write device parameters, read device status, control device 
  long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

  // Open the equipment 
  int (*open) (struct inode *, struct file *);
  // Close the equipment 
  int (*release) (struct inode *, struct file *);

  // Refresh the equipment 
  int (*flush) (struct file *, fl_owner_t id);

  // File location 
  loff_t (*llseek) (struct file *, loff_t, int);

  // Asynchronous notification 
  int (*fasync) (int, struct file *, int);
  //POLL mechanism 
  unsigned int (*poll) (struct file *, struct poll_table_struct *);
   . 
};
*/

ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
  return 0;
}

struct file fops = {
  .owner = THIS_MODULE,
  .read = myread . 
  ...
};


/*  Character device object type  */
struct cdev {
  //public  
  struct module *owner;        // Module owner ( THIS_MODULE ) for module counting 
  const struct file_operations *ops; // Operation method set (division of labor : Open, close, read / Write, ... ) 
  dev_t dev;             // Device no. (no 1 A) 
  unsigned int count;         // Number of devices 
  //private
  ...
};

static int __init chrdev_init(void)
{
  ...
  /*  structure cdev The device object  */
  struct cdev *cdev_alloc(void);

  /*  Initialize the cdev The device object  */
  void cdev_init(struct cdev*, const struct file_opeartions*);

  /*  Request device number for character device statically  */
  int register_chedev_region(dev_t from, unsigned count, const char* name);

  /*  Dynamically request the main device number for the character device  */
  int alloc_chedev_region(dev_t* dev, unsigned baseminor, unsigned count, const char* name);

  MKDEV(ma,mi)  // Combine the main device number and the secondary device number into the device number 
  MAJOR(dev)   // from dev_t Get the main device number in the data 
  MINOR(dev)   // from dev_t The secondary device number is obtained from the data 

  /*  Register character device object cdev To the kernel  */
  int cdev_add(struct cdev* , dev_t, unsigned);
  ...
}

static void __exit chrdev_exit(void)
{
  ...
  /*  Log out from the kernel cdev The device object  */
  void cdev_del(struct cdev* );

  /*  Log out from the kernel cdev The device object  */
  void cdev_put(stuct cdev *);

  /*  Recovery equipment no.  */
  void unregister_chrdev_region(dev_t from, unsigned count);
  ...
}

Implement read, write

Linux under the process of each process has its own independent space, even if is the kernel of data is mapped to a user process, the data of PID will be automatically converted into the user process PID, as a result of the existence of this mechanism, we can not directly copy the data from the kernel space and user space, and need special copy data function/macros:


long copy_from_user(void *to, const void __user * from, unsigned long n)

long copy_to_user(void __user *to, const void *from, unsigned long n)

These two functions copy kernel space data into the user process space of the user process that calls back the function. With these two functions, read and write in the kernel can copy kernel space and user space data.


ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
  long ret = 0;
  size = size > MAX_KBUF?MAX_KBUF:size;
  if(copy_to_user(user_buf, kbuf,size)
    return -EAGAIN;
  }
  return 0;
}

Implement ioctl

ioctl is a system call interface specially designed by Linux for user layer control devices. This interface has great flexibility. It can be used to realize the functions that our device intends to enable users to realize through the commands. ioctl's function pointer in the operation method set is long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); Where the commands and parameters are completely specified by the driver, Linux suggests defining the ioctl() command as shown in figure 1

Device type serial number direction data size
8bit 8bit 2bit 13/14bit

Here, the device type field is 1 magic number, which can be between 0 and 0xff. "ioctl-number.txt" in the kernel gives a recommended and used magic number (but no one has been maintaining it for a long time), and the new device driver should avoid conflicts when defining the magic number. The direction field of the command code is 2bit, indicating the direction of data transmission. The possible values are: _IOC_NONE, _IOC_READ, _IOC_WRITE and _IOC_READ|_IOC_WRITE. The data field of the command code represents the size of the user data involved, and the width of this member is architecture-dependent, usually 13 or 14 bits. The kernel also defines four macros, _IO(), _IOR(), _IOW(), and _IOWR(), to help generate commands in this format. The purpose of these macros is to generate a command code based on the passed type(device type field), nr(serial number field) and size(data length field) and the direction field shift of the macro name bank. There are also some I/O control commands predefined in the kernel. If a device driver contains a command code similar to the predefined command 1, these commands will be processed by the kernel as predefined commands rather than by the device driver. There are four types as follows:

FIOCLEX: that is, file ioctl close on exec sets a special flag for files to notify the kernel to automatically close open files when the exec() system is running FIONCLEX: file ioctl not close on exec, clear the flag set by FIOCLEX FIOQSIZE: gets the size of a file or directory, and returns an ENOTTY error when used for device files FIONBIO: file ioctl non-blocking I/O this call modifies flip- > The O_NONBLOCK logo in f_flags

We can include the driver design command in a header file, recording the user program and the driver's command contract. Here is a simple example


//mycmd.h
...
#include <asm/ioctl.h>
#define CMDT 'A'
#define KARG_SIZE 36
struct karg{
  int kval;
  char kbuf[KARG_SIZE];
};
#define CMD_OFF _IO(CMDT,0)
#define CMD_ON _IO(CMDT,1)
#define CMD_R  _IOR(CMDT,2,struct karg)
#define CMD_W  _IOW(CMDT,3,struct karg)
...

//chrdev.c
static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
  static struct karg karg = {
    .kval = 0,
    .kbuf = {0},
  };
  struct karg *usr_arg;

  switch(cmd){
  case CMD_ON:
    /*  Turn on the light  */
    break;
  case CMD_OFF:
    /*  Turn off the lights  */
    break;
  case CMD_R:
    if(_IOC_SIZE(cmd) != sizeof(karg)){
      return -EINVAL;
    }
    usr_arg = (struct karg *)arg;
    if(copy_to_user(usr_arg, &karg, sizeof(karg))){
      return -EAGAIN;
    }
    break;
  case CMD_W:   
    if(_IOC_SIZE(cmd) != sizeof(karg)){
      return -EINVAL;
    }
    usr_arg = (struct karg *)arg;
    if(copy_from_user(&karg, usr_arg, sizeof(karg))){
      return -EAGAIN;
    }
    break;
  default:
    ;
  };
  return 0;
}

Create device files

With the inserted device module, we can use the cat /proc/devices command to view the device currently registered by the system, but we have not created the corresponding device file, so the user cannot access the device through the file. The inode of the device file should contain the device number, operation method set pointer and other information of the device, so that we can find the corresponding inode and access the device through the device file. Create device file there are two kinds of methods, created manually or automatically created, manually create device file is to use mknod dev/xxx device type of main equipment, equipment, command to create, so first you need to use cat proc/devices check equipment major number and find equipment device number of times through the source code, it is important to note that the theoretical device files can be placed in any file to add clip, but in the "/ dev" to match Linux equipment management mechanism, devtmpfs is a file system designed to manage device files. Once the device file is created, it will be bound to the device specified at the time of creation. Even if the device has been uninstalled, to delete the device file, you only need to delete rm like normal file 1. In theory, there is no relationship between module name (lsmod), device name (/proc/devices), and device name (/dev), so it can be different from one. However, in principle, it is recommended to unify the three into one for easy management.

In addition to the crappy way of manually creating device nodes, we can also use the corresponding measures in the device source code to automatically create device files as soon as device 1 is loaded. The automatic creation of device files requires that we have the corresponding configuration when compiling the kernel or when making the root file system:


Device Drivers --->
    Generic Driver Options --->
      [*]Maintain a devtmpfs filesystem to mount at /dev
      [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs
OR

Make the boot script for the root file system


mount -t sysfs none sysfs /sys
mdev -s //udev To also go 

With this in mind, simply export the corresponding device information to "/sys" and automatically create the device files as per our requirements. The kernel gives us the relevant API


class_create(owner,name);
struct device *device_create_vargs(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, va_list vargs);

void class_destroy(struct class *cls);  
void device_destroy(struct class *cls, dev_t devt);

With these functions, we can automatically create and delete device files by filling in the following code in xxx_init() and xxx_exit() of the device


 /*  in /sys Export device class information  */
  cls = class_create(THIS_MODULE,DEV_NAME);

  /*  in cls Created in the class pointed to 1 group ( a ) Device file  */
  for(i= minor;i<(minor+cnt);i++){
    devp = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEV_NAME,i);
  } 


/*  in cls Delete from the class pointed to 1 group ( a ) Device file  */
  for(i= minor;i<(minor+cnt);i++){
    device_destroy(cls,MKDEV(major,i));
  }

  /*  in /sys Delete device class information in  */
  class_destroy(cls);       //1 Make sure you unload first device To uninstall class

With that done, a simple character device driver is set up, and now you can write a user program to test ^ - ^

Thank you for reading, I hope to help you, thank you for your support of this site!


Related articles: