700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > WDS1期第12课 字符设备驱动 3 led4个独立驱动 mdev根据代码自动创建次设备节点

WDS1期第12课 字符设备驱动 3 led4个独立驱动 mdev根据代码自动创建次设备节点

时间:2021-10-13 03:54:02

相关推荐

WDS1期第12课 字符设备驱动 3 led4个独立驱动 mdev根据代码自动创建次设备节点

文章目录

一、board相关原理图和芯片datasheet1. 原理图2. datasheet二、驱动/用户/Makefile代码1. led驱动led_chrdev1.c2. Makefile3. 用户user_led1.c三、插入内核模块 自动创建设备节点 执行用户led四、用次设备号改进led驱动代码1. led驱动led_chrdev2.c2. Makefile3. 用户user_led2.c4. 插入内核模块,运行用户代码,点亮和关闭对应led

1. 写驱动框架

2. 硬件相关(原理图 手册 写代码)

一、board相关原理图和芯片datasheet

1. 原理图

2. datasheet

二、驱动/用户/Makefile代码

1. led驱动led_chrdev1.c

#include <linux/init.h>// module_init module_exit#include <linux/module.h> // MODULE_LICENSE#include <linux/fs.h> // file_operations#include <linux/cdev.h>// cdev#include <linux/kernel.h>#include <linux/device.h> // class_device#include <asm/uaccess.h> // copy_from_user copy_to_user#include <asm/io.h> // ioremap iounmap// GPF引脚的con和dat寄存器,地址需要映射volatile unsigned long *gpfcon = NULL;volatile unsigned long *gpfdat = NULL;// 用于自动创建设备节点的结构class 和 class_devicestatic struct class *demo_class;static struct class_device *demo_class_devs;// 操作方法// 配置GPF4/5/6为输出模式static int led_open(struct inode *inode, struct file *filep){*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); // 先清零 8-13 01010101*gpfcon |= (0x1<<(4*2) | 0x1<<(5*2) | 0x1<<(6*2)); // 再对应位置1printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);return 0;}// 配置GPF4/5/6输出高或者低电平static ssize_t led_write(struct file *filep, const char __user *buf, size_t count, loff_t *ppos){// 在用户层传入的形式是 write(fd, &val, 4); val是buf,4是count// 在内核层获取用户层传入的参数,用copy_from_user; 内核空间到用户传参数copy_to_userint user_param = 0;copy_from_user(&user_param, buf, count); // 从buf拷贝到user_param,长度为countif (1 == user_param) // 开灯{// 4-6 000*gpfdat &= ~((0x7<<4));}else if (0 == user_param) // 关灯{*gpfdat |= (0x7<<4);}printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);return 0;}// 操作方法集static struct file_operations fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write,};// 用于保存获取到的主设备号unsigned int major = 0; // 入口函数static int demo_cdev_init(void){// 自动获取主设备号资源;主设备号对应的名字在/proc/devices中,// 内核驱动模块的名字是Makefile里指定的(一般跟驱动.c文件名字对应)major = register_chrdev(0, "major_name_led", &fops); // 0自动分配, // 以下为 在系统中生成设备信息的步骤// 1. 新建一个classdemo_class = class_create(THIS_MODULE, "led_chrdev_class1");if (IS_ERR(demo_class))return PTR_ERR(demo_class);// 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx// 在/dev目录下创建相应的设备节点demo_class_devs = class_device_create(demo_class, NULL, MKDEV(major, 0), NULL, "led_on_off1"); if (unlikely(IS_ERR(demo_class_devs)))return PTR_ERR(demo_class_devs);// led寄存器的地址映射,只需要执行一次,所以在入口函数映射gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16); // start, sizegpfdat = gpfcon + 1; // unsigned long的长度return 0;}// 出口函数static void demo_cdev_exit(void){// 对应卸载unregister_chrdev(major, "major_name_led");class_device_unregister(demo_class_devs); class_destroy(demo_class);// 去掉映射关系iounmap(gpfcon);}// 内核模块相关module_init(demo_cdev_init);module_exit(demo_cdev_exit);MODULE_LICENSE("GPL");

2. Makefile

