Linux(二)LED驱动程序框架(总线设备驱动)

总线-设备-驱动

总线-设备-驱动 又称为 设备驱动模型。
Linux(二)LED驱动程序框架(总线设备驱动)_第1张图片

一、 概念

总线(bus):负责管理挂载对应总线的设备以及驱动;
设备(device):挂载在某个总线的物理设备;
驱动(driver):与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
类(class):对于具有相同功能的设备,归结到一种类别,进行分类管理;

二、 工作原理

以下只说 总线-设备-驱动 模式下的操作

总线:

总线管理着两个链表:设备链表 和 驱动链表。

1、注册设备

当我们向内核注册一个设备时,便插入到总线的设备链表。

2、注册驱动

当我们向内核注册一个驱动时,便插入到总线的驱动链表。

3、进行匹配

在插入的同时,总线会执行一个 bus_type 结构体中的 match 方法对新插入的 设备/驱动 进行匹配。(例如以名字的方式匹配。方式有很多总,下面再详细分析。)

4、匹配调用probe函数

匹配成功后,会调用 驱动 device_driver 结构体中的 probe 方法。(通常在 probe 中获取设备资源。具体有开发人员决定。)

5、移除设备或驱动

在移除设备或驱动时,会调用 device_driver 结构体中的 remove 方法。

三、具体实现过程(代码分析)

1、注册设备

定义好要用到的硬件资源赋值到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;
}

2、注册驱动

将实体函数chip_demo_gpio_probechip_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;
}

3、驱动层

驱动层没有变化,注册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",
        },
};

你可能感兴趣的:(Linux,linux,嵌入式Linux)