Chinaunix首页 | 论坛 | 博客
  • 博客访问: 830913
  • 博文数量: 281
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2770
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:45
个人简介

邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: LINUX

2017-03-01 16:04:44

2.7 在驱动中使用中断

    2.7.1 申请和释放中断
        中断是一个处理器的稀缺资源,在系统中非常重要,通过中断能够及时高效的响应外部事件,提高系统的响应能力,
        增加系统吞吐量。
        在驱动中使用中断,其实比较简单,先申请中断号,并注册一个中断中断处理程序,在中断程序实现对外部事件的处理。
        
        1.申请中断
            通过 request_irq()可以申请中断号,并同时安装中断处理程序。 request_irq()在中声明,
            函数原型如下:
            static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
                                                        const char *name, void *dev);
            通常情况下,返回值为 0 表示申请并安装成功,负值表示出错。其中的参数简单介绍一下:
            (1)irq
                是要申请的硬件中断号。
            (2)handler
                是指实际中断处理程序的函数指针。 只要系统接收到中断, 系统调用这个函数。
            (3)flags
                设置与中断有关的一些选项。比较重要的有 SA_INTERRUPT,标明中断处理程序是快
                速处理程序( 设置 SA_INTERRUPT) 还是慢速处理程序( 不设置 SA_INTERRUPT)。快速
                处理程序被调用时屏蔽所有中断, 慢速处理程序不屏蔽。还有一个 SA_SHIRQ 属性,设置
                了以后运行多个设备共享中断,处理程序之间通过 dev 来进行区分。 如果中断由某个处理程
                序独占,则 dev 可以设置为 NULL。
            (4)*name
                传递给 request_irq 的字符串,用来在/proc/interrupts 显示中断的拥有者。使用 cat 命令查看。
            (5)*dev
                在中断共享时会用到。一般设置为这个设备的 device 结构本身或者 NULL。中断处理程
                序可以用 dev 找到相应的控制这个中断的设备。 在没有强制使用共享方式时, dev 可以被设
                置为 NULL,不过,将它指向设备的数据结构是比较好的方法。函数会将 dev 原封不动的传
                递给中断处理程序,因而可以很方便的用于向中断传递额外数据。
                
        2.释放中断
            中断时系统的稀缺资源,一旦不再使用,最好将中断号释放。释放中断通过 free_irq()实现。
            与 request_irq()一样, free_ire()函数在中声明,其函数原型如下:
                void free_irq(unsigned int irq, void *dev_id)
            第一个参数是将要释放的 irq 中断号。
            第二个参数标志设备。如果中断是该设备独占的,这里设置为 NULL;如果是共享中断,
            需要设置为中断处理程序指针。
            
        3.设置触发条件
            中断需要设置触发条件,如上升沿中断或者下降沿中断等。 Linux 提设置触发条件的接
            口函数为 irq_set_irq_type(),在定义,函数原型为:
            extern int irq_set_irq_type(unsigned int irq, unsigned int type);
            
            irq 为中断号, type 为终端类型。在中定义了如下中断类型:
                IRQ_TYPE_NONE = 0x00000000,
                IRQ_TYPE_EDGE_RISING = 0x00000001,
                IRQ_TYPE_EDGE_FALLING = 0x00000002,
                IRQ_TYPE_EDGE_BOTH = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
                IRQ_TYPE_LEVEL_HIGH = 0x00000004,
                IRQ_TYPE_LEVEL_LOW = 0x00000008,
                IRQ_TYPE_LEVEL_MASK = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
                IRQ_TYPE_SENSE_MASK = 0x0000000f,
                IRQ_TYPE_PROBE = 0x00000010,
            通常情况下,一般采用边沿触发和电平触发类型,具体如何设置,还需与实际硬件匹配。
                        
        4.使能和禁止中断
            如果没有在系统中使能中断,就算设置了触发条件,即使满足了触发条件也是不会产生中断的。
            Linux 下使能中断的函数为 enable_irq(),在中定义,函数原型如下:
                extern void enable_irq(unsigned int irq);
                irq 为需要使能的中断号。
                
        5.禁止中断
            如果一个中断使用完毕不再使用,可以将该中断禁止。禁止中断的函数为 disable_irq(),
            在中定义,函数原型如下:
                extern void disable_irq(unsigned int irq);
                irq 为要禁止的中断号。
                
    2.7.2 中断处理程序编写
        中断处理程序返回值 irqreturn_t,接受两个参数:中断号 irq 和 dev_id, dev_id 就是request_irq 时传递
        给系统的参数 dev:
            typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
            
        中断处理完毕,通常返回 IRQ_HANDLED。通常,一个中断处理程序如程序清单 2.24 所示。
            程序清单 2.24 中断处理程序
            static irqreturn_t xxxx_interrupt(int irq, void *dev_id)
            {
                ...中断处理代码
                return IRQ_HANDLED; /* 中断已经处理完毕 */
            }
                
        至于中断处理程序要做些什么,应当做些什么,取决于具体系统的具体应用,没有统一的要求,但是中断处理
        程序应当尽量短,处理只能在中断上下文中处理的事情,能放到进程上下文的工作都不要放到中断上下文中处理。
        
    例程分析。
        

