Character device driven notes

Posted by BobRoberts on Fri, 08 Nov 2019 16:21:40 +0100

Character device driven notes

I. Introduction

Among all Linux device drivers, character device driver is the most basic. This note will explain the structure of linux character device driver and explain the programming methods of its main components.

II. Main structures and API functions

  1. cdev structure
struct cdev {  
	struct kobject kobj;/*Embedded kobject object*/  
	struct module *owner;/*Subordinate module*/  
	const struct file_operations *ops;/*File operation structure*/  
	struct list_head list;  
	dev_t dev;/*Device number*/  
	unsigned int count;  
};

The dev_t member of cdev structure defines the device number as 32-bit, in which 12bit is the main device number and 20bit is the secondary device number. Use the following macro to get the primary and secondary device numbers from dev_t.

MAJOR(dev_t dev)
MINOR(dev_t dev)

Use the following macro to generate the device number with the primary and secondary device numbers

MKDEV(int major, int minor)  

The kernel provides a set of functions to operate the cdev structure:

void cdev_init(struct cdev *,struct file_operations *);/*Initialize cdev members and establish a connection between cdev and file operations*/  
struct cdev *cdev_alloc(void);/*Dynamically request a cdev memory*/  
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);  /*Add a cdev to the system*/  
void cdev_del(struct cdev *);/*Delete a cdev in the system*/  

Application and release of equipment number

  int register_chrdev_region(dev_t from, unsigned count, const char *name);/*Used to apply for a device number when the starting device number is known*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);/*Used to request unoccupied device number when the starting device number is unknown*/
void unregister_chrdev_region(dev_t from, unsigned count);/*Fortification equipment No*/
  1. File operations structure
    File operations, an important member of cdev structure, defines the interface function provided by character device driver to file system.
struct file_operations {
	struct module *owner;/*The pointer of the module with this structure, generally this? Modules*/  
	loff_t (*llseek) (struct file *, loff_t, int);/*Used to modify the current read / write location of the file*/  
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);/*Reading data synchronously from a file*/  
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);/*Send data to device*/  
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);/*Initialize an asynchronous read operation*/  
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);/*Initialize an asynchronous write operation*/  
	int (*readdir) (struct file *, void *, filldir_t);/*It is only used to read directory. For device file, this field is NULL*/  
	unsigned int (*poll) (struct file *, struct poll_table_struct *);/*Polling function to determine whether non blocking reading or writing can be performed at present*/  
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);/*Execute device IO control command*/  
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);/*For file systems without BLK, this function pointer will be used instead of ioctl*/  
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);/*On 64 bit systems, 32-bit ioctl calls will use this function pointer instead of*/  
	int (*mmap) (struct file *, struct vm_area_struct *);/*Used to request that device memory be mapped to the process address space*/  
	int (*open) (struct inode *, struct file *);/*open*/  
	int (*flush) (struct file *, fl_owner_t id);  
	int (*release) (struct inode *, struct file *);/*Close*/  
	int (*fsync) (struct file *, struct dentry *, int datasync);/*Refresh pending data*/  
	int (*aio_fsync) (struct kiocb *, int datasync);/*Asynchronous fsync*/  
	int (*fasync) (int, struct file *, int);/*Notify the device of the change of the FASYNC flag*/  
	int (*lock) (struct file *, int, struct file_lock *);  
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);/*Usually NULL*/  
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);/*An unmapped memory segment was found in the current process address space*/  
	int (*check_flags)(int);/*Allow modules to check flags passed to fcntl (f ﹣) calls*/  
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);/*Called by VFS, paste pipeline data to file*/  
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);/*Called by VFS, paste the file data to the pipeline*/  
	int (*setlease)(struct file *, long, struct file_lock **);
};  

III. driver code and analysis

The following is the simplest character device driver. The control of four LED lights can be used as the template of character device driver.

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <mach/regs-clock.h>
#include <plat/regs-timer.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>

static int led_major = 0;     /* Main equipment number */
static struct cdev LedDevs;

