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

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

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: LINUX

2017-03-02 10:50:37

2.10 Linux 设备驱动模型

    设备驱动模型,对系统的所有设备和驱动进行了抽象,形成了复杂的设备树型结构,采
    用面向对象的方法,抽象出了 device 设备、 driver 驱动、 bus 总线和 class 类等概念,所有已
    经注册的设备和驱动都挂在总线上,总线来完成设备和驱动之间的匹配。总线、设备、驱动
    以及类之间的关系错综复杂,在 Linux 内核中通过 kobject、 kset 和 subsys 来进行管理,驱
    动编写可以忽略这些管理机制的具体实现。
    
    设备驱动模型的内部结构还在不停的发生改变,如 device、 driver、 bus 等数据结构在不
    同版本都有差异,但是基于设备驱动模型编程的结构基本还是统一的。
    
    2.10.1 设备
        在 Linux 设备驱动模型中,底层用 device 结构来描述所管理的设备。 device 结构在文件
        中定义,如程序清单 2.29 所示。
        
        程序清单 2.29 device 数据结构定义
            struct device {
                struct device             *parent;         /* 父设备 */
                struct device_private     *p;             /* 设备的私有数据 */
                struct kobject             kobj;             /* 设备的 kobject 对象 */
                const char                 *init_name;     /* 设备的初始名字 */
                struct device_type         *type;             /* 设备类型 */
                struct mutex             mutex;             /* 同步驱动的互斥信号量 */
                struct bus_type         *bus;             /* 设备所在的总线类型 */
                struct device_driver     *driver;         /* 管理该设备的驱动程序 */
                void                     *platform_data; /* 平台相关的数据 */
                struct dev_pm_info         power;             /* 电源管理 */
                
                #ifdef CONFIG_NUMA
                    int                 numa_node;         /* 设备接近的非一致性存储结构 */
                #endif
                
                u64 *                    dma_mask;             /* DMA 掩码 */
                u64                     coherent_dma_mask;     /* 设备一致性的 DMA 掩码 */
                struct device_dma_parameters *    dma_parms;     /* DMA 参数 */
                struct list_head                 dma_pools;     /* DMA 缓冲池 */
                struct dma_coherent_mem *        dma_mem;     /* DMA 一致性内存 */
                
                /*体系结构相关的附加项*/
                struct dev_archdata archdata; /* 体系结构相关的数据 */
                
                #ifdef CONFIG_OF
                    struct device_node *of_node;
                #endif
                
                dev_t                 devt; /* 创建 sysfs 的 dev 文件 */
                spinlock_t             devres_lock; /* 驱动的锁 */
                struct list_head     devres_head;
                struct klist_node     knode_class;
                struct class *        class; /* 设备所属的类 */
                const struct attribute_group **groups; /* 可选的组 */
                void (*release)(struct device *dev); /* 指向设备的 release 方法*/
            };
            
        注册和注销 device 的函数分别是 device_register()和 device_unregister(),函数原型如下:
            int __must_check device_register(struct device *dev);
            void device_unregister(struct device *dev);
           
        大多数不会在驱动中单独使用 device 结构,而是将 device 结构嵌入到更高层的描述结
        构中。例如,内核中用 spi_device 来描述 SPI 设备, spi_device 结构在文件中
        定义,是一个嵌入了 device 结构的更高层的结构体,如程序清单 2.30 所示。
        
            程序清单 2.30 spi_device 数据结构
                struct spi_device {
                    struct device dev; /* device 数据结构 */
                    struct spi_master *master;
                    u32 max_speed_hz;
                    u8 chip_select;
                    u8 mode;
                    u8 bits_per_word;
                    int irq;
                    void *controller_state;
                    void *controller_data;
                    char modalias[SPI_NAME_SIZE];
                };
                
        系统提供了 device_create()函数用于在 sysfs/classs 中创建 dev文件,以供用户空间使用。
        device_create()函数定义如下:
            struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata,
                                        const char *fmt, ...);
        说明:
            (1) cls 是指向将要被注册的 class 结构;
            (2) parent 是设备的父指针;
            (3) devt 是设备的设备编号;
            (4) drvdata 是被添加到设备回调的数据;
            (5) fmt 是设备的名字。
        
        与 device_create()函数相反的是 device_destroy()函数,用于销毁 sysfs/class 目录中的 dev
        文件。 device_destroy()函数原型如下:
            void device_destroy(struct class *cls, dev_t devt);
            
    2.10.2 驱动
        与设备相对应, Linux 设备驱动模型中,对管理的驱动也用 device_driver 结构来描述,
        在中定义,如程序清单 2.31 所示。
            
            程序清单 2.31 device_driver 结构定义
                struct device_driver {
                    const char *name; /* 驱动的名称 */
                    struct bus_type *bus; /* 驱动所在的总线 */
                    struct module *owner; /* 驱动的所属模块 */
                    const char *mod_name; /* 模块名称(静态编译的时候使用) */
                    bool suppress_bind_attrs; /* 通过 sysfs 禁止 bind 或者 unbind */
                    
                    #if defined(CONFIG_OF)
                        const struct of_device_id *of_match_table; /* 匹配设备的表 */
                    #endif
                    
                    int (*probe) (struct device *dev);         /* probe 探测方法 */
                    int (*remove) (struct device *dev);        /* remove 方法 */
                    void (*shutdown) (struct device *dev);     /* shutdown 方法 */
                    int (*suspend) (struct device *dev, pm_message_t state); /* suspend 方法 */
                    int (*resume) (struct device *dev);                      /* resume 方法 */
                    const struct attribute_group **groups;
                    const struct dev_pm_ops *pm; /* 电源管理 */
                    struct driver_private *p;    /* 驱动的私有数据 */
                };
                
        系统提供了 driver_register()和 driver_ungister()分别用于注册和注销 device_driver, 函数
        原型分别如下:
            int __must_check driver_register(struct device_driver *drv);
            void driver_unregister(struct device_driver *drv);    
           
        与 device 结构类似,在驱动中一般也不会单独使用 device_driver 结构,通常也是嵌入
        在更高层的描述结构中。还是以 SPI 为例,SPI 设备的驱动结构为 spi_driver,在
        文件中定义, 是一个内嵌了 device_driver 结构的更高层的结构体, 原型如程序清单 2.32 所
        示。
            程序清单 2.32 spi_driver 数据结构
                struct spi_driver {
                    const struct spi_device_id *id_table;
                    int (*probe)(struct spi_device *spi);
                    int (*remove)(struct spi_device *spi);
                    void (*shutdown)(struct spi_device *spi);
                    int (*suspend)(struct spi_device *spi, pm_message_t mesg);
                    int (*resume)(struct spi_device *spi);
                    struct device_driver driver; /* driver 数据结构 */
                };
            
    2.10.3 总线
        在设备驱动模型中,所有的设备都通过总线相连,总线既可以是实际的物理总线,也可
        以是内核虚拟的 platform 总线。驱动也挂在总线上,总线是设备和驱动之间的媒介,为它们
        提供服务。当设备插入系统,总线将在所注册的驱动中寻找匹配的驱动,当驱动插入系统中,
        总线也会在所注册的设备中寻找匹配的设备。
        在 Linux 设备驱动模型中,总线用 bus_type 结构来描述。bus_type 结构在
        中定义,如程序清单 2.33 所示。
        
            程序清单 2.33 bus_type 结构定义
            struct bus_type {
                const char *name; /* 总线名称 */
                struct bus_attribute *bus_attrs; /* 总线属性 */
                struct device_attribute *dev_attrs; /* 设备属性 */
                struct driver_attribute *drv_attrs; /* 驱动属性 */
                int (*match)(struct device *dev, struct device_driver *drv);         /* match 方法:匹配设备和驱动 */
                int (*uevent)(struct device *dev, struct kobj_uevent_env *env);     /* uevent 方法,支持热插拔 */
                int (*probe)(struct device *dev); /* probe 方法 */
                int (*remove)(struct device *dev); /* remove 方法 */
                void (*shutdown)(struct device *dev); /* shutdown 方法 *
                int (*suspend)(struct device *dev, pm_message_t state); /* suspend 方法 */
                int (*resume)(struct device *dev); /* resume 方法 */
                const struct dev_pm_ops *pm; /* 电源管理 */
                struct bus_type_private *p; /* 总线的私有数据结构 */
            };
        
        说明一下 bus 总线的 match 方法。当往总线添加一个新设备或者新驱动的时候, match
        方法会被调用,为设备或者驱动寻找匹配的驱动程序或者设备。
        
        注册和注销 bus总线的函数分别是 bus_register()和 bus_unregister(),函数原型分别如下:
            int __must_check bus_register(struct bus_type *bus);
            void bus_unregister(struct bus_type *bus);
        一般情况下,无需用户再往系统注册一个总线,因为目前 Linux 的设备驱动模型已经比
        较完善,几乎任何设备都可以套用既有的总线。
            
    
    2.10.4 类
        类是 Linux 设备驱动模型中的一个高层抽象,为用户空间提供设备的高层视图。如在驱
        动中 SCSI 磁盘和 ATA 磁盘,它们是不同的设备,但是从类的角度来看,它们都是磁盘,在
        用户空间无需关心底层设备和驱动的具体实现。
        
        在 sysfs 中,类一般都放在/sys/class/目录下,例外的是块设备放在/sys/block 目录下。
        类子系统中,可以向用户空间导出信息,用户空间可以通过这些信息与内核交互。最典型就
        是已经接触过的 udev, udev 是用户空间的程序,根据/sys/class 目录下的 dev 文件来创建设
        备节点。
        
        在 Linux 设备驱动模型中,类用 class 结构来描述,在中定义,如程序
        清单 2.34 所示。
            程序清单 2.34 class 结构定义
                struct class {
                    const char *name;                 /* 类的名称 */
                    struct module *owner;             /* 类的所属模块 */
                    struct class_attribute *class_attrs;     /* 类的属性 */
                    struct device_attribute *dev_attrs;      /* 设备属性 */
                    struct kobject *dev_kobj;                  /* 类的 kobject 对象 */
                    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
                    char *(*devnode)(struct device *dev, mode_t *mode);    
                    void (*class_release)(struct class *class);
                    void (*dev_release)(struct device *dev);
                    int (*suspend)(struct device *dev, pm_message_t state);
                    int (*resume)(struct device *dev);
                    const struct kobj_ns_type_operations *ns_type;
                    const void *(*namespace)(struct device *dev);
                    const struct dev_pm_ops *pm;
                    struct class_private *p;
                };
                
        注册或者注销一个类的函数分别是 class_register()和 class_unregister(), 函数原型分别如下:
            int __must_check __class_register( struct class *class, struct lock_class_key *key);
            #define class_register(class)                 \
            ({                                             \
                static struct lock_class_key __key;     \
                __class_register(class, &__key);         \
            })
            
            void class_unregister(struct class *class);
            
        调用 class_create()可以在 sysfs/class 目录下创建自定义的类,调用 class_destroy()函数则
        以销毁自定义类,函数原型分别如下:
            extern struct class * __must_check __class_create(struct module *owner,
                                                                const char *name, struct lock_class_key *key);
            #define class_create(owner, name)             \
            ({                                             \
                static struct lock_class_key __key;     \
                __class_create(owner, name, &__key);    \
            })
            extern void class_destroy(struct class *cls);
        
        

2.11 平台设备和驱动

    2.6 内核引入了 platform 机制,能够实现对设备所占用的资源进行统一管理。 Platform
    机制抽象出了 platform_device 和 platform_driver 两个核心概念,与此相关的还有一个重要概
    念就是资源 resource。
    
    2.11.1 资源
        1. 描述和类型
            资源 resource 是对设备所占用的硬件信息的抽象,目前包括 I/O、内存、 IRQ、 DMA、
            BUS 这 5 类。在内核中,用 resource 结构来对资源进行描述。 resource 结构在
            文件中定义,如程序清单 2.35 所示。
            
                程序清单 2.35 resource 数据结构
                struct resource {
                    resource_size_t start;     /* 资源在 CPU 上的物理起始地址 */
                    resource_size_t end;     /* 资源在 CPU 上的物理结束地址 */
                    const char *name;         /* 资源名称 */
                    unsigned long flags;     /* 资源的标志 */
                    struct resource *parent, *sibling, *child; /* 资源的父亲、兄弟和子资源 */
                };
                
            flags通常被用来表示资源的类型,可用的资源类型有 IO、MEM、IRQ等,在
            中定义,各资源类型和定义如下:
            #define IORESOURCE_TYPE_BITS 0x00001f00 /* 资源类型 */
            #define IORESOURCE_IO 0x00000100
            #define IORESOURCE_MEM 0x00000200
            #define IORESOURCE_IRQ 0x00000400
            #define IORESOURCE_DMA 0x00000800
            #define IORESOURCE_BUS 0x00001000
            
        2. 资源定义
            一个设备的资源定义可以同时包含所占用的多种资源。例如,对于一个既占用内存资源,
            又占用 IRQ 中断资源的设备,其资源定义可以如程序清单 2.36 所示。
            
                程序清单 2.36 资源定义实例
                #define EMC_CS2_BASE 0x11000000 /* 总线片选地址 */
                static struct resource ecm_ax88796b_resource[] = {
                    [0] = {                             /* 内存资源 */
                        .start = EMC_CS2_BASE,          /* 起始地址 */
                        .end = EMC_CS2_BASE + 0xFFF,    /* 结束地址 */
                        .flags = IORESOURCE_MEM,        /* 资源类型: IORESOURCE_MEM */
                    },
                    
                    [1] = {                             /* IRQ 资源 */
                        .start = IRQ_GPIO_04,
                        .end = IRQ_GPIO_04,
                        .flags = IORESOURCE_IRQ,         /* 资源类型: IORESOURCE_IRQ */
                    }
                };
        
        3. 资源获取
            定义了一个设备的资源后, 需通过特定函数获取才能使用, 这些函数在
            文件中定义,一共有 3 个函数,分别是:
                platform_get_resource()、
                platform_get_resource_byname()、
                platform_get_irq()和 platform_get_irq_byname()。
            
            platform_get_resource()函数用于获取指定类型的资源, 函数原型如下:
                struct resource *platform_get_resource(struct platform_device *dev,
                                       unsigned int type, unsigned int num);
            dev 指向包含资源定义的 platform_device 结构; type 表示将要获取的资源类型; num 表
            示获取资源的数量。返回值为 0 表示获取失败,成功返回申请的资源地址。

            platform_get_resource_byname()则是根据平台设备的设备名称获取指定类型的资源, 函
            数原型如下:
            struct resource *platform_get_resource_byname(struct platform_device *dev,
                                      unsigned int type,
                                      const char *name);
                                      
            另外,内核还单独提供了获取 IRQ 的接口函数 platform_get_irq(), 实际上就是
            platform_get _resource()获取 IORESOURCE_IRQ 的封装,方便用户使用。
            原型如下:
                int platform_get_irq(struct platform_device *dev, unsigned int num);
                
            获取设备的私有数据,可通过宏 platform_get_drvdata 实现:
                #define platform_get_drvdata(_dev) dev_get_drvdata(&(_dev)->dev)
            实际上是获取_dev->dev->p->driver_data,可参考程序清单 2.29 device 结构的定义。
            
            platform_get_irq_byname()则可根据平台设备名称获取设备的IRQ资源,函数原型如下:
                extern int platform_get_irq_byname(struct platform_device *, const char *);

                
            在驱动编写中如何实际使用这些函数,下面给出一个代码片段,如程序清单 2.37 所示。
                程序清单 2.37 平台资源获取和使用范例
                    if (!mem){
                        res = platform_get_resource (pdev, IORESOURCE_MEM, 0); /* 获取内存资源 */
                        if (!res) {
                            printk("%s: get no resource !\n", DRV_NAME);
                            return -ENODEV;
                        }
                        mem = res->start;
                    }
                    if(!irq)
                        irq = platform_get_irq(pdev, 0);                             /* 获取 IRQ 资源 */
                        
                    if (!request_mem_region (mem, AX88796B_IO_EXTENT, "ax88796b")) { /* 申请 IO 内存 */
                        PRINTK (ERROR_MSG, PFX " request_mem_region fail !");
                        return -EBUSY;
                    }
                    
                    /* addr: 表示从物理地址映射以后得到的虚拟地址
                     * mem:  表示内存的物理起始地址
                     * AX88796B_IO_EXTENT: 表示申请内存的大小
                    **/
                    addr = ioremap_nocache(mem, AX88796B_IO_EXTENT); /* 内存映射 ioremap */
                    if (!addr) {
                        ret = -EBUSY;
                        goto release_region;
                    }
                
            该范例演示了内存资源和 IRQ 资源的获取和使用。特别说明一下内存资源,在定义内
            存资源的时候,通常使用内存的物理地址,而在驱动中须转换为虚拟地址使用,所以需要进
            行 ioremap 操作,而在 ioremap 之前又需要先申请 IO 内存,所以在代码中看到的是先使用
            request_mem_region()函数申请 IO 内存,然后再通过 ioremap_nocache()函数完成内存映射。
            
    2.11.2 平台设备
        并不是任何设备都可以抽象成为 platform_device。 platform_device 是在系统中以独立实
        体出现的设备,包括传统的基于端口的设备、主机到外设的总线以及大部分片内集成的控制
        器等。这些设备的一个共同点是 CPU 都可以通过总线直接对它们进行访问。在极少数情况
        下,一个 platform_device 可能会经过一小段其它总线,但是它的寄存器依然可以被 CPU 直
        接访问。
        
        1. platform_device
            用于描述平台设备的数据结构是 platform_device,在文件中定
            义,如程序清单 2.38 所示。
            
                程序清单 2.38 platform_device 数据结构
                struct platform_device {
                    const char * name;             /* 设备名称 */
                    int id;                     /* 设备 ID */
                    struct device dev;             /* 设备的 device 数据结构 */
                    u32 num_resources;             /* 资源的个数 */
                    struct resource * resource; /* 设备的资源 */
                    const struct platform_device_id *id_entry; /* 设备 ID 入口 */
                    /*体系结构相关的附加项*/
                    struct pdev_archdata archdata; /* 体系结构相关的数据 */
                };
            name 是设备的名称, 用于与 platform_driver 进行匹配绑定, resourse 用于描述设备的资
            源如地址、 IRQ 等。
        
        2. 分配 platform_device 结构
            注册一个 platform_device 之前,必须先定义或者通过 platform_device_alloc()函数为设备
            分配一个 platform_device 结构, platform_device_alloc()函数原型如下:
                struct platform_device *platform_device_alloc(const char *name, int id);
            
        3. 添加资源
            通过 platform_device_alloc()申请得到的 platform_device 结构,必须添加相关资源和私有
            数据才能进行注册。添加资源的函数是  platform_device_add_resources:
            int platform_device_add_resources(struct platform_device *pdev, const struct resource *res,
                                                unsigned int num);
            
            添加私有数据的函数是 platform_device_add_data:
            int platform_device_add_data(struct platform_device *pdev, const void *data, size_t size);
        
        4. 注册和注销 platform_device
            申请到 platform_device 结构后,可以通过 platform_device_register()往系统注册, platform
            _device_register()函数原型如下:
                int platform_device_register(struct platform_device *pdev);
            platform_device_register()只能往系统注册一个 platform_device,如果有多个platform_device,
            可以用 platform_add_devices()一次性完成注册, platform_add_devices()函数原型如下:
                int platform_add_devices(struct platform_device **devs, int num);
                
            通过 platform_device_unregister()可以注销系统的 platform_device, platform_device
            _unregister()函数原型如下:
                void platform_device_unregister(struct platform_device *pdev);

            如果已经定义了设备的资源和私有数据,可以用 platform_device_register_resndata()一次
            性完成数据结构申请、资源和私有数据添加以及设备注册:
            struct platform_device *__init_or_module platform_device_register_resndata(
                                                            struct device *parent,
                                                            const char *name, int id,
                                                            const struct resource *res, unsigned int num,
                                                            const void *data, size_t size
                                                        );

            platform_device_register_simple()函数是 platform_device_register_resndata()函数的简化版,
            可以一步实现分配和注册设备操作, platform_device_register_simple()函数原型如下:
                static inline struct platform_device *platform_device_register_simple(
                                                    const char *name, int id,
                                                    const struct resource *res, unsigned int num);
            实际上就是: platform_device_register_resndata(NULL, name, id, res, num, NULL, 0)。

            在文件还提供了更多的 platform_device 相关的操作接口函数,
            在有必要的时候可以查看并使用。
        
        5. 向系统添加平台设备的流程
            向系统添加一个平台设备,可以通过两种方式完成:
            方式 1:定义资源,然后定义 platform_device 结构并初始化;最后注册;
            方式 2:定义资源,然后动态分配一个 platform_device 结构,接着往结构添加资源信息,最后注册。
            
    
    2.11.3 平台驱动
    
        1. platform_driver
            platform_driver 是 device_driver 的封装,提供了驱动的 probe 和 remove 方法,也提供了
            与电源管理相关的 shutdown 和 suspend 等方法,如程序清单 2.39 所示。
                程序清单 2.39 platform_driver 数据结构
                struct platform_driver {
                    int (*probe)(struct platform_device *);         /* probe 方法 */
                    int (*remove)(struct platform_device *);         /* remove 方法 */
                    void (*shutdown)(struct platform_device *);     /* shutdown 方法 */
                    int (*suspend)(struct platform_device *, pm_message_t state); /* suspend 方法 */
                    int (*resume)(struct platform_device *);        /* resume 方法 */
                    struct device_driver driver;                     /* 设备驱动 */
                    const struct platform_device_id *id_table;         /* 设备的 ID 表 */
                };
                
            Platform_driver 有 5 个方法:
            (1)probe成员指向驱动的探测代码,在 probe方法中获取设备的资源信息并进行处理,
            如进行物理地址到虚拟地址的 remap,或者申请中断等操作,与模块的初始化代码不同;
            (2)remove 成员指向驱动的移除代码,进行一些资源释放和清理工作,如取消物理地
            址与虚拟地址的映射关系,或者释放中断号等,与模块的退出代码不同;
            (3)shutdown 成员指向设备被关闭时的实现代码;
            (4)suspend 成员执行设备挂起时候的处理代码;
            (5)resume 成员执行设备从挂起中恢复的处理代码。
            
        2. 注册和注销 platform_driver
            注册和注销 platform_driver 的函数分别是 platform_driver_register()和 platform_driver_unregister(),
            函数原型分别如下:
                int platform_driver_register(struct platform_driver *drv);
                void platform_driver_unregister(struct platform_driver *drv);
                
            另外, platform_driver_probe()函数也能完成设备注册, 原型如下:
                int platform_driver_probe(struct platform_driver *driver,
                                            int (*probe)(struct platform_device *));
            如果已经明确知道一个设备不支持热插拔,可以在__init 段代码中调用 platform_driver_probe()函数,
            以减少运行时对内存的消耗。 如程序清单 2.40 所示代码是中的范例,可以参考。
                程序清单 2.40 使用 platform_driver_probe 的范例
                int __init init_module(void)
                {
                    int retval;
                    ne_add_devices();
                    retval = platform_driver_probe(&ne_driver, ne_drv_probe);
                    if (retval) {
                        if (io[0] == 0)
                            printk(KERN_NOTICE "ne.c: You must supply \"io=0xNNN\""
                            " value(s) for ISA cards.\n");
                            
                        ne_loop_rm_unreg(1);
                        return retval;
                    }
                    /* Unregister unused platform_devices. */
                    ne_loop_rm_unreg(0);
                    return retval;
                }
            注意:在设备驱动模型中已经提到,bus 根据驱动和设备的名称寻找匹配的设备和驱动,
            因此注册驱动必须保证 platform_driver 的 driver.name 字段必须和 platform_device 的 name 相
            同, 否则无法将驱动和设备进行绑定而注册失败。
            
    2.11.4 平台驱动与普通驱动的差异
        基于 platform 机制编写的驱动与普通字符驱动,只是在框架上有差别,驱动的实际内容
        是差不多相同的,如果有必要的话,一个普通驱动很容易就可被改写为 platform 驱动。图 2.8
        是普通字符驱动与平台驱动的框架对照。    
       
        可以看到,将一个普通字符驱动改写为平台驱动,驱动各方法方法的实现以及 fops 定
        义都是一样的,不同之处是框架结构发生了变化,资源的申请和释放等代码的位置发生了变
        化:
        (1)资源申请、设备注册等从普通字符驱动的模块初始化部分移到了平台驱动的 probe
        方法,对于特殊情况,也可以继续放在模块初始化代码中;
        (2)设备注销、资源释放等从普通字符驱动的模块退出代码移到了平台驱动的 remove
        方法。
        
        平台驱动还增加了资源定义和初始化、平台设备和驱动的定义和初始化,以及驱动必要
        方法的实现等。
        平台驱动的模块初始化代码可以很简单,几乎只需简单的调用平台设备注册和注销的接
        口函数。
        
    2.11.5 平台驱动范例
        前面已经提到过,采用 platform 方式编程,能够很好的将资源与驱动分开,便于程序移
        植和驱动复用。本节继续以 LED 为例,用 platform 方式重新实现 LED 驱动。
        为了演示资源和驱动分离,本例将驱动分为如下两个模块:
        (1)led_platform 模块:实现资源定义和 platform 设备注册;
        (2)led_drv 模块:通过 platform 方式实现 LED 驱动。
        在使用的时候,须依次插入 led_platform 和 led_drv,才能生成设备节点。
        1. led_platform 模块
            led_platform 模块只有 led_platform.c 一个文件。该文件实现了 LED 资源定义,并向系
            统注册了一个 led platform 设备,代码如程序清单 2.41 所示。
        
            程序清单 2.41 led_platform.c 参考代码

点击(此处)折叠或打开

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/device.h>
  4. #include <linux/platform_device.h>

  5. #define GPIO_LED_PIN_NUM 55 /* gpio 1_23 */

  6. /* 定义 LED 资源 */
  7. static struct resource led_resources[] = {
  8.     [0] = {
  9.         .start = GPIO_LED_PIN_NUM,
  10.         .end = GPIO_LED_PIN_NUM,
  11.         .flags = IORESOURCE_IO,
  12.     },
  13. };

  14. static void led_platform_release(struct device *dev)
  15. {
  16.     return;    
  17. }

  18. /* 定义平台设备 */
  19. static struct platform_device led_platform_device = {
  20.     .name = "led", /* platform_driver 中, .name 必须与该名字相同 */
  21.     .id = -1,
  22.     .num_resources = ARRAY_SIZE(led_resources),
  23.     .resource = led_resources,
  24.     
  25.     .dev = {
  26.         /* Device 'led' does not have a release() function, it is broken and must be fixed. */
  27.         .release = led_platform_release,
  28.         .platform_data = NULL,
  29.     },
  30. };

  31. static int __init led_platform_init(void)
  32. {
  33.     int ret;

  34.     ret = platform_device_register(&led_platform_device);
  35.     if (ret < 0) {
  36.         platform_device_put(&led_platform_device);
  37.         return ret;
  38.     }

  39.     return 0;
  40. }

  41. static void __exit led_platform_exit(void)
  42. {
  43.     platform_device_unregister(&led_platform_device);
  44. }

  45. module_init(led_platform_init);
  46. module_exit(led_platform_exit);

  47. MODULE_LICENSE("GPL");
  48. MODULE_AUTHOR("xxxxx");

2. led_drv 模块
led_drv 模块由 led_drv.c 和 led_drv.h 两个文件组成:

程序清单 2.42 led_drv.c 参考代码

点击(此处)折叠或打开

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/version.h>
  4. #include <linux/fs.h>
  5. #include <linux/cdev.h>
  6. #include <linux/device.h>
  7. #include <linux/platform_device.h>
  8. #include <asm/gpio.h>
  9. #include "led_drv.h"

  10. static int major;
  11. static int minor;
  12. struct cdev *led;         /* cdev 数据结构 */
  13. static dev_t devno;     /* 设备编号 */
  14. static struct class *led_class;
  15. static int led_io;         /* 用于保存 GPIO 编号 */

  16. #define DEVICE_NAME "led"

  17. static int led_open(struct inode *inode, struct file *file )
  18. {
  19.     try_module_get(THIS_MODULE);
  20.     gpio_direction_output(led_io, 1);
  21.     return 0;
  22. }

  23. static int led_release(struct inode *inode, struct file *file )
  24. {
  25.     module_put(THIS_MODULE);
  26.     gpio_direction_output(led_io, 1);
  27.     return 0;
  28. }

  29. #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)

  30. int led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

  31. #else

  32. static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
  33. #endif

  34. {
  35.     if (_IOC_TYPE(cmd) != LED_IOC_MAGIC) {
  36.         return -ENOTTY;
  37.     }

  38.      if (_IOC_NR(cmd) > LED_IOCTL_MAXNR) {
  39.         return -ENOTTY;
  40.      }

  41.      switch(cmd) {
  42.          case LED_ON:
  43.          gpio_set_value(led_io, 0);
  44.          break;

  45.          case LED_OFF:
  46.          gpio_set_value(led_io, 1);
  47.          break;

  48.          default:
  49.          gpio_set_value(led_io, 0);
  50.          break;
  51.      }

  52.     return 0;
  53. }

  54. struct file_operations led_fops = {
  55.     .owner = THIS_MODULE,
  56.     .open = led_open,
  57.     .release = led_release,
  58.     #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
  59.     .unlocked_ioctl = led_ioctl
  60.     #else
  61.     .ioctl = led_ioctl
  62.     #endif
  63. };

  64. static int __devinit led_probe(struct platform_device *pdev)
  65. {
  66.     int ret;
  67.     struct resource *res_io;

  68.     res_io = platform_get_resource(pdev, IORESOURCE_IO, 0); /* 从设备资源获取 IO 引脚 */
  69.     led_io = res_io->start;

  70.     ret = alloc_chrdev_region(&devno, minor, 1, DEVICE_NAME); /* 从系统获取主设备号 */
  71.     major = MAJOR(devno);
  72.     if (ret < 0) {
  73.         printk(KERN_ERR "cannot get major %d \n", major);
  74.         return -1;
  75.     }

  76.     led = cdev_alloc(); /* 分配 led 结构 */
  77.     if (led != NULL) {
  78.         cdev_init(led, &led_fops); /* 初始化 led 结构 */
  79.         led->owner = THIS_MODULE;
  80.         if (cdev_add(led, devno, 1) != 0) { /* 增加 led 到系统中 */
  81.             printk(KERN_ERR "add cdev error!\n");
  82.             goto error;
  83.         }
  84.     } else {
  85.         printk(KERN_ERR "cdev_alloc error!\n");
  86.         return -1;
  87.     }

  88.     led_class = class_create(THIS_MODULE, DEVICE_NAME"_class");
  89.     if (IS_ERR(led_class)) {
  90.         printk(KERN_INFO "create class error\n");
  91.         return -1;
  92.     }

  93.     device_create(led_class, NULL, devno, NULL, DEVICE_NAME);
  94.     return 0;

  95.     error:
  96.     unregister_chrdev_region(devno, 1); /* 释放已经获得的设备号 */
  97.     return ret;
  98. }

  99. static int __devexit led_remove(struct platform_device *dev)
  100. {
  101.     cdev_del(led);                         /* 移除字符设备 */
  102.     unregister_chrdev_region(devno, 1); /* 释放设备号 */
  103.     device_destroy(led_class, devno);
  104.     class_destroy(led_class);
  105.     return 0;
  106. }

  107. /* 定义和初始化平台驱动 */
  108. static struct platform_driver led_platform_driver = {
  109.     .probe = led_probe,
  110.     .remove = __devexit_p(led_remove),
  111.     .driver = {
  112.         .name = "led", /* 该名称必须与 platform_device 的.name 相同 */
  113.         .owner = THIS_MODULE,
  114.     },
  115. };

  116. static int __init led_init(void)
  117. {
  118.     return(platform_driver_register(&led_platform_driver));
  119. }

  120. static void __exit led_exit(void)
  121. {
  122.     platform_driver_unregister(&led_platform_driver);
  123. }

  124. module_init(led_init);
  125. module_exit(led_exit);

  126. MODULE_LICENSE("GPL");
  127. MODULE_AUTHOR("xxxxxxx")

程序清单 2.19 LED 驱动头文件

点击(此处)折叠或打开

  1. #ifndef _LED_DRV_H
  2. #define _LED_DRV_H

  3. #define LED_IOC_MAGIC     'L'
  4. #define LED_ON            _IO(LED_IOC_MAGIC, 0)
  5. #define LED_OFF           _IO(LED_IOC_MAGIC, 1)

  6. #define LED_IOCTL_MAXNR 2

  7. #endif /*_LED_DRV_H*/
3. 测试程序
驱动编写完成后,还需编写一个测试程序,用来测试驱动的正确性。 程序清单 2.20 是
一个简单的测试程序。打开设备后,通过 ioctl 方法, 控制 LED 闪烁 3 次。
注意, 如果驱动定义了 ioctl 命令,则应用程序必须有这些命令的定义,通常做法是包
含驱动头文件。

程序清单 2.20 LED 测试程序 led_test.c

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/ioctl.h>
  5. #include <errno.h>
  6. #include <fcntl.h>
  7. #include "../led_drv.h"

  8. #define DEV_NAME "/dev/led"

  9. int main(int argc, char *argv[])
  10. {
  11.     int i;
  12.     int fd = 0;
  13.     fd = open (DEV_NAME, O_RDONLY);
  14.     if (fd < 0) {
  15.         perror("Open "DEV_NAME" Failed!\n");
  16.         exit(1);
  17.     }

  18.     for (i=0; i<3; i++) {
  19.         ioctl(fd, LED_ON);
  20.         sleep(1);
  21.         ioctl(fd, LED_OFF);
  22.         sleep(1);
  23.     }

  24.     close(fd);
  25.     return 0;
  26. }

4. 测试
将两份驱动分别编译得到 led_platform.ko 和 led_drv.ko 两个驱动模块,按顺序依次插入,
编译测试程序得到led_test,然后运行:
[root@ M283 mnt]# insmod led_platform.ko
[root@ M283 mnt]# insmod led_drv.ko
[root@ M283 mnt]# ./led_test



阅读(977) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~