首页 > 系统相关 >linux驱动开发

linux驱动开发

时间:2023-06-12 19:56:25浏览次数:37  
标签:struct int dev 开发 linux device 驱动 include

1、驱动开发环境   完成系统移植的三步:u-boot启动引导程序、内核镜像、文件系统,u-boot启动引导程序最好固化到开发板上,内核镜像通过tftp服务从ubuntu下载,文件系统通过nfs服务从ubuntu共享到开发板,开发板启动计数时按任意键进入u-boot命令模式设置bootcmd和bootargs   # setenv serverip 192.168.3.120 # setenv ipaddr 192.168.3.233 # setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 – 42000000\; #setenv bootargs root=/dev/nfs\; nfsroot=192.168.3.120:/source/rootfs rw console=ttySAC2,115200  init=/linuxrc  ip=192.168.3.233 # saveenv 注意:192.168.3.120 对应Ubuntu的ip   192.168.3.233 对应板子的ip   这两个ip应该根据自己的实际情况适当修改   重启开发板进入自启动模式自动加载内核和文件系统   2、驱动开发工具使用source insight软件和vim工具,当然如果不想频繁地从Windows复制到ubuntu可以使用共享文档,这样就可以在Windows上写好代码在ubuntu中直接编译。   设置共享文档步骤:   打开VMware,选择虚拟机设置选择选项选择共享文件夹,点击总是启用,点击添加就可以设置共享文件夹了   设置完成后可以在ls /mnt/hgfs查看共享目录是否挂载   如果没有挂载可以通过vmware-hgfsclient查看共享文件夹   挂载共享文件夹命令vmhgfs-fuse .host:/my /mnt/hgfs其中my是查看共享文件夹时显示的名字,/mnt/hgfs是挂载路径,挂载路径必须是空的文件夹否则可能失败   source insight---查看和编写代码工具   将ubuntu中的linux内核代码复制到Windows中   在source insight中新建项目   在第一个对话框中,第一个文本框(行编辑器),输入工程的名字   在第二个对话框中,第一个本文框中选择刚解压的Linux内核源码目录(顶层linux-3.14),点击ok   在第三个对话框中,在对话框中选择要查看的目录/文件 需要选择的目录文件: include init kernel arch/arm/kernel arch/arm/include/asm driver/base driver/char driver/i2c driver/spi fs/char_dev.c 点击close关闭   重新选择project---->open project ​ 选择刚才创建的工程名 ​ ok ​ 如果提示同步,则选择确认进行同步   开始编写驱动代码   驱动代码必须包含四部分:   a.头文件   #include <linux/init.h> #include <linux/module.h> b.加载和卸载时的函数定义   static int __init hello_init(void) {     return 0; } ​ static void __exit hello_exit(void) { ​ } c.加载和卸载的入口声明   module_init(hello_init);//当使用insmod 驱动名称.ko 加载驱动时执行函数hello_init() module_exit(hello_exit);//当使用remod 驱动名称 卸载驱动时执行函数hello_exit() d.协议选择GPL   MODULE_LICENSE("GPL"); 3、驱动操作   a.加载驱动使用命令   insmod 驱动程序路径.ko   b.加载好驱动后可以通过命令查看驱动   lsmod   c.卸载驱动命令(不用加.ko)   rmmod 驱动程序名   字符设备驱动创建框架 1、申请设备号   int register_chrdev(unsigned int major,const char *name,const struct file_operations *fops) 参数1:unsigned int major------大于0则是申请对应的主设备号;等于0则是由内核分配主设备号 参数2:const char *name------是注册时的名字 参数3:const struct file_operations *fops------是用来关联文件IO接口的结构体 返回值:当参数1大于0时,正确返回0,失败返回负数        当参数1等于0时,正确返回主设备号,失败返回负数 2、创建设备节点(生成对应的驱动文件)   创建文件信息结构体   struct class * class_create(owner,name); 参数1:owner-----拥有者,一般THIS_MODULE 参数2:name-----字符串,描述信息 返回值:struct class *------信息结构体        创建字符驱动设备文件(节点),一般默认建在/dev下,但是可以在任意目录下创建   struct device *device_create(   struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt, ...) 参数1:struct class *class--------class结构体,创建的设备文件的信息内容,通过 class_create()函数创建 参数2:struct device *parent--------表示父类对象,一般直接写NULL,结构体地址 参数3:dev_t devt--------设备号可以有函数MKDEV(ma, mi)获得,ma------主设备号, mi------次设备号,也可以由主设备号左移20位或上一个数字得到这个数字不能大于2^20,例:major<<20|0 参数4:void *drvdata-------私有数据,一般填NULL 参数5:const char *fmt, ...--------设备文件名字符串首地址 返回值:struct device *---------设备节点对象(设备文件描述),成功返回地址,失败返回NULL 文件IO接口层实现,应用程序调用文件io时,驱动程序也调用对应的文件io接口函数 在结构体 struct file_operations 每一个成员变量都代表绑定一个系统调用(文件io)函数,只要对结构体中的成员赋值,就代表值绑定上一个文件io函数   struct file_operations { struct module *owner; 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 (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 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 *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset,   loff_t len); int (*show_fdinfo)(struct seq_file *m, struct file *f); };//函数指针的集合, 驱动控制硬件,控制外设,其实就是控制地址,通过地址往寄存器写入、读出控制 内核驱动是通过虚拟地址操作 初始化硬件   地址映射: void * ioremap(cookie,size); 参数1:cookie-----物理地址 参数2:size-----映射内容大小,字节 返回值:返回映射成功后的虚拟内存地址 操作虚拟内存地址中的内容就是操作对应的物理地址空间内容 字符设备驱动卸载时需要的函数   在卸载入口中实现,清除 与初始化逆序过程进行卸载   //1、映射释放(中断释放) iounmap(映射的虚拟内存地址);----释放映射地址 //2、释放设备文件 void device_destroy(struct class * class,dev_t devt); //3、释放设备文件结构体 void class_destroy(struct class * cls) //4、释放设备号 void unregister_chrdev(unsigned int major,const char * name) 字符驱动模型举例:   通过字符设备驱动控制一颗LED灯   //头文件包含 #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/io.h>   #include <asm/uaccess.h> //使用结构体表示一个驱动对象(面向对象的编程思想) struct Led_Dev { unsigned int *gpx1con; unsigned int *gpx1dat; struct class * class; struct device * dev; unsigned int major; unsigned int dev_no; }; struct Led_Dev led; //4、文件IO功能与设备功能实现绑定 ssize_t led_read (struct file * flie, char __user * data, size_t size, loff_t * ops) {         return 0; } ssize_t led_write (struct file * file, const char __user * data, size_t size, loff_t * ops) {         //data是应用程序传递的数据的地址,size 传递的大小         int num;         copy_from_user(&num,data,size);//从应用程序获取数据存到num中                 if(num==1)         {                 *(led.gpx1dat) |= 1;         }         else         {                 *(led.gpx1dat) &= ~1;         }           return 0; } int led_open (struct inode * inode, struct file * file) {         printk("open ok\n");         return 0; } int led_close (struct inode * inode, struct file * file) {         printk("close ok\n");         return 0; }     const struct file_operations fops= { .open = led_open, .release = led_close, .write = led_write   };   //驱动加载与卸载函数实现 static int __init led_init(void) { led.major = 250; led.dev_no = led.major<<20|0; //1、申请设备号     int res = register_chrdev(led.major,"led_dev",&fops); if(res !=0 ) { printk("register dev error\n"); goto err_1; //return -1; } //2、创建设备文件 led.class = class_create(THIS_MODULE,"led_cls"); if (IS_ERR(led.class)) { printk("class create error\n"); goto err_2; //unregister_chrdev(major,name); //return -1; } led.dev = device_create(led.class, NULL, led.dev_no,NULL,"led"); if(IS_ERR(led.dev)) { printk("device create error\n"); goto err_3; //class_destroy(class); //unregister_chrdev(major,name); //return -1; } //3、驱动设备控制硬件 //硬件寄存器地址映射 led.gpx1con = ioremap(0x11000c20, 4); if(led.gpx1con==NULL) { printk("gpx1con ioremap error\n"); goto err_4; //device_destroy(class, dev_no); //class_destroy(class); //unregister_chrdev(major,name); //return -1; } led.gpx1dat = ioremap(0x11000c24, 4); if(led.gpx1dat==NULL) { printk("gpx1dat ioremap error\n"); goto err_5; //iounmap(gpx1con); //device_destroy(class, dev_no); //class_destroy(class); //unregister_chrdev(major,name); //return -1; } //硬件初始化 *(led.gpx1con) = *(led.gpx1con) & ~0xf | 1; *(led.gpx1dat) |= 1; return 0;  //出错处理的代码    err_5: //1、映射地址释放 iounmap(led.gpx1con);   err_4: //2、设备文件释放 device_destroy(led.class,led.dev_no);//释放设备文件   err_3: class_destroy(led.class);//释放文件信息结构体   err_2: //3、驱动设备号注销 unregister_chrdev(led.major,"led_dev");   err_1: return -1; }     static void __exit led_exit(void) { //卸载驱动,与初始化逆序 //1、映射地址释放 iounmap(led.gpx1con); iounmap(led.gpx1dat); //2、设备文件释放   device_destroy(led.class, led.dev_no);//释放设备文件 class_destroy(led.class);//释放文件信息结构体   //3、驱动设备号注销   unregister_chrdev(led.major,"led_dev");   }   //驱动加载与卸载 module_init(led_init); module_exit(led_exit);   //协议包含GPL MODULE_LICENSE("GPL"); 驱动中实现中断 中断驱动---检测外部中断 获取外设的数据内容,通过中断信号进行获取 在驱动中设置外设为中断模式:当外设产生设定的特定信号(就是中断) 在驱动中实现中断处理操作(函数)   本文以按键中断为例   需要使用按键设备,需要先在设备树中说明使用的按键是一个中断设备,我使用的板子是Samsung的Exynos系列,按键使用的是KEY3     开发板管脚     查看数据手册得到GPX1_2使用的中断是XEINT_10           在设备树中:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi   gpx1: gpx1 {                     gpio-controller;                     #gpio-cells = <2>;                       interrupt-controller;                     interrupt-parent = <&gic>;                     interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,                                   <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;                     #interrupt-cells = <2>;             };   在设备树中添加自己的硬件设备信息---添加key3节点-----描述当前设备的的信息内容(中断号) arch/arm/boot/dts/exynos4412-fs4412.dts:实现硬件描述(中断号)   key3_node {                     compatible = "key3";                     interrupt-parent = <&gpx1>;                     interrupts = <2 4>;//26     }; 在驱动中申请中断,实现中断处理   a、获取到中断号 获取设备树节点,返回值就是从设备树中找到的节点 struct device_node *of_find_node_by_path(const char *path);   从节点中获取到中断号,返回值就是中断号 unsigned int irq_of_parse_and_map(struct device_node *dev,int index);   b、申请中断 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev) 参数1: unsigned int irq:申请中断的中断号   参数2: irqreturn_t (*)(int, void *)    ----   irq_handler_t irq_handler_t handler:函数指针,进行注册中断,当产生中断时调用对应的函数进行处理 参数3: unsigned long flags:中断处理的触发方式 #define IRQF_TRIGGER_NONE0x00000000 #define IRQF_TRIGGER_RISING0x00000001 #define IRQF_TRIGGER_FALLING0x00000002 #define IRQF_TRIGGER_HIGH0x00000004 #define IRQF_TRIGGER_LOW0x00000008   参数4: const char *name:字符串首地址,中断的描述信息 /proc/inruppter 参数5: void *dev:传递给参数2的函数进行自动调用的(作为参数2这个函数的参数)   返回值: 成功返回0,失败返回非0 释放中断: void free_irq(unsigned int irq,void * dev_id) 参数1: unsigned int irq 中断号 参数2: void * dev_id:与申请中断第五个参数保持一致 中断分上下两部分:   上部分处理时间短、不费时间的中断处理   下部分处理一些费时间的中断   下部分就是将耗时操作延后处理,一般在上半部分的处理函数中调用   1、softirq:软中断,处理级别比较高,在内核机制中,需要修改内核源码功能 ​ 2、tasklet:实际上就是内部调用了softirq ​ 3、workqueue:工作队列   驱动中申请中断举例:   #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/interrupt.h> #include <linux/sched.h> #include <linux/wait.h>   #include <asm/io.h> #include <asm/uaccess.h>   char c;   struct key_desc{ unsigned int major; struct class * cls; dev_t devno; struct device * dev; }key;   irqreturn_t key_irq_handler(int i, void * j) { c='q'; printk("irqno : %d;input char %c\n",i,c);   return IRQ_HANDLED; }     //3、文件IO接口功能关联 ssize_t key_read (struct file * file, char __user * data, size_t size, loff_t * ops) {   printk("key read\n"); int n = copy_to_user(data, &c, 1); c='w'; return 0; } int key_open (struct inode * inode , struct file * file) { printk("key open\n"); return 0; }   int key_release (struct inode * inode , struct file *file) { printk("key close\n"); return 0; }   const struct file_operations fops = { .read = key_read, .release = key_release, .open = key_open };   static int __init keydev_init(void) { key.major = 252; //1、申请设备号 int res = register_chrdev( key.major,"key", &fops); if(res<0) { goto err1;   } //2、设备文件 key.cls = class_create(THIS_MODULE, "cls"); if(IS_ERR(key.cls)) { goto err2;   } key.devno = key.major << 20 | 0; key.dev = device_create(key.cls, NULL,key.devno ,NULL,"key_dev"); if(IS_ERR(key.dev)) { goto err3;   } //4、硬件初始化 //a.获取中断号 struct device_node * node = of_find_node_by_path("/key3");//查找设备树中节点为key_3 if(IS_ERR(node)) { goto err4;   } int irqno = irq_of_parse_and_map(node, 0);//申请中断号 if(irqno<0) { goto err5; } //b.申请中断 res = request_irq(irqno, key_irq_handler,IRQF_TRIGGER_FALLING,"key_in", NULL); if(res<0) { goto err6; } return 0; err6: irqno = -1;   err5: node=NULL;   err4: device_destroy(key.cls, key.devno);   err3: class_destroy(key.cls);   err2: unregister_chrdev(key.major, "key");   err1: return -1; }   static void __exit key_exit(void) {   device_destroy(key.cls, key.devno); class_destroy(key.cls); unregister_chrdev(key.major, "key"); }   module_init(keydev_init); module_exit(key_exit);   MODULE_LICENSE("GPL");             测试程序   #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h>     int main() {     char buf; int num; int fd = open("/dev/key_dev",O_RDONLY); while(1) {   num=0; num = read(fd,&buf,1); printf("%d%c\n",num,buf); } return 0; } Makefile   #编译驱动代码 KERNEL_PATH = /home/ubuntu/code/kernel/linux-3.14 #APP=beep_test#测试程序的名字不包括后缀 MODULES_PATH = $(shell pwd) obj-m += key_int.o #把.c编译为.o文件注意.o前的名字与驱动文件名字一致   #编译为驱动程序.ko 要借助已经编译过的内核 all: make modules -C $(KERNEL_PATH) M=$(MODULES_PATH) # arm-none-linux-gnueabi-gcc $(APP).c -o $(APP)   install: # cp *.ko $(APP) /home/ubuntu/rootfs cp *.ko /home/ubuntu/rootfs   驱动中的阻塞IO与非阻塞IO实现   驱动中实现阻塞: 要创建等待队列头: wait_queue_head_t head; init_waitqueue_head(&head);   1、在需要等待的位置(没有数据),就阻塞等待   wait_event_interruptible(wq,condition)-----根据参数是否进行阻塞等待,完成阻塞等待   参数1: wq:等待队列头,把当前进程加入到哪个等待队列中   参数2: condition:是否执行阻塞等待的条件   condition:真---不进行阻塞 condition:假---进行阻塞   2、合适位置进行阻塞唤醒 wake_up_interruptible(&head); 非阻塞:在进行读写操作时,如果没有数据,就立即返回,如果有数据读取数据然后立即返回 应用程序:设置为非阻塞打开 int fd = open("/dev/key3",O_RDONLY | O_NONBLOCK);   驱动文件中在阻塞前面添加: if((file->f_flags & O_NONBLOCK != 0) && (condition == 0)) return -1; 驱动总线模型: 驱动框架: 0、声明实现入口函数(module_init、module_exit) 1、申请设备号(register_chrdev) 2、创建设备节点(class_create、device_create) 3、硬件初始化 ioremap地址映射 中断申请 4、实现文件IO接口   总线模型: 总线bus 驱动driver 设备device   总线bus: struct bus_type:总线对象,描述一条总线,管理device、driver,进行匹配   struct bus_type { const char*name;:总线名字 int (*match)(struct device *dev, struct device_driver *drv);总线调用匹配设备和驱动,返回值就表示匹配成功与否 }; 注册总线: ​ int bus_register(struct bus_type * bus); ​ 参数: ​ struct bus_type * bus:总线对象 ​ 注销总线: ​ void bus_unregister(struct bus_type * bus);   驱动driver: struct device_driver :驱动对象,描述一个驱动,对驱动进行说明   struct device_driver { ​ const char *name;:驱动的名字 ​ struct bus_type bus;总线对象,表示要把驱动注册到哪条总线 ​ int (probe) (struct device dev);如果匹配成功,则调用该驱动的probe函数,创建驱动(申请设备号。。。) ​ int (remove) (struct device *dev);当设备对象和驱动对象移除总线时会调用 ​ } ​ 注册驱动到总线: ​ int driver_register(struct device_driver * drv); ​ 从总线上注销: ​ void driver_unregister(struct device_driver * drv);   设备device: struct device { struct kobject kobj;//所有对象的父类 const char *init_name;设备名 struct bus_type *bus;总线对象,表示要把设备注册到哪条总线 void platform_data;自定义数据,指向任意类型,可以存储设备信息 void (release)(struct device *dev);设备对象从总线移除时会调用 }; 注册设备到总线: int device_register(struct device * dev); 从总线上注销: void device_unregister(struct device * dev);  

