驱动模型
驱动分类
- 字符设备驱动
- 网络设备驱动
- 块设备驱动
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
- 字符设备
字符设备是一种按照字节来访问的设备,字符设备则是负责驱动字符设备,这样的驱动通常实现
open
close
read
write
系统调用。
- 块设备
在大部分的Unix系统,块设备不能按字节处理数据,只能一次一次或着对个长度为512字节(或一个更大的2次幂的数)的整块数据。
而Linux则允许块设备传送任意数目的字节。因此,块设备和字符设备的区别仅仅是驱动与内核的接口不同。
块的大小我们在制作的时候可以配置,如512KB/块、1024KB/块……
- 网络接口
任何网络事务都通过一个接口来进行,一个接口通常是一个硬件设备(eth0),但是它也可以是一个纯粹的软件设备,比如回环接口(lo),一个网络接口负责发送和接收数据报文。
驱动程序安装
- 模块方式(动态安装)
- 直接编译进入内核
-
直接编译进内核
将helloworld 编译进内核?
修改: Kconfig 和 Makefile 两个文件就可以
(1) 进入内核源码包
linux-2.6.32.2
,找到我们需要修改的两个文件: ---->Kconfig文件:用于配置在执行
make menuconfig
时的菜单。 我们要加入到内核的模块驱动文件为:
mini2440_hello_module.c
那么我们在内核文件中,Kconfig我们这么来配置:
config MINI2440_HELLO_MODULE tristate "Mini2440 module sample" depends on MACH_MINI2440 default m if MACH_MINI2440 help Mini2440 module sample.
如下,是我配置好的:
至此,我们就配置好了菜单了,下面执行make menuconfig,我们看看是什么效果:
Kconfig 用于配置项目菜单项的
---->Makefile文件:见我们需要编译成模块的文件加入编译项目文件Makefile中
在Makefile中,我们加入这样的成分:
obj-$(CONFIG_MINI2440_HELLO_MODULE) += mini2440_hello_module.o
添加完成后的效果是:
-
用户程序如何调用驱动
对驱动的操作,实际是对设备文件的操作!
A:Linux用户程序通过设备文件(设备节点)来使用驱动程序操作字符设备和块设备。
Q:(字符、块)设备文件在哪儿?
A:文件放置在/dev/文件夹下。
如下,我们看下,在dev目录下的设备文件(设备节点)
可以看到,在我们这里已经有了很多的设备驱动,如:ADC、buttons、camera、leds、还有一些通信协议的如i2c、spi等
那么我们就可以操作这里的设备文件,来让对应的驱动做出相应的输出动作,实现对外设的控制。至于具体我们如何使用,在我们后面讲解了字符设备驱动的程序结构,我们再来解释。
字符设备程序设计
在设计字符设备驱动时,我们从下面角度来进一步了解:
-
设备号
字符设备通过字符设备文件来存取。字符设备文件可以
ls -l
的输出的第一列“C”标示,使用命令,会看到在设备文件项中有2个数(由逗号分隔开)这就是设备的主次设备编号。上图作为实例,两个数字
10,62
这就是设备号了!!!=》设备号用来做什么?
主设备号用来反映设备类型
次设备号用来区分同类型的设备
Q:内核中如何描述设备号?
A:dev_t(其实质是32位无符号的整数,高12为主设备号,低20为次设备号)
Q:如何从dev_t中解释出主设备号?
A:
MAJOR(dev_t dev)
Q:如何从dev_t中解释出次设备号?
A:
MINOR(dev_t dev)
=》分配主设备号
-
静态申请
(1)根据Documentation/device.txt来确定一个没有使用的主设备号。
(2)使用
register_chrdev_region
函数注册设备号。int register_chrdev_region(dev_t from, unsigned count, const char *name) 功能:申请使用从from开始的count个设备号(主设备号不变,次设备号递增) 参数: from 希望申请使用的设备号 count 希望申请使用设备号数目 name 设备名(体现在/proc/devices)
-
动态分配
直接使用
alloc_chrdev_region
由内核分配设备号。安装驱动后,从/proc/devices 中查询设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 功能:请求内核动态的分配count个设备号,且次设备号从baseminor开始。 参数: dev 分配到的设备号 baseminor 起始次设备号 count 需要分配的设备号数目 name 设备名(体现在/proc/devices)
-
注销设备号
在不使用该设备时,都要释放这些设备号
void unregister_chrdev_region(dev_t from, unsigned count) 功能:释放从from开始的count个设备号
-
-
创建设备文件
-
手工创建
使用mknod命令手工创建
mknod filename type major minor 参数: filename: 设备文件名 type: 设备文件类型 major: 主设备号 minor: 次设备号
举例:创建两个串口设备
mknod serial0 c 200 0
mknod serial1 c 200 1
=》主设备号均为200,说明都是由同一个驱动程序来控制的
=》次设备号不同,用于区分不同的操作对象
-
自动创建
-
-
设备注册
【分配cdev】 —> 【初始化cdev】 —> 【添加cdev】
-
分配cdev
struct cdev *cdev_alloc(void)
-
初始化
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev:待初始化的cdev结构
fops:设备对应的操作函数集
-
注册
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p:待添加到内核的字符设备结构
dev:设备号
count:添加的设备个数
-
设备操作
int (*open)(struct inode *,struct file *)
在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项目为NULL,设备的打开操作永远成功。
void (*release)(struct inode *, struct file *)
当设备文件被关闭时调用这个操作。与open相仿,release也可以没有。
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *)
从设备中读取数据。
ssize_t (*write)(struct file *, const __user *, size_t, loff_t *)
向设备发送数据
unsigned int (*poll)(struct file *, struct poll_table_struct *)
对应select系统调用
int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long)
控制设备
int (*mmap)(struct file *, struct vm_area_struct *)
将设备映射到进程虚拟地址空间中
off_t (*llseek)(struct file *,loff_t int)
修改文件的当前读写位置,并将新位置作为返回值。
-
Open方法
open方法是驱动程序用来为以后的操作完成初始化准备工作的,一般:【初始化】 --> 【表明次设备号】
-
Release方法
关闭设备。
-
读和写
ssize_t xxx_read(struct file * filp, char __user * buff, size_t count, loff_t * offp);
ssize_t xxx_write(struct file * filp, char __user * buff, size_t count, loff_t * offp);
filp是文件指针,count是请求传输的数据量,buff 参数指向数据缓存,offp指出文件当前的访问位置。
Read和Write方法的buff参数是用户空间指针。因此,它不能被内核代码直接引用,原因是用户空间指针在内核空间时可能根本是无效的----没有那个地址的映射。
内核提供了专门的函数用于访问用户空间的指针,
int copy_from_user(void *to, const void __user *from, int n) int copy_to_user(void __user *to,const void *from, int n)
-
-
设备注销
字符设备的注销使用cdev_del函数完成。
int cdev_del(struct cdev *p)
p:要注销的字符设备结构。
-
-
源码分析:下面是mini2440的6个按键的驱动程序
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/irq.h> #include <asm/irq.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <linux/sched.h> #include <linux/gpio.h> #define DEVICE_NAME "buttons" struct button_irq_desc { /*这里定义了一个按键的结构体*/ int irq; int pin; int pin_setting; int number; char *name; }; /*将开发板上的六个按键规划到数组当中*/ static struct button_irq_desc button_irqs [] = { {IRQ_EINT8 , S3C2410_GPG(0) , S3C2410_GPG0_EINT8 , 0, "KEY0"}, {IRQ_EINT11, S3C2410_GPG(3) , S3C2410_GPG3_EINT11 , 1, "KEY1"}, {IRQ_EINT13, S3C2410_GPG(5) , S3C2410_GPG5_EINT13 , 2, "KEY2"}, {IRQ_EINT14, S3C2410_GPG(6) , S3C2410_GPG6_EINT14 , 3, "KEY3"}, {IRQ_EINT15, S3C2410_GPG(7) , S3C2410_GPG7_EINT15 , 4, "KEY4"}, {IRQ_EINT19, S3C2410_GPG(11), S3C2410_GPG11_EINT19, 5, "KEY5"}, }; static volatile char key_values [] = {'0', '0', '0', '0', '0', '0'}; static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static volatile int ev_press = 0; static irqreturn_t buttons_interrupt(int irq, void *dev_id) { struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id; int down; // udelay(0); down = !s3c2410_gpio_getpin(button_irqs->pin); if (down != (key_values[button_irqs->number] & 1)) { // Changed key_values[button_irqs->number] = '0' + down; ev_press = 1; wake_up_interruptible(&button_waitq); } return IRQ_RETVAL(IRQ_HANDLED); } static int s3c24xx_buttons_open(struct inode *inode, struct file *file) { int i; int err = 0; for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH, button_irqs[i].name, (void *)&button_irqs[i]); if (err) break; } if (err) { i--; for (; i >= 0; i--) { if (button_irqs[i].irq < 0) { continue; } disable_irq(button_irqs[i].irq); free_irq(button_irqs[i].irq, (void *)&button_irqs[i]); } return -EBUSY; } ev_press = 1; return 0; } static int s3c24xx_buttons_close(struct inode *inode, struct file *file) { int i; for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { if (button_irqs[i].irq < 0) { continue; } free_irq(button_irqs[i].irq, (void *)&button_irqs[i]); } return 0; } static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { unsigned long err; if (!ev_press) { if (filp->f_flags & O_NONBLOCK) return -EAGAIN; else wait_event_interruptible(button_waitq, ev_press); } ev_press = 0; err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count)); return err ? -EFAULT : min(sizeof(key_values), count); } static unsigned int s3c24xx_buttons_poll( struct file *file, struct poll_table_struct *wait) { unsigned int mask = 0; poll_wait(file, &button_waitq, wait); if (ev_press) mask |= POLLIN | POLLRDNORM; return mask; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = s3c24xx_buttons_open, .release = s3c24xx_buttons_close, .read = s3c24xx_buttons_read, .poll = s3c24xx_buttons_poll, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init dev_init(void) { int ret; ret = misc_register(&misc); printk (DEVICE_NAME"\tinitialized\n"); return ret; } static void __exit dev_exit(void) { misc_deregister(&misc); } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("FriendlyARM Inc.");
下面,我们再来看看一个led的驱动以及调用驱动的测试程序:
#include <linux/miscdevice.h> #include <linux/delay.h> #include <asm/irq.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/delay.h> #include <linux/moduleparam.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/string.h> #include <linux/list.h> #include <linux/pci.h> #include <linux/gpio.h> #include <asm/uaccess.h> #include <asm/atomic.h> #include <asm/unistd.h> #define DEVICE_NAME "leds" static unsigned long led_table [] = { S3C2410_GPB(5), S3C2410_GPB(6), S3C2410_GPB(7), S3C2410_GPB(8), }; static unsigned int led_cfg_table [] = { S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, S3C2410_GPIO_OUTPUT, }; static int sbc2440_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { case 0: case 1: if (arg > 4) { return -EINVAL; } s3c2410_gpio_setpin(led_table[arg], !cmd); return 0; default: return -EINVAL; } } static struct file_operations dev_fops = { .owner = THIS_MODULE, .ioctl = sbc2440_leds_ioctl, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init dev_init(void) { int ret; int i; for (i = 0; i < 4; i++) { s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]); s3c2410_gpio_setpin(led_table[i], 0); } ret = misc_register(&misc); printk (DEVICE_NAME"\tinitialized\n"); return ret; } static void __exit dev_exit(void) { misc_deregister(&misc); } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("FriendlyARM Inc.");
下面是leds的测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>
static int led_fd;
static int type = 1;
static void push_leds(void)
{
static unsigned step;
unsigned led_bitmap;
int i;
switch(type) {
case 0:
if (step >= 6) {
step = 0;
}
if (step < 3) {
led_bitmap = 1 << step;
} else {
led_bitmap = 1 << (6 - step);
}
break;
case 1:
if (step > 255) {
step = 0;
}
led_bitmap = step;
break;
default:
led_bitmap = 0;
}
step++;
for (i = 0; i < 4; i++) {
ioctl(led_fd, led_bitmap & 1, i);
led_bitmap >>= 1;
}
}
int main(void)
{
int led_control_pipe;
int null_writer_fd; // for read endpoint not blocking when control process exit
double period = 0.5;
led_fd = open("/dev/leds0", 0);
if (led_fd < 0) {
led_fd = open("/dev/leds", 0);
}
if (led_fd < 0) {
perror("open device leds");
exit(1);
}
unlink("/tmp/led-control");
mkfifo("/tmp/led-control", 0666);
led_control_pipe = open("/tmp/led-control", O_RDONLY | O_NONBLOCK);
if (led_control_pipe < 0) {
perror("open control pipe for read");
exit(1);
}
null_writer_fd = open("/tmp/led-control", O_WRONLY | O_NONBLOCK);
if (null_writer_fd < 0) {
perror("open control pipe for write");
exit(1);
}
for (;;) {
fd_set rds;
struct timeval step;
int ret;
FD_ZERO(&rds);
FD_SET(led_control_pipe, &rds);
step.tv_sec = period;
step.tv_usec = (period - step.tv_sec) * 1000000L;
ret = select(led_control_pipe + 1, &rds, NULL, NULL, &step);
if (ret < 0) {
perror("select");
exit(1);
}
if (ret == 0) {
push_leds();
} else if (FD_ISSET(led_control_pipe, &rds)) {
static char buffer[200];
for (;;) {
char c;
int len = strlen(buffer);
if (len >= sizeof buffer - 1) {
memset(buffer, 0, sizeof buffer);
break;
}
if (read(led_control_pipe, &c, 1) != 1) {
break;
}
if (c == '\r') {
continue;
}
if (c == '\n') {
int tmp_type;
double tmp_period;
if (sscanf(buffer,"%d%lf", &tmp_type, &tmp_period) == 2) {
type = tmp_type;
period = tmp_period;
}
fprintf(stderr, "type is %d, period is %lf\n", type, period);
memset(buffer, 0, sizeof buffer);
break;
}
buffer[len] = c;
}
}
}
close(led_fd);
return 0;
}
重要的结构
-
Struct file
代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件的时候创建,在文件关闭后释放。
重要的组成成员:
loff_t f_pos /*文件读写位置*/
struct file_operations *f_op
-
Struct inode
用来记录文件的物理上的信息。因此,它和代表打开的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构
重要的组成成员:
dev_t i_rdev : 设备号
-
Struct file_operations
一个函数指针的集合,定义能在设备上进行的操作,结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL
例如:
Struct file_operations mem_fops={ .owner=THIS_MODULE, .llseek=mem_seek, .read=mem_read, .write=mem_write, .ioctl=mem_ioctl, .open=mem_open, .release=mem_release, };
=》把应用程序上的操作转化成驱动程序的对应函数操作!!!