2.8 混杂设备驱动编程

    2.8.1 混杂设备和驱动
        回想一下 2.6 内核字符驱动编程的基本过程:
        (1) 首先需要通过 alloc_chrdev_region()获得设备编号;
        (2) 然后需要通过 cdev_alloc()申请一个 cdev 结构;
        (3) 接着需要通过 cdev_init()对申请到的 cdev 进行初始化;
        (4) 最后才能将申请到的 cdev 通过 cdev_add()往系统添加。
        
        这样编写驱动稍微显得有点繁琐,能否简化驱动编写的初始化过程呢? 2.6 内核在此方
        面作了很大努力,为驱动初始化提供了更加简便的方法。
        
        2.6 内核的驱动按照各类设备的特性,在 cdev 基础上进行了进一步封装,增加了各类设
        备的特性功能管理,抽象出了多子系统,如 input、 usb、 scsi、 sound 和 framebuffer 等。
        于子系统编程,子系统能够完成与 sysfs 的交互,直接生成设备节点,简化了驱动编程。
        
        混杂设备( misc decive),是一些无法按照特定子系统的特性进行抽象的一些设备,在
        内核中用 miscdevice 来描述。 所有的混杂设备被用同一个主设备号 MISC_MAJOR(10),每
        个设备只能选择自己的次设备号。如果希望为某个设备单独分配主设备号,或者一个驱动想
        驱动多个设备,那这份驱动就不能通过混杂设备驱动来实现。
        
        描述 misc 设备的结构体 miscdevice 在中定义,如程序清单 2.26 所示。
            程序清单 2.26 miscdevice 结构
            struct miscdevice {
                int minor;
                const char *name;
                const struct file_operations *fops;
                struct list_head list;
                struct device *parent;
                struct device *this_device;
            };
        从描述混杂设备的 miscdevice 结构也可以看到,不同混杂设备只有次设备号不同。编写
        混杂设备驱动,通常只需实现次设备号 minor、设备名称 name 和操作方法 fops 的定义即可。
        
        为混杂设备定义一个 miscdevice 结构并进行初始化后,就可以通过注册函数
        misc_register()完成设备注册, misc_register()函数原型如下:
            int misc_register(struct miscdevice * misc);
        混杂设备的注销函数如下:
            int misc_deregister(struct miscdevice *misc);
        
    2.8.2 混杂设备驱动框架
        以一个空驱动为例来实现混杂设备编程,实际上也是混杂设备的驱动框架,代码如程序
        清单 2.27 所示。
            程序清单 2.27 混杂设备驱动框架

