嵌入式linux-嵌入式内核及驱动开发,字符设备驱动,驱动和应用程序的设计思想,编写字符设备驱动的步

2019-07-12 18:23发布

文章目录

1, 驱动和应用程序的设计思想

1.1,应用程序和驱动扮演的是什么角 {MOD}

用户态:应用程序 玩策略: 怎么去做 1, 一闪一闪 2,10s闪一次,也可以1s闪一次 3,一直亮 4,跑马灯 控制权是在应用程序(程序员) -------------------------------------- 内核态:驱动 玩机制: 能做什么 led:亮 和 灭

2,编写字符设备驱动的步骤和规范

2.1,步骤:

1,实现模块加载和卸载入口函数 module_init(chr_dev_init); module_exit(chr_dev_exit); 2,在模块加载入口函数中 a,申请主设备号 (内核中用于区分和管理不同字符设备) register_chrdev(dev_major, "chr_dev_test", &my_fops); b,创建设备节点文件 (为用户提供一个可操作到文件接口--open()) struct class *class_create(THIS_MODULE, "chr_cls"); struct device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2"); c, 硬件的初始化 1,地址的映射 gpx2conf = ioremap(GPX2_CON, GPX2_SIZE); 2,中断到申请 3,实现硬件的寄存器的初始化 // 需要配置gpio功能为输出 *gpx2conf &= ~(0xf<<28); *gpx2conf |= (0x1<<28); e,实现file_operations const struct file_operations my_fops = { .open = chr_drv_open, .read = chr_drv_read, .write = chr_drv_write, .release = chr_drv_close, };

2.2,规范:

1,面向对象编程思想 用一个结构体来表示一个对象 //设计一个类型,描述一个设备的信息 struct led_desc{ unsigned int dev_major; //设备号 struct class *cls; struct device *dev; //创建设备文件 void *reg_virt_base; }; struct led_desc *led_dev;//表示一个全局的设备对象 // 0(在init中第一步做), 实例化全局的设备对象--分配空间 // GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠) // #include led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); if(led_dev == NULL) { printk(KERN_ERR "malloc error "); return -ENOMEM; } led_dev->dev_major = 250; 2,做出错处理 在某个位置出错了,要将之前申请到资源进行释放 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops); if(led_dev->dev_major < 0) { printk(KERN_ERR "register_chrdev error "); ret = -ENODEV; goto err_0; } err_0: kfree(led_dev); return ret;

3,操作寄存器地址的方式 readl/writel():

3.1,传统的方式

volatile unsigned long *gpxcon; *gpxcon &= ~(0xf<<28);

3.2,内核提供的方式

readl/writel(); u32 readl(const volatile void __iomem *addr) //从地址中读取地址空间的值 void writel(unsigned long value , const volatile void __iomem *addr) // 将value的值写入到addr地址 例子1: // gpio的输出功能的配置 u32 value = readl(led_dev->reg_virt_base); value &= ~(0xf<<28); value |= (0x1<<28); writel(value, led_dev->reg_virt_bas); 例子2: *gpx2dat |= (1<<7); 替换成: writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );

4,例—LED灯闪烁

4.1,驱动代码 led_drv.c

