【Linux】LDD学习笔记——操作硬件——IO内存
2017-07-02 23:51
381 查看
原文出处:http://blog.chinaunix.net/uid-25014876-id-80627.html
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
在之前章节的驱动,都没有对硬件进行操作,接写来将从我之前学的裸板驱动开始,讲解在linux系统下如何访问硬件。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、IO端口与IO内存
介绍之前可以看看以下的博客:http://blogold.chinaunix.net/u2/66435/showart_2137870.html
x86体系和ARM体系的寻址方式是有差别的:
在x86下,为了能够满足CPU高速地运行,内存与CPU之间通过北桥相连并通过地址方式访问,而外设通过南桥与CPU相连并通过端口访问。
在ARM下也实现了类似的操作,通过两条不同的总线(AHB
BUS和APB BUS)来连接不同访问速度的外设。但是它与x86不同,无论是内存还是外设,ARM都是通过地址访问。
因为这两种访问方式的不同,linux分出了两种不同的访问操作:
以地址方式访问硬件——使用IO内存操作。
以端口方式访问硬件——使用IO端口操作。
在ARM下,访问寄存器就像访问内存一样——从指定的寄存器地址获取数据,修改。所以,ARM下一般是使用IO内存的操作。但这并不是说IO端口的操作在ARM下不能用,它们的代码差不多,只是没有使用的必要,下面也将介绍IO内存操作。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、如何使用IO内存获得硬件的地址
之前已经说过,不能在linux使用实际的物理地址,要对指定的物理地址进行操作,必须要先将物理地址与虚拟地址对应,通过虚拟地址访问。于是有了以下的物理地址映射函数:
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
其实这也是上一节介绍的内存分配的一种方式,它同样会建立新页表来管理虚拟地址。函数传入两个参数,需要访问的物理内存(寄存器)的首地址phys_addr和这段内存区域的大小size,返回与该段物理地址对应的虚拟地址。这段地址可以多次被映射,当然,每次映射的虚拟地址也不一样。
对应的也有撤销映射关系的函数:
void ioumap(void *addr);
接下来,我将会从一个裸板的ARMled驱动开始,讲解linux下的操作和裸板有什么不一样。
我的ARM裸板程序是在linux下编写的,我不知道这跟win下使用ADS有什么区别,在裸板驱动中,一般我是通过这样的办法来操作寄存器的:
首先,先给个地址定义个容易记的名字:
#define GPECON *(volatile unsigned long *) 0x56000040
接着,我就要操作这个GPECON寄存器了:
*GPECON &= ~(3 << 24); //将24和25位清零
*GPECON |= (1 << 24); //将24和25位分别赋值为1、0
可以看到,操作寄存器其实就是拿个地址出来进行操作。其实在linux下也是一样,只是操作的时候不能使用物理地址,需要用映射出来的虚拟地址。
上个函数,这个程序我将要点亮连在我开发板上的led灯,这个灯接在我开发板的GPE12上,如果需要下载程序运行,需要改一下接口。
从上面的程序可以看到,除了获得地址有点和裸板驱动不一样外,寄存器的操作还是一样的。
接下来验证一下:
[root: 1st]# insmod test.ko
hello led! //这时候灯亮了
[root: 1st]# rmmod test
bye //灯灭了
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、改进函数,使用更好的内存访问接口
为了实现更好的移植性,上面的程序就有缺陷了。内核建议,尽量使用内核提供的内存访问接口:
#include <asm/io.h>
//从内存读取数据,返回值是指定内存地址中的值
unsigned int ioread8(void *addr)
unsigned int ioread16(void *addr)
unsigned int ioread32(void *addr)
//往指定内存地址写入数据
void iowrite8(u8 value, void *addr)
void iowrite16(u16 value, void *addr)
void iowrite32(u32 value, void *addr)
一般常用的是32位内存存取接口。
接下来就改进一下函数,其实实质没有改变,上面的函数是根据对应的平台体系结构编写的,这样可以提高驱动的移植性。
会发现发现,程序将原来直接访问内存的一句话变成了3句话,其他都没有改变。
我就不验证了,效果其实是一样的。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、再改进一下程序:
在使用IO内存映射操作之前,其实还可以添加一个步骤:分配内存区域。
#include <ioport.h>
struct resource *request_mem_region(unsigned long start, unsinged long len, char *name)
该函数从start开始分配len字节长的内存空间。如果成功,返回一个结构体指针,但这结构体我们没必要用,如果失败返回NULL。成功后,可以在.proc/iomem查看到name的信息。
其实调用request_mem_region()不是必须的,但是建议使用。该函数的任务是检查申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其他驱动想再申请该资源时就会失败。
如果不再使用,需要调用释放函数:
void release_mem_region(unsigned
long start, unsigned long len)
现在把这两个函数加上去:
写完就得验证一下:
[root: 3rd]# insmod test.ko
hello led! //灯亮了
[root: 3rd]# cat /proc/iomem
19000300-19000310 : cs8900
19000300-19000310 : cs8900
。。。。
56000000-5600000b : LED_MEM //看到了
57000000-570000ff : s3c2410-rtc
57000000-570000ff : s3c2410-rtc
5a000000-5a0fffff : s3c2440-sdi
[root: 3rd]# rmmod test
bye //灯灭了
[root: 3rd]# cat /proc/iomem //LED_MEM不见了
19000300-19000310 : cs8900
19000300-19000310 : cs8900
。。。。。。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、总结
今天介绍的内容不多,其实就几个函数,下面重温一下使用IO内存的步骤:
其中第一步和最后一步可以不做。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
源代码:5th_mm_2.rar
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
在之前章节的驱动,都没有对硬件进行操作,接写来将从我之前学的裸板驱动开始,讲解在linux系统下如何访问硬件。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、IO端口与IO内存
介绍之前可以看看以下的博客:http://blogold.chinaunix.net/u2/66435/showart_2137870.html
x86体系和ARM体系的寻址方式是有差别的:
在x86下,为了能够满足CPU高速地运行,内存与CPU之间通过北桥相连并通过地址方式访问,而外设通过南桥与CPU相连并通过端口访问。
在ARM下也实现了类似的操作,通过两条不同的总线(AHB
BUS和APB BUS)来连接不同访问速度的外设。但是它与x86不同,无论是内存还是外设,ARM都是通过地址访问。
因为这两种访问方式的不同,linux分出了两种不同的访问操作:
以地址方式访问硬件——使用IO内存操作。
以端口方式访问硬件——使用IO端口操作。
在ARM下,访问寄存器就像访问内存一样——从指定的寄存器地址获取数据,修改。所以,ARM下一般是使用IO内存的操作。但这并不是说IO端口的操作在ARM下不能用,它们的代码差不多,只是没有使用的必要,下面也将介绍IO内存操作。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、如何使用IO内存获得硬件的地址
之前已经说过,不能在linux使用实际的物理地址,要对指定的物理地址进行操作,必须要先将物理地址与虚拟地址对应,通过虚拟地址访问。于是有了以下的物理地址映射函数:
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
其实这也是上一节介绍的内存分配的一种方式,它同样会建立新页表来管理虚拟地址。函数传入两个参数,需要访问的物理内存(寄存器)的首地址phys_addr和这段内存区域的大小size,返回与该段物理地址对应的虚拟地址。这段地址可以多次被映射,当然,每次映射的虚拟地址也不一样。
对应的也有撤销映射关系的函数:
void ioumap(void *addr);
接下来,我将会从一个裸板的ARMled驱动开始,讲解linux下的操作和裸板有什么不一样。
我的ARM裸板程序是在linux下编写的,我不知道这跟win下使用ADS有什么区别,在裸板驱动中,一般我是通过这样的办法来操作寄存器的:
首先,先给个地址定义个容易记的名字:
#define GPECON *(volatile unsigned long *) 0x56000040
接着,我就要操作这个GPECON寄存器了:
*GPECON &= ~(3 << 24); //将24和25位清零
*GPECON |= (1 << 24); //将24和25位分别赋值为1、0
可以看到,操作寄存器其实就是拿个地址出来进行操作。其实在linux下也是一样,只是操作的时候不能使用物理地址,需要用映射出来的虚拟地址。
上个函数,这个程序我将要点亮连在我开发板上的led灯,这个灯接在我开发板的GPE12上,如果需要下载程序运行,需要改一下接口。
/*5th_mm_2/1st/test.c*/ #include #include #include //上面介绍的函数需要包含该头文件 volatile unsigned long virt, phys; //用于存放虚拟地址和物理地址 volatile unsigned long *GPECON, *GPEDAT, *GPEUP; //用与存放三个寄存器的地址 void led_device_init(void) { phys = 0x56000000; //1、指定物理地址 virt = (unsigned long)ioremap(phys, 0x0c); //2、通过ioremap获得对应的虚拟地址 //0x0c表示只要12字节的大小 GPECON = (unsigned long *)(virt + 0x40); //3、指定需要操作的三个寄存器的地址 GPEDAT = (unsigned long *)(virt + 0x44); PEUP = (unsigned long *)(virt + 0x48); } void led_configure(void) //led配置函数 { *GPECON &= ~(3 << 24); //配置GPE12为输出端口 *GPECON |= (1 << 24); //先清零再赋值 *GPEUP |= (1 << 12); //禁止上拉电阻 } void led_on(void) //点亮led { *GPEDAT &= ~(1 << 12); } void led_off(void) //灭掉led { *GPEDAT |= (1 << 12); } static int __init test_init(void) //模块初始化函数 { led_device_init(); led_configure(); led_on(); printk("hello led!\n"); return 0; } static void __exit test_exit(void) //模块卸载函数 { led_off(); iounmap((void *)virt); //注意,即使取消了映射,通过之前的虚拟地址还能访问硬件, printk("bye\n"); //但不是肯定可以,只要该虚拟地址被内核改动后就不行了。 } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xoao bai"); MODULE_VERSION("v0.1");
从上面的程序可以看到,除了获得地址有点和裸板驱动不一样外,寄存器的操作还是一样的。
接下来验证一下:
[root: 1st]# insmod test.ko
hello led! //这时候灯亮了
[root: 1st]# rmmod test
bye //灯灭了
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、改进函数,使用更好的内存访问接口
为了实现更好的移植性,上面的程序就有缺陷了。内核建议,尽量使用内核提供的内存访问接口:
#include <asm/io.h>
//从内存读取数据,返回值是指定内存地址中的值
unsigned int ioread8(void *addr)
unsigned int ioread16(void *addr)
unsigned int ioread32(void *addr)
//往指定内存地址写入数据
void iowrite8(u8 value, void *addr)
void iowrite16(u16 value, void *addr)
void iowrite32(u32 value, void *addr)
一般常用的是32位内存存取接口。
接下来就改进一下函数,其实实质没有改变,上面的函数是根据对应的平台体系结构编写的,这样可以提高驱动的移植性。
/*5th_mm_2/1st/test.c*/ #include #include #include #include volatile unsigned long virt, phys; volatile unsigned long *GPECON, *GPEDAT, *GPEUP; unsigned long reg; void led_device_init(void) { phys = 0x56000000; virt = (unsigned long)ioremap(phys, SZ_16); //这里只是想介绍一下,在asm/sizes.h中有一下 //定义好用来表示内存大小的宏,这里其实我只 GPECON = (unsigned long *)(virt + 0x40); //需要12个字节,并不需要16个字节。 GPEDAT = (unsigned long *)(virt + 0x44); GPEUP = (unsigned long *)(virt + 0x48); } void led_configure(void) { //*GPECON &= ~(3 << 24); //*GPECON |= (1 << 24); reg = ioread32(GPECON); reg &= ~(3 << 24); reg |= (1 << 24); iowrite32(reg, GPECON); //*GPEUP |= (1 << 12); reg = ioread32(GPEUP); reg &= ~(3 << 12); iowrite32(reg, GPEUP); } void led_on(void) { //*GPEDAT &= ~(1 << 12); reg = ioread32(GPEDAT); reg &= ~(1 << 12); iowrite32(reg, GPEDAT); } void led_off(void) { //*GPEDAT |= (1 << 12); reg = ioread32(GPEDAT); reg |= (1 << 12); iowrite32(reg, GPEDAT); } static int __init test_init(void) //模块初始化函数 { led_device_init(); led_configure(); led_on(); printk("hello led!\n"); return 0; } static void __exit test_exit(void) //模块卸载函数 { led_off(); iounmap((void *)virt); printk("bye\n"); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("xoao bai"); MODULE_VERSION("v0.1");
会发现发现,程序将原来直接访问内存的一句话变成了3句话,其他都没有改变。
我就不验证了,效果其实是一样的。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、再改进一下程序:
在使用IO内存映射操作之前,其实还可以添加一个步骤:分配内存区域。
#include <ioport.h>
struct resource *request_mem_region(unsigned long start, unsinged long len, char *name)
该函数从start开始分配len字节长的内存空间。如果成功,返回一个结构体指针,但这结构体我们没必要用,如果失败返回NULL。成功后,可以在.proc/iomem查看到name的信息。
其实调用request_mem_region()不是必须的,但是建议使用。该函数的任务是检查申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其他驱动想再申请该资源时就会失败。
如果不再使用,需要调用释放函数:
void release_mem_region(unsigned
long start, unsigned long len)
现在把这两个函数加上去:
/*5th_mm_2/3rd/test.c*/ 1 #include 2 #include 3 4 #include 5 #include 6 7 volatile unsigned long virt, phys; 8 volatile unsigned long *GPECON, *GPEDAT, *GPEUP; 9 unsigned long reg; 10 struct resource *led_resource; 11 12 void led_device_init(void) 13 { 14 phys = 0x56000000; 15 virt = (unsigned long)ioremap(phys, 0x0c); 16 17 GPECON = (unsigned long *)(virt + 0x40); 18 GPEDAT = (unsigned long *)(virt + 0x44); 19 GPEUP = (unsigned long *)(virt + 0x48); 20 } 21 22 void led_configure(void) 23 { 24 reg = ioread32(GPECON); 25 reg &= ~(3 << 24); 26 reg |= (1 << 24); 27 iowrite32(reg, GPECON); 28 29 reg = ioread32(GPEUP); 30 reg &= ~(3 << 12); 31 iowrite32(reg, GPEUP); 32 } 33 34 void led_on(void) 35 { 36 reg = ioread32(GPEDAT); 37 reg &= ~(1 << 12); 38 iowrite32(reg, GPEDAT); 39 } 40 41 void led_off(void) 42 { 43 reg = ioread32(GPEDAT); 44 reg |= (1 << 12); 45 iowrite32(reg, GPEDAT); 46 } 47 48 static int __init test_init(void) //模块初始化函数 49 { 50 led_device_init(); 51 52 led_resource = request_mem_region(phys, 0x0c, "LED_MEM"); 53 if(NULL == led_resource){ 54 printk("request mem error!\n"); 55 return - ENOMEM; 56 } 57 58 led_configure(); 59 led_on(); 60 printk("hello led!\n"); 61 return 0; 62 } 63 64 static void __exit test_exit(void) //模块卸载函数 65 { 66 if(NULL != led_resource){ 67 led_off(); 68 iounmap((void *)virt); 69 release_mem_region(phys, 0x0c); 70 } 71 printk("bye\n"); 72 } 73 74 module_init(test_init); 75 module_exit(test_exit); 76 77 MODULE_LICENSE("GPL"); 78 MODULE_AUTHOR("xoao bai"); 79 MODULE_VERSION("v0.1");
写完就得验证一下:
[root: 3rd]# insmod test.ko
hello led! //灯亮了
[root: 3rd]# cat /proc/iomem
19000300-19000310 : cs8900
19000300-19000310 : cs8900
。。。。
56000000-5600000b : LED_MEM //看到了
57000000-570000ff : s3c2410-rtc
57000000-570000ff : s3c2410-rtc
5a000000-5a0fffff : s3c2440-sdi
[root: 3rd]# rmmod test
bye //灯灭了
[root: 3rd]# cat /proc/iomem //LED_MEM不见了
19000300-19000310 : cs8900
19000300-19000310 : cs8900
。。。。。。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、总结
今天介绍的内容不多,其实就几个函数,下面重温一下使用IO内存的步骤:
其中第一步和最后一步可以不做。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
源代码:5th_mm_2.rar
相关文章推荐
- linux内存操作--ioremap和mmap学习笔记
- linux内存操作--ioremap和mmap学习笔记
- linux内存操作--ioremap和mmap学习笔记
- linux内存操作--ioremap和mmap学习笔记
- linux内存操作--ioremap和mmap学习笔记
- linux内存操作--ioremap和mmap学习笔记
- arm-linux内存管理学习笔记(1)-内存页表的硬件原理
- linux UART 驱动学习笔记——用户如何操作硬件
- arm-linux内存管理学习笔记(1)-内存页表的硬件原理
- Java IO学习笔记(五):内存操作流
- linux内存操作--ioremap和mmap学习笔记
- 【Linux开发】linux设备驱动归纳总结(五):2.操作硬件——IO内存
- Linux 学习笔记( LINUX运行的硬件基础1)
- 【学习笔记】Linux下磁盘IO性能评估
- 【学习笔记】Linux平台的文件,目录及操作
- Linux学习笔记二、权限操作,ls,chgrp,chown,chmod,cp,mkdir,touch,rm,su,exit,cat,.,..,uname -r,lsb_release -a
- linux学习笔记-读《Linux设备驱动开发详解》~第二章 驱动设计的硬件基础
- C++学习笔记:IO操作
- Linux学习笔记_常用操作_1
- 我的Linux学习笔记1(文件操作常用命令)