• Linux字符设备驱动


    Linux字符设备驱动

    Linux的各种设备都以文件的形式存放在 /dev 目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。Linux为了管理这些设备,为设备编了号,每个设备号分为主设备号和次设备号。主设备号用来区分不同类型的设备,而次设备号用来区分同一类型的多个设备。
    设备号的范围:
    主设备号范围:0~4095(212)
    次设备号范围:0~1048575(220)

    创建字符设备驱动的步骤:(函数中的参数要根据实际情况更改)

    注意:以下函数的具体含义在后面有说明。
    1.申请设备号:alloc_chrdev_region(&devid, 0, 1, "test");
    2.定义字符设备结构体:struct cdev test_cdev;
    3.初始化定义的字符设备,主要是将字符设备与对应的file_operation文件操作集进行绑定:cdev_init(&test_cdev, &test_fops);
    4.向 Linux系统添加字符设备 (cdev结构体变量 ):cdev_add(&testcdev, devid, 1);
    5.自动创建设备节点,先创建类class_create(),再创建设备device_create()
    6.卸载驱动的时候一定要使用 cdev_del函数从 Linux内核中删除相应的字符设备:cdev_del(&testcdev);

    /*以上步骤的抽象示例,实际可用的字符驱动源码在文末*/
    dev_t devid; /* 声明设备号 */
    
    alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
    
    struct cdev test_cdev; //定义字符设备结构体
    
    //定义该设备相应的文件操作集
    static struct file_operations test_fops = {
    	.owner = THIS_MODULE,
    	/* 其他具体的初始项 */
    };
    
    test_cdev.owner = THIS_MODULE;
    cdev_init(&test_cdev, &test_fops); /* 初始化cdev结构体变量 */
    cdev_add(&test_cdev, devid, 1); /* 添加字符设备 */
    
    cdev_del(&testcdev);/* 删除cdev,当卸载驱动时 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1. 设备号的分配

    1.1 静态分配设备号

    静态分配设备号就是人为地为一个设备指定一个设备号,指定的设备号需要是当前系统尚未使用的设备号,否则会影响设备的使用。
    查看当前系统所有已经使用了的主设备号的命令: cat /proc/devices
    注意:静态分配设备号较麻烦,需要查看当前系统已经使用的设备号,推荐使用动态分配设备号的方法。
    静态注册设备号函数如下:

    函数int register_chrdev_region(dev_t *dev, unsigned count,const char *name);
    参数 dev设备号的起始值。类型是dev_t 类型
    参数 count要申请的次设备号的个数。
    参数 name设备名字
    返回值成功返回0,失败返回负数

    1.2 动态分配设备号

    由于静态分配设备号可能存在冲突问题,因此建议使用动态分配设备号。在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可,设备号的动态注册设备号函数如下:

    函数int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
    参数 dev保存申请到的设备号
    参数baseminor次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以baseminor 为起始地址开始递增。一般baseminor 为0,也就是说次设备号从0 开始
    参数 count要申请的次设备号的个数
    参数 name设备名字
    返回值成功返回0,失败返回负数。使用动态分配会优先使用255 到234

    1.3 释放设备号

    设备号释放函数如下:

    函数void unregister_chrdev_region(dev_t from, unsigned count);
    参数 from要释放的设备号
    参数 count表示从from 开始,要释放的设备号数量

    2. 注册字符类设备

    在Linux中使用cdev结构体表示一个字符设备,cdev结构体的定义如下:

    struct cdev { 
    	struct kobject kobj; 
    	struct module *owner; 
    	const struct file_operations *ops; //字符设备文件操作集合
    	struct list_head list; 
    	dev_t dev; //设备号
    	unsigned int count; 
     };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    编写字符设备驱动之前需要定义一个cdev结构体变量,这个变量就表示一个字符设备,定义字符设备结构体:struct cdev test_cdev;
    注册字符类设备要使用到的函数:

    函数void cdev_init(struct cdev *, const struct file_operations *);
    第一个参数要初始化的cdev
    第二个参数文件操作集cdev->ops = fops; //实际就是把文件操作集写给ops
    功能cdev_init()函数用于初始化cdev 的成员,并建立cdev 和file_operations 之间的连接。
    函数int cdev_add(struct cdev *, dev_t, unsigned);
    第一个参数cdev 的结构体指针
    第二个参数设备号
    第三个参数次设备号的数量
    功能cdev_add()函数用于向Linux 系统添加字符设备(cdev 结构体变量)
    函数void cdev_del(struct cdev *);
    第一个参数cdev 的结构体指针
    功能cdev_del()函数用于向Linux 系统删除字符设备(cdev 结构体变量)

    3. 自动创建设备节点

    自动创建设备节点需要先创建类函数,然后再使用这个类函数去创建设备函数,从而会在 /dev 目录下创建相应的设备节点。

    3.1 类的创建和删除

    函数struct class *__class_create(struct module *owner, const char *name);
    参数 owner一般为THIS_MODULE
    参数 name类名字
    返回值指向结构体class 的指针,也就是创建的类。

    卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:

    函数void class_destroy(struct class *cls);
    参数 cls要删除的类

    3.2 设备函数的创建和删除

    函数struct device *device_create(struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt, …)
    参数 class设备要创建哪个类下面
    参数 parent父设备,一般为NULL,也就是没有父设备
    参数 devt设备号
    参数 drvdata设备可能会使用的一些数据,一般为NULL
    参数 fmt设备名字,如果设置fmt=xxx 的话,就会生成/dev/xxx 这个设备文件
    返回值返回创建好的设备

    卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原型如下:

    函数void device_destroy(struct class *class, dev_t devt)
    参数 class要删除的设备所处的类
    参数 devt要删除的设备号

    LED驱动源码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    //定义字符设备的名称
    #define DEV_NAME            "led_chrdev"
    //定义字符设备的设备数量
    #define DEV_CNT                 (3)
    
    //定义字符设备的设备号
    static dev_t devid;
    struct class *led_chrdev_class;
    
    struct led_chrdev {
    	struct cdev dev;	//定义字符设备结构体
    	unsigned int __iomem *va_dr;	//数据寄存器虚拟地址指针
    	unsigned int __iomem *va_gdir;	//输入输出方向寄存器虚拟地址指针
    	unsigned int __iomem *va_iomuxc_mux;	//端口复用寄存器虚拟地址指针
    	unsigned int __iomem *va_ccm_ccgrx;	//时钟寄存器虚拟地址指针
    	unsigned int __iomem *va_iomux_pad;	//电气属性寄存器虚拟地址指针
    
    	unsigned long pa_dr;	//装载数据寄存器(物理地址)的变量
    	unsigned long pa_gdir;	//装载输出方向寄存器(物理地址)的变量
    	unsigned long pa_iomuxc_mux;	//装载端口复用寄存器(物理地址)的变量
    	unsigned long pa_ccm_ccgrx;	//装载时钟寄存器(物理地址)的变量
    	unsigned long pa_iomux_pad;	//装载电气属性寄存器(物理地址)的变量
    
    	unsigned int led_pin;	//LED的引脚
    	unsigned int clock_offset;	//时钟偏移地址
    };
    
    static struct led_chrdev led_cdev[DEV_CNT] = {
    	{.pa_dr = 0x0209C000, .pa_gdir = 0x0209C004, .pa_iomuxc_mux = 0x20E006C, .pa_ccm_ccgrx = 0x20C406C,
    	 .pa_iomux_pad = 0x20E02F8, .led_pin = 4, .clock_offset = 26},
    	{.pa_dr = 0x20A8000, .pa_gdir = 0x20A8004, .pa_iomuxc_mux = 0x20E01E0, .pa_ccm_ccgrx = 0x20C4074,
    	 .pa_iomux_pad = 0x20E046C, .led_pin = 20, .clock_offset = 12},
    	{.pa_dr = 0x20A8000, .pa_gdir = 0x20A8004, .pa_iomuxc_mux = 0x20E01DC, .pa_ccm_ccgrx = 0x20C4074,
    	 .pa_iomux_pad = 0x20E0468, .led_pin = 19, .clock_offset = 12},
    };
    
    static int led_chrdev_open(struct inode *inode, struct file *filp)
    {
    	unsigned int val = 0;
    	//结构体的首地址 = container_of(结构体变量中某个成员的地址,结构体类型,该结构体变量的具体名字)
    	//inode->i_cdev 指向找到的 cdev
    	struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev, dev);
    	//filp->private_data保存着自定义设备结构体的地址
    	filp->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);
    
    	printk("open file success!\n");
    
    	led_cdev->va_dr = ioremap(led_cdev->pa_dr, 4);
    	led_cdev->va_gdir = ioremap(led_cdev->pa_gdir, 4);
    	led_cdev->va_iomuxc_mux = ioremap(led_cdev->pa_iomuxc_mux, 4);
    	led_cdev->va_ccm_ccgrx = ioremap(led_cdev->pa_ccm_ccgrx, 4);
    	led_cdev->va_iomux_pad = ioremap(led_cdev->pa_iomux_pad, 4);
    
    	val = ioread32(led_cdev->va_ccm_ccgrx);
    	val &= ~(3 << led_cdev->clock_offset);
    	val |= (3 << led_cdev->clock_offset);
    	iowrite32(val, led_cdev->va_ccm_ccgrx);
    
    	iowrite32(5, led_cdev->va_iomuxc_mux);
    
    	iowrite32(0x1F838, led_cdev->va_iomux_pad);
    
    	val = ioread32(led_cdev->va_gdir);
    	val &= ~(1 << led_cdev->led_pin);
    	val |= (1 << led_cdev->led_pin);
    	iowrite32(val, led_cdev->va_gdir);
    
    	val = ioread32(led_cdev->va_dr);
    	val |= (0x01 << led_cdev->led_pin);
    	iowrite32(val, led_cdev->va_dr);
    
    	return 0;
    }
    
    static int led_chrdev_release(struct inode *inode, struct file *filp)
    {
    	struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev, dev);
    	iounmap(led_cdev->va_dr);
    	iounmap(led_cdev->va_gdir);
    	iounmap(led_cdev->va_iomuxc_mux);
    	iounmap(led_cdev->va_ccm_ccgrx);
    	iounmap(led_cdev->va_iomux_pad);
    
    	printk("close file success!\n");
    	return 0;
    }
    
    static ssize_t led_chrdev_write(struct file *filp, const char __user * buf, size_t count, loff_t * ppos)
    {
    	char kbuf[2] = {0};
    	unsigned long val = 0;
    
    	struct led_chrdev *led_cdev = (struct led_chrdev *)filp->private_data;
    	// copy_from_user 从应用层传递数据到内核层
    	if(copy_from_user(kbuf, buf, count) != 0){
    		printk("copy_from_user error\n");
    		return -1;
    	}
    
    	val = ioread32(led_cdev->va_dr);
    	printk("kbuf[0] is %d", kbuf[0]);
    	if (kbuf[0] == 0)
    		val &= ~(0x01 << led_cdev->led_pin);
    	else
    		val |= (0x01 << led_cdev->led_pin);
    	iowrite32(val, led_cdev->va_dr);
    
    	return 0;
    }
    
    static struct file_operations led_chrdev_fops = {
    	.owner = THIS_MODULE,
    	.open = led_chrdev_open,
    	.release = led_chrdev_release,
    	.write = led_chrdev_write,
    };
    
    static __init int led_chrdev_init(void)
    {
    	int i = 0;
    	dev_t cur_dev;
    	printk("led chrdev init\n");
    	/*第一步
    	 *采用动态分配的方式,获取设备编号,次设备号为0,
    	 *设备名称为 DEV_NAME ,可通过命令cat /proc/devices查看
    	 *DEV_CNT为3,当前申请3个次设备编号
        */
    	alloc_chrdev_region(&devid, 0, DEV_CNT, DEV_NAME);
    
    	/*自动创建设备节点,先创建类*/
    	led_chrdev_class = class_create(THIS_MODULE, "led_chrdev");
    
    	for (; i < DEV_CNT; i++) {
    		/*关联字符设备结构体cdev与文件操作结构体file_operations*/
    		cdev_init(&led_cdev[i].dev, &led_chrdev_fops);
    		led_cdev[i].dev.owner = THIS_MODULE;
    
    		cur_dev = MKDEV(MAJOR(devid), MINOR(devid) + i);
    
    		/*添加设备至cdev_map散列表中
    		 *向 Linux系统添加字符设备 (cdev结构体变量 )
    		*/
    		cdev_add(&led_cdev[i].dev, cur_dev, 1);
    
    		/*自动创建设备节点,创建设备*/
    		device_create(led_chrdev_class, NULL, cur_dev, NULL,
    			      DEV_NAME "%d", i);
    	}
    
    	return 0;
    }
    
    static __exit void led_chrdev_exit(void)
    {
    	int i;
    	dev_t cur_dev;
    	printk("led chrdev exit\n");
    
    	for (i = 0; i < DEV_CNT; i++) {
    		cur_dev = MKDEV(MAJOR(devid), MINOR(devid) + i);
    
    		device_destroy(led_chrdev_class, cur_dev);
    
    		cdev_del(&led_cdev[i].dev);
    
    	}
    	unregister_chrdev_region(devid, DEV_CNT);
    	class_destroy(led_chrdev_class);
    
    }
    
    module_init(led_chrdev_init);
    module_exit(led_chrdev_exit);
    
    MODULE_AUTHOR("learn");
    MODULE_LICENSE("GPL");
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186

    测试LED驱动的应用程序源码

    #include 
    #include 
    #include 
    #include 
    
    // input "./app 0" in command line to open led
    int main(int argc, char *argv[])
    {
        char buf[2] = {0};
    
        // atoi()将字符串转为整型
        buf[0] = atoi(argv[1]);
    
        printf("led_cdev test\n");
        //打开设备
        int fd = open("/dev/led_chrdev0", O_RDWR);
        if(fd>0)
            printf("led_chrdev0 open success\n");
        else
            printf("led_chrdev0 open fail\n");
        //写入数据
        write(fd, buf, sizeof(buf));
        //写入完毕,关闭文件
        close(fd);
        sleep(1);
        //打开设备
         fd = open("/dev/led_chrdev1", O_RDWR);
        if(fd>0)
            printf("led_chrdev1 open success\n");
        else
            printf("led_chrdev1 open fail\n");
        
        //写入数据
        write(fd, buf, sizeof(buf));
        //写入完毕,关闭文件
        close(fd);
        sleep(1);
         //打开设备
         fd = open("/dev/led_chrdev2", O_RDWR);
        if(fd>0)
            printf("led_chrdev2 open success\n");
        else
            printf("led_chrdev2 open fail\n");
        //写入数据
        write(fd, buf, sizeof(buf));
        //写入完毕,关闭文件
        close(fd);
        sleep(1);
    
        buf[0] = 1; //close led
        //关闭设备
        fd = open("/dev/led_chrdev0", O_RDWR);
        write(fd, buf, sizeof(buf));
        close(fd);
         fd = open("/dev/led_chrdev1", O_RDWR);
        write(fd, buf, sizeof(buf));
        close(fd);
         fd = open("/dev/led_chrdev2", O_RDWR);
        write(fd, buf, sizeof(buf));
        close(fd);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
  • 相关阅读:
    Prometheus的部署
    打开word文档报错,提示HRESULT 0x80004005 位置: 部分: /word/comments.xml,行: 0,列: 0
    【云原生之Docker实战】使用Docker部署Homepage应用程序仪表盘
    【BOOST C++ 16 语言扩展】(4) Boost.Conversion
    SpringBoot案例(黑马学习笔记)
    将神经网络粒子化的内在合理性
    Excel VLOOKUP实用教程之 07 vlookup如何解决肉眼看完全匹配,但是就是返回N/A错误(教程含数据excel)
    python GUI
    自定义类型(结构体、位段、联合体、枚举)
    状态管理艺术——借助Spring StateMachine驭服复杂应用逻辑
  • 原文地址:https://blog.csdn.net/jiawenhao_/article/details/126423866