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

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

    源代码: 5th_mm_2.rar   

  • 相关阅读:
    开灯问题
    独木舟上的旅行
    剑指offer--从尾到头打印链表
    映芬视觉网页练习
    游标的使用
    数据库操作
    关系型数据库
    数据库基础知识
    TCPSocket系列二
    HTML5新标签与css3选择器
  • 原文地址:https://www.cnblogs.com/huty/p/8518581.html
Copyright © 2011-2022 走看看