
本文共 8513 字,大约阅读时间需要 28 分钟。
软件环境:vivado 2017.4 硬件平台:XC7Z020
因为这两种方法和前一篇提到的/sys/class/leds方法路数不太一样,前一篇说白了,用的都是系统自带的驱动,自动生成的设备节点,相当于操作个黑箱,而本篇通过mmap和ioremap方法直接操作emio对应的地址,操作很直接,对整个操作过程可以有更直观的理解。
用这种方法的前提是emio一定是挂在总线上的,即是有地址的,能被核看到的,如下红框,地址是0x41200000。
再来说下mmap函数,引用百度mmap(),具体也可自查百度mmap()。
#includevoid* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理prot:期望的内存保护标志,不能与文件的打开模式冲突。flags:指定映射对象的类型,映射选项和映射页是否可以共享。fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。off_toffset:被映射对象内容的起点。成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1]。
而ioremap函数,引用百度ioremap(),具体也可自查百度ioremap()。
#includevoid *ioremap(unsigned long phys_addr, unsigned long size)入口: phys_addr:要映射的起始的IO地址;size:要映射的空间的大小;flags:要映射的IO空间的和权限有关的标志;phys_addr:是要映射的物理地址size:是要映射的长度,单位是字节
要说这两个函数有什么差别,感觉主要差别是在使用范围上吧(我也不确定)?ioremap 是内核函数,在内核(驱动)中可以通过ioremap函数,将IO映射内存的机制来访问硬件,存活在内核空间。而mmap是用户空间的函数,将进程内存区域映射到文件的内容,说直白一点,一个在写驱动时候用,一个在写应用时候用,都能达到目的。
方法1.直接写app做地址映射
由此,应用mmap写应用直接往总线写值,值写进相应寄存器里,直接对应emio管脚。
#include#include #include #include #include #include #include int main(){ int fd = open("/dev/mem", O_RDWR | O_SYNC); volatile unsigned int* led_base = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x41200000);while(1){ memset((void *)led_base, 1, 1); sleep(1); memset((void *)led_base, 2, 1); sleep(1); memset((void *)led_base, 4, 1); sleep(1); memset((void *)led_base, 8, 1); sleep(1);} return 0;}
这里还有一点需要注意, mmap() 必须以PAGE_SIZE为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射(引自百度)。
方法2.驱动中实现ioremap+简单app(杂项设备注册)
这里因为是使用ioremap直接映射地址,所以可以不用管驱动与devicetree中有没有compatible对应关系,没有上一篇devicetree中的gpio-leds部分也是可以的,只要驱动写的没问题,在/dev下能正常生成节点,app能加载open上,那就没问题。
#include#include #include #include #include #include #include #include #include #include #include static void __iomem *led_base; //led寄存器基址指针//打开设备static int led_open(struct inode * inode, struct file * filp) { printk("device is open!"); return 0;}//写设备 file:设备 buf:数据缓存区 count:数据个数单位字节static int led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos){ int val,ret; //把buf缓存数据拷贝到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;}//LED接口结构体static const struct file_operations led_fops ={ .owner = THIS_MODULE, .open = led_open, .write = led_write, };//LED设备结构体static struct miscdevice led_dev={ .minor = MISC_DYNAMIC_MINOR, .name = "led_device", // /dev目录下的设备节点名 .fops = &led_fops,};//LED设备初始化static int led_init(void){ int ret; //地址映射:把物理地址转换为虚拟地址 led_base = ioremap(0x41200000, 4); printk("LED: Access address to device is:0x%x\n", (unsigned int)led_base); //注册设备到/dev目录 ret = misc_register(&led_dev); if(ret) { printk("[ERROR]leds Misc device register failed.\n"); return ret; } printk("Module init complete!\n"); return 0;}//LED设备退出static void led_exit(void){ printk("Module exit!\n"); iounmap(led_base); //取消物理地址的映射 misc_deregister(&led_dev); //删除/dev/目录下的设备节点}module_init(led_init); //模块初始化接口module_exit(led_exit); //模块推出接口//以下代码可解决一些错误信息MODULE_AUTHOR("joker@ ");MODULE_DESCRIPTION("ledDriver");MODULE_ALIAS("Just test");MODULE_LICENSE("Dual BSD/GPL");
编写makefile文件。
ifneq ($(KERNELRELEASE),)#param-objs := file1.o file1.oobj-m := led_device.oelse KERN_DIR = /mnt/workspace/joker-7020/sources/kernelall: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.orderendif
再来是应用。
#include#include #include #include #include int main(int argc, char **argv){ int i; int fd; int set_value=1; fd = open("/dev/led_device", O_RDWR); //打开设备 //LED流水灯 while(1) { for(i=0;i<4;i++) { set_value = (1<
驱动和应用分别编译,传输完成后,先分别通过chmod 777 led_app led_device.ko提权,然后先加载驱动insmod led_device.ko,再启动应用./led_app,就能看到led挨个闪烁啦。
方法3.驱动中实现ioremap+简单app(正规字符设备注册)
方法2中杂项设备驱动,其实就是主设备号为10的字符驱动,在方法3中用的方法是通常字符设备使用的一般流程,设备名称与驱动名称对应上了以后,/dev设备节点可见,然后app操作设备节点,达到控制的目的。
led_device.c 注册设备
#include#include #include #include #include #define IORESOURCE_MEM 0x00000200#define IORESOURCE_IRQ 0x00000400static struct resource led_resource[] ={ [0] = { .start = 0x41200000, .end = 0x41200000+0x10000-1, .flags = IORESOURCE_MEM, }};static void run_led_release(struct device *dev){ printk("led_device_release\n"); return ;} static struct platform_device led_device={ .name = "led_test", .id = -1, .dev.release = run_led_release, .num_resources = ARRAY_SIZE(led_resource), .resource = led_resource,}; static int led_dev_init(void){ printk("led_device_init"); return platform_device_register(&led_device);}static void led_dev_exit(void){ printk("led_device_exit"); platform_device_unregister(&led_device); return;} module_init(led_dev_init); //模块初始化接口module_exit(led_dev_exit); //模块推出接口 MODULE_LICENSE("GPL");
这里led_resource[]下.end = 0x41200000+0x10000-1中的0x10000,与devicetree中reg保持一致,是reg长度。
led_driver.c 注册驱动
#include#include #include #include #include #include #include #include #include #include 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 value,ret; //把buf缓存数据拷贝到val地址空间 ret = copy_from_user(&value, buf, count); //把val的值写进led_base寄存器 iowrite32(value, led_base); printk("led : Write 0x%x to 0x%x.\n", value, (unsigned int)led_base); return 0;} //打开设备static int led_open (struct inode *inode, struct file *filep){ printk("device is open!"); return 0;} //释放资源static int led_release(struct inode *inode, struct file *filep){ printk("device is release!"); return 0;} //file_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("device and driver match ok!"); //获取到资源,资源的定义在led_dev.c文件中 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取到AXI_GPIO的地址空间,完成内存映射 led_base = ioremap(res->start, res->end - res->start + 1); printk("led_probe, found led_gpio\n"); //注册设备 major = register_chrdev(0, "led_test", &led_fops); cls = class_create(THIS_MODULE, "led_test"); //生成设备节点 /dev/led_device gpio_device = device_create(cls,NULL,MKDEV(major, 0),NULL,"led_device"); if(IS_ERR(gpio_device)){ //出错则解除 class_destroy(cls); unregister_chrdev(major,"led_test"); 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); //删除/dev/目录下的设备节点 unregister_chrdev(major,"led_test"); //解除物理地址的映射 iounmap(led_base); return 0; } static struct platform_driver led_drv = { .probe = led_probe, .remove = led_remove, .driver = { .name = "led_test", } }; static int led_drv_init(void) { printk("led_driver_init"); return platform_driver_register(&led_drv); }static void led_drv_exit(void) { printk("led_driver_exit"); platform_driver_unregister(&led_drv); return; } module_init(led_drv_init); //模块初始化接口module_exit(led_drv_exit); //模块推出接口 MODULE_LICENSE("GPL");
makefile
ifneq ($(KERNELRELEASE),)#param-objs := file1.o file1.oobj-m := led_driver.o led_device.oelse KERN_DIR = /mnt/workspace/joker-7020/sources/kernelall: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.orderendif
led_app.c 应用与方法2中的一样
#include#include #include #include #include int main(int argc, char **argv){ int i; int fd; int set_value=1; fd = open("/dev/led_device", O_RDWR); //打开设备 //LED流水灯 while(1) { for(i=0;i<4;i++) { set_value = (1<
接下来的操作和方法2的差不多,先提权,然后分别加载led_device.ko和led_driver.ko,最后运行led_app。
led_device和led_driver都加载了以后,设备和设备的驱动才能配对,配了对以后,才能初始化,才能在/dev下发现设备,就像上图两个红框显示的一样,这时候再运行led_app,自然就能看见像方法2一样led轮流闪烁。
怎么说呢,三个方法方便程度肯定是1 > 2 > 3。但是论规范程度的话,还是3最规范。实际当中还是看个人吧,哪个理解最透,用的最熟就使哪个吧。
发表评论
最新留言
关于作者
