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


    总线-设备-驱动

    总线-设备-驱动 又称为 设备驱动模型。
    在这里插入图片描述

    一、 概念

    总线(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",
            },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    把刚才注册好的设备资源放到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,
             },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在注册设备入口函数进行设备注册,就是在总线的设备列表能找到刚才注册的设备。

    static int __init led_dev_init(void)
    {
        int err;
        
        err = platform_device_register(&board_A_led_dev);   
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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",
        },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    通过匹配原则在驱动注册时进行设备驱动匹配,匹配成功后会调用刚才的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;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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;
    }
    
    • 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

    举个例子,驱动层实体函数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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    板级的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,
    };
    
    • 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

    总线设备驱动,实现了设备和驱动的分离,匹配算法和设备树中的方法是一致的,这种模型虽然易于扩展,但是,冗余代码太多, 修改引脚时设备端的代码需要重新编译。更换引脚时, 上图中的 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",
            },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    【数据结构-进阶】二叉搜索树
    【Python】字符串‘(25, 140, 39, 143)‘如何变为元组(25, 140, 39, 143)?有哪些方法?
    【编译原理】概述
    Win11遇到问题需要重启怎么办?
    H5通过打开小程序的坑
    凸印的印刷原理及工艺介绍
    系统架构师--面向对象选择题
    举个栗子!Tableau 技巧(236):创建简单的参数样式
    数字孪生与GIS的完美融合
    Service Weaver:以单体形式编码,以微服务形式部署
  • 原文地址:https://blog.csdn.net/qq_26972441/article/details/126766601