KERN_DIR := /home/wuyexkx/Desktop/韦东山/system/linux-2.6.22.6PWD := $(shell pwd)# 执行pwd命令并把结果赋给PWDobj-m := led_chrdev1.o # .ko的生成依赖于.o,.o默认依赖.call:make -C $(KERN_DIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm # modules为编译目标clean:make -C $(KERN_DIR) M=$(PWD) clean

3. 用户user_led1.c

#include <fcntl.h>#include <stdio.h>// main传入2个参数 user_led1 on/offint main(int argc, char **argv){int fd;int val = 1;// 打开 mdev根据驱动程序中的class_device_create自动创建的 设备节点fd = open("/dev/led_on_off1", O_RDWR);if(fd < 0)printf("can't open '/dev/led_on_off1'\n");// main参数个数判断if(argc != 2) {printf("Usage:\n");printf("\t%s <on|off>\n", argv[0]);return 0;}// 第二参数判断if(0 == strcmp(argv[1], "on"))// 开灯参数{val = 1;}else if(0 == strcmp(argv[1], "off"))// 关灯参数{val = 0;}// 根据第二参数writewrite(fd, &val, 4);return 0;}

三、插入内核模块 自动创建设备节点 执行用户led

查看插入的内核驱动模块,cat proc/devices

查看自动创建的设备节点,ls dev/led_on_off1 -l

执行应用程序,./mnt/tzb/user_led1提示使用信息;

./mnt/tzb/user_led1 on开灯;

./mnt/tzb/user_led1 off关灯:

四、用次设备号改进led驱动代码

1. led驱动led_chrdev2.c

#include <linux/init.h>// module_init module_exit#include <linux/module.h> // MODULE_LICENSE#include <linux/fs.h> // file_operations#include <linux/cdev.h>// cdev#include <linux/kernel.h>#include <linux/device.h> // class_device#include <asm/uaccess.h> // copy_from_user copy_to_user#include <asm/io.h> // ioremap iounmap#define DEVICE_NAME"leds" // 加载模式后,执行”cat /proc/devices”命令看到的设备名称int major = 0; // 用于保存获取到的主设备号// GPF引脚的con和dat寄存器,地址需要映射volatile unsigned long *gpfcon = NULL;volatile unsigned long *gpfdat = NULL;#define GPFCON(*gpfcon)#define GPFDAT(*gpfdat)// 用于自动创建设备节点的结构class 和 class_devicestatic struct class *leds_class;static struct class_device *leds_class_dev[4];// 操作方法// 根据次设备号 配置GPF4/5/6为输出模式static int leds_open(struct inode *inode, struct file *filep){// 从inode中提取次设备号int minor = MINOR(inode->i_rdev); switch(minor){case 0: // leds{GPFCON &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); // 先清零 8-13 01010101GPFCON |= (0x1<<(4*2) | 0x1<<(5*2) | 0x1<<(6*2)); // 再对应位置1break;}case 1: // led1{GPFCON &= ~(0x3<<(4*2));GPFCON |= (0x1<<(4*2));break;}case 2: // led2{GPFCON &= ~(0x3<<(5*2));GPFCON |= (0x1<<(5*2));break;}case 3: // led3{GPFCON &= ~(0x3<<(6*2));GPFCON |= (0x1<<(6*2));break;}}printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);return 0;}// 配置GPF4/5/6输出高或者低电平static ssize_t leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){// 获取次设备号int minor = MINOR(filp->f_dentry->d_inode->i_rdev);// 在用户层传入的形式是 write(fd, &val, 4); val是buf,4是count// 在内核层获取用户层传入的参数,用copy_from_user; 内核空间到用户传参数copy_to_userint user_param = 0;copy_from_user(&user_param, buf, count); // 从buf拷贝到user_param,长度为countswitch(minor){case 0:{if (user_param == 1)GPFDAT |= (0x7 << 4); else if (user_param == 0)GPFDAT &= ~(0x7 << 4); // 4-6 000break;}case 1:{if (user_param == 1)GPFDAT |= (0x1 << 4); else if (user_param == 0)GPFDAT &= ~(0x1 << 4); // 4 0break;}case 2:{if (user_param == 1)GPFDAT |= (0x1 << 5); else if (user_param == 0)GPFDAT &= ~(0x1 << 5); // 5 0break;}case 3:{if (user_param == 1)GPFDAT |= (0x1 << 6); else if (user_param == 0)GPFDAT &= ~(0x1 << 6); // 6 0break;}}printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);return 0;}// 操作方法集static struct file_operations fops = {.owner = THIS_MODULE,.open = leds_open,.write = leds_write,};// 入口函数static int leds_init(void){int minor = 0; // 保存次设备号// led寄存器的地址映射,只需要执行一次,所以在入口函数映射gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16); // start, sizegpfdat = gpfcon + 1; // unsigned long的长度// 0. 自动获取主设备号资源;主设备号对应的名字在/proc/devices中,//内核驱动模块的名字是Makefile里指定的(一般跟驱动.c文件名字对应)major = register_chrdev(0, DEVICE_NAME, &fops); // 0自动分配, if (major < 0){printk(DEVICE_NAME " can't register major number.\n");return major;}// 以下为 在系统中生成设备信息的步骤// 1. 新建一个classleds_class = class_create(THIS_MODULE, "leds_class");if (IS_ERR(leds_class))return PTR_ERR(leds_class);// 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx// 在/dev目录下创建相应的设备节点,0全部led设备节点leds_class_dev[0] = class_device_create(leds_class, NULL, MKDEV(major, 0), NULL, "led0"); if (unlikely(IS_ERR(leds_class_dev[0])))return PTR_ERR(leds_class_dev[0]);//1,2,3对应led1/2/3设备节点for (minor=1; minor<4; ++minor){leds_class_dev[minor] = class_device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor); if (unlikely(IS_ERR(leds_class_dev[minor])))return PTR_ERR(leds_class_dev[minor]);} printk(DEVICE_NAME " device initialized successfully...\n\n");return 0;}// 出口函数static void leds_exit(void){int minor = 0; // 保存次设备号// 对应卸载unregister_chrdev(major, DEVICE_NAME);for (minor=0; minor<4; ++minor){class_device_unregister(leds_class_dev[minor]); } class_destroy(leds_class);// 去掉映射关系iounmap(gpfcon);}// 内核模块相关module_init(leds_init);module_exit(leds_exit);MODULE_LICENSE("GPL");

