地址映射(MMU)
MMU全称Memory Manage Unit,也就是内存管理单元
主要功能:完成虚拟空间到物理空间的映射
虚拟地址范围为0~2^32=4GB
需要用到两个函数实现物理内存与虚拟内存之间的转换
ioremap//ioremap用于获取指定物理地址空间对应的虚拟地址空间
iounmap//iounmao用于取消映射的虚拟地址空间
函数原型如下
#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)
ioremap是个宏,正真起作用的是__arm_ioremap。__arm_ioremap有3个参数:phys_addr size mtype。
phys_addr:要映射给的物理起始地址
size:要映射的内存空间大小
mtype:ioremap 的类型(ioremap 函数选择 MT_DEVICE)
void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址
使用方法:
注意__arm_ioremap函数的返回值,返回值是一个void __iomem类型的指针。指向映射后的虚拟空间首地址
1.定义一个指针,并使用static修饰
static void __iomem ADDRESS
2.调用ioremap实现地址映射
ADDRESS = ioremap(0x00000000,4);
3.调用iounmap取消地址映射
iounmap(ADDRESS);
I/O内存访问函数
这里说的 I/O 不是我们学习单片机的时候讲的 GPIO 引脚。
这里涉及到两个概念(I/O 端口和 I/O 内存)
I/O 端口: 当外部寄存器或内存映射到 IO空间 时,称为 I/O 端口
I/O 内存: 当外部寄存器或内存映射到 内存空间 时,称为 I/O 内存。
对于ARM来说没有IO空间这个概念。只有IO内存,使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址。
但是 但是 但是!!!
Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。操作函数如下
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
驱动代码实现
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LED_MAJOR 200
#define LED_NAME "led"
#define LED_OFF 0
#define LED_ON 1
#define CCM_CCGR1_BASE (0x020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0x020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0x020E02F4)
#define GPIO1_DR_BASE (0x0209C000)
#define GPIO1_GDIR_BASE (0x0209C004)
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LED_ON){
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if (sta == LED_OFF) {
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
}
}
void led_device_init(void)
{
u32 val = 0;
/*寄存器地址映射*/
CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/*使能GPIO1时钟*/
val = readl(CCM_CCGR1);
val &= ~(3<<26);
val |= (1<<26);
writel(val, CCM_CCGR1);
/*设置GPIO1_IO3的复用功能*/
writel(5, SW_MUX_GPIO1_IO03);
/*设置IO属性*/
writel(0x10B0, SW_PAD_GPIO1_IO03);
/*设置GPIO1_IO3为输出功能*/
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
/*LED默认关闭*/
led_switch(LED_OFF);
}
void led_device_deinit(void)
{
iounmap(CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
}
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
u32 val = 0;
u32 led_status = 0;
int retvalue = 0;
val = readl(GPIO1_DR);
led_status = !(val&(1<<3));
retvalue = copy_to_user(buf, &led_status, sizeof(u32));
return retvalue;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
u32 led_value = 0;
int retvalue = 0;
retvalue = copy_from_user(&led_value, buf, sizeof(u32));
led_switch(led_value&0x01);
return retvalue;
}
struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
static int __init led_init(void)
{
int retvalue = 0;
led_device_init();
retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
printk("led init success\r\n");
return retvalue;
}
static void __exit led_exit(void)
{
led_device_deinit();
unregister_chrdev(LED_MAJOR, LED_NAME);
printk("led deinit success\r\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("warren");
测试代码实现
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc, int argv[])
{
int retvalue = 0;
int Dir = 0;
char *filename;
int fd = 0;
unsigned int buff;
if(argc < 4){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Open %s error!\r\n",filename);
return -1;
}
Dir = atoi(argv[2]);
if(Dir == 1){//写入
buff = atoi(argv[3]);
retvalue = write(fd, &buff, sizeof(unsigned int));
}else if(Dir == 2){//读取
retvalue = read(fd, &buff, sizeof(unsigned int));
printf("Led status is %d\r\n", buff);
}
return 0;
}
脚本文件
编译脚本
#!/bin/bash
make clean
make
rm -rf ledApp
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
rm -rf ../../tftpboot/*
cp *App *.ko ../../tftpboot
chmod 777 ../../tftpboot/*
tftp获取脚本
#!/bin/bash
rm -rf led*
tftp -g -r led.ko 192.168.0.100
tftp -g -r ledApp 192.168.0.100
chmod 777 ledApp
测试及实验
1.编译驱动模块及测试程序
2.在开发板上获取驱动模块及测试程序
3.加载驱动模块
insmod led.ko
4.创建设备节点
insmod led.ko
4.开打LED灯并获取状态
./ledApp /dev/led 1 1
./ledApp /dev/led 2 x
5.关闭LED灯并获取状态
./ledApp /dev/led 1 0
./ledApp /dev/led 2 x
OVER
评论 (0)