0%

Linux驱动学习日记(15) 驱动设计的思想,总线设备驱动模型

驱动设计的思想

面向对象

  字符设备驱动程序抽象出一个 file_operations 结构体,在这个结构体中实现驱动相关的 open、write 等函数,把一个事件抽象为一个对象。在 Linux 驱动中,可以认为面向对象就是用结构体表示某一个对象。

分层

  上下分层,可以将 LED 驱动程序分为两层:
  ①、上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c。
  ②、下层实现硬件相关的操作,比如 board_A.c 实现单板 A 的 LED 操作。

分离

  上下分层,下层左右分离。在上层统一使用 leddrv.c 实现 file_operations 注册驱动;下层定义两个文件,board_A_led.c 指明在 A 板上 LED 的引脚,chipY_gpio.c 实现了这款芯片的 GPIO 操作,所以使用不同的 board_B_led.c 时可以使用同一个 chipY_gpio.c ,减少我们的工作量。

flowchart TB
  %% 上下分层:上层统一驱动注册,下层左右分离:板级/芯片级
  subgraph L1["上层:统一驱动层"]
    leddrv["leddrv.c\n(file_operations 注册/驱动入口)"]
  end

  subgraph L2["下层:适配层(左右分离)"]
    direction LR

    subgraph B["左:板级适配(Board)"]
      boardA["board_A_led.c\n(声明 A 板 LED 引脚/资源)"]
      boardB["board_B_led.c\n(声明 B 板 LED 引脚/资源)"]
    end

    subgraph C["右:芯片抽象(Chip)"]
      chipY["chipY_gpio.c\n(GPIO 操作实现:read/write/dir...)"]
    end
  end

  leddrv --> boardA
  leddrv --> boardB
  boardA --> chipY
  boardB --> chipY

  %% 强调复用关系
  chipY -.复用.-> boardA
  chipY -.复用.-> boardB

  在 board_A_led.c 中,实现 led_resources 结构体。在 Linux 驱动中,这里的板级文件一般指驱动树,我们只需要修改驱动树中的 GPIO 组就可以了。

总线设备驱动模型

  在传统写法中,我们用什么引脚、引脚的寄存器操作都写在一起,绑定紧密(分配/设置/注册 file_operations/使用 ioremap 映射寄存器/操作寄存器)。虽然简单,但是程序扩展性差。同时,Linux 需要支持很多的设备,如果按照上文所说的分离思想,要定义多个结构体,很明显这是不现实的,因此引入了总线设备驱动模型。

  Linux 驱动里的“总线-设备-驱动模型”,本质上是一个相亲系统。内核不直接让驱动去“找硬件”,而是建立一个三方结构:总线(bus)负责牵线,设备(device)是被描述的硬件实体,驱动(driver)是实现功能的软件代码。当一个设备被注册到某条总线上时,总线会拿这个设备的“身份证”(比如 name、compatible、id 表)去匹配所有已经注册的驱动;如果某个驱动声明“我支持这种设备”,匹配成功,总线就调用驱动的 probe(),从此这个设备就“被驱动”了。设备拔掉时会调用 remove(),生命周期也归总线统一管理。一般情况下,在 ARM 这类 SOC 平台上,设备通常通过设备树修改。

  具体上来说,平台设备和平台驱动是如何挂钩的呢?上文说到是通过总线(BUS)连接的。BUS 左侧是设备列表,右侧是驱动列表。当我们注册一个 platform_device时,总线会将其与驱动列表中已注册的 platform_driver 一一比较,若匹配成功,就调用那个 platform_driver 中的 probe 函数;一样的,当我们注册一个 platform_driver 时,总线会将其与设备列表中的设备一一驱动,若匹配成功,则调用其中的 probe 函数处理匹配的设备。

  匹配规则的细节会随内核版本演进,但总体思路是:如果设备指定了 driver_override 就优先按它绑定;否则优先走设备树/ACPI 这类“硬件描述”匹配;再退回到 id_table 与名字匹配。匹配成功后调用 probe() 完成资源解析与设备初始化。

  一般情况下,一个 driver 可以匹配一个或多个 device。如何匹配呢?第一步,若 device 中定义了 driver_override,bus 会将它和 driver 中的 driver.name 先比较;若没有定义driver_override,再将 device 中的 name 和 driver 的id_table 去比较;若还是匹配不上,则最后将 device 中的 name 和 driver 中的 driver.name 比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;

const struct platform_device_id *id_entry;

const char *driver_override;

struct mfd_cell *mfd_cell;

struct pdev_archdata archdata;
};

struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*remove_new)(struct platform_device *);

void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;

bool driver_managed_dma;
};

代码解析

  基于韦东山的最小总线设备驱动模型框架代码(LED),我们来简要分析如何一步一步写出这份框架。代码放在文章末尾。

文件分别介绍

  leddrv.c 作为上层框架做两件事:
  1、register_chrdev()注册字符设备(使用 file_operation 结构体),提供 /dev/100ask_ledX 这种用户态入口;
  2、class_create() 创建 class,用于后续 device_create() 自动生成设备节点。
  这个文件不管硬件操作,只负责将用户写进的0/1转交给指针函数 p_led_opr->ctl(minor, status);。同时他还导出了三个函数给别的模块使用led_class_create_device(minor),led_class_destroy_device(minor),register_led_operations(opr)(注册“底层怎么点灯”的实现)

  chip_demo_gpio.c 作为驱动层,干了以下两件事
  1、platform_driver 去匹配板级设备并读取资源。