标签:struct,int,dev,开发,linux,device,驱动,include
From: https://www.cnblogs.com/kn-zheng/p/17475981.html

相关文章

  • LINUX系列-awk命令篇
    1awk常用功能awk命令的常用功能简要说明指定分隔符显示某几列awk-F"GET|HTTP"'{print$2}'access.log直接取出显示日志文件的url这一列通过正则表达式取出你想要的内容awk'$6~/Failed/{print$11}'/var/log/secure.分析生产环境中日志找出谁在破解用户密......
  • linux 免交互
    目录一、免交互概念二、基本免交互例子三、expect四、实验演示        一、免交互概念概念:对于shell脚本的自动化运维,就要实现免交互来达到自动化运维的效果 二、基本免交互例子可以免交互统计出行号1.命令行免交互统计 2.使用脚本......
  • 人车网租赁软件|租赁系统定制|租赁小程序开发
    在如今网络时代,移动互联早已成为人们日常日常生活不可或缺的一部分。伴随着移动终端的不断普及化,更多的企业逐渐把目光看向移动智能终端的开发。人车网租赁小程序定制开发是近几年新型的一种开发方式,对于租赁行业来说线上租赁模式提升了线上线下的发展,那么租赁小程序有哪些功能呢?人......
  • 广州拼团小程序如何开发?微信拼团小程序开发方案!
    广州拼团小程序如何开发?随着社交电商的兴起,拼团活动的火热,拼团已经成为了一种非常流行的购物方式。而拼团小程序便成为了一个备受关注的选择。广州拼团小程序的开发,可以帮助商家更好地开展拼团业务,提高销售额和用户粘性。下面,名锐讯动跟大家一起探讨一下广州微信拼团小程序如何开发......
  • Linux日志切割神器logrotate原理介绍和配置详解
    1、原理介绍create这也就是默认的方案,可以通过create命令配置文件的权限和属组设置;这个方案的思路是重命名原日志文件,创建新的日志文件。详细步骤如下:重命名正在输出日志文件,因为重命名只修改目录以及文件的名称,而进程操作文件使用的是inode,所以并不影响原程序继续输出日志......
  • Java开发环境搭建
    以下内容均是来自于尚硅谷教育提供电子教材节选【存在部分修改】,方便个人阅读复习!如有侵权,联系删除!Java开发环境搭建(掌握)1什么是JDK、JREJDK(JavaDevelopmentKit):是Java程序开发工具包,包含JRE和开发人员使用的工具。**JRE**(JavaRuntimeEnvironment):是Java程序的......
  • 2023年度Linux系统安装与移除JDK保姆级教程
    简介本篇文章介绍了如何在CentOS系统上安装与移除JDK,并提供了两种不同的安装与移除方法。我们还将针对每种方法的优点和缺点进行对比前置条件在开始之前,请确保您已经在虚拟机中安装CentOS系统如果没有安装请参考我之前的VMwareWorkstation17Pro安装配置CentOS7与ssh......
  • 2023年度Linux安装与移除tomcat保姆级教程
    前言Tomcat是一个流行的JavaServlet容器,用于开发和部署JavaWeb应用程序。本文将介绍如何在CentOS操作系统上安装与移除Tomcat,并提供了逐步说明以及相关命令。读者需要具备一定的Linux基础知识,如使用命令行工具等。安装前置条件在开始安装Tomcat之前,请确保您已满足以下条件:......
  • 开发一次、运行多端:Weex与小程序容器的卓越优势解析
    Weex是一个跨平台的移动应用开发框架,由阿里巴巴旗下的阿里巴巴前端团队开发。它允许开发者使用单一的代码库来构建同时适用于iOS和Android平台的移动应用。Weex使用基于Vue.js的声明式语法来描述应用程序的界面,并通过JavaScript运行时引擎在移动设备上解析和渲染界面。 Weex......
  • linux服务器CPU飙高排查
    文章目录前言一、第一步top二、根据pid查找具体线程2.根据pid找到16进制3.根据进程和线程查找原因总结前言系统cpu飙高,尤其对于后端人员来说,其实应该学会排查,这样也算是综合能力的体现;那么当出现了cpu严重飙高的时候怎么排查呢?一、第一步top直接在问题服务器输入命令:to......