点击(此处)折叠或打开

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/fs.h>
  4. #include <linux/miscdevice.h>

  5. #define DEVICE_NAME "char_misc"

  6. static int char_misc_open(struct inode *inode, struct file *file )
  7. {
  8.     try_module_get(THIS_MODULE);
  9.     printk(KERN_INFO DEVICE_NAME "opened!\n");
  10.     return 0;
  11. }

  12. static int char_misc_release(struct inode *inode, struct file *file )
  13. {
  14.     printk(KERN_INFO DEVICE_NAME "closed!\n");
  15.     module_put(THIS_MODULE);
  16.     return 0;
  17. }

  18. static ssize_t char_misc_read(struct file *file, char *buf,size_t count, loff_t *f_pos)
  19. {
  20.     printk(KERN_INFO DEVICE_NAME "read method!\n");
  21.     return count;
  22. }

  23. static ssize_t char_misc_write(struct file *file, const char *buf, size_t count, loff_t *f_pos)
  24. {
  25.     printk(KERN_INFO DEVICE_NAME "write method!\n");
  26.     return count;
  27. }

  28. static int char_misc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
  29. {
  30.     printk(KERN_INFO DEVICE_NAME "ioctl method!\n");
  31.     return 0;
  32. }

  33. struct file_operations char_misc_fops = {
  34.     .owner = THIS_MODULE,
  35.     .read = char_misc_read,
  36.     .write = char_misc_write,
  37.     .open = char_misc_open,
  38.     .release = char_misc_release,
  39.     .ioctl = char_misc_ioctl
  40. };

  41. /* misc 结构体定义和初始化 */
  42. static struct miscdevice char_misc = {
  43.     .minor = MISC_DYNAMIC_MINOR,
  44.     .name = DEVICE_NAME,
  45.     .fops = &char_misc_fops,
  46. };

  47. static int __init char_misc_init(void)
  48. {
  49.     int ret;

  50.     ret = misc_register(&char_misc); /* 注册 misc 设备 */
  51.     if (ret < 0) {
  52.         printk(KERN_ERR "misc_register error!\n");
  53.         return -1;
  54.     }

  55.     return 0;
  56. }

  57. static void __exit char_misc_exit(void)
  58. {
  59.     misc_deregister(&char_misc); /* 卸载 misc 设备 */
  60. }

  61. module_init(char_misc_init);
  62. module_exit(char_misc_exit);

  63. MODULE_LICENSE("GPL")
编译后得到 char_misc.ko 模块,插入内核,可以看到/dev/char_misc 文件,查看详细信息:
            # ls /dev/char_misc -l
            crw------- 1 root root 10, 55 2011-01-22 09:39 /dev/char_misc
            
        主设备号 10,次设备号为 55。
        查看一下 sysfs 中的 dev 文件和 uevent 文件:
            # cat /sys/class/misc/char_misc/dev
            10:55
            # cat /sys/class/misc/char_misc/uevent
            MAJOR=10
            MINOR=55
            DEVNAME=char_misc
        

