Linux设备驱动工程师之路——硬件访问及混杂设备LED驱动


一、重要知识点   

    1.I/O端口和I/O内存

寄存器和常规内存的区别:寄存器和RAM主要不同在于寄存器有边际效果,读取某个地址时可能导致该地址的内容发生变化,比如说很多设备的中断状态寄存器只要一读取,便自动清0。所以硬件寄存器不能直接访问,而要通过I/O端口和I/O内存两种方式访问。

在硬件层,I/O内存区域和I/O端口区域没有概念上的区别:它们都是通过向地址总线和控制总线发生电平信号进行访问,再通过数据总线读写数据。

       a.I/O端口:

       一些CPU制造厂在它们的芯片中使用单一的地址空间,而一些则为外设保留独立的地址空间,以便和内存区间分开来,这段独立与内存地址空间的地址空间就叫I/O端口。在/proc/ioport中可以看到。嵌入式处理器大部分不支持I/O端口。

       访问I/O端口有两步:1.申请I/O端口2.读写I/O端口

       申请I/O端口:

       structresource *request_region(unsigned long first, unsigned long n, const char *name)

       申请从first开始的n个端口。参数name为设备名称。如果分配成功则返回非NULL值。

       释放I/O端口:

       voidrelease_region(unsigned long start, unsigned long n)

       读写I/O端口:

读写一个字节

       unsignedinb(unsigned port)

       voidoutb(usigned char byte, unsigned port)

       读写二个字节

       unsignedinb(unsigned port)

       voidoutb(usigned short byte, unsigned port)

       读写四个字节

       unsignedinb(unsigned port)

       voidoutb(usigned long byte, unsigned port)

 

       b.I/O内存

       通过将外设寄存器映射到内存空间来进行访问叫做I/O内存,嵌入式大多只支持这种操作。

       访问I/O内存有三步:1.申请I/O内存区域2.映射I/O内存区域3.读写I/O内存

       申请I/O内存区域

       structresource *request_mem_region(unsigned long start, unsigned long len, char *name)

       申请访问从start(I/O物理地址)开始的len长度的I/O内存区域,如成功则返回非NULL值。在/proc/iomem中可可以查看到已经被申请的I/O内存区域。在后面的我写的驱动程序中并没用使用申请这一步,是因为我使用的GPIO内存区域已经被申请,如果在申请会导致失败。但是这样做法是不安全的做法,因为同一I/O内存区域域被多个模块使用。

       释放I/O内存区域

       voidrelease_mem_region(unsigned long start, unsigned long len)

       映射I/O内存区域

       void*ioremap(unsigned long phy_addr, unsigned long size)

       映射从物理地址phy_addr开始的size长度的的地址空间。返回可以访问I/O内存地址。由ioreamp返回的地址不应该直接引用,必须通过下面一些列的读写函数完成。

       读写操作I/O内存

       读1、2、4个字节:

       unsignedint read8(void *addr);

       unsignedint read16(void *addr)

       unsignedint read32(void *addr)

       写读1、2、4个字节:

       voidiowrite8(u8 value, void *addr)

       voidiowrite16(u8 value, void *addr)

       voidiowrite32(u8 value, void *addr)

 

    2.混杂设备驱动

         在Linux系统中,存在一类字符设备,他们共享一个主设备号(10),但此设备号不同,我们称这类设备为混杂设备(miscdeivce),查看/proc/device中可以看到一个名为misc的主设备号为10。所有的混杂设备形成一个链表,对设备访问时内存根据次设备号找到对应的miscdevice设备。

         Linux内核使用structmiscdeivce来描述一个混杂设备

         structmiscdevice{
         int minor;

         conststruct file_opreations *fops;

         structlist_head list;

         structdevice *parent;

         structdevice *this_device;     

}

使用时只需填写minor次设备号,*name设备名,*fops文件操作函数集即可。

Linux内核使用misc_register函数注册一个混杂设备。注册成功后,linux内核为自动为该设备创建设备文件。

intmisc_register(struct miscdevice *misc)

 

二、驱动代码

 

