参考资料:
[1] 【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.71
1 地址映射
在老版本的Linux中要求处理器必须有MMU(Memory Manage Unit,内存管理单元),但是现在Linux内核已经支持无MMU的处理器。MMU主要完成的功能如下:
- 完成虚拟空间(VA,Virtual Address)到物理空间(PA,Physical Address)的映射
- 内存保护,设置存储器的访问权限以及虚拟空间的缓冲特性
对于32位的处理器来说,虚拟地址范围是2^32=4GB。 Linux内核启动的时候会初始化MMU,设置好地址映射后,访问的都是虚拟地址。因此无法直接对物理地址进行读写操作。物理内存和虚拟内存之间的转换,需要用到以下两个函数,定义在arch/arm/include/asm/io.h:
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
void __iomem *__arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype);
#define iounmap __arm_iounmap
void __arm_iounmap(volatile void __iomem *addr);
ioremap的返回值为void __iomem类型的指针,指向映射后的虚拟空间首地址,另外,cookie表示要映射的物理起始地址,size表示需要映射的内存空间大小。iounmap用于释放映射的虚拟内存,只需要填入需要取消的虚拟空间首地址即可。
注意,在使用时ioremap和iounmap要成对出现。
2 I/O内存与I/O端口
这里的I/O是输入/输出的意思。关于I/O内存和I/O端口的区别可以参考IO端口和IO内存的区别及分别使用的函数接口_insoonior的博客-CSDN博客
I/O内存读操作函数通过宏定义替换如下:
u8 readb(const volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
u32 readl(const volatile void __iomem *addr);
三个函数分别表示8bit、16bit、32bit读操作,入口参数表示需要读取的内存地址,返回值为读取到的数据。
I/O内存写操作函数通过宏定义替换如下:
void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);
三个函数分别表示8bit、16bit、32bit写操作,入口参数分别表示要写入的值和写入的内存地址。
3 工程验证
驱动代码如下:
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define DEVICE_MAJOR 201
#define DEVICE_NAME "leddevice"
// led 状态
#define LED_ON 1
#define LED_OFF 0
// 寄存器物理地址
#define CCM_CCGR1_BASE (0x020C406C)
#define SW_MUX_GPIO1_IO3_BASE (0x020E0068)
#define SW_PAD_GPIO1_IO3_BASE (0x020E02F4)
#define GPIO1_GDIR_BASE (0x0209C004)
#define GPIO1_DR_BASE (0x0209C000)
// 虚拟内存地址
static void __iomem *ccm_ccgr1;
static void __iomem *mux_gpio1_io3;
static void __iomem *pad_gpio1_io3;
static void __iomem *gpio1_gdir;
static void __iomem *gpio1_dr;
void led_switch(u8 sta)
{
u32 val = 0;
if(LED_ON == sta)
{
val = readl(gpio1_dr);
val &= ~(1 << 3);
writel(val, gpio1_dr);
}
else if(LED_OFF == sta)
{
val = readl(gpio1_dr);
val |= (1 << 3);
writel(val, gpio1_dr);
}
}
int led_open(struct inode *inode, struct file *file)
{
return 0;
}
int led_release(struct inode *inode, struct file *file)
{
return 0;
}
ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
char databuf[1];
ret = copy_from_user(databuf, buf, count);
if(ret != 0)
{
printk("receive faild\r\n");
}
else
{
printk("receive data:%d\r\n", databuf[0]);
}
if(databuf[0] == LED_ON)
{
led_switch(LED_ON);
}
else if(databuf[0] == LED_OFF)
{
led_switch(LED_OFF);
}
return 0;
}
const struct file_operations fops =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
static int __init leddevice_init(void)
{
u8 ret = 0;
u32 readval = 0;
// 地址映射
ccm_ccgr1 = ioremap(CCM_CCGR1_BASE, 4);
mux_gpio1_io3 = ioremap(SW_MUX_GPIO1_IO3_BASE, 4);
pad_gpio1_io3 = ioremap(SW_PAD_GPIO1_IO3_BASE, 4);
gpio1_gdir = ioremap(GPIO1_GDIR_BASE, 4);
gpio1_dr = ioremap(GPIO1_DR_BASE, 4);
// 使能时钟,因为此寄存器控制着其他外设时钟,防止影响,采用读改写
readval = readl(ccm_ccgr1);
readval &= ~(3 << 26);
readval |= (3 << 26);
writel(readval, ccm_ccgr1);
// 引脚复用,此寄存器只控制GPIO1_IO3,因此不需要读改写
writel(0x5, mux_gpio1_io3);
// 引脚属性
writel(0x10B0, pad_gpio1_io3);
// 引脚为输出
readval = readl(gpio1_gdir);
readval &= ~(1 << 3);
readval |= (1 << 3);
writel(readval, gpio1_gdir);
// 输出高电平
readval = readl(gpio1_dr);
readval &= ~(1 << 3);
readval |= (1 << 3);
writel(readval, gpio1_dr);
printk("leddevice_init\r\n");
ret = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &fops);
if(ret < 0)
{
printk("register_chrdev failed\r\n");
}
return 0;
}
static void __exit leddevice_exit(void)
{
// 释放虚拟内存
iounmap(ccm_ccgr1);
iounmap(mux_gpio1_io3);
iounmap(pad_gpio1_io3);
iounmap(gpio1_gdir);
iounmap(gpio1_dr);
printk("leddevice_exit\r\n");
unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);
}
// 注册模块加载和卸载函数
module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
APP代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
// ./leddeviceAPP /dev/leddevice 1 open
// ./leddeviceAPP /dev/leddevice 0 close
const char app_data[] = "app";
int main(int argc, char *argv[])
{
int fd = 0, ret = 0;
char *filename = argv[1], writebuf[100];
char readbuf[100];
if(argc != 3)
{
printf("Error usage!\r\n");
return -1;
}
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("Can't open file %s\r\n", filename);
return -1;
}
else
{
}
writebuf[0] = atoi(argv[2]);
ret = write(fd, writebuf, 1);
if(ret < 0)
{
printf("write file %s failed\r\n", filename);
return -1;
}
else
{
}
ret = close(fd);
if(ret < 0)
{
printf("close file %s failed\r\n", filename);
return -1;
}
else
{
}
return 0;
}