天天看点

字符设备驱动笔记字符设备驱动笔记

字符设备驱动笔记

一、简介

在所有Linux设备驱动中,字符设备驱动最为基础,本笔记将讲解Linux字符设备驱动的结构,并解释其主要组成部分的编程方法。

二、主要结构体及API函数

  1. cdev结构
struct cdev {  
	struct kobject kobj;/*内嵌的kobject对象*/  
	struct module *owner;/*所属模块*/  
	const struct file_operations *ops;/*文件操作结构体*/  
	struct list_head list;  
	dev_t dev;/*设备号*/  
	unsigned int count;  
};
           

cdev结构体的dev_t成员定义了设备号,为32位,其中12bit为主设备号,20bit为次设备号。使用如下宏可以从dev_t获得主设备号和次设备号。

MAJOR(dev_t dev)
MINOR(dev_t dev)
           

使用如下宏可以用主设备号和次设备号生成设备号

MKDEV(int major, int minor)  
           

内核提供了一组函数用于操作cdev结构体:

void cdev_init(struct cdev *,struct file_operations *);/*初始化cdev成员,建立cdev和file_operations的连接*/  
struct cdev *cdev_alloc(void);/*动态申请一个cdev内存*/  
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);  /*向系统添加一个cdev*/  
void cdev_del(struct cdev *);/*删除系统中的一个cdev*/  
           

设备号的申请和释放

int register_chrdev_region(dev_t from, unsigned count, const char *name);/*用于已知起始设备号的情况下申请设备号*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);/*用于未知起始设备号的情况下申请未被占用的设备号*/
void unregister_chrdev_region(dev_t from, unsigned count);/*设防设备号*/
           
  1. file_operations结构

    cdev结构体的一个重要成员file_operations定义了字符设备驱动提供给文件系统的接口函数。

struct file_operations {
	struct module *owner;/*拥有该结构的模块的指针,一般为THIS_MODULES*/  
	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);/*仅用于读取目录,对于设备文件,该字段为NULL*/  
	unsigned int (*poll) (struct file *, struct poll_table_struct *);/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/  
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);/*执行设备IO控制命令*/  
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);/*不使用BLK的文件系统,将使用此函数指针代替ioctl*/  
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);/*在64位的系统上,32位的ioctl调用,将使用此函数指针代替*/  
	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);/*异步fsync*/  
	int (*fasync) (int, struct file *, int);/*通知设备FASYNC标志发生变化*/  
	int (*lock) (struct file *, int, struct file_lock *);  
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);/*通常为NULL*/  
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);/*在当前进程地址空间找到一个未映射的内存段*/  
	int (*check_flags)(int);/*允许模块检查传递给fcntl(F_SETEL...)调用的标志*/  
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);/*由VFS调用,将管道数据粘贴到文件*/  
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);/*由VFS调用,将文件数据粘贴到管道*/  
	int (*setlease)(struct file *, long, struct file_lock **);
};  
           

三、驱动代码及分析

如下是一个最简单的字符设备驱动,四个LED灯的控制,可以作为字符设备驱动的模板。

#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;     /* 主设备号 */
static struct cdev LedDevs;

/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */
#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)

/* 用来指定LED所用的GPIO引脚 */
static unsigned long led_table [] = {
    S3C2410_GPB(5),
    S3C2410_GPB(6),
    S3C2410_GPB(7),
    S3C2410_GPB(8),
};

/* 应用程序对设备文件/dev/led执行open(...)时,
 * 就会调用s3c24xx_leds_open函数
 */
static int s3c2440_leds_open(struct inode *inode, struct file *file)
{
    int i;    
    for (i = 0; i < 4; i++) {
        // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能
        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);
    }
}

/* 应用程序对设备文件/dev/leds执行ioctl(...)时,
 * 就会调用s3c24xx_leds_ioctl函数
 */
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:
            // 设置指定引脚的输出电平为0
            s3c2410_gpio_setpin(led_table[data], 0);
            return 0;

        case IOCTL_LED_OFF:
            // 设置指定引脚的输出电平为1
            s3c2410_gpio_setpin(led_table[data], 1);
            return 0;
            
        case IOCTL_LED_RUN:
            // 跑马灯
            {
               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 闪烁
            {
                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:
            // 设置指定引脚的输出电平为0
            leds_all_on();
            return 0;
        case IOCTL_LED_ALLOFF:
            // 设置指定引脚的输出电平为1
            leds_all_off();
            return 0;

        default:
            return -EINVAL;
    }
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations s3c2440_leds_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_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);
}

/*
 * 执行“insmod s3c24xx_leds.ko”命令时就会调用这个函数
 */
static int __init s3c2440_leds_init(void)
{
	int result;
	dev_t dev = MKDEV(led_major, 0);
	char dev_name[]="led";  /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */

	/* 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;
}

/*
 * 执行”rmmod s3c24xx_leds”命令时就会调用这个函数 
 */
static void __exit s3c2440_leds_exit(void)
{
    /* 卸载驱动程序 */
	cdev_del(&LedDevs);
	unregister_chrdev_region(MKDEV(led_major, 0), 1);
	printk("Led device uninstalled\n");
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("eurphan");             					// 驱动程序的作者
MODULE_DESCRIPTION("s3c2440 LED Driver");  			 // 一些描述信息
MODULE_LICENSE("Dual BSD/GPL");                        // 遵循的协议
           

四、测试应用程序

写好的驱动怎么测试呢,这就需要编写应用程序来测试了

#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);  // 打开设备
	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);    // 点亮它
		} else if (!strcmp(argv[1], "off")) {
			ioctl(fd, IOCTL_LED_ALLOFF, &count);   // 熄灭它
		} else if (!strcmp(argv[1], "run")) {
			ioctl(fd, IOCTL_LED_RUN, &count);   //运行跑马灯
                } else if (!strcmp(argv[1], "shine")) {
			ioctl(fd, IOCTL_LED_SHINE, &count);   //闪烁
		} else {
			goto err;
		}
	}
		
	if (argc == 3) {
		led_no = strtoul(argv[1], NULL, 0) - 1;    // 操作哪个LED?
		if (led_no > 3)
			goto err;	    
		if (!strcmp(argv[2], "on")) {
			ioctl(fd, IOCTL_LED_ON, &led_no);    // 点亮
		} else if (!strcmp(argv[2], "off")) {
			ioctl(fd, IOCTL_LED_OFF, &led_no);   // 熄灭
		} else {
			goto err;
		}
	}
    
	close(fd);
	return 0;
    
err:
	if (fd > 0) 
		close(fd);
	usage(argv[0]);
	return -1;
}