【JokerのZYNQ7020】LINUX_EMIO_LED(mmap、ioremap方式)。

软件环境: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最规范。实际当中还是看个人吧,哪个理解最透,用的最熟就使哪个吧。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值