Linux 设备驱动开发 —— platform设备驱动应用实例解析

时间:2021-04-19 23:35:05

前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 —— platform 设备驱动 ,下面将通过一个实例来深入我们的学习。

一、platform 驱动的工作过程

platform模型驱动编程,platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳。

在一般情况下,2.6内核中已经初始化并挂载了一条platform总线在sysfs文件系统中。那么我们编写platform模型驱动时,需要完成两个工作:

a -- 实现platform驱动

架构就很简单,如下所示:

Linux 设备驱动开发 —— platform设备驱动应用实例解析

platform驱动模型三个对象:platform总线platform设备platform驱动

platform总线对应的内核结构:struct bus_type-->它包含的最关键的函数:match() (要注意的是,这块由内核完成,我们不参与)

platform设备对应的内核结构:struct platform_device-->注册:platform_device_register(unregister)

platform驱动对应的内核结构:struct platform_driver-->注册:platform_driver_register(unregister)

那具体platform驱动的工作过程是什么呢:

如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的probe函数等;

如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致它的probe函数暂不调用,而是要等到设备注册成功并与自己匹配绑定后才会调用。


二、实现platform 驱动与设备的详细过程

1、思考问题?

在分析platform 之前,可以先思考一下下面的问题:

a -- 为什么要用 platform 驱动?不用platform驱动可以吗?

b -- 设备驱动中引入platform 概念有什么好处?

现在先不回答,看完下面的分析就明白了,后面会附上总结。

2、platform_device 结构体 VS platform_driver 结构体

这两个结构体分别描述了设备和驱动,二者有什么关系呢?先看一下具体结构体对比

设备(硬件部分):中断号,寄存器,DMA等
                   platform_device 结构体
 驱动(软件部分)
                         platform_driver 结构体       
struct platform_device {
    const char    *name;       名字
    int        id;
    bool        id_auto;
    struct device    dev;   硬件模块必须包含该结构体
    u32        num_resources;        资源个数
    struct resource    *resource;         资源  人脉
    const struct platform_device_id    *id_entry;
    /* arch specific additions */
    struct pdev_archdata    archdata;
};
struct platform_driver {
    int (*probe)(struct platform_device *);
    硬件和软件匹配成功之后调用该函数
    int (*remove)(struct platform_device *);
    硬件卸载了调用该函数
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;内核里所有的驱动程序必须包含该结构体
    const struct platform_device_id *id_table;  八字
};
设备实例:
static struct platform_device hello_device=
{
    .name = "bigbang",
    .id = -1,
    .dev.release = hello_release,
};
驱动实例:
static struct platform_driver hello_driver=
{
    .driver.name = "bigbang",
    .probe = hello_probe,
    .remove = hello_remove,
};

前面提到,实现platform模型的过程就是总线对设备和驱动的匹配过程 。打个比方,就好比相亲,总线是红娘,设备是男方,驱动是女方:

a -- 红娘(总线)负责男方(设备)和女方(驱动)的撮合;

b -- 男方(女方)找到红娘,说我来登记一下,看有没有合适的姑娘(汉子)—— int (*probe)(struct platform_device *) 匹配成功后驱动执行的第一个函数),当然如果男的跟小三跑了(设备卸载),女方也不会继续待下去的(
 int (*remove)(struct platform_device *)
)。

3、设备资源结构体

在struct platform_device 结构体中有一重要成员 struct resource *resource

  1. struct resource {
  2. resource_size_t start;  资源起始地址
  3. resource_size_t end;   资源结束地址
  4. const char *name;
  5. unsigned long flags;   区分是资源什么类型的
  6. struct resource *parent, *sibling, *child;
  7. };
  8. #define IORESOURCE_MEM        0x00000200
  9. #define IORESOURCE_IRQ        0x00000400

flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ  这两种。start 和 end 的含义会随着 flags而变更,如

a -- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值

b -- flags为 IORESOURCE_IRQ
  时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值

下面看一个实例:

  1. static struct  resource beep_resource[] =
  2. {
  3. [0] = {
  4. .start = 0x114000a0,
  5. .end = 0x114000a0+0x4,
  6. .flags = IORESOURCE_MEM,
  7. },
  8. [1] = {
  9. .start = 0x139D0000,
  10. .end = 0x139D0000+0x14,
  11. .flags = IORESOURCE_MEM,
  12. },
  13. };
