一、地址映射
- 1、
裸机led实验
就是 操作6ull的寄存器
。 - 2、linux驱动开发也可以操作寄存器,但不能直接对寄存器的物理地址进行读写操作。
比如寄存器A物理地址
为0x01010101,裸机时可以直接对0x01010101这个物理地址进行操作,但在linux下是不行的。
因为linux会使能mmu。
在linux里面操作的都是虚拟地址,所以需要先得到0x01010101这个物理地址对应的虚拟地址。
获取物理地址对应的虚拟地址使用宏ioremap(cokkie, size)
。第一个参数
是物理地址起始地址
,第二个参数
是要转换的字节数量
,返回映射后的虚拟地址
。因此不仅可以对一个物理地址
进行转换,也可以对一段物理地址
进行转换。使用示例:ioremap(0x01010101, 10)
卸载驱动时使用iounmap
来释放掉ioremap
函数所做的映射。void iounmap (volatile void __iomem *addr)
使用ioremap
函数将寄存器的物理地址
映射到虚拟地址
以后,我们就可以直接通过指针
访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组读写操作函数
来对映射后的内存
进行读写操作。
读操作函数有如下几个:分别对应 8bit、 16bit 和 32bit 读操作.
参数 addr 就是要读取写内存地址,返回值就是读取到的数据。
u8 readb(const volatile void __iomem *addr) 8bit
u16 readw(const volatile void __iomem *addr) 16bit
u32 readl(const volatile void __iomem *addr) 32bit
写操作函数有如下几个:分别对应 8bit、 16bit 和 32bit 写操作.
参数 value 是要写入的数值, addr 是要写入的地址。
void writeb(u8 value, volatile void __iomem *addr) 8bit
void writew(u16 value, volatile void __iomem *addr) 16bit
void writel(u32 value, volatile void __iomem *addr) 32bit
- 3、mmu的两个功能
内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
完成虚拟空间到物理空间的映射。对于 32 位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间,如下图所示:
二、LED灯字符设备驱动框架搭建
- 1、初始化时钟,IO等等,可以放在
led_init
也可以放在led_open
里面。 - 2、初始化完成后进行测试,若用的是教程提供的内核,此时led默认被配置为心跳灯,因此必须要先关闭心跳灯。l
- 3、uboot下载系统失败的话,要检查
开发板 的 ip
和ubuntu 的 ip
是唯一的。检测到ubuntu的ip冲突,则重新设置ubuntu的ip,然后重启网卡(图标点一下即可),然后ping www.baidu.com
。然后改uboot的环境变量:serverip,bootargs(不改此环境变量无法挂载根文件系统)。
三、驱动源码
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
//#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.h>
#define LED_MAJOR 200
#define LED_NAME "led"
// led 相关寄存器的物理地址
#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 *IMX6U_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;
#define LED_OFF 0
#define LED_ON 1
static void led_switch(u8 state)
{
u32 val;
if(state == LED_ON)
{
// 操作 DR 寄存器来实现开灯操作
val = readl(GPIO1_DR);
val &= (~(1<<3));
writel(val, GPIO1_DR);
}
else if(state == LED_OFF)
{
// 操作 DR 寄存器来实现关灯操作
val = readl(GPIO1_DR);
val |= (1<<3); // turn off led
writel(val, GPIO1_DR);
}
}
// @param – inode : 传递给驱动的 inode
// @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量.一般在 open 的时候将 private_data 指向设备结构体
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
// @param - filp : 设备文件,表示打开的文件描述符
// @param - buf : 要写给设备写入的数据
// @param - count : 要写入的数据长度
// @param - offt : 相对于文件首地址的偏移
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
int retValue;
unsigned char databuf[1];
retValue = copy_from_user(databuf, buf, count);
if(retValue < 0)
{
printk("Kernel write failed.\r\n");
return -EIO;
}
led_switch(databuf[0]);
return 0;
}
static const struct file_operations led_fops =
{
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
// 驱动入口函数,调用 modprobe 时会执行该函数
static int __init led_init(void)
{
int ret = 0;
unsigned int val = 0;
// 寄存器地址映射
IMX6U_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);
// 初始化相关寄存器
val = readl(IMX6U_CCM_CCGR1);
val &= (~(3<<26));
val |= (3<<26);
writel(val, IMX6U_CCM_CCGR1);
writel(0X5, SW_MUX_GPIO1_IO03);// io 复用
writel(0X10B0, SW_PAD_GPIO1_IO03); // 设置电气属性
val = readl(GPIO1_GDIR); // 设置方向
val |= (1<<3);
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR); // 设置高低电平
val &= (~(1<<3));
writel(val, GPIO1_DR);
// 注册字符设备
ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if(ret < 0)
{
printk("Register led_dev fail.\r\n");
return -EIO;
}
printk("led_init\r\n");
return 0;
}
// 驱动出口函数
static void __exit led_exit(void)
{
unsigned int val = 0;
val = readl(GPIO1_DR);
val |= (1<<3); // turn off led
writel(val, GPIO1_DR);
// 取消地址映射
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
// 注销字符设备驱动
unregister_chrdev(LED_MAJOR, LED_NAME);
printk("led_exit\r\n");
}
//向内核注册一个模块加载函数(入口函数),指定 led 设备驱动加载函数
module_init(led_init);
//向内核注册一个模块卸载函数(出口函数),指定 led 设备驱动卸载函数
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("J");
- Makefile
KERNELDIR := /home/jl/linux/imx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
四、测试APP源码
- 注意,APP在Makefile里面没有对其进行编译,需要自己在shell下编译。
arm-linux-gnueabihf-gcc ledAPP.c -o ledAPP
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
// ./ledAPP filename 0/1
// 0 -> turn off led
// 1 -> turn on led
#define LED_OFF 0
#define LED_ON 1
int main(int argc, char **argv)
{
int ret = 0;
int fd = 0;
char *filename;
unsigned char databuf[1];
if(argc != 3)
{
printf("Error usage!\r\n");// user room
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("Can't open file \"%s\"\r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0)
{
printf("Led control failed.\r\n");
ret = close(fd);
if(ret < 0)
{
printf("Close file \"%s\" error!\r\n", filename);
return -1;
}
return -1;
}
ret = close(fd);
if(ret < 0)
{
printf("Close file \"%s\" error!\r\n", filename);
return -1;
}
else
{
;
}
return 0;
}
五、测试
1、depmod
2、modprobe led.ko
3、cat /proc/devices
查看主设备号;lsmod
查看驱动模块是否存在
4、mknod /dev/led c 200 0
创建设备结点:c字符设备,200主设备号,0次设备号
5、./ledAPP /dev/led ( 0 / 1 )
使用测试app进行开灯关灯
5、rmmod led.ko
- 最重要的地址映射,内核提供的两个映射函数,内存读写函数