[X]关闭
0

LINUX篇 基于debian9系统 CH13-platform-dev-drv 设备驱动

摘要: 13.1概述 在上一节课我们体验了LINUX下字符驱动驱动程序,通过Memory Mappted 的方式,实现了对XILINX FPGA AXI-GPIO 寄存器地址空间的访问,从而实现对GPIO输出的控制。相信实现第一个例子后,大家对基于XILINX Z ...

13.1概述

       在上一节课我们体验了LINUX下字符驱动驱动程序,通过Memory Mappted 的方式,实现了对XILINX FPGA AXI-GPIO 寄存器地址空间的访问,从而实现对GPIO输出的控制。相信实现第一个例子后,大家对基于XILINX ZYNQ 芯片LINUX下驱动开发的信心大增。

       在这节课中,我们通过Memory Mapped platform设备驱动来实现同样的点灯功能。学习本章的目录当然不是单单为了点灯,毕竟前面一节课我们已经学会了LINUX下点灯了。这节课的内容主要是为了讲解Memory Mapped platform设备驱动的概念。

13.2 VIVADO硬件工程搭建

        由于硬件工程和上一节课完全相同,如果读者不会搭建VIVADO工程请参考上一节课程的内容。我们下面直接进入驱动部分。

13.3 platform设备驱动概述

       从linux2.6内核起,引入一套新的驱动管理和注册机制:platform_device 和 platform_driver 。Linux 中大部分的设备驱动,都可以使用这套机制,设备用 platform_device 表示;驱动用 platform_driver 进行注册。

       platform将驱动分为platform_device (设备文件)和platform_driver(驱动文件),他们会通过platform(虚拟总线)来相配对。当platform_device注册到总线时,会通过platform总线去寻找有没有相对应的驱动文件,有的话则将他两配对。同理,当驱动注册到总线时,platform_driver会通过总线去寻找有没有相对应的设备文件,有的话也将他两进行配对。当匹配成功后,就可以作为一个普通的字符设备进行操作了。在前面一节课我们已经学习过字符设备驱动的编写方法。所以platform驱动本质上还是字符设备驱动,他们只是改变注册方式。所以我们需要关系的核心内容依然是这些结构体:cdev、file_operation(包含write、read等实现具体功能的函数接口)、dev_t(设备号)、设备文件(/dev)等。

       设备(或驱动)注册的时候,都会引发总线调用自己的match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将双方绑定;

        platform_device或者platform_driver被注册到platform总线上的时候,都会引起总线去调用各自的match函数,来寻找是否有同名的platform_device或者platform_driver。platform_device和platform_driver中定义的驱动名字一致的时候,才会触发platform_driver的probe函数等。

13.4 Platform device驱动程序分析

13.4.1 platform_device 结构体

完整 platform_device 结构体

代码中的platform_device 结构体定义

struct platform_device {

const char    *name;       名字

    int        id;

    bool        id_auto;

    struct device    dev; //硬件模块必须包含该结构体

    u32        num_resources; //资源个数

struct resource    *resource; //资源

const struct platform_device_id    *id_entry;

    /* arch specific additions */

    struct pdev_archdata    archdata;

};

static struct platform_device led_device=

{

    .name = "myled",

    .id = -1,

    .dev.release = run_led_release,

    .num_resources = ARRAY_SIZE(led_resource),

    .resource = led_resource,

};


13.4.2 struct resource *resource 结构体

       在上面platform_device结构体中,有一个struct resource *resource 的结构体指针。

完整resource 结构体

代码中用到的resource 结构体定义

#define IORESOURCE_MEM       0x00000200

#define IORESOURCE_IRQ        0x00000400


struct resource {

resource_size_t start; //资源起始地址   

resource_size_t end; //资源结束地址

const char *name;      

unsigned long flags; // 区分是资源什么类型的

struct resource *parent, *sibling, *child;

};

#define IORESOURCE_MEM       0x00000200

#define IORESOURCE_IRQ        0x00000400


static struct  resource led_resource[] =

