Linux嵌入式驱动开发09——平台总线详解及实战

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

平台总线模型介绍

  1. 什么是平台总线模型?
    平台总线模型也叫platform总线模型,是
    Linux内核虚拟出来的一条总线,不是真正的导线。

    平台总线模型实际上就是把原来的驱动C文件分成了两个C文件,一个是device.c,一个是driver.c

    把稳定不变的放在driver.c里面,需要变动的放在device.c里面

  2. 为什么会有平台总线模型?
    (1). 提高代码的重用性
    (2). 减少重复性代码
    设备(device.c)
    总线
    驱动(driver.c)

  3. 平台总线模型的优点
    除了上面提到的,还有就是都挂载在了总线上,方便管理。

  4. 怎么编写以平台总线模型设计的驱动?
    一个是device.c,一个是driver.c,然后分别注册device.c和driver.c。

    平台总线是以名字来匹配的,实际上就是字符串比较

注册platform设备(device)

device.c里面写的是硬件资源,这里的硬件资源是指寄存器的地址,终端号,时钟等硬件资源。

platform_device结构体

在Linux的内核中,我们用一个结构体来描述硬件资源

/include/linux/platform_device.h

在这里插入图片描述
platform_device 就是我们要使用的结构体

struct platform_device {           //  platform总线设备
    const char    * name;          //  平台设备的名字
    int        id;                 //   ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
    struct device    dev;          //   内置的device结构体
    u32        num_resources;      //   资源结构体数量
    struct resource    * resource; //   指向一个资源结构体数组

    const struct platform_device_id    *id_entry; //  用来进行与设备驱动匹配用的id_table表

    /* arch specific additions */
    struct pdev_archdata    archdata;             //  自留地    添加自己的东西
};

其中 结构体中的成员变量:

  1. 平台设备的名字,平台总线进行匹配的时候用到的name,/sys/bus…
const char    * name;
  1. 设备ID,一般写-1
int  id;
  1. 内置的device结构体
struct device    dev;
  1. 资源的个数
 u32        num_resources;  
  1. device里面的硬件资源
  struct resource    * resource;

在ioport.h中可以找到resource的结构体定义,主要用来描述硬件资源

struct resource {      // 资源结构体
    resource_size_t start;      // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
    resource_size_t end;        // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
    const char *name;           // 资源名
    unsigned long flags;        // 资源的标示,用来识别不同的资源
    struct resource *parent, *sibling, *child;   // 资源指针,可以构成链表
};

在这里插入图片描述
在这里还有很多的宏定义来表示地址,常用的有以下几个

  1. IO的内存
#define IORESOURCE_IO           0x00000100      /* PCI/ISA I/O ports */
  1. 表述一段物理内存
#define IORESOURCE_MEM          0x00000200
  1. 表示中断
#define IORESOURCE_IRQ          0x00000400
  1. dma的地址
#define IORESOURCE_DMA          0x00000800
  1. 总线号
#define IORESOURCE_BUS          0x00001000

这里使用开发板上的EIM_A17,通过/arch/arm/boot/dts/imx6q-pinfunc.h中查找
在这里插入图片描述
我们可以知道对应的是GPIO2_IO21.,然后去数据手册,查找引脚的配置信息
在这里插入图片描述
根据数据手册得到了代码
在这里插入图片描述

实战代码

Linux嵌入式platform设备模块modules_platform_device
device.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

struct resource beep_resource[] = {
    [0] = {
        .start  = 0x20a0000,
        .end    = 0x20a0003,
        .flags  = IORESOURCE_MEM,
        .name   = "GPIO2_DR"
    }
};

struct platform_device beep_device = {                  //  platform总线设备
    
    .name = "beep_device_test",                         //  平台设备的名字
    .id = -1,                                           //   ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
    .num_resources = ARRAY_SIZE(beep_resource),          //   资源结构体数量
    .resource = beep_resource,                          //   指向一个资源结构体数组
};