4、将字符设备添加到 platform的driver中
      前面我们提到platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳,下面我们看一下添加的过程:
  1. static struct file_operations hello_ops=
  2. {
  3. .open = hello_open,
  4. .release = hello_release,
  5. .unlocked_ioctl = hello_ioctl,
  6. };
  7. static int hello_remove(struct platform_device *pdev)
  8. {
  9. 注销分配的各种资源
  10. }
  11. static int hello_probe(struct platform_device *pdev)
  12. {
  13. 1.申请设备号
  14. 2.cdev初始化注册,&hello_ops
  15. 3.从pdev读出硬件资源
  16. 4.对硬件资源初始化,ioremap,request_irq( )
  17. }
  18. static int hello_init(void)
  19. {
  20. 只注册 platform_driver
  21. }
  22. static void hello_exit(void)
  23. {
  24. 只注销 platform_driver
  25. }
      可以看到,
5、platform是如何匹配device和driver
      这时就该总线出场了,系统为platform总线定义了一个bus_type 的实例platform_bus_type,其定义如下:
  1. struct bus_type platform_bus_type = {
  2. .name        = "platform",
  3. .dev_groups    = platform_dev_groups,
  4. .match        = platform_match,
  5. .uevent        = platform_uevent,
  6. .pm        = &platform_dev_pm_ops,
  7. };
      其又是怎样工作的呢?在platform.c (e:\linux-3.14-fs4412\drivers\base)    31577    2014/3/31 中可以看到
  1. __platform_driver_register()
  2. {
  3. drv->driver.bus = &platform_bus_type;     536行
  4. }
    在 platform_bus_type 中调用 了platform_match:
  1. static int platform_match(struct device *dev, struct device_driver *drv)
  2. {
  3. struct platform_device *pdev = to_platform_device(dev);
  4. struct platform_driver *pdrv = to_platform_driver(drv);
  5. 匹配设备树信息,如果有设备树,就调用 of_driver_match_device() 函数进行匹配
  6. if (of_driver_match_device(dev, drv))
  7. return 1;
  8. 匹配id_table
  9. if (pdrv->id_table)
  10. return platform_match_id(pdrv->id_table, pdev) != NULL;
  11. 最基本匹配规则
  12. return (strcmp(pdev->name, drv->name) == 0);
  13. }

6、解决问题
      现在可以回答这两个问题了

a -- 为什么要用 platform 驱动?不用platform驱动可以吗?

b -- 设备驱动中引入platform 概念有什么好处?

      引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;

      Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动, 下面来看一下,套上platform 外壳后的程序:

1、device.c

  1. #include <linux/module.h>
  2. #include <linux/device.h>
  3. #include <linux/platform_device.h>
  4. #include <linux/ioport.h>
  5. static struct resource beep_resource[] =
  6. {
  7. [0] ={
  8. .start = 0x114000a0,
  9. .end =  0x114000a0 + 0x4,
  10. .flags = IORESOURCE_MEM,
  11. },
  12. [1] ={
  13. .start = 0x139D0000,
  14. .end =  0x139D0000 + 0x14,
  15. .flags = IORESOURCE_MEM,
  16. }
  17. };
  18. static void hello_release(struct device *dev)
  19. {
  20. printk("hello_release\n");
  21. return ;
  22. }
  23. static struct platform_device hello_device=
  24. {
  25. .name = "bigbang",
  26. .id = -1,
  27. .dev.release = hello_release,
  28. .num_resources = ARRAY_SIZE(beep_resource),
  29. .resource = beep_resource,
  30. };
  31. static int hello_init(void)
  32. {
  33. printk("hello_init");
  34. return platform_device_register(&hello_device);
  35. }
  36. static void hello_exit(void)
  37. {
  38. printk("hello_exit");
  39. platform_device_unregister(&hello_device);
  40. return;
  41. }
  42. MODULE_LICENSE("GPL");
  43. module_init(hello_init);
  44. module_exit(hello_exit);