{

[0] = {

        .start = 0x41200000,//AXI GPIO起始地址

.end = 0x41200000+0x10000-1, //结束地址

        .flags = IORESOURCE_MEM,

}

};

     当Flags=IORESOURCE_MEM时,start 、end分别表示该platform_device占据的内存的开始地址和结束值;

     当Flags=IORESOURCE_IRQ时,start 、end分别表示该platform_device使用的中断号的开始地址和结束值;

     由于我们这里没有用到中断,所有就没有分配中断资源。

     可以看到基于platform的设备驱动把资源都定义在platform device驱动中。

13.4.3 led_dev.c

      前面我们platform device驱动中重要的结构体分析下,下面看下例程中详细的驱动源码。

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/device.h>

#include <linux/platform_device.h>

#include <linux/ioport.h>

#define IORESOURCE_MEM      0x00000200

#define IORESOURCE_IRQ       0x00000400


static struct  resource led_resource[] =

{

[0] = {

        .start = 0x41200000,

.end = 0x41200000+0x10000-1,

        .flags = IORESOURCE_MEM,

}

};


static void run_led_release(struct device *dev)

{

printk("led_dev_release\n");

return ;

}

static struct platform_device led_device=

{

    .name = "myled",

    .id = -1,

    .dev.release = run_led_release,

    .num_resources = ARRAY_SIZE(led_resource),

    .resource = led_resource,

};

static int led_dev_init(void)

{

printk("led_dev_init");

return platform_device_register(&led_device);

}


static void led_dev_exit(void)

{

printk("led_dev_exit");

platform_device_unregister(&led_device);

return;

}

module_init(led_dev_init); //模块初始化接口

module_exit(led_dev_exit); //模块推出接口

MODULE_LICENSE("GPL");

13.5 Platform driver驱动程序分析

       Platform driver驱动程序本质就是字符设备驱程序。模块的加载通过函数paltform_driver_register();模块的卸载通过函数paltform_driver_unregister();注册字符驱动通过platform_driver的probe()函数实现;注销字符设备通过remove()函数实现。后面阅读源码的时候我看可以看到相关的函数。

13.5.1 platform_driver 结构体

完整的platform_driver 结构体

代码中的platform_driver 结构体

struct platform_driver {

    int (*probe)(struct platform_device *);//硬件和软件匹配成功之后调用该函数

    int (*remove)(struct platform_device *);//硬件卸载了调用该函数

    void (*shutdown)(struct platform_device *);

    int (*suspend)(struct platform_device *, pm_message_t state);

    int (*resume)(struct platform_device *);

    struct device_driver driver;//内核里所有的驱动程序必须包含该结构体

    const struct platform_device_id *id_table;   };

static struct  platform_driver led_drv={    .probe = led_probe,    .remove = led_remove,    .driver  =  {

.name = "myled",

}

};


13.5.2 file_operations结构体

完整的file_operations结构体

代码中的file_operations结构体

struct file_operations {

struct module *owner;

loff_t(*llseek) (struct file *, loff_t,int);

ssize_t(*read) (struct file *, char __user*, size_t, loff_t *);

ssize_t(*aio_read) (struct kiocb *, char__user *, size_t, loff_t);

ssize_t(*write) (struct file *, const char__user *, size_t, loff_t *);

ssize_t(*aio_write) (struct kiocb *, constchar __user *, size_t,loff_t);

int (*readdir) (struct file *, void *,filldir_t);

unsigned int (*poll) (struct file *, structpoll_table_struct *);

int (*ioctl) (struct inode *, struct file*, unsigned int,unsigned long);

int (*mmap) (struct file *, structvm_area_struct *);

int (*open) (struct inode *, struct file*);

int (*flush) (struct file *);

int (*release) (struct inode *, struct file*);

int (*fsync) (struct file *, struct dentry*, int datasync);

int (*aio_fsync) (struct kiocb *, intdatasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, structfile_lock *);

ssize_t(*readv) (struct file *, conststruct iovec *, unsigned long,loff_t *);

ssize_t(*writev) (struct file *, conststruct iovec *, unsigned long,loff_t *);

ssize_t(*sendfile) (struct file *, loff_t*, size_t, read_actor_t,void __user *);

ssize_t(*sendpage) (struct file *, structpage *, int, size_t,loff_t *, int);

unsigned long (*get_unmapped_area) (structfile *, unsigned long,unsigned long, unsigned long,unsigned long);

};

static struct platform_driver led_drv ={

.probe  =  led_probe,      .remove  =  led_remove,      .driver  = {

.name  =  "myled",

    

};