static int device_init(void)
{
    printk("beep_device ok!!!\n");                           // 在内核中无法使用c语言库,所以不用printf
     
    return platform_device_register(&beep_device);      // platform_device_register有返回值,所以要判断
}

static void device_exit(void)
{
    printk("beep_device byb!!!\n");
    platform_device_unregister(&beep_device);
}

module_init(device_init);
module_exit(device_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

注意: 这里其实是不全的,因为我们的这里只配置了DR寄存器,没有配置电器属性,这个需要在设备树中配置,会放在后面。这里我是配置过了,所以才能后正常的实现代码的功能。

好,我们继续走

验证总线设备

首先安装模块

insmod device.ko

可以看到,加载成功
在这里插入图片描述
然后去查看是不是在总线上

ls /sys/bus/platform/devices/

我们可以找到我们代码中设置的平台设备的名字
在这里插入图片描述
然后我们测试注销功能

 rmmod device.ko

发现有警告,提示需要release
在这里插入图片描述
回到代码进行添加修改
在这里插入图片描述
重新编译验证,没有了警告,问题解决
在这里插入图片描述
修改后的代码device.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

struct resource beep_resource[] = {
    [0] = {
        .start  = 0x20a0000,
        .end    = 0x20a0003,
        .flags  = IORESOURCE_MEM,
        .name   = "GPIO2_DR"
    }
};

void beep_release(struct device *dev){
    printk("beep_release ok!!!\n");
}

struct platform_device beep_device = {                          //  platform总线设备
    
    .name           = "beep_device_test",                       //  平台设备的名字
    .id             = -1,                                       //   ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
    .num_resources  = ARRAY_SIZE(beep_resource),                //   资源结构体数量
    .resource       = beep_resource,                            //   指向一个资源结构体数组
    .dev            = {
        .release = beep_release,
    },    
};

static int device_init(void)
{
    printk("beep_device ok!!!\n");                              // 在内核中无法使用c语言库,所以不用printf
     
    return platform_device_register(&beep_device);              // platform_device_register有返回值,所以要判断
}

static void device_exit(void)
{
    printk("beep_device byb!!!\n");
    platform_device_unregister(&beep_device);
}

module_init(device_init);
module_exit(device_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

注册platform驱动(driver)

思路

首先定义一个platform_driver结构体变量,然后去实现结构体中的各个成员变量,那么当我们的driver和device匹配成功的时候,就会执行probe函数,所以匹配成功以后的重点就在于probe函数的编写

platform_driver

struct platform_driver {
    int (*probe)(struct platform_device *);     //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    int (*remove)(struct platform_device *);    //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;                //   内置的device_driver 结构体
    const struct platform_device_id *id_table;  //  该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组
};
  1. driverdevice匹配成功的时候,就会执行probe函数
int (*probe)(struct platform_device *);
  1. driverdevice任意一个remove的时候,就会执行这个函数
int (*remove)(struct platform_device *);
  1. 当设备收到shutdown命令的时候,就会执行这个函数
void (*shutdown)(struct platform_device *);
  1. 当设备收到suspend命令的时候,就会执行这个函数
int (*suspend)(struct platform_device *, pm_message_t state);
  1. 当设备收到resume命令的时候,就会执行这个函数
int (*resume)(struct platform_device *);

对于device_driver 结构体,在device.h文件下,我们可以打开查看

linux-4.1.15/include/linux/device.h

在这里插入图片描述

struct device_driver {
        const char              *name;
        struct bus_type         *bus;

        struct module           *owner;
        const char              *mod_name;      /* used for built-in modules */

        bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */

        const struct of_device_id       *of_match_table;
        const struct acpi_device_id     *acpi_match_table;

        int (*probe) (struct device *dev);
        int (*remove) (struct device *dev);
        void (*shutdown) (struct device *dev);
        int (*suspend) (struct device *dev, pm_message_t state);
        int (*resume) (struct device *dev);
        const struct attribute_group **groups;

        const struct dev_pm_ops *pm;

        struct driver_private *p;
};

注意:const char *name;这个名字是匹配设备时候用到的名字,就是之前在device.c中的platform_device里面定义的名字
在这里插入图片描述

代码

driver.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

int beep_probe(struct platform_device *pdev){
    printk("beep_probe ok!!!\n");
    return 0;
}

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct platform_device_id beep_id_table = {
    .name = "beep_device_test",
};

struct platform_driver beep_device = {
    .probe = beep_probe,                //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,              //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .name  = "beep_device_test",    //  有了id_table,这个driver->name可有可无,优先级低
    },                                  //   内置的device_driver 结构体
    .id_table = &beep_id_table,          //  该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");          // 在内核中无法使用c语言库,所以不用printf
    
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
    
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

结果验证

分别加载我们的

insmod device.ko

insmod driver.ko

在这里插入图片描述
然后卸载一下
在这里插入图片描述

这样我们就可以在driver.c文件中添加我们所需要的功能代码了,驱动蜂鸣器,或者点亮led灯等操作

编写probe函数

第一步:从device.c里面获得硬件资源

方法一:直接获得,不推荐

方法二:使用函数间接获得

打开platform_device.h

/include/linux/platform_device.h

找到函数platform_get_resource
在这里插入图片描述


extern struct resource *platform_get_resource(struct platform_device *,
                                              unsigned int, unsigned int);

参数:

第一个参数:形参pdev

第二个参数:类型,在device中
在这里插入图片描述

第三个参数:同类参数的第几个,指的是.flags = IORESOURCE_MEM,,同类型的第几个

根据我们的device,第一个参数填写形参pdev,第二个参数填写类型IORESOURCE_MEM,因为IORESOURCE_MEM类型中这是第一个,所以第三个参数是0

    platform_get_resource(pdev, IORESOURCE_MEM, 0);

注意:函数有返回值

代码

probe部分的代码

int beep_probe(struct platform_device *pdev){
   
    printk("beep_probe ok!!!\n");

    //方法1
    // printk("beep_res is %s\n", pdev->resource[0].name);

    //方法2
    beep_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);

    if(beep_mem == NULL) {

        printk("beep_get_resource is NULL!!!\n");
        return -EBUSY;
    }

    printk("beep_resource start is 0x%x!!!\n", beep_mem->start);

    printk("beep_resource end is 0x%x!!!\n", beep_mem->end);

    return 0;
}

结果验证

insmod device.ko

在这里插入图片描述

insmod driver.ko

在这里插入图片描述

第二步:注册杂项/字符设备,完善file_operation,生成设备节点

注册前要先登记

登记到内核request_mem_region

request_mem_region函数和release_mem_region函数在ioport.h文件中

/include/linux/ioport.h

在这里插入图片描述

#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)
#define release_mem_region(start,n)     __release_region(&iomem_resource, (start), (n))

登记到内核的原因是,因为需要告诉内核需要用到某个寄存器,别的设备不可以重复使用。

我们这里使用到了GPIO2的DR寄存器,不登记也可以。一般都是要进行登记操作

    beep_mem_tmp = request_mem_region(beep_mem->start, beep_mem->end - beep_mem->start + 1, "beep");

    if(beep_mem_tmp == NULL){
        printk("request_mem_region is error!!!\n");
        goto err_region;                                        // 没有注册成功,需要释放掉
    }

完善file_operation

配置杂项设备驱动,现在只需要从probe函数中操作就可以了

/********probe编写***********************************************************************/
int beep_probe(struct platform_device *pdev){
   int ret = 0;
    printk("beep_probe ok!!!\n");

    //方法1
    // printk("beep_res is %s\n", pdev->resource[0].name);

    //方法2
/*********获取硬件资源*********************************************************************************/    
    beep_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);

    if(beep_mem == NULL) {

        printk("beep_get_resource is NULL!!!\n");
        return -EBUSY;
    }

    printk("beep_resource start is 0x%x!!!\n", beep_mem->start);

    printk("beep_resource end is 0x%x!!!\n", beep_mem->end);

/********登记******************************************************************************************/
    // beep_mem_tmp = request_mem_region(beep_mem->start, beep_mem->end - beep_mem->start + 1, "beep");

    // if(beep_mem_tmp == NULL){
    //     printk("request_mem_region is error!!!\n");
    //     goto err_region;                                        // 没有注册成功,需要释放掉
    // }
/*******杂项设备驱动ioremap转化成虚拟地址**************************************************************/
    vir_gpio5_dr = ioremap(beep_mem->start, 4);                    // 物理地址到虚拟地址的映射

    if(vir_gpio5_dr == NULL){                                       //如果映射失败
        printk("GPIO2_DR ioremap error!!!\n");
        return -EBUSY;                                              //EBUSY是Linux的预留参数,前面加个负号
    }else{
        printk("GPIO2_DR ioremap ok!!!\n");
    }
/*******ioremap之后注册杂项设备********************************************************************/
    ret = misc_register(&misc_dev);

    if(ret < 0){
        printk("misc_register failed!!!\n");
        return -1;
    }

    printk("misc_register succeed!!!\n");                             // 在内核中无法使用c语言库,所以不用printf
/********注册之后,完善file_operation*******************************************************************/
    return 0;

// err_region:
//     release_mem_region(beep_mem->start, beep_mem->end - beep_mem->start + 1);
//     return -EBUSY;
}

