韦东山总线驱动设备模板代码

面向对象

  • 抽象结构体,进行注册和控制函数

  • Linux 驱动 = 驱动框架 + 单片机

总线驱动设备

  • 缺点 每一个硬件需要在内核中写一个.c文件生成 platform_device 结构体

因此采用dts 在外部编译为 dtb 再放入内核,不会导致内核臃肿

  • platform_driver 结构体和 platform_driver 通过名字匹配

  • 总线进行匹配顺序(platform_match 函数进行互相匹配):

    • 先查看 driver_override(使用最少)

    • 再匹配驱动程序中idtable 和device 中的name

    • 最后比较 name 和driver里的name

  • 写程序

    • 分配/设置/注册 platform_device 结构体

    • 分配/设置/注册 platform_driver 结构体

    • 在其中的 probe 函数里,

    • 分配/设置/注册 file_operations 结构体,

    • 并从 platform_device 中确实所用硬件资源。 指定 platform_driver 的名字。

LED驱动框架

写在前面:

什么是ko文件? ko 是一个模块文件,可以装载进内核

模块是一个目标文件,可以完成某种独立的功能,但是自身不是一个独立的进程,不能单独运行,可以动态的载入模块,使其成为内核代码的一部分,与内核其他代码的地位完全相同,当不需要某个模块功能时,还可以卸载模块。

因此可通过 insmod 和rmmod 装载和卸载

board_A_led.c

  • 根据编译顺序进行梳理 首先是board_A_led.c

  • 该文件为创建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,
             },
    };

  • 其中resource定义资源

    • start 表示开始的地址, phys_addr_t 是一个 ui 变量

    • flag 标记,表示资源的类型

    • name 标记该类资源的名字,注意 resource

static struct resource resources[] = {
        {
                .start = GROUP_PIN(5,8),
                .flags = IORESOURCE_IRQ,
                .name = "100ask_led_pin",
        },
        {
                .start = GROUP_PIN(5,8),
                .flags = IORESOURCE_IRQ,
                .name = "100ask_led_pin",
        },
};
  • 要将其编译成ko文件需要有入口函数和出口

  • 在insmod时候调用入口函数 init 将 结构体注册进platform_devic 的链表中platform_device_register(&board_A_led_dev); 注册时会匹配一次,依靠bus总线中的match函数,match成功会调用,驱动的probe 但是现在还没注册驱动所以不会匹配成功

    static int __init led_dev_init(void)
    {
        int err;
        
        err = platform_device_register(&board_A_led_dev);   
        
        return 0;
    }

  • exit 中platform_device_unregister(&board_A_led_dev);

  • 同样需要定义一下入口和出口以及协议

    module_init(led_dev_init);
    module_exit(led_dev_exit);
    ​
    MODULE_LICENSE("GPL");
    ​

leddrv.c

由于设备节点的创建需要根据具体的资源来决定,但device_create 需要主设备号 ,因此在文件chip_demo_gpio.c想调用device_create 等函数 需要导出,因此需要先insmod ,注意不要出现交叉依赖,比如A 编译需要B 的函数 ,B编译需要 A 的函数,导致交叉依赖

​
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);
​
​
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,
};
​
  • 结构体定义如上,注册在初始化函数中

  • init 函数中 ,注册,创建类,但不创建设备节点

    major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
    led_class = class_create(THIS_MODULE, "100ask_led_class");
    ​

    而真正的操作 也就是 open 和write 需要chip_demo_gpio.c 中调用register_led_operations 函数

    通过该函数获得p_led_opr = opr; 也就是获得单板的具体操作 ,初始化以及control 在Open 以及write函数中调用

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

chip_demo_gpio.c

  • 创建platform_driver 结构体,注册时,bus 根据名字进行匹配,具体的匹配原则可看手册,先override 再 id tabel 最后是名字,在注册时,若匹配成功会调用probe 函数

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

  • 初始化函数,先注册platform 结构体,再将led_opr 传给全局变量,也就是注册led_operations结构体

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

    在调用platform_driver_register后bus 的match 函数会将名字 和device 中进行匹配,若匹配成功调用probe函数

    • 通过一个resource结构体指针,在platform_device结构体中寻找对应类型的资源,i 表示索引号 也就是对应个数,如果返回为NULL表示已经找完了

    • 记录每个资源的start 值,后续根据该值来判断设备,并且创建设备节点

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

chip_demo_gpio_remove 函数,同样是挨个寻找资源并销毁设备节点

  • 接下来是led_opeations 结构体,其中对led 进行操作,注意源代码只根据Group进行判断,并不会依据pin值进行判断,which值得是资源中的第几个,也就是说若Group相同,那么你两个资源都可以点亮同一个LED,此处应该添加PIN的判断

    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 5:
            {
    ​
                
                if (status) /* on: output 0*/
                {
                    /* d. 设置GPIO5_DR输出低电平
                    * set GPIO5_DR to configure GPIO5_IO03 output 0
                    * GPIO5_DR 0x020AC000 + 0
                    * bit[3] = 0b0
                    */
                    *GPIO5_DR &= ~(1<<3);
                }
                else  /* off: output 1*/
                {
                    /* e. 设置GPIO5_IO3输出高电平
                    * set GPIO5_DR to configure GPIO5_IO03 output 1
                    * GPIO5_DR 0x020AC000 + 0
                    * bit[3] = 0b1
                    */ 
                    *GPIO5_DR |= (1<<3);
                }
                break;
            }
            case 1:
            ……

至此整个框架大致梳理完毕

细节: 次设备号用于区分同一主设备下的不同类型,主设备号相同则说明是同一类型,而次设备号在创建的时候就会指定对应的次设备号,也就是全局变量g_ledcnt ,而具体的单板操作同样也是通过次设备号进行操作!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值