zoukankan      html  css  js  c++  java
  • inux设备驱动归纳总结(五):2.操作硬件——IO内存【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-80627.html

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

    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

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

    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    一、IO端口与IO内存

    介绍之前可以看看以下的博客:http://blogold.chinaunix.net/u2/66435/showart_2137870.html

    x86体系和ARM体系的寻址方式是有差别的:

    x86下,为了能够满足CPU高速地运行,内存与CPU之间通过北桥相连并通过地址方式访问,而外设通过南桥与CPU相连并通过端口访问。

    ARM下也实现了类似的操作,通过两条不同的总线(AHB BUSAPB 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); //2425位清零

    *GPECON |= (1 << 24); //2425位分别赋值为10

    可以看到,操作寄存器其实就是拿个地址出来进行操作。其实在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! ");

    43 return 0;

    44 }

    45

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

    47 {

    48 led_off();

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

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

    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! ");

    58 return 0;

    59 }

    60

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

    62 {

    63 led_off();

    64 iounmap((void *)virt);

    65 printk("bye ");

    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! ");

    55 return - ENOMEM;

    56 }

    57

    58 led_configure();

    59 led_on();

    60 printk("hello led! ");

    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 ");

    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

  • 相关阅读:
    我的浏览器收藏夹分类
    我的浏览器收藏夹分类
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6248625.html
Copyright © 2011-2022 走看看