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 |
-
米联客FMC-SFP模块支持4路SFP光纤接口,最大支持10Gbps速率。可以安装到具有HPC的FMC接口开发板上使用。控
14760
-
FMC-3G SDI子卡测试1.1概述使用FMC-3GSDI子卡来实现 FPGA 通过 GTH 高速收发器从同轴电缆接收 3G-SDI 信号
15390
-
-
双十一欢乐购!米联客天猫商城和京东商城狂欢15日!2022.10.28-2022.11.11店铺推出满减优惠券!领券下单享
12830
-
1产品概述FEP-CARD-DAQ9248是一款14bits双通道65MSPS ADC采集模块,该方案采用了ADI的AD9248芯片,扩展接口
55620
-
1产品概述FEP-DAQ976X是一款14bits双通道125MSPS DAC数模转换模块,该方案采用了ADI的AD9767芯片,扩展接口
34910
-
本文在 AXI_DMA_LOOP 环路测试架构的基础上,在 DATA FIFO 端加入 FPGA 代码,对 FIFO 写,实现将 PL 端数
86233
-
在前文中我们学习了AXI总线协议,而且通过VIVADO自定义了AXI-LITE总线协议的IP CORE,并且实现了寄存器的读
49521
-
FDMA是米联客的基于AXI4总线协议定制的一个DMA控制器。有了这个IP我们可以统一实现用FPGA代码直接读写PL的D
79273
-
在前文的实验中我们详细介绍了FDMA的使用方法,以及使用了AXI-BRAM演示了FDMA的使用,现在我们已经掌握了FD
89218
-
1.1 FPGA技术背景笔者也是在偶然的机缘下接触到FPGA的,当初只有感性的认识就是FPGA速
1018520
-
淘宝购买链接:https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-18659455309
1288915
-
在前面的课程种,我们已经提供了FDMA和XDMA配合使用,应用于PCIE传图的方案。但是前面
14318015
-
8B/10B编码是1983年由IBM公司的Al Widmer和PeterFranaszek所提出的数据传输编码标准,
676410
-
淘宝购买连接:https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-18659455309
89038
-
在前文的实验中我们详细介绍了FDMA的使用方法,以及使用了AXI-BRAM演示了FDMA的使用,
89218
-
1概述在米联客老版本的MIA701开发板(2017版本)中,米联开源了1套原创的UDP IP协议栈。
115388
-
1、电源改动版本使用电压输入电压范围201712275V4.7-5.2V201909185V2.0-16V新版本采用
58258
-
前面我们完成了一个PCIE中断采集图像的方案,但是很多应用中我们需要采集分析ADC的数
93768
-
本章讲解使用PL端以太网口实现UDP通信。开发板中实现千兆网 UDP 传输的基本逻辑框架如
126948
-
米联客FMC-SFP模块支持4路SFP光纤接口,最大支持10Gbps速率。可以安装到具有HPC的FMC
14760
-
FMC-3G SDI子卡测试1.1概述使用FMC-3GSDI子卡来实现 FPGA 通过 GTH 高速收发器从同轴
15390
-
双十一欢乐购!米联客天猫商城和京东商城狂欢15日!2022.10.28-2022.11.11店铺推出满
12830
-
1产品概述FEP-CARD-DAQ9248是一款14bits双通道65MSPS ADC采集模块,该方案采用了ADI的
55620
-
1产品概述FEP-DAQ976X是一款14bits双通道125MSPS DAC数模转换模块,该方案采用了ADI的
34910
-
本文在 AXI_DMA_LOOP 环路测试架构的基础上,在 DATA FIFO 端加入 FPGA 代码,对 FIFO
86223
-
在前文中我们学习了AXI总线协议,而且通过VIVADO自定义了AXI-LITE总线协议的IP CORE,
49521
-
FDMA是米联客的基于AXI4总线协议定制的一个DMA控制器。有了这个IP我们可以统一实现用F
79273
-
在前文的实验中我们详细介绍了FDMA的使用方法,以及使用了AXI-BRAM演示了FDMA的使用,
89218
-
基于FDMA可以完成很多数据读写存储类的应用,本文将展示通过FDMA读写AXI-BRAM
本文实
49761
|