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

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

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

在之前章节的驱动,都没有对硬件进行操作,接写来将从我之前学的裸板驱动开始,讲解在linux系统下如何访问硬件。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

一、IO端口与IO内存

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获得对应的虚拟地址

13

//0x0c表示只要12字节的大小

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

{

66if(NULL

!= led_resource){

67

led_off();

68

iounmap((void *)virt);

69release_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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值