20181215《linux设备驱动开发详解》宋宝华 学习笔记(1)

1. 设备驱动的作用

设备驱动充当了硬件和应用软件的纽带(那设备驱动和操作系统之间的关系是什么?)应用软件时只需要调用系统软件的应用编程接口(API)就可让硬件去完成要求的工作。在系统没有操作系统的情况下,工程师可以根据硬件设备的特点自行定义接口,如对串口定义SerialSend()、SerialRecv(),对LED定义LightOn()、LightOff(),对Flash定义FlashWr()、FlashRd()等。而在有操作系统的情况下,驱动的架构则由相应的操作系统定义,驱动工程师必须按照相应的架构设计驱动,这样,驱动才能良好地整合入操作系统的内核中。

2. 无操作系统的设备驱动

有些环境下不需要操作系统就能完成,但一定会有设备驱动。

(而我目前的理解是:驱动就是一些设备功能的通用实现,设备是区别化的地址和变量信息)

一个例子:一个无限循环中夹杂着对设备中断的检测或者对设备的轮询是这种系统中软件的典型架构

5 if (serialInt == 1)
6 /* 有串口中断 */
7 {
8 ProcessSerialInt(); /* 处理串口中断 */
9 serialInt = 0; /* 中断标志变量清 0 */
10 }
11 if (keyInt == 1)
12 /* 有按键中断 */
13 {
14 ProcessKeyInt(); /* 处理按键中断 */
15 keyInt = 0; /* 中断标志变量清 0 */
16 }
17 status = CheckXXX();
18 switch (status)

其他模块想要使用这个设备的时候,只需要包含设备驱动的头文件serial.h,然后调用其中的外部接口函数。由此可见,在没有操作系统的情况下,设备驱动的接口被直接提交给应用软件工程师,应用软件没有跨越任何层次就直接访问设备驱动的接口。驱动包含的接口函数也与硬件的功能直接吻合,没有任何附加功能。所示为无操作系统情况下硬件、设备驱动与应用软件的关系。
20181215《linux设备驱动开发详解》宋宝华 学习笔记(1)_第1张图片

3. 有操作系统的设备驱动

驱动需要融入操作系统内核:为了实现这种融入,必须在所有设备的驱动上设计面向操作系统内核的接口。在有操作系统的时候,驱动变成了连接内核与硬件的桥梁,它对外呈现为操作系统的API, 不再给应用软件工程师提供直接接口。

以驱动的复杂换取上层应用的便利。

20181215《linux设备驱动开发详解》宋宝华 学习笔记(1)_第2张图片

4. Linux设备驱动

驱动针对的是存储器和外设(包括CPU内部集成的存储器和外设,但不针对于CPU内核)

Linux将存储器和外设分为3个基础大类:
- 字符设备   字符设备必须以串行顺序依次进行访问的设备,例如触摸屏,磁带驱动器,鼠标等。
- 块设备   可以按任意顺序进行访问,以块为单位进行操作,如硬盘,EMMC

以上两种设备均可以使用文件系统的操作接口open() close() read() write()等进行访问
- 网络设备     面向数据包的接收和发送设计

Linux设备驱动与整个软硬件系统关系

20181215《linux设备驱动开发详解》宋宝华 学习笔记(1)_第3张图片

5. QEMU实验平台

6. 阅读Linux源码

7. 设备驱动Hello world: LED驱动

无操作系统时的LED驱动

在嵌入式系统的设计中,LED一般直接由CPU的GPIO(通用可编程I/O)口控制。GPIO一般由两组寄存器控制,即一组控制寄存器和一组数据寄存器。控制寄存器可设置GPIO口的工作方式为输入或者输出。当引脚被设置为输出时,向数据寄存器的对应位写入1和0会分别在引脚上产生高电平和低电平;当引脚设置为输入时,读取数据寄存器的对应位可获得引脚上的电平为高或低。

在本例子中,我们屏蔽具体CPU的差异,假设在GPIO_REG_CTRL物理地址中控制寄存器处的第n位写入1可设置GPIO口为输出,在地址GPIO_REG_DATA物理地址中数据寄存器的第n位写入1或0可在引脚上产生高或低电平,则在无操作系统的情况下,设备驱动见代码清单

 #define reg_gpio_ctrl *(volatile int *)(ToVirtual(GPIO_REG_CTRL))
 #define reg_gpio_data *(volatile int *)(ToVirtual(GPIO_REG_DATA))
 /* 初始化 LED */
 void LightInit(void)
{
    reg_gpio_ctrl |= (1 << n); /* 设置 GPIO 为输出 */  // |= 按位或后赋值
 }
/* 点亮 LED */
void LightOn(void)
 {
    reg_gpio_data |= (1 << n); /* 在 GPIO 上输出高电平 */
 }
/* 熄灭 LED */
void LightOff(void)
 {
    reg_gpio_data &= ~ (1 << n); /* 在 GPIO 上输出低电平 */
 }

linux下的LED驱动

在内核中实际实现了一个提供sysfs节点的GPIO LED驱动。

20181215《linux设备驱动开发详解》宋宝华 学习笔记(1)_第4张图片