1.驱动代码一

这段LED驱动代码采用手动I/O内存映射的方式访问。没有使用申请内存区域函数,这样使不安全的。直接访问I/O内存地址而不是通过读写函数访问也是不安全。同时驱动代码包含硬件相关代码也是移植性不好的。写这段代码是为了帮助理解I/O内存映射的过程。

  1. #include <linux/miscdevice.h>  
  2. #include <linux/delay.h>  
  3. #include <asm/irq.h>  
  4. #include <mach/regs-gpio.h>  
  5. #include <mach/hardware.h>  
  6. #include <linux/kernel.h>  
  7. #include <linux/module.h>  
  8. #include <linux/init.h>  
  9. #include <linux/mm.h>  
  10. #include <linux/fs.h>  
  11. #include <linux/types.h>  
  12. #include <linux/delay.h>  
  13. #include <linux/moduleparam.h>  
  14. #include <linux/slab.h>  
  15. #include <linux/errno.h>  
  16. #include <linux/ioctl.h>  
  17. #include <linux/cdev.h>  
  18. #include <linux/string.h>  
  19. #include <linux/list.h>  
  20. #include <linux/pci.h>  
  21. #include <asm/uaccess.h>  
  22. #include <asm/atomic.h>  
  23. #include <asm/unistd.h>  
  24. #include <asm/io.h>  
  25. #include <asm/system.h>  
  26. #include <asm/uaccess.h>  
  27. #include <linux/ioport.h>  
  28.    
  29. volatile unsigned int long *gpb_con = NULL;  
  30. volatile unsigned int long *gpb_data = NULL;  
  31.    
  32. static int leds_ioctl(struct inode *inode, struct file *file,  
  33.                     unsigned int cmd, unsigned long arg)  
  34. {  
  35.    
  36.          if((cmd>1) |(arg>3))  
  37.                    return-EINVAL;  
  38.                     
  39.          switch(cmd)  
  40.          {  
  41.                    case 0:  
  42.                             *gpb_data&= ~(1<<arg+5);  
  43.                             break;  
  44.                    case 1:  
  45.                             *gpb_data|= (1<<arg+5);  
  46.                             break;  
  47.                              
  48.                    default:  
  49.                             return-EINVAL;  
  50.                              
  51.          }  
  52.           
  53.          return 0;  
  54. }  
  55.    
  56. static const struct file_operations leds_fops = {  
  57.          .owner = THIS_MODULE,  
  58.          .ioctl = leds_ioctl,  
  59. };  
  60.    
  61. static struct miscdevice misc = {  
  62.          .minor =MISC_DYNAMIC_MINOR,  
  63.          .name ="my_leds",  
  64.          .fops =&leds_fops,  
  65. };  
  66.    
  67. static int __init leds_init(void)  
  68. {  
  69.          int ret;  
  70.    
  71.          //注册混杂设备  
  72.          ret =misc_register(&misc);  
  73.           
  74.          //映射I/O内存  
  75.          gpb_con = (volatileunsigned long *)ioremap(0x56000010, 16); //0x56000010为GPIOB控制寄存器的物理地址  
  76.          gpb_data = gpb_con+1;  
  77.           
  78.          //配置LED对应的GPIOB 5、6、7、8口为输出并初始化为1,LED灭  
  79.          *gpb_con |=(1<<5*2)|(1<<6*2)|(1<<7*2)|(1<<8*2);  
  80.          *gpb_data |=(1<<5) | (1<<6) | (1<<7) | (1<<8);  
  81.           
  82.          printk("ledsinit.\n");  
  83.          return ret;  
  84. }  
  85.    
  86. static void leds_exit(void)  
  87. {  
  88.    
  89.          misc_deregister(&misc);          
  90.           
  91.          printk("leds_exit\n");  
  92. }  
  93.    
  94. module_init(leds_init);  
  95. module_exit(leds_exit);  
  96.    
  97. MODULE_AUTHOR("Y-Kee");  
  98. MODULE_LICENSE("GPL");  
  99.    
  • 1
  • 2
  • 下一页

相关内容