总线(bus):负责管理挂载对应总线的设备以及驱动;
设备(device):挂载在某个总线的物理设备;
驱动(driver):与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
类(class):对于具有相同功能的设备,归结到一种类别,进行分类管理;
以下只说 总线-设备-驱动 模式下的操作
总线管理着两个链表:设备链表 和 驱动链表。
当我们向内核注册一个设备时,便插入到总线的设备链表。
当我们向内核注册一个驱动时,便插入到总线的驱动链表。
在插入的同时,总线会执行一个 bus_type 结构体中的 match 方法对新插入的 设备/驱动 进行匹配。(例如以名字的方式匹配。方式有很多总,下面再详细分析。)
匹配成功后,会调用 驱动 device_driver 结构体中的 probe 方法。(通常在 probe 中获取设备资源。具体有开发人员决定。)
在移除设备或驱动时,会调用 device_driver 结构体中的 remove 方法。
定义好要用到的硬件资源赋值到resource结构体进行注册,例如我的板子的用户LED为GROUP_PIN(5,3)。
static struct resource resources[] = {
{
.start = GROUP_PIN(5,3),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
};
把刚才注册好的设备资源放到platform_device进行二次注册。
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;
}
将实体函数chip_demo_gpio_probe、chip_demo_gpio_remove注册到platform_driver结构体
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led",
},
};
通过匹配原则在驱动注册时进行设备驱动匹配,匹配成功后会调用刚才的probe函数,这个函数会将刚才匹配的节点进行注册( led_class_create_device(g_ledcnt)),probe 函数中根据 platform_device 的资源确定了引脚,probe 将g_ledpins 数组填充,将来设备层调用的资源g_ledpins[g_ledcnt]都是来自于这。
我们用platform_device结构体来指定设备信息时,platform_drive是直接从platform_device中拿资源的,如下platform_get_resource函数。
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;
}
platform_drive从platform_device中拿资源时正式之前注册设备时的资源。
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}
驱动层没有变化,注册app对应到的实体函数,写好入口函数。
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,
};
/* 1. 把file_operations结构体告诉内核:注册驱动程序 */
/* 2. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
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;
}
举个例子,驱动层实体函数led_drv_write 会被app层write调用,led_drv_write中则调用注册好的ctl函数
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;
}
板级的ctl函数源码如下,GROUP(g_ledpins[which])中用到的g_ledpins资源就是刚才设备驱动匹配时probe函数中的g_ledpins资源。
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;
}
case 4:
{
printk("set pin of group 2 ...\n");
break;
}
case 5:
{
//板载对应的port
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,
};
总线设备驱动,实现了设备和驱动的分离,匹配算法和设备树中的方法是一致的,这种模型虽然易于扩展,但是,冗余代码太多, 修改引脚时设备端的代码需要重新编译。更换引脚时, 上图中的 led_drv.c 基本不用改, 但是需要修改 板级的指定硬件资源的结构体(如下图),这样的话一个板子对应一个C文件,会使得Linux内核非常庞大,垃圾文件太多,所以还需要继续用设备树的方式进行改进。
static struct resource resources[] = {
{
.start = GROUP_PIN(5,3),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
};