#include #include #include #include #include #include #include #include //设计一个类型,描述一个设备的信息 struct led_desc{ unsigned int dev_major; //主设备号 struct class *cls; struct device *dev; //创建设备文件 void *reg_virte_base; //存放虚拟地址的首地址(寄存器地址的基准值) }; //物理地址 //#define GPX2_CON (*(volatile unsigned int *)0x11000C40) //此处是裸机驱动开发时的用法,此处不能用 #define GPX2_CON 0x11000C40 //GPX2_DATA就是GPX2_CON + 4 #define GPX2_SIZE 8 struct led_desc *led_dev; //申明设备对象 static int kernel_val = 555; ssize_t led_drv_read(struct file *filep, char __user *buf, size_t count, loff_t *fpos) { int ret; printk("---------%s------------- ",__FUNCTION__); //从内核空间拷贝数据到用户空间 ret = copy_to_user(buf, &kernel_val, count); if(ret > 0) { printk("copy_to_user error "); return -EFAULT; } return 0; } ssize_t led_drv_write(struct file *filep, const char __user *buf, size_t count, loff_t *fops) { int ret; int value; printk("---------%s------------- ",__FUNCTION__); //从用户空间拷贝数据到内核空间 ret = copy_from_user(&value, buf, count); if(ret > 0) { printk("copy_from_user error "); return -EFAULT; } //控制GPX2_7 I/O口电平变化 if(value) { writel((readl(led_dev->reg_virte_base + 4) | (0x1 << 7)), led_dev->reg_virte_base + 4); } else { writel((readl(led_dev->reg_virte_base + 4) & ~(0x1 << 7)), led_dev->reg_virte_base + 4); } return 0; } int led_drv_open(struct inode *inode, struct file *filep) { printk("---------%s------------- ",__FUNCTION__); return 0; } int led_drv_close(struct inode *inode, struct file *filep) { printk("---------%s------------- ",__FUNCTION__); return 0; } const struct file_operations my_fops = { .open = led_drv_open, .read = led_drv_read, .write = led_drv_write, .release = led_drv_close, }; static int __init led_dev_init(void) { int ret; printk("---------%s------------- ",__FUNCTION__); //实例化全局的设备对象---分配空间 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); if(led_dev == NULL) { printk(KERN_ERR "malloc error "); return -ENOMEM; } //装载一般都是申请设备号资源 //申请主设备号 #if 0 //静态申请主设备号 led_dev->dev_major = 250; ret = register_chrdev(led_dev->dev_major, "led_dev_test", &my_fops); if(ret == 0){ printk("register ok "); } else{ printk("register failed "); ret = -EINVAL; goto err_0; } #else //动态态申请主设备号 led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops); if(led_dev->dev_major < 0){ printk(KERN_ERR "register_chrdev error "); ret = -ENODEV; goto err_0; } else{ printk("register ok "); } #endif //自动创建设备节点 led_dev->cls = class_create(THIS_MODULE, "led_cls"); if(IS_ERR(led_dev->cls)) { printk(KERN_ERR "class_create error "); ret = PTR_ERR(led_dev->cls); //将指针出错的具体原因转换成一个出错码 goto err_1; } led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major, 0),NULL,"led%d",0); // /dev/led0 if(IS_ERR(led_dev->dev)) { printk(KERN_ERR "device_create error "); ret = PTR_ERR(led_dev->dev); //将指针出错的具体原因转换成一个出错码 goto err_2; } //地址映射 led_dev->reg_virte_base = ioremap(GPX2_CON, GPX2_SIZE); if(led_dev->reg_virte_base == NULL) { printk(KERN_ERR "ioremap error "); ret = -ENOMEM; goto err_3; } //配置GPIO的功能为输出 #if 0 u32 value = readl(led_dev->reg_virte_base); value = (value & ~(0xf << 28)) | (0x1 << 28); writel(value, led_dev->reg_virte_base); #else writel(((readl(led_dev->reg_virte_base) & ~(0xf << 28)) | (0x1 << 28)), led_dev->reg_virte_base); #endif return 0; err_3: device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, 0)); err_2: class_destroy(led_dev->cls); err_1: unregister_chrdev(led_dev->dev_major,"led_dev_test"); err_0: kfree(led_dev); return ret; } static void __exit led_dev_exit(void) { printk("---------%s------------- ",__FUNCTION__); //卸载一般都是释放资源 //去映射 iounmap(led_dev->reg_virte_base); //销毁节点和类 device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, 0)); class_destroy(led_dev->cls); //释放主设备号 unregister_chrdev(led_dev->dev_major,"led_dev_test"); //释放动态内存 kfree(led_dev); } module_init (led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL");

4.2,应用程序 led_test.c

#include #include #include #include int main(int argc, char *argv[]) { //调用驱动 int fd; int value = 0; fd = open("/dev/led0", O_RDWR); if(fd < 0) { perror("led_test open"); exit(1); } read(fd,&value,4); printf("__USER__ : value = %d ",value); while(1) { value = 1; write(fd, &value, 4); sleep(1); value = 0; write(fd, &value, 4); sleep(1); } close(fd); return 0; }

4.3,Makefile

ROOTFS_DIR = /nfs/rootfs#根文件系统路径 APP_NAME = led_test MODULE_NAME = led_drv CROSS_COMPILE = arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE),) KERNEL_DIR = /home/linux/linux-3.14.79 #编译过的内核源码的路径 CPU_DIR = $(shell pwd) #当前路径 all: make -C $(KERNEL_DIR) M=$(CPU_DIR) modules #把当前路径编成modules @#make -C 进入到内核路径 @#M 指定当前路径 $(CC) $(APP_NAME).c -o $(APP_NAME) clean: make -C $(KERNEL_DIR) M=$(CPU_DIR) clean rm $(APP_NAME) install: sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module #把当前的所有.ko文件考到根文件系统的drv_module目录 else obj-m += $(MODULE_NAME).o #指定内核要将哪个文件编译成ko endif

4.4,串口终端信息

在这里插入图片描述