/* The second parameter when the application executes ioctl(fd, cmd, arg) */
#define LED_MAGIC 'k'
#define IOCTL_LED_ON _IOW (LED_MAGIC, 1, int)
#define IOCTL_LED_OFF _IOW (LED_MAGIC, 2, int)
#define IOCTL_LED_RUN _IOW (LED_MAGIC, 3, int)
#define IOCTL_LED_SHINE _IOW (LED_MAGIC, 4, int)
#define IOCTL_LED_ALLON _IOW (LED_MAGIC, 5, int)
#define IOCTL_LED_ALLOFF _IOW (LED_MAGIC, 6, int)

/* Used to specify GPIO pin for LED */
static unsigned long led_table [] = {
    S3C2410_GPB(5),
    S3C2410_GPB(6),
    S3C2410_GPB(7),
    S3C2410_GPB(8),
};

/* When the application performs an open(...) on the device file / dev/led,
 * It will call the S3C24XX ﹐ LEDs ﹐ open function
 */
static int s3c2440_leds_open(struct inode *inode, struct file *file)
{
    int i;    
    for (i = 0; i < 4; i++) {
        // Function of setting GPIO pin: the GPIO pin involved in the LED in this driver is set as the output function
        s3c2410_gpio_cfgpin(led_table[i], S3C2410_GPIO_OUTPUT);
    }
    return 0;
}

//LEDS all light on
void leds_all_on()
{
    int i;
    for (i=0; i<4; i++) {
        s3c2410_gpio_setpin(led_table[i], 0);
    }
}

//LEDs all light off
void leds_all_off()
{
    int i;
    for (i=0; i<4; i++) {
        s3c2410_gpio_setpin(led_table[i], 1);
    }
}

/* When the application executes ioctl(...) on the device file / dev/leds,
 * The S3C24XX ﹣ LEDs ﹣ IOCTL function will be called
 */
static int s3c2440_leds_ioctl(struct inode *inode, 
								struct file *file, 
								unsigned int cmd, 
								unsigned long arg)
{
    unsigned int data;

    if (__get_user(data, (unsigned int __user *)arg)) 
        return -EFAULT;

    switch(cmd) {
        case IOCTL_LED_ON:
            // Set the output level of the specified pin to 0
            s3c2410_gpio_setpin(led_table[data], 0);
            return 0;

        case IOCTL_LED_OFF:
            // Set the output level of the specified pin to 1
            s3c2410_gpio_setpin(led_table[data], 1);
            return 0;
            
        case IOCTL_LED_RUN:
            // horse race lamp
            {
               int i,j;
                leds_all_off();            
                //printk("IOCTL_LED_RUN");
                for (i=0;i<data;i++)
                    for (j=0;j<4;j++) {
                        s3c2410_gpio_setpin(led_table[j], 0);
                        mdelay(400); //delay 400ms
                        s3c2410_gpio_setpin(led_table[j], 1);
                        mdelay(400); //delay 400ms
                    }  
                return 0;
             }
          
        case IOCTL_LED_SHINE:
            // LED flicker
            {
                int i,j;
                leds_all_off();
                printk("IOCTL_LED_SHINE\n");
                for (i=0;i<data;i++) {
                    for (j=0;j<4;j++)
                        s3c2410_gpio_setpin(led_table[j], 0);
                    mdelay(400); //delay 400ms
                    for (j=0;j<4;j++)
                        s3c2410_gpio_setpin(led_table[j], 1);
                    mdelay(400);
                }
                return 0;
           }
        case IOCTL_LED_ALLON:
            // Set the output level of the specified pin to 0
            leds_all_on();
            return 0;
        case IOCTL_LED_ALLOFF:
            // Set the output level of the specified pin to 1
            leds_all_off();
            return 0;

        default:
            return -EINVAL;
    }
}

/* This structure is the core of character device driver
 * The open, read, write and other functions called when the application operates the device file,
 * Finally, the corresponding function specified in this structure will be called
 */
static struct file_operations s3c2440_leds_fops = {
    .owner  =   THIS_MODULE,    /* This is a macro, which automatically creates the "this" module variable when it is pushed to compile the module */
    .open   =   s3c2440_leds_open,     
    .ioctl  =   s3c2440_leds_ioctl,
};

/*
 * Set up the cdev structure for a device.
 */