      struct file_operation-是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。在系统内部,I/O设备的存取操作通过特定的入口点来进行,而这组特定的入口点恰恰是由设备驱动程序提供的。通常这组设备驱动程序接口是由结构file_operations结构体向系统说明的,它定义在include/linux/fs.h中。

      可以看到实际上file_operation结构体中不是所有的指针函数都要定义,一般我们用到多少定义多少。所以说结构中的每个成员必须指向驱动中的函数, 这些函数实现一个特别的操作, 或者对于不支持的操作留置为 NULL. file_operation结构体中少参数都包含字串_user.对于正常的编译, _user 没有效果, 但是它可被外部检查软件使用来找出对用户空间地址的错误使用。

      这里只对重要的File_operations的数据结构做下注释说明后面我们会用到


struct module *owner

       第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.


int (*open) (struct inode *, struct file *);

尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.


ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).

初始化一个异步读 -- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).


ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.


int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.

13.5.3 led_drv.c

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/device.h>

#include <asm/io.h>

#include <linux/init.h>

#include <linux/platform_device.h>

#include <linux/ioport.h>

#include <linux/of.h>

#include <linux/uaccess.h>

static int major;

static struct class *cls;

static struct device *gpio_device;

static unsigned int *led_base; //led寄存器基地址

//写设备触发的服务函数 file:设备  buf:数据缓存区 count:数据个数单位字节

static int led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)

{

int val,ret;

//把buff缓存数据拷贝到val地址空间

ret = copy_from_user(&val, buf, count);

//把val的值写进led_base寄存器

iowrite32(val, led_base);

printk("led : Write 0x%x to 0x%x.\n", val, (unsigned int)led_base);

return 0;

}//打开设备函数

static int led_open (struct inode *inode, struct file *filep)

{

printk("dev is open!");

return 0;

}

//释放资源

static int led_release(struct inode *inode, struct file *filep)

{

     printk("dev is release!");

 return 0;

}

// ile_operations结构体

static const struct file_operations led_fops=

{

.owner   = THIS_MODULE,

.open    = led_open,

.write    = led_write,

.release   = led_release,

};

// probe探测函数,在驱动和设备匹配后执行这个函数

static int led_probe(struct platform_device *pdev)

