linux设备驱动归纳总结(五):2.操作硬件——IO内存

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

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*/

1 #include

2 #include

3

4 #include //上面介绍的函数需要包含该头文件

5

6 volatile unsigned long virt, phys; //用于存放虚拟地址和物理地址

7 volatile unsigned long *GPECON, *GPEDAT, *GPEUP; //用与存放三个寄存器的地址

8

9 void led_device_init(void)

10 {

11 phys = 0x56000000;
//1、指定物理地址

12 virt = (unsigned
long)ioremap(phys, 0x0c); //2、通过ioremap获得对应的虚拟地址

字节的大小

14 GPECON = (unsigned
long *)(virt + 0x40); //3、指定需要操作的三个寄存器的地址

15 GPEDAT = (unsigned
long *)(virt + 0x44);

16 GPEUP = (unsigned
long *)(virt + 0x48);

17 }

18

19 void led_configure(void) //led配置函数

20 {

21 *GPECON &= ~(3 << 24); //配置GPE12为输出端口

22 *GPECON |= (1 << 24); //先清零再赋值

23

24 *GPEUP |= (1 << 12); //禁止上拉电阻

25 }

26

27 void led_on(void) //点亮led

28 {

29 *GPEDAT &= ~(1 << 12);

30 }

31

32 void led_off(void) //灭掉led

33 {

34 *GPEDAT |= (1 << 12);

35 }

36

37 static int __init test_init(void) //模块初始化函数

38 {

39 led_device_init();

40 led_configure();

41 led_on();

42 printk("hello led!\n");

43 return 0;

44 }

45

46 static void __exit test_exit(void) //模块卸载函数

47 {

48 led_off();

49 iounmap((void
*)virt); //注意,即使取消了映射,通过之前的虚拟地址还能访问硬件,

50 printk("bye\n"); //但不是肯定可以,只要该虚拟地址被内核改动后就不行了。

51 }

52

53 module_init(test_init);

54 module_exit(test_exit);

55

56 MODULE_LICENSE("GPL");

57 MODULE_AUTHOR("xoao bai");

58 MODULE_VERSION("v0.1");

从上面的程序可以看到,除了获得地址有点和裸板驱动不一样外,寄存器的操作还是一样的。

接下来验证一下:

[root: 1st]# insmod test.ko

hello led! //这时候灯亮了

[root: 1st]# rmmod test

bye //灯灭了

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

三、改进函数,使用更好的内存访问接口

为了实现更好的移植性,上面的程序就有缺陷了。内核建议,尽量使用内核提供的内存访问接口:

#include

//从内存读取数据,返回值是指定内存地址中的值

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*/

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

11 void led_device_init(void)

12 {

13 phys = 0x56000000;

14 virt = (unsigned long)ioremap(phys, SZ_16);
//这里只是想介绍一下,在asm/sizes.h中有一下

15 //定义好用来表示内存大小的宏,这里其实我只

16 GPECON = (unsigned long *)(virt + 0x40); //需要12个字节,并不需要16个字节。

17 GPEDAT = (unsigned long *)(virt + 0x44);

18 GPEUP = (unsigned long *)(virt + 0x48);

19 }

20

21 void led_configure(void)

22 {

23 //*GPECON &= ~(3 << 24);

24 //*GPECON |= (1 << 24);

25 reg = ioread32(GPECON);

26 reg &= ~(3
<< 24);

27 reg |= (1
<< 24);

28 iowrite32(reg,
GPECON);

29

30 //*GPEUP |= (1 << 12);

31 reg = ioread32(GPEUP);

32 reg &= ~(3
<< 12);

33 iowrite32(reg,
GPEUP);

34 }

35

36 void led_on(void)

37 {

38 //*GPEDAT &= ~(1 << 12);

39 reg = ioread32(GPEDAT);

40 reg &= ~(1
<< 12);

41 iowrite32(reg,
GPEDAT);

42 }

43

44 void led_off(void)

45 {

46 //*GPEDAT |= (1 << 12);

47 reg = ioread32(GPEDAT);

48 reg |= (1
<< 12);

49 iowrite32(reg,
GPEDAT);

50 }

51

52 static int __init test_init(void) //模块初始化函数

53 {

54 led_device_init();

55 led_configure();

56 led_on();

57 printk("hello led!\n");

58 return 0;

59 }

60

61 static void __exit test_exit(void) //模块卸载函数

62 {

63 led_off();

64 iounmap((void *)virt);

65 printk("bye\n");

66 }

67

68 module_init(test_init);

69 module_exit(test_exit);

70

71 MODULE_LICENSE("GPL");

72 MODULE_AUTHOR("xoao bai");

73 MODULE_VERSION("v0.1");