static void led_setup_cdev(struct cdev *dev, int minor,
		struct file_operations *fops)
{
	int err, devno = MKDEV(led_major, minor);
    
	cdev_init(dev, fops);
	dev->owner = THIS_MODULE;
	dev->ops = fops;
	err = cdev_add (dev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk (KERN_NOTICE "Error %d adding Led%d", err, minor);
}

/*
 * This function is called when the command "insmod s3c24xx_leds.ko" is executed
 */
static int __init s3c2440_leds_init(void)
{
	int result;
	dev_t dev = MKDEV(led_major, 0);
	char dev_name[]="led";  /* After loading mode, execute "cat /proc/devices" command to see the device name */

	/* Figure out our device number. */
	if (led_major)
		result = register_chrdev_region(dev, 1, dev_name);
	else {
		result = alloc_chrdev_region(&dev, 0, 1, dev_name);
		led_major = MAJOR(dev);
	}
	if (result < 0) {
		printk(KERN_WARNING "leds: unable to get major %d\n", led_major);
		return result;
	}
	if (led_major == 0)
		led_major = result;

	/* Now set up cdev. */
	led_setup_cdev(&LedDevs, 0, &s3c2440_leds_fops);
	printk("Led device installed, with major %d\n", led_major);
	printk("The device name is: %s\n", dev_name);
	return 0;
}

/*
 * This function will be called when executing the command "rmmod S3C24XX ﹣" 
 */
static void __exit s3c2440_leds_exit(void)
{
    /* Uninstall driver */
	cdev_del(&LedDevs);
	unregister_chrdev_region(MKDEV(led_major, 0), 1);
	printk("Led device uninstalled\n");
}

/* These two lines specify the driver's initialization and uninstall functions */
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);

/* Some information describing the driver is not necessary */
MODULE_AUTHOR("eurphan");             					// Driver author
MODULE_DESCRIPTION("s3c2440 LED Driver");  			 // Some descriptive information
MODULE_LICENSE("Dual BSD/GPL");                        // Agreement to be followed

IV. test application

How to test the written driver? You need to write an application to test it

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

#define LED_MAGIC 'k'
#define IOCTL_LED_ON _IOW (LED_MAGIC, 1, int)
#define IOCTL_LED_OFF _IOW (LED_MAGIC, 2, int)
#define IOCTL_LED_RUN _IOW (LED_MAGIC, 3, int)
#define IOCTL_LED_SHINE _IOW (LED_MAGIC, 4, int)
#define IOCTL_LED_ALLON _IOW (LED_MAGIC, 5, int)
#define IOCTL_LED_ALLOFF _IOW (LED_MAGIC, 6, int)

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;
        unsigned int count=10;
    
	if (argc > 3 || argc == 1)
		goto err;
        
	fd = open("/dev/led", 0);  // open device
	if (fd < 0) {
		printf("Can't open /dev/leds\n");
		return -1;	
	}	
		
	if (argc == 2) {
		if (!strcmp(argv[1], "on")) {
			ioctl(fd, IOCTL_LED_ALLON, &count);    // Light it up
		} else if (!strcmp(argv[1], "off")) {
			ioctl(fd, IOCTL_LED_ALLOFF, &count);   // Extinguish it
		} else if (!strcmp(argv[1], "run")) {
			ioctl(fd, IOCTL_LED_RUN, &count);   //Running the running lamp
                } else if (!strcmp(argv[1], "shine")) {
			ioctl(fd, IOCTL_LED_SHINE, &count);   //Twinkle
		} else {
			goto err;
		}
	}
		
	if (argc == 3) {
		led_no = strtoul(argv[1], NULL, 0) - 1;    // Which LED to operate?
		if (led_no > 3)
			goto err;	    
		if (!strcmp(argv[2], "on")) {
			ioctl(fd, IOCTL_LED_ON, &led_no);    // Lighten up
		} else if (!strcmp(argv[2], "off")) {
			ioctl(fd, IOCTL_LED_OFF, &led_no);   // Extinguish
		} else {
			goto err;
		}
	}
    
	close(fd);
	return 0;
    
err:
	if (fd > 0) 
		close(fd);
	usage(argv[0]);
	return -1;
}

Topics: Linux Programming