2.9 I/O 内存访问

    先看一个在无操作系统的情况下,用 C 语言访问片上寄存器的范例,这是访问 S3C2440
    UART1 的 FIFO 控制寄存器的示例,先定义 FIFO 控制寄存器为 UFCON1:
    #define UFCON1 (*(volatile unsigned *)0x50004008) /* UART 1 FIFO 控制寄存器 */
    给 UFCON1 赋值:
    UFCON1 = 0x00; // 禁止 FIFO 功能
    
    这个示例的使用条件是禁止 CPU 的 MMU。在禁止 MMU 的情况下,可以直接访问 CPU的物理地址。
    
    Linux 内核运行后,开启了 MMU,所以不能直接访问 CPU 的物理地址,也就是说,不
    能直接使用物理地址访问系统的 IO 内存。必须将物理地址转换为虚拟地址,内核通过虚拟
    地址来访问系统的 IO 内存。
    
    在内核中,物理地址到虚拟地址的转换,可以采用静态 I/O 映射,也可以采用动态 I/O
    映射。通常情况下, CPU 片上寄存器和内部总线都采用静态 I/O 映射,外部总线扩展 I/O 则
    通常采用动态 I/O 映射,也可以添加到系统中,采用静态 I/O 映射的方式。
    
    下面分别来看这两种方式的实现和使用方法。
    
    2.9.1 静态 I/O 映射
        静态 I/O 映射在内核中很常见,最常见的是处理器的片内寄存器的操作,如 GPIO、串
        口、定时器等等这些片上外设的寄存器,在内核中都通过静态 I/O 映射后被访问。一般的操
        作方式是这样的:
        __raw_writel(camdivn, S3C2440_CAMDIVN);
        submsk = __raw_readl(S3C2410_INTSUBMSK);
        
        1. io_p2v
            要实现静态 I/O 映射,首先需要定义物理地址到虚拟地址的转换规则,在内核中用宏定
            义 io_p2v(x)实现,将物理地址映射到 3G~4G 的内核地址空间。不同处理器的具体实现是不
            同的,但是前提是必须能将处理器的全部有效 I/O 空间映射到内核空间。对于一个 32 位的
            处理器,最大可访问地址空间为 2的32次方,即 4G,但是实际上绝大部分地址空间都是保留的,可
            访问的有效地址仅仅局限于有实际物理外设地址空间。
            PXA2xx 系列处理器的移植代码是这样实现的:
                /*
                * Intel PXA2xx internal register mapping:
                *
                * 0x40000000 - 0x41ffffff <--> 0xf2000000 - 0xf3ffffff
                * 0x44000000 - 0x45ffffff <--> 0xf4000000 - 0xf5ffffff
                * 0x48000000 - 0x49ffffff <--> 0xf6000000 - 0xf7ffffff
                * 0x4c000000 - 0x4dffffff <--> 0xf8000000 - 0xf9ffffff
                * 0x50000000 - 0x51ffffff <--> 0xfa000000 - 0xfbffffff
                * 0x54000000 - 0x55ffffff <--> 0xfc000000 - 0xfdffffff
                * 0x58000000 - 0x59ffffff <--> 0xfe000000 - 0xffffffff
                *
                * Note that not all PXA2xx chips implement all those addresses, and the
                * kernel only maps the minimum needed range of this mapping.
                */
                #define io_p2v(x) (0xf2000000 + ((x) & 0x01ffffff) + (((x) & 0x1c000000) >> 1))
            
            LPC32XX 系列处理器则是这实现的:
                /* Start of virtual addresses for IO devices */
                #define IO_BASE 0xF0000000
                #define io_p2v(x) (IO_BASE | (((x) & 0xff000000) >> 4) | ((x) & 0x000fffff))
                    
        2. 定义寄存器
            实现了 io_p2v(x)后,就可以很方便的实现对某个 I/O 内存的操作了。在很多处理器的实
            现代码中,都实现了一个定义寄存器的宏定义__REG(x):
            # define __REG(x) (*((volatile u32 *)io_p2v(x)))
            
            通过 REG(x)来实现一个寄存器的定义。例如, PXA27x 处理器的电源管理通用配置寄
            存器 PCFR 的物理地址是 0x40F0001C,在内核中被定义为:
            
            #define PCFR __REG(0x40F0001C) /* Power Manager General Configuration Register */
        
        3. 建立映射表
            到现在为止,尽管已经定义了 PCFR 寄存器,但是并不能被访问,因为还没有建立映射
            表。只有通过 iotable_init 建立了映射表的 I/O 空间才可被访问。将全部空间按照既定的映射
            关系建立一张 map_desc 映射描述表,如程序清单 2.28 所示。
            
                程序清单 2.28 map_desc 映射描述表
                    static struct map_desc standard_io_desc[] __initdata = {
                    
                        { /* Devs */
                            .virtual = 0xf2000000,
                            .pfn = __phys_to_pfn(0x40000000),
                            .length = 0x02000000,
                            .type = MT_DEVICE
                        },
                        
                        { /* Mem Ctl */
                            .virtual = 0xf6000000,
                            .pfn = __phys_to_pfn(0x48000000),
                            .length = 0x00200000,
                            .type = MT_DEVICE
                        },

                        { /* USB host */
                            .virtual = 0xf8000000,
                            .pfn = __phys_to_pfn(0x4c000000),
                            .length = 0x00100000,
                            .type = MT_DEVICE
                        },
                        
                        { /* Camera */
                            .virtual = 0xfa000000,
                            .pfn = __phys_to_pfn(0x50000000),
                            .length = 0x00100000,
                            .type = MT_DEVICE
                            
                        }, { /* IMem ctl */
                        
                            .virtual = 0xfe000000,
                            .pfn = __phys_to_pfn(0x58000000),
                            .length = 0x00100000,
                            .type = MT_DEVICE
                            
                        }, { /* UNCACHED_PHYS_0 */
                        
                            .virtual = 0xff000000,
                            .pfn = __phys_to_pfn(0x00000000),
                            .length = 0x00100000,
                            .type = MT_DEVICE
                        }
                    };
                然后通过 iotable_init 添加到内核:
                iotable_init(standard_io_desc, ARRAY_SIZE(standard_io_desc));
                
        4. 操作寄存器
            操作 PCFR 寄存器的示例:
            PCFR = 0x66;
            采用“ =”直接赋值这样的方法虽然可以操作,但是不推荐。内核中更多的是采用可移
            植性强的__raw_writel、 writel 系列函数:
            __raw_writel(sscr0, ssp->mmio_base + SSCR0);
    
    2.9.2 动态 I/O 映射
        动态 I/O 映射无需将物理 I/O 内存空间写入映射表,调用 ioremap 即可映射到虚拟地址
        空间。这种方式使用起来比较灵活,不过在外扩总线设备寄存器较多的情况下使用起来就不
        太方便了,一般建议在寄存器较少的情况下使用。操作完毕后,用 iounmap 取消 I/O 映射。
        
        ioremap 和 iounmap 相关定义在文件中:
            extern void __iomem *__arm_ioremap(unsigned long, size_t, unsigned int);
            #define ioremap(cookie,size) __arm_ioremap(cookie, size, MT_DEVICE)
            extern void __iounmap(volatile void __iomem *addr);
        
        看一个实际应用案例。在 CPU 的外部总线上外扩了一个 SJA1000 芯片,根据硬件电路
        得到操作 SJA1000 的锁存器和数据端口寄存器的地址分别是:
        #define SJA_ALE_PADR 0x20000008 /* SJA1000 锁存器端口物理地址 */
        #define SJA_DAT_PADR 0x20000004 /* SJA1000 数据端口物理地址 */
        在驱动中采用动态 I/O 映射,可以这样实现。先定义两个全局变量,用于存放映射后的
        虚拟地址:
            void __iomem *sja1000_ale;
            void __iomem *sja1000_dat;
            
        在初始化中用 ioremap 获得虚拟地址:
            sja1000_dat = ioremap(SJA_DAT_PADR, 4);
            sja1000_ale = ioremap(SJA_ALE_PADR, 4)
            
        这两个寄存器的最大有效位是 32 位( 4 字节),所以 ioremap 的第 2 个参数为 4。
        驱动中操作寄存器用 I/O 内存专用接口函数操作:
            writeb(0x09, sja1000_ale);
            writeb(1<
            writeb(0x09, sja1000_ale);
            
        当不再需要映射,可以取消映射:
            iounmap(sja1000_ale);
            iounmap(sja1000_dat);
            
    2.9.3 I/O 内存访问函数
        前面的代码中出现了__raw_writel 和 writeb 这样的函数,这些都是老式操作接口函数,
        目前在内核中还大量使用,但是在新代码中不建议再使用这些函数:
            __raw_readb/readb         从 I/O 端口读取 8 位数
            __raw_readw/readw         从 I/O 端口读取 16 位数
            __raw_readl/readl         从 I/O 端口读取 32 位数
            __raw_writeb/writeb     往 I/O 端口写入 8 位数
            __raw_writew/writew     往 I/O 端口写入 16 位数
            __raw_writel/writel     往 I/O 端口写入 32 位数
            
        在新代码中建议使用下列 I/O 操作函数:
            #define ioread8(p)         ({ unsigned int __v = __raw_readb(p); __v; })
            #define ioread16(p)     ({ unsigned int __v = le16_to_cpu((__force __le16)__raw_readw(p)); __v; })
            #define ioread32(p)     ({ unsigned int __v = le32_to_cpu((__force __le32)__raw_readl(p)); __v; })
            #define iowrite8(v,p)     __raw_writeb(v, p)
            #define iowrite16(v,p)     __raw_writew((__force __u16)cpu_to_le16(v), p)
            #define iowrite32(v,p)     __raw_writel((__force __u32)cpu_to_le32(v), p)
           
阅读(1040) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~