嵌入式Linux设备驱动开发笔记(一)

2019-07-12 14:11发布

一、Linux设备的分类 字符设备、块设备、网络设备,三种设备之间的区别是数据的交互模式,分别为:
字节流、数据块、数据包。 二、VFS核心结构体 VFS核心结构体定义在”linux/fs.h”头文件中。 1、struct inode结构体
记录文件的属主、访问时间等信息。当第一次打开文件的时候由VFS创建并初始化。当文件的所有引用都退出后,释放inode; 如果用户态有多个人同时打开一个文件,则VFS只需要分配一个inode。 2、struct file结构体
对应用户态的open操作。如果多次打开同一个文件,内核会生成多个file。file中记录文件的打开方式,文件内部指针等。当文件彻底关闭时,释放file。 3、struct file_operations结构体
该结构体包含若干函数指针,这些函数由驱动来实现,并集中到file_operations中,注册到VFS。
驱动一般要实现的函数有:
open
release
read
write
unlocked_ioctl
驱动可能会实现的有:
poll
mmap
fasync
flush
llseek 三、字符设备驱动开发流程 (1)确定硬件信息
要确定硬件的数量,物理地址,中断号等信息; (2)为要支持的设备准备一个私有结构体
内核并不要求必须有私有结构体,但如果驱动支持多个设备,最好设计一个。私有结构体完全由驱动人员自行设计,一般来说,会把和设备相关的信息写入该结构体,比如设备的地址等。 (3)为要支持的每个设备分配对应的设备号
设备号由char驱动分配,要求唯一。一般来说,如果char驱动可支持多个类似的设备,则应该为这些设备选择一个主设备号,然后为每个设备选择一个特定的次设备号。尽量挑选和其他驱动不一样的主设备号。可以看/proc/devices,文件中记录了其他驱动选择的主设备号;也可以向内核申请,由内核分配一个主设备号。 #define DEV_MAJOR 50 ... dev_id = MKDEV(DEV_MAJOR, 0); (4)准备file_operations结构体
函数集中包括open/release/read/write/unlocked_ioctl等函数,如果驱动支持多个设备,在函数中必须能区分自己访问的是哪个设备。 static struct file_operations mem_fops = { .owner = THIS_MODULE, .open = mem_open, .release = mem_release, .read = mem_read, .write = mem_write, .unlocked_ioctl = mem_ioctl, }; (5)注册设备 方法一: 利用cdev结构体,将设备号和file_operations注册到VFS。一般来说,将cdev结构体包含到私有结构体中。采用cdev注册的设备,不会自动创建设备文件。 cdev_init(&mem_cdev, &mem_fops); cdev_add(&mem_cdev, dev_id, 1); cdev_del(&mem_cdev); //注销cdev 方法二: 注册miscdevice(用misc代替cdev注册,可以自动创建设备文件)
这种情况下不需要定义主设备号,即省去#define DEV_MAJOR 50,同时需要用头文件”linux/miscdevice.h”代替”linux/cdev.h”头文件。 static struct miscdevice mem_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "mem", //对应/dev/mem设备文件 .fops = &mem_fops, }; ret = misc_register(&mem_miscdev); //注册miscdevice misc_deregister(&mem_miscdev); //注销miscdevice cdev和miscdevice比较:
(1)cdev可以实现同一个驱动对应多个设备,而miscdevice只能实现一个驱动对应一个设备;
(2)cdev不能实现设备文件的自动创建,而miscdevice可以实现设备文件的自动创建。 上述的过程比较适合较简单的设备,比如看门狗,led灯,各种传感器等。较复杂设备的char驱动,常常要利用内核提供的驱动子系统代码进行设计。 四、如何在linux驱动中访问寄存器(SFR) 1、片内外设(pripheral)
(1)基于三总线访问
(2)用寄存器控制
(3)寄存器有物理地址,可通过手册查到,不可更改 2、片外外设
(1)很少通过三总线相连,一般是通过UART,CAN,I2C,USB,SPI,MIPI,I2S,AC97等总线相连。主芯片内部必须提供对应的控制器:内部的控制器 <–> 外部的外设
(2)片外外设基本都是智能设备(不但有硬件,还有软件)
(3)有些片外外设通过寄存器控制,有些则通过命令控制
(4)如果用寄存器控制,寄存器没有物理地址,只有偏移。
(5)要控制片外外设,需要首先了解对应的总线 3、访问寄存器的流程
由于linux使能了MMU,因此对于驱动来说,不能直接使用寄存器的物理地址,必须将其映射为虚拟地址才可以使用。 (1)定义寄存器物理基地址以及寄存器的偏移 #define GPIO_BASE 0x11000000 #define GPIO_SIZE 0x1000 //0x8 #define GPM4CON 0x2E0 //偏移地址 #define GPM4DAT 0x2E4 //偏移地址 GPIO_SIZE为寄存器的范围,可以按照使用的寄存器的总大小进行计算,比如用了两个寄存器,范围是0x8;但由于地址映射的最小单位是4K,因此小于4K的值都是可以的。 (2)将寄存器物理地址映射到虚拟地址,如果映射不成功,则无法访问寄存器 static void __iomem *vir_base; vir_base = ioremap(GPIO_BASE, GPIO_SIZE); if (!vir_base) { printk("Cannot ioremap "); return -EIO; } (3)访问寄存器,一般采用基地址加偏移的模式,内核根据寄存器的大小,提供了一系列函数 8位寄存器的访问 char value; value = readb(vir_base + offset); writeb(value, (vir_base + offset)); 16位寄存器的访问 short value; value = readw(vir_base + offset); writew(value, (vir_base + offset)); 32位寄存器的访问 int value; value = readl(vir_base + offset); writel(value, (vir_base + offset)); 64位寄存器的访问 u64 value; value = readq(vir_base + offset); writeq(value, (vir_base + offset)); (4)取消寄存器的映射 iounmap(vir_base);