2. Makefile

KERN_DIR := /home/wuyexkx/Desktop/韦东山/system/linux-2.6.22.6PWD := $(shell pwd)# 执行pwd命令并把结果赋给PWDobj-m := led_chrdev2.o # .ko的生成依赖于.o,.o默认依赖.call:make -C $(KERN_DIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm # modules为编译目标clean:make -C $(KERN_DIR) M=$(PWD) clean

3. 用户user_led2.c

#include <fcntl.h>#include <stdio.h>#include <string.h>void print_usage(const char *argv0){printf("Usage:\n");printf("\t%s led<x> <on|off>\t[x=0~3]\n", argv0);printf("Example:\n");printf("\t%s led0 on\n", argv0);printf("\t%s led0 off\n\n", argv0);}// main传入3个参数 user_led2 led0 on/offint main(int argc, char **argv){int fd;int is_off = 1;// main参数个数判断if (argc != 3){print_usage(argv[0]);return 0;} // 保存设备节点名称char device_name[10] = "/dev/";// 根据用户输入得到完整设备节点名称 "/dev/" + "led0" = "/dev/led0"strcat(device_name, argv[1]);// 打开 mdev根据驱动程序中的class_device_create自动创建的 设备节点fd = open(device_name, O_RDWR);if(fd < 0){printf("can't open %s.\n", argv[1]);return 0;} // 第三参数判断if(0 == strcmp(argv[2], "on"))// 开灯参数{is_off = 0;// 根据第三参数writewrite(fd, &is_off, 1);}else if(0 == strcmp(argv[2], "off"))// 关灯参数{is_off = 1;// 根据第三参数writewrite(fd, &is_off, 1);}else // 第三参数错误{printf("Unknown args '%s'.\n\n", argv[2]);}return 0;}

4. 插入内核模块,运行用户代码,点亮和关闭对应led

插入内核模块,显示初始化成功,并在proc/devices中看到为leds设备驱动分配的主设备号252,

dev/中看到四个对应的设备节点,依次申请到的次设备号0/1/2/3,都是252主设备号,

运行用户代码,打印帮助信息,打开led对应的设备节点;第三参数错误也打印出来。

WDS1期第12课 字符设备驱动 3 led4个独立驱动 mdev根据代码自动创建次设备节点 获取次设备号 用户代码useage

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。