Linux 驱动开发基础知识——总线设备驱动模型(八)

简介: Linux 驱动开发基础知识——总线设备驱动模型(八)

一、LED 模板驱动程序的改造

1.1 原来的框架

1.2 要实现的框架

二、代码分析:

2.1 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");
 

第27~38行:

static struct resource resources[] = {

如果我们再想增加一盏灯的话,我们可以在这里的平台设备在这里的资源再增加一盏灯

   

.start = GROUP_PIN(3,1),

指定第3组里面的第1个引脚

第41~48行:

static struct platform_device board_A_led_dev = {

注册一个board_A_led_dev

将上面代码资源数组中的资源添加到platform_device

.name = "100ask_led":平台设备的名字,与chip_demo_gpio_driver进行匹配

.num_resources = ARRAY_SIZE(resources):资源的个数

.resource = resources:指向这个数组

第50~62行:

board_A.c 作为一个可加载模块,里面也有入口函数、出口函数。

static int __init led_dev_init(void)
static void __exit led_dev_exit(void)

设置注册好board_A_led_dev的入口函数和出口函数

第64~67行:

module_init(led_dev_init);
module_exit(led_dev_exit);

修饰入口函数和出口函数

MODULE_LICENSE("GPL");

确定GPL协议

第50~57行:

       在入口函数中注册 platform_device 结构体,在 platform_device 结构体中指定使用哪个 GPIO 引脚。

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

第23~48行:board_A_led_dev 结构体定义如下:

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

       在 resouces 数组中指定了 2 个引脚(第 27~38 行); 我们还提供了一个空函数         led_dev_release(第 23~25 行),它被赋给 board_A_led_dev 结构体(第 46 行),这个函数在卸载 platform_device 时会 被调用,如果不提供的话内核会打印警告信息。

第27~38行:

static struct resource resources[] = {

定义资源数组

2.2 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");
 

第138~144行:

chip_demo_gpio.c 中注册 platform_driver 结构体 , 它使用Bus/Dev/Drv 模型,当有匹配的 platform_device 时,它的 probe 函数就会被调用。

在 probe 函数中所做的事情跟之前的代码没有差别。

138 static struct platform_driver chip_demo_gpio_driver = {
139 .probe = chip_demo_gpio_probe,
140 .remove = chip_demo_gpio_remove,
141 .driver = {
142 .name = "100ask_led",
143 },
144 };
145
146 static int __init chip_demo_gpio_drv_init(void)
147 {
148 int err;
149
150 err = platform_driver_register(&chip_demo_gpio_driver);
151 register_led_operations(&board_demo_led_opr);
152
153 return 0;
154 }

注册一个chip_demo_gpio_driver

.driver  = {
        .name   = "100ask_led",
   },

设备名称,与board_A_led_dev中设备名称进行对应

.probe      = chip_demo_gpio_probe:记录引脚

.remove     = chip_demo_gpio_remove:销毁设备

        第 150 行:向内核注册一个 platform_driver 结构体,这个结构体的核心在于第 100 行的 chip_demo_gpio_probe 函数。 chip_demo_gpio_probe 函数代码如下:

100 static int chip_demo_gpio_probe(struct platform_device *pdev)
101 {
102 struct resource *res;
103 int i = 0;
104
105 while (1)
106 {
107 res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
108 if (!res)
109 break;
110
111 g_ledpins[g_ledcnt] = res->start;
112 led_class_create_device(g_ledcnt);
113 g_ledcnt++;
114 }
115 return 0;
116
117 }

       第 107 行:从匹配的 platform_device 中获取资源,确定 GPIO 引脚。

       第 111 行:把引脚记录下来,在操作硬件时要用。

       第 112 行:新发现了一个 GPIO 引脚,就调用上层驱动的代码创建设备节点

第146~159行:

static int __init chip_demo_gpio_drv_init(void)
static void __exit lchip_demo_gpio_drv_exit(void)

设置注册好chip_demo_gpio_driver的入口函数和出口函数

第64~67行:

module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);

修饰入口函数和出口函数

MODULE_LICENSE("GPL");

确定GPL协议

第100~117行:

static int chip_demo_gpio_probe(struct platform_device *pdev)

board_A_led_dev提供 .probe

       第105~144行:从资源里确定引脚

               第107行:获得设备pdev中的第i个IORESOURCE_IRQ资源

               第111行:记录引脚

               第112行:创建device

第119~135行:

static int chip_demo_gpio_remove(struct platform_device *pdev)

board_A_led_dev提供 .remove

       第126行:销毁设备pdev中的第i个IORESOURCE_IRQ资源

操作硬件的代码如下,第 31、63 行的代码里用到了数组 g_ledpins,里面的值来自 platform_device,在 probe 函数中根据 platform_device 的资源确定了引脚:

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

第30和61行: 打印出想操作的引脚

第32和63行:判断引脚

第151行:

register_led_operations(&board_demo_led_opr);

底层向上层调用

2.3 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");
 
 

第30~33行:创建设备

     

void led_class_create_device(int minor)
EXPORT_SYMBOL(led_class_create_device);

产生依赖只有使用led_class_create_device才能产生led_class_create_device

第34~37行:销毁设备

void led_class_destroy_device(int minor)
EXPORT_SYMBOL(led_class_destroy_device);

产生依赖只有使用(led_class_destroy_device才能产生led_class_destroy_device

第38~41行:底层向上层注册函数

void register_led_operations(struct led_operations *opr)
EXPORT_SYMBOL(register_led_operations);

产生依赖只有底层调用才能产生register_led_operations

三、注意事项

3.1  release 函数

       如果 platform_device 中不提供 release 函数,如下图所示不提供红框部分的函数:

       则在调用 platform_device_unregister 时会出现警告,如下图所示:

       你可以提供一个 release 函数,如果实在无事可做,把这函数写为空。

3.2 EXPORT_SYMBOL

       a.c 编译为 a.ko,里面定义了 func_a;如果它想让 b.ko 使用该函数,那么 a.c 里需要导出此函数(如果 a.c, b.c 都编进内核,则无需导出):

EXPORT_SYMBOL(led_device_create);

       并且,使用时要先加载 a.ko。如果先加载 b.ko,会有类似如下“Unknown symbol”的提示:

四、上机测试

4.1 编译

编译程序,把代码上传代服务器后执行 make 命令。

4.2 挂载到开发板

在开发板上挂载 NFS

[root@100ask:/mnt]# insmod board_A_led.ko
[root@100ask:/mnt]# insmod  leddrv.ko
[root@100ask:/mnt]# insmod chip_demo_gpio.ko
[root@100ask:/mnt]# ls /dev/100ask_led*
[root@100ask:/mnt]# ./ledtest /dev/100ask_led0 on
[root@100ask:/mnt]# ./ledtest /dev/100ask_led0 off

目录
相关文章
|
3月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
29天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
81 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
3月前
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
38 3
|
3月前
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
21 1
|
3月前
|
存储 数据采集 缓存
Linux 设备驱动程序(三)(中)
Linux 设备驱动程序(三)
34 1
|
3月前
|
存储 缓存 安全
Linux 设备驱动程序(三)(下)
Linux 设备驱动程序(三)
31 0
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
18 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2