会发现发现,程序将原来直接访问内存的一句话变成了3句话,其他都没有改变。

我就不验证了,效果其实是一样的。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

四、再改进一下程序:

在使用IO内存映射操作之前,其实还可以添加一个步骤:分配内存区域。

#include

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开发】linux设备驱动归纳总结(五):2.操作硬件——IO内存的更多相关文章

  1. inux设备驱动归纳总结(五):2.操作硬件——IO内存【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-80627.html inux设备驱动归纳总结(五):2.操作硬件——IO内存 xxxxxxxxxxxx ...

  2. linux设备驱动归纳总结

    前言: (总结已经基本写完,这段时间我会从新排版和修正.错误总会有的,望能指正!) 前段时间学习了嵌入式驱动,趁着没开始找工作,这段时间我会每天抽出时间来复习. 我的总结是根据学习时的笔记(李杨老师授 ...

  3. 【Linux】linux设备驱动归纳总结

    前言: (总结已经基本写完,这段时间我会从新排版和修正.错误总会有的,望能指正!) 前段时间学习了嵌入式驱动,趁着没开始找工作,这段时间我会每天抽出时间来复习. 我的总结是根据学习时的笔记(李杨老师授 ...

  4. 【Linux开发】linux设备驱动归纳总结(五):1.在内核空间分配内存

    linux设备驱动归纳总结(五):1.在内核空间分配内存 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  5. 【Linux开发】linux设备驱动归纳总结(五):3.操作硬件——IO静态映射

    linux设备驱动归纳总结(五):3.操作硬件--IO静态映射 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  6. 【Linux开发】linux设备驱动归纳总结(五):4.写个简单的LED驱动

    linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  7. linux设备驱动归纳总结(五):3.操作硬件——IO静态映射【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-83299.html linux设备驱动归纳总结(五):3.操作硬件——IO静态映射 xxxxxxxxx ...

  8. linux设备驱动归纳总结(五):1.在内核空间分配内存【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-79134.html linux设备驱动归纳总结(五):1.在内核空间分配内存 xxxxxxxxxxxx ...

  9. 【Linux开发】linux设备驱动归纳总结(六):1.中断的实现

    linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

随机推荐

  1. [学习笔记] 平衡树——Treap

    前置技能:平衡树前传:BST 终于学到我们喜闻乐见的平衡树啦! 所以我们这次讲的是平衡树中比较好写的\(Treap\). (以后会写splay的先埋个坑在这) 好了,进入正题. step 1 我们知道 ...

  2. 【C#-程序时间计数器】如何计算某些步骤/过程耗时多少?

    使用Stopwatch对象,TimeSpan对象 Stopwatch sw = new Stopwatch();//跑表,该类可以进行时间的统计 命名空间using System.Diagnostic ...

  3. sql 同一行中,不同结果在不同列显示

    对不同条件查询到的结果在同一行中展示 尝试写过使用","和inner join两种方式,感觉使用","的更加直观 select table1.guid, tab ...

  4. 设置PyCharm中选择文本的背景颜色和代码中和选中单词相同单词的背景颜色

    1 设置选中单词的背景颜色 首先进入File->Setting->Editor->Color Scheme后复制一个存在的颜色主题作为自定义的颜色主题(默认的颜色主题是无法修改的,也 ...

  5. c源码编译

    #include<stdio.h> #include<math.h> //程序中要调用求平方根函数sqrt int main() { double a,b,c,disc,x1, ...

  6. flask 第四篇 模板语言jinja2

    是时候开始写个前端了,Flask中默认的模板语言是Jinja2 现在我们来一步一步的学习一下 Jinja2 捎带手把 render_template 中留下的疑问解决一下 首先我们要在后端定义几个字符 ...

  7. C++入门经典-例7.5-对象的指针,函数指针调用类成员

    1:指向相应对象的指针就是对象的指针,它的生明方法与其他类型一样,如下: 类名 *p; 类的指针可以调用它所指向对象的成员.形式如下: p->类成员; 2:代码如下: (1)cat.h #inc ...

  8. LeetCode 36. 有效的数独(Valid Sudoku)

    题目描述 判断一个 9x9 的数独是否有效.只需要根据以下规则,验证已经填入的数字是否有效即可. 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗 ...

  9. Vue -3:单文件组件

    在很多 Vue 项目中,我们使用 Vue.component 来定义全局组件,紧接着用 new Vue({ el: '#container '}) 在每个页面内指定一个容器元素. 这种方式在很多中小规 ...

  10. [django]update_or_create使用场景

    update_or_create 作用是为了添加数据时防止重复. 先去查询, 如果没有在创建, 如果有则更新. update_or_create用法与密码存储实例 create方法 如果id是None ...