2、driver.c

  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/cdev.h>
  4. #include <linux/device.h>
  5. #include <linux/platform_device.h>
  6. #include <asm/io.h>
  7. static int major = 250;
  8. static int minor=0;
  9. static dev_t devno;
  10. static struct class *cls;
  11. static struct device *test_device;
  12. #define TCFG0         0x0000
  13. #define TCFG1         0x0004
  14. #define TCON          0x0008
  15. #define TCNTB0        0x000C
  16. #define TCMPB0        0x0010
  17. static unsigned int *gpd0con;
  18. static void *timer_base;
  19. #define  MAGIC_NUMBER    'k'
  20. #define  BEEP_ON    _IO(MAGIC_NUMBER    ,0)
  21. #define  BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
  22. #define  BEEP_FREQ   _IO(MAGIC_NUMBER   ,2)
  23. static void fs4412_beep_init(void)
  24. {
  25. writel ((readl(gpd0con)&~(0xf<<0)) | (0x2<<0),gpd0con);
  26. writel ((readl(timer_base +TCFG0  )&~(0xff<<0)) | (0xff <<0),timer_base +TCFG0);
  27. writel ((readl(timer_base +TCFG1 )&~(0xf<<0)) | (0x2 <<0),timer_base +TCFG1 );
  28. writel (500, timer_base +TCNTB0  );
  29. writel (250, timer_base +TCMPB0 );
  30. writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x2 <<0),timer_base +TCON );
  31. }
  32. void fs4412_beep_on(void)
  33. {
  34. writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x9 <<0),timer_base +TCON );
  35. }
  36. void fs4412_beep_off(void)
  37. {
  38. writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x0 <<0),timer_base +TCON );
  39. }
  40. static void beep_unmap(void)
  41. {
  42. iounmap(gpd0con);
  43. iounmap(timer_base);
  44. }
  45. static int beep_open (struct inode *inode, struct file *filep)
  46. {
  47. fs4412_beep_on();
  48. return 0;
  49. }
  50. static int beep_release(struct inode *inode, struct file *filep)
  51. {
  52. fs4412_beep_off();
  53. return 0;
  54. }
  55. #define BEPP_IN_FREQ 100000
  56. static void beep_freq(unsigned long arg)
  57. {
  58. writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0  );
  59. writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );
  60. }
  61. static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
  62. {
  63. switch(cmd)
  64. {
  65. case BEEP_ON:
  66. fs4412_beep_on();
  67. break;
  68. case BEEP_OFF:
  69. fs4412_beep_off();
  70. break;
  71. case BEEP_FREQ:
  72. beep_freq( arg );
  73. break;
  74. default :
  75. return -EINVAL;
  76. }
  77. return 0;
  78. }
  79. static struct file_operations beep_ops=
  80. {
  81. .open     = beep_open,
  82. .release = beep_release,
  83. .unlocked_ioctl      = beep_ioctl,
  84. };
  85. static int beep_probe(struct platform_device *pdev)
  86. {
  87. int ret;
  88. printk("match ok!");
  89. gpd0con = ioremap(pdev->resource[0].start,pdev->resource[0].end - pdev->resource[0].start);
  90. timer_base = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start);
  91. devno = MKDEV(major,minor);
  92. ret = register_chrdev(major,"beep",&beep_ops);
  93. cls = class_create(THIS_MODULE, "myclass");
  94. if(IS_ERR(cls))
  95. {
  96. unregister_chrdev(major,"beep");
  97. return -EBUSY;
  98. }
  99. test_device = device_create(cls,NULL,devno,NULL,"beep");//mknod /dev/hello
  100. if(IS_ERR(test_device))
  101. {
  102. class_destroy(cls);
  103. unregister_chrdev(major,"beep");
  104. return -EBUSY;
  105. }
  106. fs4412_beep_init();
  107. return 0;
  108. }
  109. static int beep_remove(struct platform_device *pdev)
  110. {
  111. beep_unmap();
  112. device_destroy(cls,devno);
  113. class_destroy(cls);
  114. unregister_chrdev(major,"beep");
  115. return 0;
  116. }
  117. static struct platform_driver beep_driver=
  118. {
  119. .driver.name = "bigbang",
  120. .probe = beep_probe,
  121. .remove = beep_remove,
  122. };
  123. static int beep_init(void)
  124. {
  125. printk("beep_init");
  126. return platform_driver_register(&beep_driver);
  127. }
  128. static void beep_exit(void)
  129. {
  130. printk("beep_exit");
  131. platform_driver_unregister(&beep_driver);
  132. return;
  133. }
  134. MODULE_LICENSE("GPL");
  135. module_init(beep_init);
  136. module_exit(beep_exit);

3、makefile

  1. ifneq  ($(KERNELRELEASE),)
  2. obj-m:=device.o driver.o
  3. $(info "2nd")
  4. else
  5. #KDIR := /lib/modules/$(shell uname -r)/build
  6. KDIR := /home/fs/linux/linux-3.14-fs4412
  7. PWD:=$(shell pwd)
  8. all:
  9. $(info "1st")
  10. make -C $(KDIR) M=$(PWD) modules
  11. clean:
  12. rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
  13. endif

4、test.c

    1. #include <sys/types.h>
    2. #include <sys/stat.h>
    3. #include <fcntl.h>
    4. #include <stdio.h>
    5. main()
    6. {
    7. int fd,i,lednum;
    8. fd = open("/dev/beep",O_RDWR);
    9. if(fd<0)
    10. {
    11. perror("open fail \n");
    12. return ;
    13. }
    14. sleep(10);
    15. close(fd);
    16. }