{ struct resource *res; printk("match ok!"); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取到资源,资源的定义在led_dev.c文件中

led_base = ioremap(res->start, res->end - res->start + 1);//获取到AXI-GPIO的地址空间,实现内存映射

printk("led_probe, found led\n"); major = register_chrdev(0, "myled", &led_fops);//注册设备

cls = class_create(THIS_MODULE, "myled"); gpio_device = device_create(cls,NULL,MKDEV(major, 0),NULL,"led");//mknod /dev/hello if(IS_ERR(gpio_device)) { class_destroy(cls); unregister_chrdev(major,"myled"); return -EBUSY; } return 0;}

static int led_remove(struct platform_device *pdev){ printk("Module exit!\n"); device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major,"myled");//删除/dev/目录下的设备节点 iounmap(led_base); //取消物理地址的映射 return 0;}

static struct platform_driver led_drv ={

.probe  =  led_probe,      .remove  =  led_remove,      .driver  = {

.name  =  "myled",

    

};

static int led_drv_init(void){ printk("led_drv_init"); return platform_driver_register(&led_drv);

}


static void led_drv_exit(void){ printk("led_drv_exit"); platform_driver_unregister(&led_drv); return;}

module_init(led_drv_init); //模块初始化接口module_exit(led_drv_exit); //模块推出接口

MODULE_LICENSE("GPL");

13.6 设备驱动在总线上注册过程

    当注册platform device 驱动的时,会去调用platform_device_register(&led_device)同理,当注册platform driver驱动的时候会调用platform_driver_register(&led_drv)函数。通过代码的分析我们可以看到最终他们都调用了driver_probe_device。driver_probe_device函数中只要能够实现platform_driver和platform_device的匹配就可以触发probe函数了,然后probe函数会激活驱动程序,这样我们用户的APP程序就可以开始访问驱动了。在笔者把他们的调用过程总结在如下表格,并且通过下面的程序代码展示出来调用过程。由于很多代码笔者代码理解能力有限,阅读中也不知道具体阐述什么作用,所以仅仅主要把笔者自己理解的思路写下来。

platform_driver

platform_device

platform_driver_register

driver_register

    bus_add_driver

      driver_attach

        __driver_attach

          driver_probe_device

            really_probe

platform_device_register

platform_device_add

bus_probe_device

device_initial_probe

__device_attach

driver_probe_device

really_probe

13.6.1 platform_device的注册过程

13.6.1.1 platform_device_register函数

int platform_device_register(struct platform_device *pdev)

{

device_initialize(&pdev->dev);

arch_setup_pdev_archdata(pdev);

return platform_device_add(pdev);

}

       由于笔者只是抱着理解platform驱动的调用过程,而不准备详细分析里面每一个步骤发生了什么,所以代码不是精读。大部分靠直接去猜测假设。所以笔者认为device_initialize(&pdev->dev)和arch_setup_pdev_archdata(pdev)作用就是对platform_device 结构体初始化,platform_device_add(pdev)函数才是具体动作的函数。


13.6.1.2 platform_device_add函数

/**

 * platform_device_add - add a platform device to device hierarchy

 * @pdev: platform device we're adding

 *

 * This is part 2 of platform_device_register(), though may be called

 * separately _iff_ pdev was allocated by platform_device_alloc().

 */

int platform_device_add(struct platform_device *pdev)

{

int i, ret;


if (!pdev)

return -EINVAL;


if (!pdev->dev.parent)

pdev->dev.parent = &platform_bus; //父设备设置为platform_bus


pdev->dev.bus = &platform_bus_type; //设置挂在platform总线上


switch (pdev->id) {

default:

dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);

break;

case PLATFORM_DEVID_NONE:

dev_set_name(&pdev->dev, "%s", pdev->name);

break;

case PLATFORM_DEVID_AUTO:

/*

 * Automatically allocated device ID. We mark it as such so

 * that we remember it must be freed, and we append a suffix

 * to avoid namespace collision with explicit IDs.

 */

ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);

if (ret < 0)

goto err_out;

pdev->id = ret;

pdev->id_auto = true;

dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);

break;

}


for (i = 0; i < pdev->num_resources; i++) {

struct resource *p, *r = &pdev->resource[i];


if (r->name == NULL)

r->name = dev_name(&pdev->dev);


p = r->parent;

if (!p) {

if (resource_type(r) == IORESOURCE_MEM)

p = &iomem_resource;

else if (resource_type(r) == IORESOURCE_IO)

p = &ioport_resource;

}


if (p && insert_resource(p, r)) {

dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r);

ret = -EBUSY;

goto failed;

}

}


pr_debug("Registering platform device '%s'. Parent at %s\n",

 dev_name(&pdev->dev), dev_name(pdev->dev.parent));


ret = device_add(&pdev->dev);

if (ret == 0)

return ret;


 failed:

if (pdev->id_auto) {

ida_simple_remove(&platform_devid_ida, pdev->id);

pdev->id = PLATFORM_DEVID_AUTO;

}


while (--i >= 0) {

struct resource *r = &pdev->resource[i];

if (r->parent)

release_resource(r);

}


 err_out:

return ret;

}

      platform_device_add函数中我们可以看到大量关于name和resource的函数合作参数。所有在这里面猜测device里面的resource做一些初始化以及对驱动的名字做一些初始化。另外通过pdev->dev.bus = &platform_bus_type函数,可以看出来把device挂到了platform bus总线上然后调用了device_add(&pdev->dev)函数。

13.6.1.3 device_add函数

/**

 * device_add - add device to device hierarchy.

 * @dev: device.

 *

 * This is part 2 of device_register(), though may be called

 * separately _iff_ device_initialize() has been called separately.

 *

 * This adds @dev to the kobject hierarchy via kobject_add(), adds it

 * to the global and sibling lists for the device, then

 * adds it to the other relevant subsystems of the driver model.

 *

 * Do not call this routine or device_register() more than once for

 * any device structure.  The driver model core is not designed to work

 * with devices that get unregistered and then spring back to life.

 * (Among other things, it's very hard to guarantee that all


路过

雷人

握手

鲜花

鸡蛋

最新评论

本文作者
2019-10-9 17:08
  • 1
    粉丝
  • 1494
    阅读
  • 0
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