Linux Driver Development

Keywords: Linux network Makefile

1. Software system is divided into application program, library, operating system (kernel) and driver. The developer concentrates on one layer and knows the interface of the neighboring layer. For example, the application calls the library function open, and the library executes swi instructions according to the parameters passed in by open, causing the CPU to enter the kernel abnormally. The exception handling function of the kernel finds the corresponding driver according to the parameters. There is no boundary between the kernel and the driver, because the driver is ultimately programmed into the kernel. Drivers never run on their own initiative. In systems with MMU, applications are in user space and drivers are in kernel space.

2. Linux peripherals are divided into character devices (read and write in bytes), block devices (data read and write in blocks, data in format), and network interfaces (data read and write in blocks of variable size).

3. Developing steps of Linux device driver
(1) Initialize the driver, such as registering the driver with the kernel, so that when the application passes in the file name, the kernel can find the corresponding driver.
(2) the operation functions designed and implemented, such as open, etc.
(3) Implementing interruption service
(4) Compile the driver to the kernel. If the driver is dynamically compiled into a module, load and unload it with the insmod rmmod command. Loading: Initialization function of module is called and driver is registered with kernel. Uninstall: Call module cleanup function.
(5) Testing

4. The application program calls the hardware startup program by using a unified interface function (system call). The function set of the character device driver is in the file_operations structure of include/linux/fs.h

//Provide uniform file-operated interfaces for driver functions, such as open, read, etc.
//When an application opens a device using the open function, the open function in file_operations is called. From this point of view, writing a character device driver is to write the required functions for the file_operations structure of the specific hardware.
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*dir_notify)(struct file *filp, unsigned long arg);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};

5. Initialization function is invoked when installing driver, and file_operations structure and main device number that appear in driver are registered with the kernel.

//For character devices, use the following functions to register
int register_chrdev(unsigned int major, consr char *name, struct file_operations *fops);

//Then when the application operates the device, the Linux system finds file_operations based on the device file type and the main device number. 

Simple driver writing (no interrupt, select mechanism, fasync asynchronous notification mechanism)
(1) Writing driver initialization function
(2) Constructing member functions in file_operations

6. Analysis of LED Driver
(1) Initialization, specifying loading and unloading functions

//Executing insmod s3c24xx_leds calls the function
static int __init s3c24xx_leds_init(void)
{
    int ret;

    /* Registered Character Device Driver
     * The parameters are the main device number, device name, file_operations structure.
     * In this way, the main device number is associated with the specific file_operations structure.
     * When operating the device file with the main device as LED_MAJOR, the relevant member functions in s3c24xx_leds_fops are called.
     * LED_MAJOR It can be set to 0 to indicate that the main device number is automatically assigned by the kernel.
     */
    ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }

    printk(DEVICE_NAME " initialized\n");
    return 0;
}

 // This function is called when executing the command "rmmod s3c24xx_leds.ko" 
static void __exit s3c24xx_leds_exit(void)
{
    /* Unload driver */
    unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}

/* These two lines specify the initialization function and uninstall function of the driver */
//Change the load and unload function names to init_module and cleanup_module if you don't apply these two lines
module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);

(2) file_operations structure

static struct file_operations s3c24xx_leds_fops = 
{
    .owner  =   THIS_MODULE,    /* This is a macro that automatically creates _this_module variables when pushing to compile modules */
    .open   =   s3c24xx_leds_open,     
    .ioctl  =   s3c24xx_leds_ioctl,
};

(3) Functions invoked by an application when it executes open and ioclt on device file / dev/leds

// When the application executes open(...) on the device file / dev/leds, the s3c24xx_leds_open function is called.
 //open sets the LED pin as output
static int s3c24xx_leds_open(struct inode *inode, struct file *file)
{
    int i; 
    for (i = 0; i < 4; i++) 
    {
        // Setting up the function of GPIO pin: The GPIO pin of LED in this driver is set as output function, which is implemented in the kernel.
        s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
    }
    return 0;
}

/* When the application executes ioclt(...) on the device file / dev/leds,
 * The s3c24xx_leds_ioctl function is called
 */
 //According to the command, the LED is turned on and off. open returns inode and file to ioctl.
static int s3c24xx_leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    if (arg > 4) {
        return -EINVAL;
    }

    switch(cmd) {
    case IOCTL_LED_ON:
        // Setting the output level of the specified pin to 0, the function is implemented in the kernel.
        s3c2410_gpio_setpin(led_table[arg], 0);
        return 0;

    case IOCTL_LED_OFF:
        // Set the output level of the specified pin to 1. This function is implemented in the kernel.
        s3c2410_gpio_setpin(led_table[arg], 1);
        return 0;

    default:
        return -EINVAL;
    }
}

7. Driver compilation
(1) Add file s3c24xx_leds.c under driverschar;
(2) Add a line to drivers char Makefile

obj-m += s3c24xx_leds.o

(3) Execute make modules to generate driverscharss3c24xx_leds.ko under the kernel root directory
(4) Place s3c24xx_leds.ko (by means of network transmission, etc.) under the development board file system/lib/modules/2.6.22.6/.
(5) This can be loaded and unloaded using insmod s3c24xx_leds and rmmod s3c24xx_leds commands.

8. Test procedures
(1) Compile and generate executable files on PC and put them in the development board file system/user/bin directory
(2) Establishing Device Files in the Development Board File System

mknod /dev/leds c 231 0

(3) Testing with commands

led_test 1 on
led_test 2 off

System calls such as open and ioctl of application programs do not correspond one-to-one with the parameters of the corresponding functions in the driver program. They are converted through the kernel file layer.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define IOCTL_LED_ON    0
#define IOCTL_LED_OFF   1

void usage(char *exename)
{
    printf("Usage:\n");
    printf("    %s <led_no> <on/off>\n", exename);
    printf("    led_no = 1, 2, 3 or 4\n");
}

int main(int argc, char **argv)
{
    unsigned int led_no;
    int fd = -1;

    if (argc != 3)
        goto err;

    fd = open("/dev/leds", 0);  // open device
    if (fd < 0) {
        printf("Can't open /dev/leds\n");
        return -1;
    }

    led_no = strtoul(argv[1], 0, 0) - 1;    // Which LED to operate?
    if (led_no > 3)
        goto err;

    if (!strcmp(argv[2], "on")) {
        ioctl(fd, IOCTL_LED_ON, led_no);    // Light it up
    } else if (!strcmp(argv[2], "off")) {
        ioctl(fd, IOCTL_LED_OFF, led_no);   // Extinguish it
    } else {
        goto err;
    }

    close(fd);
    return 0;

err:
    if (fd > 0) 
        close(fd);
    usage(argv[0]);
    return -1;
}

Posted by cwscribner on Thu, 30 May 2019 12:04:18 -0700