软件环境:vivado 2017.4 硬件平台:XC7Z020
因为这两种方法和前一篇提到的/sys/class/leds方法路数不太一样,前一篇说白了,用的都是系统自带的驱动,自动生成的设备节点,相当于操作个黑箱,而本篇通过mmap和ioremap方法直接操作emio对应的地址,操作很直接,对整个操作过程可以有更直观的理解。
用这种方法的前提是emio一定是挂在总线上的,即是有地址的,能被核看到的,如下红框,地址是0x41200000。
再来说下mmap函数,引用百度mmap(),具体也可自查百度mmap()。
#include <sys/mman.h>
void* 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()。
#include <io.h>
void *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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/mman.h>
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 <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/miscdevice.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/uaccess.h>
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.o
obj-m := led_device.o
else
KERN_DIR = /mnt/workspace/joker-7020/sources/kernel
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
endif
再来是应用。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
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<<i);
//把set_value的值写到fd设备中,大小为4字节
write(fd,&set_value,4);
sleep(1);
}
}
return 0;
}
驱动和应用分别编译,传输完成后,先分别通过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 <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_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 <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 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.o
obj-m := led_driver.o led_device.o
else
KERN_DIR = /mnt/workspace/joker-7020/sources/kernel
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
endif
led_app.c 应用与方法2中的一样
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
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<<i);
//把set_value的值写到fd设备中,大小为4字节
write(fd,&set_value,4);
sleep(1);
}
}
return 0;
}
接下来的操作和方法2的差不多,先提权,然后分别加载led_device.ko和led_driver.ko,最后运行led_app。
led_device和led_driver都加载了以后,设备和设备的驱动才能配对,配了对以后,才能初始化,才能在/dev下发现设备,就像上图两个红框显示的一样,这时候再运行led_app,自然就能看见像方法2一样led轮流闪烁。
怎么说呢,三个方法方便程度肯定是1 > 2 > 3。但是论规范程度的话,还是3最规范。实际当中还是看个人吧,哪个理解最透,用的最熟就使哪个吧。