Linux设备驱动开发流程(转)

一、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\n");

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);

猜你喜欢

转载自www.cnblogs.com/yangjiquan/p/11488661.html
今日推荐