之前的杂项设备注册是在init函数中,现在在probe函数中,因为probe函数是设备和驱动匹配后执行的函数。

结果验证

加载模块
在这里插入图片描述

ls /dev/

可以看到我们的杂项设备已经成功注册
在这里插入图片描述
运行app.c的可执行文件
在这里插入图片描述

源码下载

Linux嵌入式platform驱动模块modules_platform_dirver

Linux嵌入式驱动开发——平台总线实例modules_platform

总结

在probe函数我们可以拿到device里面描述的硬件资源,然后在probe里面注册杂项设备和字符设备,这样就可以完成一个以总线模型设计的驱动
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

引出设备树

device本质上是描述我们的底板硬件资源,所以,对于不同的soc,不同的底版,就有了不同的device,为了规范这些,所以有了设备树

设备树文件本质上就是我们device部分
在这里插入图片描述
设备树是一种描述硬件资源的数据结构,它通过bootloader将硬件资源传递给内核,使得内核和硬件资源描述相对独立。

这样,与开发板底版相关的代码,就不在Linux内核中,减少了内核的臃肿,这些硬件信息都在设备树中。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 嵌入Linux设备驱动开发是指为嵌入系统中的硬件设备开发驱动程序,使其能够在Linux操作系统上正常工作。嵌入设备通常具有特定的功能和限制,因此其驱动开发与常规PC设备有所不同。 嵌入Linux设备驱动开发需要掌握嵌入系统的硬件架构、设备注册与初始化、设备访问接口、设备的中断处理和DMA传输等关键概念和技术。 首先,了解嵌入系统的硬件架构是必要的,包括处理器架构、总线、外设等。这有助于理解设备的寄存器操作和驱动程序的编写。 其次,设备的注册与初始化是驱动开发的第一步。这包括将硬件设备与驱动程序关联起来,设定设备的基本参数并进行初始化,如配置中断、设置工作模等。 接下来,需要实现设备访问接口。这包括读写设备寄存器、处理设备传感器数据、执行控制命令等。根据设备的特点,可以采用内存映射、IO端口访问等方法来完成设备的访问。 同时,中断处理也是嵌入设备驱动开发的重要一环。通过中断,设备可以及时响应外部事件,提高系统的实时性。驱动程序需要实现中断处理函数,对中断事件进行处理,并及时通知系统做出相应的响应。 还有一种常用的数据传输方是DMA。DMA传输可以大大提高数据传输效率,特别适用于高速数据传输设备。在驱动程序中,需要实现DMA传输的初始化和管理。 此外,嵌入Linux设备驱动开发还需要关注功率管理、设备的热插拔支持、设备的调试和错误处理等方面的内容。 总的来说,嵌入Linux设备驱动开发需要掌握嵌入系统的硬件架构、设备访问接口、中断处理和DMA传输等关键技术。只有充分理解设备的特点和运行环境,才能开发出稳定、高效并且可靠的设备驱动程序。 ### 回答2: 嵌入Linux设备驱动开发是指针对嵌入系统中特定硬件设备的驱动程序编写和开发过程。设备驱动程序作为操作系统与硬件之间的桥梁,负责控制和管理硬件设备的功能。 嵌入Linux设备驱动开发需要有一定的硬件和软件知识基础。首先,需要了解目标设备的硬件架构以及所使用的处理器类型和架构。其次,需要熟悉Linux内核的架构和编程模型,了解Linux设备模型和驱动框架。 开发嵌入Linux设备驱动的主要步骤包括以下几个方面: 1. 编写设备驱动模块:根据设备的硬件特性和功能需求,编写相应的设备驱动模块。这涉及到底层硬件访问和控制,包括寄存器操作、中断处理、DMA等。 2. 设备的初始化和资源分配:在设备驱动模块中,在设备初始化阶段,需要分配和初始化设备所需的各种资源,如内存、中断、I/O端口等。 3. 实现驱动程序的接口:设备驱动程序需要提供一组接口,供用户空间的应用程序调用,以实现对设备的读写、控制等操作。 4. 注册和卸载设备驱动:在Linux内核启动时,通过注册设备驱动模块,将其与目标设备相关联。在不需要使用设备的时候,可以通过卸载设备驱动来释放资源。 5. 进行设备的测试和调试:编写驱动程序后,需要进行相应的测试和调试工作,以确保其正常运行。可以使用Linux提供的一些工具和调试技术,例如sysfs、devfs、strace等。 嵌入Linux设备驱动开发需要深入了解Linux内核和硬件设备的工作原理,同时需要熟练掌握C语言和汇编语言等编程技术。开发者还需具备良好的调试和排错能力,能够解决因硬件设备和驱动之间的兼容性、稳定性等问题带来的挑战。 ### 回答3: 嵌入Linux设备驱动开发是指在嵌入Linux系统中开发驱动程序,使得硬件设备能够在Linux系统中正常工作。它是为了满足特定应用需求而针对特定硬件设备编写的一段软件代码。 嵌入Linux设备驱动开发需要具备扎实的嵌入系统和Linux内核的理论基础,熟悉设备驱动的工作原理和开发流程。开发者需要了解设备的硬件特性、寄存器的操作方法和设备的工作模。 开发驱动的第一步是在Linux内核源码中查找相关设备的驱动框架,并创建一个新的驱动模块。驱动模块通常包含设备初始化、资源分配、中断处理和数据传输等功能。 在驱动模块中,开发者需要编写设备的初始化函数和操作函数,以初始化设备和提供设备的读写操作接口。对于不同的设备类型,开发者需要根据驱动框架中的规范和硬件特性进行相应的编码和配置。 在设备驱动的开发过程中,我们需要通过嵌入Linux系统提供的工具链来编译和生成设备驱动的二进制代码,并将其加载到系统中。一旦驱动程序加载成功,设备就可以被系统正确地识别和使用。 嵌入Linux设备驱动开发需要进行严格的测试和调试,以确保驱动程序的正确性和稳定性。调试过程中,我们可以利用调试工具和打印信息来排查问题并进行修复。 总之,嵌入Linux设备驱动开发是一个复杂而有挑战性的任务,但同时也是非常重要的。通过开发设备驱动,我们可以在嵌入Linux系统中充分发挥硬件设备的功能,实现各种特定应用需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值