但这里,可以使用字符设备驱动的框架编写对应的LED设备驱动,操作硬件的LightInit()、LightOn()、LightOff()函数仍然需要,但是,遵循Linux编程的命名习惯,重新将其命名为light_init()、light_on()、light_off()。这些函数将被LED设备驱动中独立于设备并针对内核的接口进行调用,给出了Linux下的LED驱动。

1  #include .../* 包含内核中的多个头文件 */
2  /* 设备结构体 */
3  struct light_dev {
4     struct cdev cdev; /* 字符设备 cdev 结构体 */
5     unsigned char vaule; /* LED 亮时为 1 ,熄灭时为 0 ,用户可读写此值 */
6  };
7  struct light_dev *light_devp;
8  int light_major = LIGHT_MAJOR;
9  MODULE_AUTHOR("Barry Song <[email protected]>");
10 MODULE_LICENSE("Dual BSD/GPL");
11 /* 打开和关闭函数 */
12 int light_open(struct inode *inode, struct file *filp)
13 {
14     struct light_dev *dev;
15     /* 获得设备结构体指针 */
16     dev = container_of(inode->i_cdev, struct light_dev, cdev); //container_of
17     /* 让设备结构体作为设备的私有信息 */
18     filp->private_data = dev;
19     return 0;
20 }
21 int light_release(struct inode *inode, struct file *filp)
22 {
23     return 0;
24 }
25 /* 读写设备 : 可以不需要 */
26 ssize_t light_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
28 {
29     struct light_dev *dev = filp->private_data; /* 获得设备结构体 */
30     if (copy_to_user(buf, &(dev->value), 1))
31     return -EFAULT;
32     return 1;
33 }
34 ssize_t light_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
36 {
37     struct light_dev *dev = filp->private_data;
38     if (copy_from_user(&(dev->value), buf, 1))
39          return -EFAULT;
40     /* 根据写入的值点亮和熄灭 LED */
41     if (dev->value == 1)
42         light_on();
43     else
44     light_off();
45     return 1;
46 }
47 /* ioctl 函数 */
48 int light_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg)
50 {
51     struct light_dev *dev = filp->private_data;
52     switch (cmd) {
53     case LIGHT_ON:
54         dev->value = 1;
55         light_on();
56         break;
57     case LIGHT_OFF:
58         dev->value = 0;
59         light_off();
60     break;
61     default:
62     /* 不能支持的命令 */
63     return -ENOTTY;
64     }
65     return 0;
66 }
67 struct file_operations light_fops = {
68 .owner = THIS_MODULE,
69 .read = light_read,
70 .write = light_write,
71 .ioctl = light_ioctl,
72 .open = light_open,
73 .release = light_release,
74 };
75 /* 设置字符设备 cdev 结构体 */
76 static void light_setup_cdev(struct light_dev *dev, int index)
77 {
78     int err, devno = MKDEV(light_major, index);
79     cdev_init(&dev->cdev, &light_fops);
80     dev->cdev.owner = THIS_MODULE;
81     dev->cdev.ops = &light_fops;
82     err = cdev_add(&dev->cdev, devno, 1);
83     if (err)
84         printk(KERN_NOTICE "Error %d adding LED%d", err, index);
85 }
86 /* 模块加载函数 */
87 int light_init(void)
88 {
89     int result;
90     dev_t dev = MKDEV(light_major, 0);
91     /* 申请字符设备号 */
92     if (light_major)
93         result = register_chrdev_region(dev, 1, "LED");
94     else {
95         result = alloc_chrdev_region(&dev, 0, 1, "LED");
96     light_major = MAJOR(dev);
97 }
98 if (result < 0)
99     return result;
100 /* 分配设备结构体的内存 */
101 light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL);
102 if (!light_devp) {
103     result = -ENOMEM;
104     goto fail_malloc;
105 }
106 memset(light_devp, 0, sizeof(struct light_dev));
107 light_setup_cdev(light_devp, 0);
108 light_gpio_init();
109 return 0;
110 fail_malloc:
111     unregister_chrdev_region(dev, light_devp);
112     return result;
113 }
114 /* 模块卸载函数 */
115 void light_cleanup(void)
116 {
117     cdev_del(&light_devp->cdev); /* 删除字符设备结构体 */
118     kfree(light_devp); /* 释放在 light_init 中分配的内存 */
119     unregister_chrdev_region(MKDEV(light_major, 0), 1); /* 删除字符设备 */
120 }
121 module_init(light_init);
122 module_exit(light_cleanup);

以上两段代码量的对比中,除了基本的灯的开关功能外,还包括了结构体如结构体file_operations、cdev,Linux内核模块声明用的MODULE_AUTHOR、MODULE_LICENSE、module_init、module_exit,以及用于字符设备注册、分配和注销的函register_chrdev_region()、alloc_chrdev_region()、unregister_chrdev_region()等。

此时,我们只需要有一个感性认识,那就是,上述暂时陌生的元素都是Linux内核为字符设备定义的,以实现驱动与内核接口而定义的。Linux对各类设备的驱动都定义了类似的数据结构和函数。

 

 

 

 

 

 

 

 

你可能感兴趣的:(QEMU)