第四章 IO内存
1 什么是IO内存
在嵌入式是平台上,系统内存(DDR2,512MB)和外设(GPIO ADC LCD ...)是统一编址的,是在同一地址空间内的。即SFR的地址和系统内存的地址是在同一地址空间上。
这样我们访问外设的方法和访问系统内存的方法是一样--->都是通过地址来访问。
[root@GEC210 /]# cat /proc/iomem
30000000-3fffffff : System RAM 第0个通道的系统内存DDR2
3008e000-307d4fff : Kernel text
307d6000-30881337 : Kernel data
40000000-4fffffff : System RAM 第1个通道的系统内存DDR2
88001000-88001fff : dm9000
88001000-88001fff : dm9000
8800400c-8800500b : dm9000
8800400c-8800500b : dm9000
b0e00000-b0efffff : s5pv210-nand
b0e00000-b0efffff : s5pv210-nand
e0900000-e0901000 : s3c-pl330.1
e0900000-e0901000 : s3c-pl330
e0a00000-e0a01000 : s3c-pl330.2
e0a00000-e0a01000 : s3c-pl330
e1300000-e13000ff : s3c64xx-spi.0
e1300000-e13000ff : s3c64xx-spi
e1400000-e14000ff : s3c64xx-spi.1
e1400000-e14000ff : s3c64xx-spi
e1700000-e1700fff : s3c-adc
e1800000-e1800fff : s3c2440-i2c.0
e1800000-e1800fff : s3c2440-i2c
e1a00000-e1a00fff : s3c2440-i2c.2
e1a00000-e1a00fff : s3c2440-i2c
e1b00000-e1b00fff : s5p-cec
e1b00000-e1b00fff : s5p-cec
e2700000-e27fffff : s3c2410-wdt
e2800000-e28000ff : smdkc110-rtc
e2800000-e28000ff : smdkc110-rtc
e2900000-e2900100 : s5pv210-uart.0
e2900000-e29000ff : s5pv210-uart
e2900400-e2900500 : s5pv210-uart.1
e2900400-e29004ff : s5pv210-uart
e2900800-e2900900 : s5pv210-uart.2
e2900800-e29008ff : s5pv210-uart
e2900c00-e2900d00 : s5pv210-uart.3
e2900c00-e2900cff : s5pv210-uart
eb000000-eb000fff : s3c-sdhci.0
eb000000-eb000fff : mmc0
eb100000-eb100fff : s3c-sdhci.1
eb100000-eb100fff : mmc1
eb200000-eb200fff : s3c-sdhci.2
eb200000-eb200fff : mmc2
eb300000-eb300fff : s3c-sdhci.3
eb300000-eb300fff : mmc3
ec000000-ec0fffff : s3c-usbgadget
ec200000-ec2fffff : s5p-ehci
ec200000-ec2fffff : ehci_hcd
ec300000-ec3fffff : s5p-ohci
ec300000-ec3fffff : ohci_hcd
eee30000-eee300ff : s3c64xx-iis.0
eee30000-eee300ff : s3c64xx-i2s
f1700000-f17fffff : s3c-mfc
f1700000-f17fffff : s3c-mfc
f8000000-f80fffff : s3cfb
f8000000-f80fffff : s3cfb
f9000000-f90fffff : s5p-tvout
f9000000-f90fffff : s5p-tvout
f9100000-f91fffff : s5p-tvout
f9100000-f91fffff : s5p-tvout
f9200000-f92fffff : s5p-tvout
f9200000-f92fffff : s5p-tvout
fa000000-fa0fffff : s3c-g2d
fa000000-fa0fffff : s3c-g2d
fa100000-fa1fffff : s5p-tvout
fa100000-fa1fffff : s5p-tvout
fa200000-fa201000 : s3c-pl330.0
fa200000-fa201000 : s3c-pl330
fa600000-fa6fffff : s3c-csis
fa600000-fa6fffff : s3c-csis
fa900000-fa9003ff : s5p-tvout
fa900000-fa9003ff : s5p-tvout
fab00000-fab00fff : s3c2440-i2c.1
fab00000-fab00fff : s3c2440-i2c
fb200000-fb2fffff : s3c-fimc.0
fb200000-fb2fffff : s3c-fimc
fb300000-fb3fffff : s3c-fimc.1
fb300000-fb3fffff : s3c-fimc
fb400000-fb4fffff : s3c-fimc.2
fb400000-fb4fffff : s3c-fimc
fb600000-fb6fffff : s3c-jpg
fb600000-fb6fffff : s3c-jpg
都是物理地址。
2 IO内存的使用方法
linux 不能直接访问物理地址
2.1 申请物理内存区
物理内存 ---> 一段SFR的物理地址,例如:我们想控制LED1~LED4,需要控制GPJ2CON和GPJ2DAT两个寄存器, 这个寄存器的物理地址范围:0xe0200280~0xe200287。
通过寄存器访问硬件的第一步骤就是申请物理内存区,将这个物理内存区作为一个资源resource,存放在内核中,注意,资源只能申请一次,这也是对资源的保护。
2.1.1申请物理内存区的函数
#include
struct resource * request_mem_region(resource_size_t start,resource_size_t n, const char *name)//(物理内存开始地址, 物理内存地址大小,物理内存名字)
例:申请GPJ2CON和GPJ2DAT两个寄存器的物理内存区
static struct resource * gec210_led_res; //定义一个物理内存区的资源
gec210_led_res = request_mem_region(0xe0200280,8,"GPJ2_LED"); //(物理内存开始地址, 地址大小-每个寄存器4两个是8,物理内存名字)cat /proc/iomem
if(gec210_led_res == NULL)
{
return -EBUSY; //资源在忙,申请不了
}
2.1.2释放物理内存区
void release_mem_region(resource_size_t start,resource_size_t n)//(物理内存开始地址, 物理内存地址大小)
例:
release_mem_region(0xe0200280,8)
2.2 IO内存的动态映射
我们查找ARM的处理器使用手册,查看到的寄存器都是物理地址,linux系统不能直接使用物理地址。使用IO内存的动态映射由物理地址得到它对应的虚拟地址,然后我们就可以直接使用虚拟地址了。
注意:IO内存的动态映射与MMU是一个反向的过程。IO内存的动态映射实际上是改写MMU的页表的过程。MMU通过虚拟地址来查找这个页表,然后找到页表中的物理地址,这样完成了虚拟地址到物理地址的转换。
2.2.1 IO内存的动态映射函数
void __iomem *ioremap(phys_addr_t start, unsigned long size) //(物理内存作动态映射的开始地址,动态映射的物理内存区的大小)
例:我们想得到GPJ2CON和GPJ2DAT的虚拟地址
static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针
static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针
gpj2con_va = ioremap(0xe0200280,8); //返回虚拟地址的开始值(首地址)
if(gpj2con_va == NULL)
{
return -EFAULT;
}
gpj2dat_va = gpj2con_va + 1; //不是4, int类型指针偏移一个单位为4字节
2.2.2取消IO内存的动态映射
void iounmap(void *addr)
参数说明:
void *add --- ioremap映射后的虚拟地址。
我们访问虚拟地址的方法和访问物理地址的方法是一样的。
3 虚拟地址的使用方法
常用的函数:
readl(addr)
writel(b,addr)
或
__raw_readl(addr)
__raw_writel(u32 b, volatile void __iomem *addr)
将GPJ2_0~3设置成输出,输出高电平。
static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针
static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针
*gpj2con_va &= ~0xffff;
*gpj2con_va |= 0x1111;
*gpj2dat_va |= 0xf;
风格-->裸机的编程风格,不是linux的风格。
linux的风格:
writel( (readl(gpj2con_va) & ~0xffff)|0x1111,gpj2con_va );
writel( (readl(gpj2dat_va) |0xf,gpj2dat_va );
访问虚拟地址的函数:
#define readl(addr) __le32_to_cpu(__raw_readl(addr))
static inline u32 __raw_readl(const volatile void __iomem *addr)
{
return *(const volatile u32 __force *) addr;
}
#define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)
static inline void __raw_writel(u32 b, volatile void __iomem *addr)
{
*(volatile u32 __force *) addr = b;
}
A代码一
1. Filename: led_drv.c
#include
#include
#include
#include
#include
#include
#include
//1)定义一个字符设备
static struct cdev led_drv;
static unsigned int led_major = 100; //0-->动态分配,>0-->静态注册
static unsigned int led_minor = 0;
static dev_t led_drv_num;
static struct resource * gec210_led_res;
static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针
static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针
//3)定义文件操作集,并初始化
static int gec210_led_open (struct inode *inode, struct file *filp)
{
*gpj2con_va &= ~0xffff; //低16位清0
*gpj2con_va |= 0x1111; //低16位为0x1111,四个IO口就配置成输出,四个位控制一个引脚
*gpj2dat_va |= 0xf; //打开驱动,四个灯灭,高电平 ,一个位对应一个引脚
printk("openning the driver of led\n");
return 0;
}
//char buf[2],两个字节数据 buf[1]灯的状态:1--on灯亮,0-->off灯灭 (自定义)
// buf[0] 哪一个led:1/2/3/4 ,实际控制的位为 第0/1/2/3位
ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) //应用程序向驱动程序写数据,用此数据控制灯亮灭,
{
int ret;
char kbuf[2]; //两个字节 ,第一字节buf[1]灯的状态,第二字节 ,buf[0]哪一个led
if(len != 2)
return -EINVAL;
ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据放到kbuf
if(ret!= 0)
return -EFAULT;
if( (kbuf[0]<1) || (kbuf[0]>4) ) //灯数量,有四个灯,因此不能小于1 ,大于4
return -EINVAL;
if(kbuf[1]==1) //灯亮,
*gpj2dat_va &= ~(1<<(kbuf[0]-1)); //buf[0] 哪一个led:1/2/3/4 ,实际控制的位为 第0/1/2/3位
else if(kbuf[1]==0) //灯灭
*gpj2dat_va |= (1<<(kbuf[0]-1));
else
return -EINVAL;
return len;
}
static int gec210_led_release(struct inode *inode, struct file *filp)
{
*gpj2dat_va |= 0xf; //关闭驱动,四个灯灭,高电平 ,一个位对应一个引脚
printk("closing the driver of led\n");
return 0;
}
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
.open = gec210_led_open,
.write = gec210_led_write,
.release = gec210_led_release,
};
static int __init gec210_led_init(void) //驱动的初始化及安装函数
{
int ret;
//2)申请/注册设备号
if(led_major == 0){
ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");
}
else{
led_drv_num = MKDEV(led_major,led_minor);
ret = register_chrdev_region(led_drv_num, 1, "gec210_leds");
}
if(ret < 0){
printk("led_drv_num is error \n");
return ret;
}
//4)初始化cdev
cdev_init(&led_drv, &gec210_led_fops);
//5)将cdev加入kernel
ret = cdev_add(&led_drv,led_drv_num, 1 );
if(ret < 0){
printk("cdev add error\n");
goto failed_cdev_add;
}
//6 申请物理内存区,作为一个资源
gec210_led_res = request_mem_region(0xe0200280,8,"GPJ2_LED"); //cat /proc/iomem
if(gec210_led_res == NULL)
{
printk("requst mem region error\n");
ret = -EBUSY;
goto failed_request_mem_region;
}
//7 io内存动态映射
gpj2con_va = ioremap(0xe0200280,8);
if(gpj2con_va == NULL)
{
printk("ioremap error\n");
ret = -EFAULT;
goto failed_ioremap;
}
gpj2dat_va = gpj2con_va + 1; //不是4
printk("gpj2con_va=%p,gpj2dat_va=%p\n", gpj2con_va,gpj2dat_va);
return 0;
failed_ioremap:
release_mem_region(0xe0200280,8);
failed_request_mem_region:
cdev_del(&led_drv);
failed_cdev_add:
unregister_chrdev_region(led_drv_num, 1);
return ret;
}
static void __exit gec210_led_exit(void) //驱动卸载函数
{
unregister_chrdev_region(led_drv_num, 1);
cdev_del(&led_drv);
release_mem_region(0xe0200280,8);
printk("good bye gec210\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//-------------------------------
2. Filename: test.c
#include
#include
int main(void)
{
int fd;
int ret;
char buf[2]; //写两个字节数据
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/led_drv", O_WRONLY);
if(fd <0)
{
perror("open led_drv:");
return -1;
}
while(1)
{
buf[1] = 1;buf[0]=3; //led3 on 第三个灯亮
ret = write(fd,buf,sizeof(buf)); //把数据写到驱动程序里
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
buf[1] = 0;buf[0]=3; //led3 off 第三个灯灭
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
}
close(fd);
return 0;
}
//-------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
第五章 自动生成设备文件
1 字符设备驱动的设计流程
1.1 定义一个字符设备--->cdev
1.2 申请一个设备号 --->10--主设备号, 131--次设备号,设备号10<<20+130。动态申请or静态注册
1.3 定义文件操作集并初始化--->file_operations给应用程序提供一个标准的接口
1.4 字符设备的初始化
1.5 将字符设备加入内核
1.6 申请SFR的物理内存区,作为一个资源
1.7 IO内存的动态映射,得到了虚拟地址,访问虚拟地址。
//自动生成设备文件:不用mknod
1.8 创建字符设备的class
1.9 创建属于该class的device
2 如何创建class和device
#include
2.1 class的创建
/**
* class_create - create a struct class structure
* @owner: pointer to the module that is to "own" this struct class
* @name: pointer to a string for the name of this class.
*
* This is used to create a struct class pointer that can then be used
* in calls to device_create().
*
* Returns &struct class pointer on success, or ERR_PTR() on error.
*
* Note, the pointer created here is to be destroyed when finished by
* making a call to class_destroy().
*/
原型
struct class * class_create(struct module *owner, const char *name)
参数说明:
struct module *owner ---class的所有者,THIS_MODULE
const char *name --- class的名字。 /sys/class/
返回值:
成功,返回创建好的class的指针
失败,ERR_PTR()确定失败的原因
例:
static struct class *gec210_led_class;
gec210_led_class = class_create(THIS_MODULE, "led_class");
if(gec210_led_class == NULL)
{
return -EBUSY;
}
2.2销毁一个class
void class_destroy(struct class *cls);
2.3 device的创建
原型
struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt)
参数说明:
struct class *cls --->创建的device输入哪个class
struct device *parent --->device的父设备,一般都为NULL
dev_t devt --->设备号
void *drvdata --->device的数据,一般也为NULL
const char *fmt --->device的名字,也就是设备文件的名字
返回值:
创建好的device
2.4销毁device函数:
void device_destroy(struct class *cls, dev_t devt);
3 为什么有class和device就可以自动生成设备文件?
使用的一个自动生成设备文件的工具---mdev
# which mdev
/sbin/mdev ---> busybox生成的
mdev工具会根据/sys路径下的class找到device,然后再根据device创建设备文件。
#vi /etc/init.d/rcS
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
将/sbin/mdev工具写入到/proc/sys/kernel/hotplug。hotplug热插拔的过程,向内核中安装驱动和卸载驱动都是一个热插拔过程。
在我们安装驱动和卸载驱动的时候,就会立即调用mdev工具。安装驱动的时候生成设备文件,卸载驱动的时候,设备文件会自动删掉。
A代码一
1. Filename: led_drv.c
#include
#include
#include
#include
#include
#include
#include
#include
//1)定义一个字符设备cdev
static struct cdev led_drv;
static unsigned int led_major = 0; //0-->动态分配,>0-->静态注册
static unsigned int led_minor = 0;
static dev_t led_drv_num;
static struct resource * gec210_led_res;
static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针
static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针
static struct class *gec210_led_class;
static struct device *gec210_led_device;
//3)定义文件操作集,并初始化
//char buf[2],buf[1]灯的状态:1--on,0-->off
// buf[0]哪一个led:1/2/3/4
ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
int ret;
char kbuf[2];
if(len != 2)
return -EINVAL;
ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据
if(ret!= 0)
return -EFAULT;
if( (kbuf[0]<1) || (kbuf[0]>4) )
return -EINVAL;
if(kbuf[1]==1)
*gpj2dat_va &= ~(1<<(kbuf[0]-1));
else if(kbuf[1]==0)
*gpj2dat_va |= (1<<(kbuf[0]-1));
else
return -EINVAL;
return len;
}
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
.write = gec210_led_write,
};
static int __init gec210_led_init(void) //驱动的初始化及安装函数
{
int ret;
//2)申请/注册设备号
if(led_major == 0){
ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");
}
else{
led_drv_num = MKDEV(led_major,led_minor);
ret = register_chrdev_region(led_drv_num, 1, "gec210_leds");
}
if(ret < 0){
printk("led_drv_num is error \n");
return ret;
}
//4)初始化cdev
cdev_init(&led_drv, &gec210_led_fops);
//5)将cdev加入kernel
ret = cdev_add(&led_drv,led_drv_num, 1 );
if(ret < 0){
printk("cdev add error\n");
goto failed_cdev_add;
}
//6)申请物理内存区,作为一个资源
gec210_led_res = request_mem_region(0xe0200280,8,"GPJ2_LED"); //cat /proc/iomem
if(gec210_led_res == NULL)
{
printk("requst mem region error\n");
ret = -EBUSY;
goto failed_request_mem_region;
}
//7)io内存动态映射
gpj2con_va = ioremap(0xe0200280,8);
if(gpj2con_va == NULL)
{
printk("ioremap error\n");
ret = -EFAULT;
goto failed_ioremap;
}
gpj2dat_va = gpj2con_va + 1; //不是4
printk("gpj2con_va=%p,gpj2dat_va=%p\n", gpj2con_va,gpj2dat_va);
//8)创建class
gec210_led_class = class_create(THIS_MODULE, "led_class");
if(gec210_led_class == NULL)
{
printk("class create error\n");
ret = -EBUSY;
goto failed_class_create;
}
//9)创建device
gec210_led_device = device_create(gec210_led_class,NULL,
led_drv_num,NULL,"led_drv"); // /dev/led_drv
if(gec210_led_device == NULL)
{
printk("class device error\n");
ret = -EBUSY;
goto failed_device_create;
}
//led1~4,初始状态是灭的
*gpj2con_va &= ~0xffff;
*gpj2con_va |= 0x1111;
*gpj2dat_va |= 0xf;
return 0;
failed_device_create:
class_destroy(gec210_led_class);
failed_class_create:
iounmap(gpj2con_va);
failed_ioremap:
release_mem_region(0xe0200280,8);
failed_request_mem_region:
cdev_del(&led_drv);
failed_cdev_add:
unregister_chrdev_region(led_drv_num, 1);
return ret;
}
static void __exit gec210_led_exit(void) //驱动卸载函数
{
unregister_chrdev_region(led_drv_num, 1);
cdev_del(&led_drv);
release_mem_region(0xe0200280,8);
iounmap(gpj2con_va);
device_destroy(gec210_led_class,led_drv_num);
class_destroy(gec210_led_class);
printk("good bye gec210\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//-------------------------------------------------------------------------
2. Filename: test
#include
#include
int main(void)
{
int fd;
int ret;
char buf[2];
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/led_drv", O_WRONLY);
if(fd <0)
{
perror("open led_drv:");
return -1;
}
while(1)
{
buf[1] = 1;buf[0]=3; //led3 on
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
buf[1] = 0;buf[0]=3; //led3 on
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
}
close(fd);
return 0;
}
//-------------------------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko