Chinaunix首页 | 论坛 | 博客
  • 博客访问: 33308
  • 博文数量: 4
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 25
  • 用 户 组: 普通用户
  • 注册时间: 2014-12-28 20:46
个人简介

calm and learn

文章分类
文章存档

2015年(4)

我的朋友

分类: LINUX

2015-03-17 19:27:48

      下文中除了具体使用上的总结以外,还有一些个人认为帮助我自己理解的一些认识,不知道比喻的或者总结的对不对,如有错误,请斧正。
        对于编程者来说,编写一个设备驱动来驱动一个设备要有两个方面:一是设备实体,二是设备抽象,这些内容要在驱动框架 中体现。Linux操作系统将字符设备的这两个方面定义为: cdev 和file两个结构,设备相关的内容以及操作方法(file_operations)都存在cdev结构中,对于上层操作系统来说,将所有的设备都抽象为 文件(file),打开设备即得到一个代表设备的file(设备描述符),其中包含了操作设备的方法file_operations,以及设备实体的描述 cdev(“潜规则”的将其存在private_data字段)。cdev和file通过绑定和操作函数产生关系。
        每打开一个文件或者设备文件都会有一个对应的file,但是相同的file只有一个inode节点。
        platform设备驱动也一样有两个方面:platform devices 和platform driver。不一样的是,多出一个platform bus 的虚拟总线。

一、字符设备驱动的使用

字符设备重要结构:
cdev 、 file 和 file_operations
cdev结构

点击(此处)折叠或打开

  1. struct cdev {
  2.             struct kobject kobj;
  3.             struct module *owner;
  4.             const struct file_operations *ops;
  5.             struct list_head list;
  6.             dev_t dev;
  7.             unsigned int count;
  8.     }
file_oprations结构:

点击(此处)折叠或打开

  1. struct file_operations {
  2.          struct module *owner;
  3.          loff_t (*llseek) (struct file *, loff_t, int);
  4.          ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  5.          ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  6.          ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  7.          ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  8.          int (*iterate) (struct file *, struct dir_context *);
  9.          unsigned int (*poll) (struct file *, struct poll_table_struct *);
  10.          long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  11.          long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  12.          int (*mmap) (struct file *, struct vm_area_struct *);
  13.          int (*open) (struct inode *, struct file *);
  14.          int (*flush) (struct file *, fl_owner_t id);
  15.          int (*release) (struct inode *, struct file *);
  16.         ……
  17.   }
file结构:

点击(此处)折叠或打开

  1. struct file {
  2.          union {
  3.                  struct llist_node fu_llist;
  4.                  struct rcu_head fu_rcuhead;
  5.          } f_u;
  6.          struct path f_path;
  7.   #define f_dentry f_path.dentry
  8.          struct inode *f_inode; /* cached value */
  9.          const struct file_operations *f_op;
  10.     ……
  11.          /* needed for tty driver, and maybe others */
  12.          void *private_data; /*<<<<<<<------------这个指针*/
  13.     ……
  14.   }


字符设备的具体使用过程如下:

1、设备首先要有设备号,自定义设备号:
        设备号类型:dev_t ,宏定义的u32类型
        生成设备号函数:dev_t devno = MKDEV(unsigned int major, unsigned int minor);
        从设备号中获取主次设备号: unsigned int dev_major = MAJOR(dev_t devno);
                                                             unsigned int dev_minor = MINOR(dev_t devno);

2、设备号有了以后,需要看看自己定义的设备号合不合法,需要调用:
        register_chrdev_region(dev_t  first,  unsigned int count, char *name);
        定义的设备号如果注册成功,说明设备号可用,否则返回错误。
        first为自定义的设备号,count为需要申请的设备个数,可以为1,可以为12,等。

        有时驱动作者并不知道系统中哪些设备号可以用,如果自定义的被占用则会注册失败,所以内核提供了一种动态申请设备号的方法:
        alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
        *dev 是接收动态分配的设备号的变量;firstminor通常为0,表示动态分配;count、name 同上。
        动态分配每次的设备号可能是不同的,在cat  /proc/devices文件中可以得到。
无论那种方法,注销设备号都调用:unregister_chrdev_region(dev_t first, unsigned int count);

        另外,从源码看,静态和动态申请设备号的函数,内部都是调用了 __register_chrdev_region,如果主设备号为0,表示动态分配。

3、构造设备实体相关的结构,并为结构体分配空间(cdev_alloc)
        每个设备各不相同,所以要为不同设备定义自己特有的设备实体的描述,这里面会包含cdev结构,以及其他的特殊信息。比如一个用内存来模拟的虚拟设备可以有如下定义:

点击(此处)折叠或打开

  1. struct mydev{
  2.                 struct cdev cdev;
  3.                 unsigned int dev_mem[ 2048 ];
  4.                 /*设备版本、一些全局变量等内容,我想也可以放在这里统一管理*/
  5. }

cdev相关的操作函数有:

点击(此处)折叠或打开

  1. struct cdev * cdev_alloc(void); /*为cdev分配空间,调用了kzalloc函数,如果自定义了设备的结构,如上面的mydev,可以之直接用kmalloc给整个mydev分配空间,在memset为0,效果相同*/
  2. void cdev_init(struct cdev *dev, struct file_operations *fops); /*初始化cdev,即将cdev->ops和自定义的file_operation方法想关联*/
  3. int cdev_add(struct cdev *dev, dev_t num, unsigned int count); /*向系统添加一个cdev*/
  4. void cdev_del(staruc cdev *dev); /*从系统删除一个cdev*/

4、初始化该设备结构,并向系统添加
        分配好空间后,调用cdev_init();  cdev.owner = THIS_MODULE;再调用cdev_add();  将cdev添加到系统即可完成对字符设备的注册

5、字符设备的操作函数
        如前所说的file_operations结构,需要实现open,release,read, write, ioctl等操作方法,然后在cdev初始化时,将file_operations结构作为第二个参数传入。


模块编译到内核,开机之后cat /proc/devices 可以看到mydev设备节点,但是在/dev下面是没有的。
……
249 flash_test
250 ttyGS
251 ttyV
252 mydev
253 iio
254 rtc
……
        也就是说,单纯cdev_add之后没有设备节点,不能供上层应用程序使用。即,不能调用open close read write等函数来操作设备。需要手动创建节点:mknod /dev/mydev c 252 0,才会出现。

注意: 注册设备的旧接口,已经不再使用:
        register_chrdev();和unregister_chrdev();在2.6之后不再提倡使用。

        字符设备每次在系统启动后都要手动创建节点,肯定是不现实的,所以从2.6开始,udev代替了之前devfs,作为新的设备管理器(udev是应用层程序),内核提供了接口来自动创建设备节点:
        #define class_create(owner, name)
        和
        device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

相应的注销函数:
        class_destory() class_devices_destory()
并且需要声明 #include 头文件,然后在/dev目录就会产生设备节点。

使用示例:
struct class *myclass = class_create(THIS_MODULE, “char_dev”);
device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “char_dev”);

注:class_device_create函数已经在2.6.29以后改为device_create

几个概念:设备节点(在/dev下) 和 设备属性文件(/sys/devices 等目录下)

二、混杂设备misc的使用

        从源码可以看出混杂设备是具有固定主设备号的字符设备,主设备号为10,那么它的作用和字符设备就类似了。只是misc设备的实现接口更加简单。
         并且更重要的一点是:可以自动在/dev目录下创建设备节点,而不需要跟字符设备一样,手动创建,或者调用clase_create、devices_create函数
混杂设备的数据结构为:

点击(此处)折叠或打开

  1. struct miscdevice {
  2.     int minor; //次设备号,若为 MISC_DYNAMIC_MINOR 自动分配
  3.     const char *name; //设备名
  4.     const struct file_operations *fops; //设备文件操作结构体
  5.     struct list_head list; //misc_list链表头
  6.     struct device *parent;
  7.     struct device *this_device;
  8.     const char *nodename;
  9.     mode_t mode;
  10. }

        可以看出,由于主设备号为10,在结构中只有次设备号需要自己定义。另外,跟字符设备类似,需要自己定义文件操作函数,填充到file_operations 字段;设备名字,同时也会以此名称,在/dev下创建相应的设备节点。示例定义如下:

点击(此处)折叠或打开

  1. static struct miscdevice misc = {
  2.     .minor = MISC_DYNAMIC_MINOR,
  3.     .name = DEVICE_NAME,
  4.     .fops = &dev_fops,
  5. }
        定义好之后,注册和注销misc设备只需要调用如下函数即可:
int misc_register(struct miscdevice * misc);     //在加载模块时会自动创建设备文件
int misc_deregister(struct miscdevice *misc);     //在卸载模块时会自动删除设备文件

        下面从misc设备的初始化和注册函数源码中看看,如何自动创建设备节点的:
初始化函数中创建了misc的class

点击(此处)折叠或打开

  1. static int __init misc_init(void)
  2. {
  3.     ……
  4.      /*在/sys/class/目录下创建一个名为misc的类*/
  5.     misc_class = class_create(THIS_MODULE, "misc");
  6.    ……
  7.     /*调用字符驱动的注册函数, 设备的主设备号为MISC_MAJOR,为10*/
  8.     if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
  9.         goto fail_printk;
  10.     misc_class->devnode = misc_devnode;
  11.     return 0;
  12. ……
  13. }
  14. /*向内核注册misc子系统*/
  15. subsys_initcall(misc_init)
注册函数中创建了设备节点:

点击(此处)折叠或打开

  1. int misc_register(struct miscdevice * misc)
  2. {
  3.     ……
  4.      /*动态分配设备的次设备号*/
  5.     if (misc->minor == MISC_DYNAMIC_MINOR) {
  6.         int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
  7.         if (i >= DYNAMIC_MINORS) {
  8.             mutex_unlock(&misc_mtx);
  9.             return -EBUSY;
  10.         }
  11.         misc->minor = DYNAMIC_MINORS - i - 1;
  12.         set_bit(i, misc_minors);
  13.     }

  14.      /*使用固定的主设备号,动态分配的次设备号构造设备号*/
  15.     dev = MKDEV(MISC_MAJOR, misc->minor);

  16.     /*创建设备文件,使用了miscdevice结构的name和this_device字段*/
  17.     misc->this_device = device_create(misc_class, misc->parent, dev,
  18.                       misc, "%s", misc->name);
  19. ……
  20.      /*到这一步也就注册成功了,将新注册的misc设备加入到内核维护的misc_list链表中*/
  21.     list_add(&misc->list, &misc_list);
  22. ……
  23. }
        由此可见,misc设备的初始化和注册过程中只是集成了class_create和device_create函数而已,流程其实和字符设备大同小异,具体使用就不再赘述。

三、platform驱动模型的使用

        platform驱动使用同样比较简单,包括了两个结构(platform_device/platform_driver)和两个函数(platform_device_register/platform_driver_register)。
        使用过程可以简单概括为两步:1、实现platform_device以及其中的resource字段,调用 platform_device_register注册设备;2、实现platform_driver结构,调用 platform_driver_register注册驱动。

platform_device/platform_driver结构:
在2.6内核中platform设备用结构体platform_device来描述,该结构体定义在kernel\include\linux\platform_device.h中

点击(此处)折叠或打开

  1. struct platform_device {
  2.     const char * name; //设备名
  3.     u32 id;
  4.     struct device dev;
  5.     u32 num_resources; //resources个数,可以不赋值
  6.     struct resource * resource;
  7. };
该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel\include\linux\ioport.h中,

点击(此处)折叠或打开

  1. struct resource {
  2.     const char *name;
  3.      unsigned long start, end;
  4.     unsigned long flags;
  5.     struct resource *parent, *sibling, *child;
  6. }
driver相关的结构

点击(此处)折叠或打开

  1. struct platform_driver {
  2.     int (*probe)(struct platform_device *);
  3.     int (*remove)(struct platform_device *);
  4.     void (*shutdown)(struct platform_device *);
  5.     int (*suspend)(struct platform_device *, pm_message_t state);
  6.     int (*resume)(struct platform_device *);
  7.      struct device_driver driver; //driver字段下有需要实现的部分
  8.     const struct platform_device_id *id_table;
  9. };
  10. //上面结构中的device_driver结构
  11. struct device_driver {
  12.     const char *name; //必须要与device中的相同,不然会注册失败
  13.     struct bus_type *bus;
  14.     struct module *owner;
  15.     const char *mod_name; /* used for built-in modules */
  16.     bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
  17.     const struct of_device_id *of_match_table;
  18.     const struct acpi_device_id *acpi_match_table;
  19.     int (*probe) (struct device *dev);
  20.     int (*remove) (struct device *dev);
  21.     void (*shutdown) (struct device *dev);
  22.     int (*suspend) (struct device *dev, pm_message_t state);
  23.     int (*resume) (struct device *dev);
  24.     const struct attribute_group **groups;
  25.     const struct dev_pm_ops *pm;
  26.     struct driver_private *p;
  27. }

以下面的例子说明,盗用2410 i2c代码:

        第一步,实现device相关内容。

点击(此处)折叠或打开

  1. static struct resource s3c_i2c_resource[] = {
  2.       [0] = {
  3.                .start = S3C24XX_PA_IIC,
  4.                .end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
  5.                .flags = IORESOURCE_MEM,
  6.       },
  7.       [1] = {
  8.                .start = IRQ_IIC, //S3C2410_IRQ(27)
  9.                .end = IRQ_IIC,
  10.                .flags = IORESOURCE_IRQ,
  11.       }
  12. }
        这里定义了两组resource,它描述了一个I2C设备的资源,第1组描述了这个I2C设备所占用的总线地址范 围,IORESOURCE_MEM表示第 1组描述的是内存类型的资源信息,第2组描述了这个I2C设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。 设备驱动会根据flags来获取相应的资源信息。
有了resource信息,就可以定义platform_device了:

点击(此处)折叠或打开

  1. struct platform_device s3c_device_i2c = {
  2.       .name = "s3c2410-i2c",
  3.       .id = -1,
  4.       .num_resources = ARRAY_SIZE(s3c_i2c_resource),
  5.       .resource = s3c_i2c_resource,
  6. }

驱动程序需要实现结构体struct platform_driver

点击(此处)折叠或打开

  1. static struct platform_driver s3c2410_i2c_driver = {
  2.       .probe = s3c24xx_i2c_probe,
  3.       .remove = s3c24xx_i2c_remove,
  4.       .resume = s3c24xx_i2c_resume,
  5.       .driver = {
  6.                .owner = THIS_MODULE,
  7.                .name = "s3c2410-i2c",
  8.       },
  9. }
        如果device和driver的name一致的话,在执行platform的match函数时,就会将驱动与设备匹配,并且调用probe函数,将设备初始化,也就完成了设备和驱动程序的注册。

        在resource中定义了设备的硬件资源,寄存器地址或者memory空间或者中断号,在驱动中想要获取resource中定义的资源,则调用platform_get_resource函数即可得到。

点击(此处)折叠或打开

  1. /**
  2.  * platform_get_resource - get a resource for a device
  3.  * @dev: platform device
  4.  * @type: resource type
  5.  * @num: resource index
  6.  */
  7. struct resource *platform_get_resource(struct platform_device *dev,
  8.                        unsigned int type, unsigned int num)

如上面的例子,获取IO空间可以这样:
    struct resource *res = NULL;
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
获取IRQ可以这样:res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

        以 上是platform驱动的一般实现方式,这里的resource结构也可统一的在其他地方定义,比如一个大的系统,各种设备的资源、名称、寄存器等,可 以统一定义,在模块驱动文件中直接使用。但是不论统一定义,还是模块内定义,都会给内核带来大量的垃圾代码,并且可维护性降低。引用一段话,说明对于上述 情况的解决办法:
        Linus Torvalds 在 2011 年 3 月 17 日的 ARM Linux 邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发 ARM Linux 社区的地震,随后 ARM 社区进行了一系列 的重大修正。在过去的 ARM Linux 中,arch/arm/plat-xxx 和 arch/arm/mach-xxx 中充斥着大 量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不 过是垃圾,如板上的 platform 设备、resource、i2c_board_info、spi_board_info 以及各种硬 件的 platform_data。读者有兴趣可以统计下常见的 s3c2410、s3c6410 等板级目录,代码量 在数万行。 
        社区必须改变这种局面,于是 PowerPC 等其他体系架构下已经使用的 Flattened Device Tree(FDT)进入 ARM 社区的视野。Device Tree 是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在 Linux 2.6 中,ARM 架构的板极硬件细节过多地被硬编码在 arch/arm/plat-xxx 和 arch/arm/mach-xxx,采用 Device Tree 后,许多硬件的细节可以直接透 过它传递给 Linux,而不再需要在 kernel 中进行大量的冗余编码。 
        所以现在有节操的代码中,可能没有了resource的定义,设备相关的描述统一的放在了arch/arm(arm64)/boot/dts路径下的dts文件中,dt的内容本文不做讨论。 

        在加入dt以后,驱动程序的变化是,不再需要对设备资源有描述,没有了platfrom_device_register函数来注册设备,这些工作都由内核来替我们完成(解析dt的代码为of_*开头的源文件), 驱动只要做driver相关的定义和注册即可。相应的如果想要使用本设备的资源,依然是调用platform_get_resource函数获取;如果想要使用其他设备的资源,则需要获取该设备的节点,再获取其resource内容。 

假如DT的一个节点如下:

点击(此处)折叠或打开

  1. device01{
  2.                   compatible = "compa ny,device01";
  3.                   reg = <0 0x30040000 0 0xA0000 //设备资源的描述
  4.                          0 0x20F00000 0 0x300000>;
  5.                   interrupts = <0 86 0x0>;
  6.                  }
如果使用dt,驱动中还要给platform_driver中.driver的.of_match_table填充一个of_device_id结构,比如: 

点击(此处)折叠或打开

  1. static const struct of_device_id of_match[] = {
  2.     { .compatible = "company,device01", },
  3.     { }
  4. };
  5. static struct platform_driver xx_driver = {
  6.     .probe = xx_probe,
  7.     .remove = xx_remove,
  8.     .suspend = xx_suspend,
  9.     .resume = xx_resume,
  10.     .driver = {
  11.         .owner = THIS_MODULE,
  12.         .name = "device01",
  13.          .of_match_table = of_match
  14.     }
  15. }
        如果驱动中想要使用其他节点(设备)定义的资源,由于没有相应platform device在本驱动中,不能简单的通过platform_get_resource来获取,platform_get_resource第一个参数就是paltform device,即本设备。则需要通过一系列的of_*开头的函数来获取。在driver/of目录下有相关的源文件,比如下面广两个函数可以满足简单需求: 
        获取dt节点: 
        struct device_node *of_find_compatible_node (struct device_node *from, const char *type, const char *compatible) 
        struct device_node *of_find_node_by_name(struct device_node *from, const char *name) 

        将节点中的指定资源,转到resource变量中: 
        int of_address_to_resource (struct device_node *dev, int index, struct resource *r) 

        最后:在项目过程中,感觉简单的字符设备应用并不常见,有的完全可以用misc设备代替,并且platform引入以后,linux下大部分的驱动都可以以此框架实现,所以platfrom更为常见,并可配合misc设备向上提供节点,供引用层使用。也可以配合sys节点,创建设备的属性文件,供上层使用,方便并且容易实现。 

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