.driver.name = "100ask_led"
probe() 里通过 platform_get_resource(... IORESOURCE_IRQ ...) 把 board_A_led.c 提供的 .start 读出来,存进 g_ledpins[]
每读到一个资源就led_class_create_device(g_ledcnt) → 创建设备节点 /dev/100ask_led0, /dev/100ask_led1…,并且g_ledcnt++。

  2、提供真正控制 LED 的函数实现

board_demo_led_init(which):根据 g_ledpins[which] 初始化。
board_demo_led_ctl(which, status):根据 g_ledpins[which] 设置高低电平(这里用 printk 模拟)。
并在 init 时调用:register_led_operations(&board_demo_led_opr); 让 leddrv.c 里的 p_led_opr 指向它。

  board_A_led.c:板级资源声明
  这里注册一个 platform_device,名字也叫 “100ask_led”,所以可以被 bus 匹配。通过 resources[] 把“本板子有哪些 LED、各自接在哪个 GPIO”描述出来。

串联

Step 0:
  加载 leddrv.ko,注册字符设备(拿到 major),建立 led_class。加载 board_A_led.ko,platform_device_register() 注册一个名叫 “100ask_led” 的设备。加载 chip_demo_gpio.ko,platform_driver_register() 注册名叫 “100ask_led” 的驱动,内核发现已经存在同名 device,于是触发 probe()。

Step 1:
  在 probe 函数中,从 platform_device 里把 resources 一个个读出来,填到 g_ledpins[],为每个 LED 创建一个 /dev/100ask_ledX。

Step 2:
  用户态写入:

write(fd, &val, 1);  // val=0/1

  内核进入 leddrv.c: led_drv_write():通过 iminor(inode) 拿到 minor(决定是 LED0 还是 LED1),p_led_opr->ctl(minor, status)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// leddrv.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

#include "led_opr.h"


/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;


#define MIN(a, b) (a < b ? a : b)


void led_class_create_device(int minor)
{
device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
}
void led_class_destroy_device(int minor)
{
device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
p_led_opr = opr;
}

EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);



/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
struct inode *inode = file_inode(file);
int minor = iminor(inode);

printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);

/* 根据次设备号和status控制LED */
p_led_opr->ctl(minor, status);

return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
int minor = iminor(node);

printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 根据次设备号初始化LED */
p_led_opr->init(minor);

return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}

/* 2. 定义自己的file_operations结构体 */
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
int err;

printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */


led_class = class_create(THIS_MODULE, "100ask_led_class");
err = PTR_ERR(led_class);
if (IS_ERR(led_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "led");
return -1;
}

return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点 */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// chip_demo_gpio.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_opr.h"
#include "leddrv.h"
#include "led_resource.h"

static int g_ledpins[100];
static int g_ledcnt = 0;

static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
{
//printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);

printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
switch(GROUP(g_ledpins[which]))
{
case 0:
{
printk("init pin of group 0 ...\n");
break;
}
case 1:
{
printk("init pin of group 1 ...\n");
break;
}
case 2:
{
printk("init pin of group 2 ...\n");
break;
}
case 3:
{
printk("init pin of group 3 ...\n");
break;
}
}

return 0;
}

static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
//printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));

switch(GROUP(g_ledpins[which]))
{
case 0:
{
printk("set pin of group 0 ...\n");
break;
}
case 1:
{
printk("set pin of group 1 ...\n");
break;
}
case 2:
{
printk("set pin of group 2 ...\n");
break;
}
case 3:
{
printk("set pin of group 3 ...\n");
break;
}
}

return 0;
}

static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
return &board_demo_led_opr;
}

static int chip_demo_gpio_probe(struct platform_device *pdev)
{
struct resource *res;
int i = 0;

while (1)
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
if (!res)
break;

g_ledpins[g_ledcnt] = res->start;
led_class_create_device(g_ledcnt);
g_ledcnt++;
}
return 0;

}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
struct resource *res;
int i = 0;

while (1)
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if (!res)
break;

led_class_destroy_device(i);
i++;
g_ledcnt--;
}
return 0;
}


static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led",
},
};

static int __init chip_demo_gpio_drv_init(void)
{
int err;

err = platform_driver_register(&chip_demo_gpio_driver);
register_led_operations(&board_demo_led_opr);

return 0;
}

static void __exit lchip_demo_gpio_drv_exit(void)
{
platform_driver_unregister(&chip_demo_gpio_driver);
}

module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);

MODULE_LICENSE("GPL");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// board_A_led.c
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_resource.h"


static void led_dev_release(struct device *dev)
{
}

static struct resource resources[] = {
{
.start = GROUP_PIN(3,1),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
};


static struct platform_device board_A_led_dev = {
.name = "100ask_led",
.num_resources = ARRAY_SIZE(resources),
.resource = resources,
.dev = {
.release = led_dev_release,
},
};

static int __init led_dev_init(void)
{
int err;

err = platform_device_register(&board_A_led_dev);

return 0;
}

static void __exit led_dev_exit(void)
{
platform_device_unregister(&board_A_led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);

MODULE_LICENSE("GPL");