九、总线设备驱动模型

 


由于TINY4412被学长借去做毕设了,因此从本章开始,以后章节的示例代码均基于iTOP4412_SCP精英版

如读者使用TINY4412开发板,可自行修改代码


  

本章所说的总线是虚拟的总线,只是为了让设备属性和驱动行为更好的分离所提出的概念

 

实际的Linux设备和驱动通常都会挂接在一种总线上,对于USB、I2C、SPI等总线设备而言,自然不是问题。但是挂接在SoC之外的外设却不依附于此类总线,因此Linux发明了虚拟的总线,称为platform总线,所有直接通过内存寻址的设备都映射到这条总线上。总线相应的结构体为struct bus_type,相应的设备为platform_device,相应的驱动为platform_drvier

 

在使用总线分层时,如果设备代码需要更改,而驱动代码不需要更改。那么我们只需要更改设备代码即可,而不需要大片地更改驱动代码

 

在以下章节中,我会依次介绍platform_device、platform_driver和总线结构体platform_bus_type

 

 

一、platform_device

之前说过platform_device定义的是属性,其结构体定义如下:

struct platform_device {
    const char    * name;        // 名字,用于与driver匹配
    int        id;
    struct device    dev;
    u32        num_resources;    // resource的个数
    struct resource    * resource;    // 存储数据

    const struct platform_device_id    *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

 

我们需要重点关注的是struct resource * resource;,此变量存储设备资源信息,其定义和示例如下:

struct resource {
    resource_size_t start;    // 起始地址
    resource_size_t end;    // 结束地址
    const char *name;
    unsigned long flags;    // 资源类型
    struct resource *parent, *sibling, *child;
};

static struct resource led_resource[] = { 
    /* 在iTOP4412中LED3对应GPK1_1 */
    [0] = { 
        .start = 0x11000060,            // GPK1CON地址
        .end   = 0x11000060 + 8 - 1,
        .flags = IORESOURCE_MEM,        // 内存
    },
    /* 要控制哪个I/O口(它属于参数,这里为了方便,用resource传参) */
    [1] = { 
        .start = 1,
        .end   = 1,
        .flags  = IORESOURCE_IRQ,        // 中断属性
    },
};

 

资源有以下几种常用类型:

#define IORESOURCE_IO        0x00000100
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_IRQ        0x00000400
#define IORESOURCE_DMA        0x00000800
#define IORESOURCE_BUS        0x00001000

因为ARM中寄存器和内存是统一编址的,所以GPIO所使用的资源标志用IORESOURCE_MEM和IORESOURCE_REG都是可以的

需要注意的是,这里不能用I/O,这里的IORESOURCE_IO特指PCI/ISA总线的I/O

 

在定义完成resource之后,我们可以定义如下platform_device:

1 static struct platform_device led_platform_dev = {
2     .name    = "led",
3     .id        = 0,
4     .resource    = led_resource,
5     .num_resources = ARRAY_SIZE(led_resource),
6 };

 

在paltform_device定义完成后,我们需要使用如下函数向总线bus_type注册/注销:

/* 注册platform_device */
platform_device_register(&led_platform_dev);

/* 注销platform_driver */
platform_device_unregister(&led_platform_dev);

 

platform_device_register()函数调用过程如下:

platform_device_register()
  -> device_initialize(&pdev->dev);    // 初始化struct device
  -> platform_device_add(pdev);        // 添加设备到链表中
    -> pdev->dev.bus = &platform_bus_type;    // 指定总线
    -> device_add(&pdev->dev);
      -> bus_probe_device(struct device *dev)
        -> device_attach(struct device *dev)
          -> bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
            -> __device_attach()
              -> driver_match_device(drv, dev);
                // 调用总线成员函数match()
                -> return drv->bus->match ? drv->bus->match(dev, drv) : 1;
              -> driver_probe_device(drv, dev);
                -> really_probe(dev, drv);
                  -> drv->probe(dev);    // 调用probe()函数

 

 

二、platform_driver

platform_driver定义的是行为,其定义如下:

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;
};

 

platform_driver示例如下:

static struct platform_driver led_platform_drv = {
    .driver = {
        .name    = "led",
        .owner    = THIS_MODULE,
    },
    .probe        = led_probe,
    .remove        = __devexit_p(led_remove),  /* __devexit_p(x) x */
};

 

和platform_device一样,在paltform_driver定义完成后,我们需要使用如下函数向总线bus_type注册/注销:

/* 注册platform_driver */
platform_driver_register(&led_platform_drv);

/* 注销platform_driver */
platform_driver_unregister(&led_platform_drv);

 

platform_driver_register()函数调用过程如下:

platform_driver_register()
  -> drv->driver.bus = &platform_bus_type;    // 指定总线
  -> driver_register(&drv->driver);
    -> bus_add_driver(drv);            // 添加驱动到链表中
      -> driver_attach(drv);
        -> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
          -> __driver_attach()
            -> driver_match_device(drv, dev);
                // 调用总线成员函数match()
                -> return drv->bus->match ? drv->bus->match(dev, drv) : 1;
          -> driver_probe_device(drv, dev);
            -> really_probe(dev, drv);
              -> drv->probe(dev);    // 调用probe()函数

和platform_device_register()一样,platform_driver_register()也会调用总线的成员函数match();匹配成功则会调用paltform_driver的probe()函数

因此我们要在paltform_driver结构体中提供probe()函数

 

 

下面,我们来分析总线结构体platform_bus_type

三、platform_bus_type

platform_bus_type定义如下:

struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .pm        = &platform_dev_pm_ops,
};

 

我们需要分析其match()函数:

/* 1. 使用设备树进行匹配,我们没有用到 */
of_driver_match_device(dev, drv)

/* 2. 使用platform_driver的id_table进行匹配,我们也没有用到 */
platform_match_id(pdrv->id_table, pdev)


/* 3. 匹配名字,我们使用这个 */
strcmp(pdev->name, drv->name)

 

 

四、总结

总线与输入子系统不同,总线并没有提供struct file_operations结构体,因此仍需要我们自己定义

 

1. 注册platform_driver:platform_driver_register()

1.1 设置platform_driver的总线为platform_bus_type

1.2 添加platform_driver到总线的drv链表中

1.3 调用drv->bus->match(dev, drv)进行匹配

 

2. 注册platform_device:platform_device_register()

2.1 设置platform_device的总线为platform_bus_type

2.2 添加platform_device到总线的dev链表中

2.3 调用drv->bus->match(dev, drv)进行匹配

 

3. 匹配:drv->bus->match(dev, drv)

3.1 匹配设备树信息

3.2 匹配dev和drv->id_table

3.3 匹配dev->name和drv->name

3.4 成功,调用drv->probe(dev)

 

4. 驱动初始化:probe()

4.1 在probe()中做之前init()函数所做的事

 

5. 驱动注销:remove()

5.1 在remove()中做之前exit()函数所做的事

 

 

五、更改led.c为总线设备驱动

此代码我们需要完成platform_device和platform_driver两个结构体,因此分为两个文件

 

我们在probe()函数中需要获取platform_device的数据,此时需要使用platform_get_resource()函数,示例代码如下:

struct resource *led_resource;
led_resource = platform_get_resource(platdev, IORESOURCE_MEM, 0);
GPFCON = (volatile unsigned long *)ioremap(led_resource->start, led_resource->end - led_resource->start + 1);
GPFDAT = GPFCON + 1;    // 映射

led_resource = platform_get_resource(platdev, IORESOURCE_IRQ, 0);
pin = led_resource->start;

 

device源代码:

 1 #include <linux/module.h>
 2 #include <linux/kernel.h>
 3 #include <linux/init.h>
 4 #include <linux/device.h>
 5 #include <linux/platform_device.h>
 6 #include <linux/ioport.h>
 7 
 8 #include <asm/uaccess.h>
 9 #include <asm/irq.h>
10 #include <asm/io.h>
11 
12 static struct resource led_resource[] = { 
13     /* 在iTOP4412中LED3对应GPK1_1 */
14     [0] = { 
15         .start = 0x11000060,            // GPK1CON地址
16         .end   = 0x11000060 + 8 - 1,
17         .flags = IORESOURCE_MEM,        // 内存
18     },
19     /* 要控制哪个I/O口(它属于参数,这里为了方便,用resource传参) */
20     [1] = { 
21         .start = 1,
22         .end   = 1,
23         .flags  = IORESOURCE_IRQ,        // 中断属性
24     },
25 };
26 
27 static void led_release(struct device *dev)
28 {
29     /* NULL */
30 }
31 
32 static struct platform_device led_platform_dev = {
33     .name    = "led",
34     .id        = 0,
35     .resource    = led_resource,
36     .num_resources = ARRAY_SIZE(led_resource),
37     .dev = {
38         .release = led_release,
39     },
40 };
41 
42 static int led_dev_init(void)
43 {
44     return platform_device_register(&led_platform_dev);
45 }
46 
47 static void led_dev_exit(void)
48 {
49     platform_device_unregister(&led_platform_dev);
50 }
51 
52 module_init(led_dev_init);
53 module_exit(led_dev_exit);
54 
55 MODULE_LICENSE("GPL");
View Code

driver源代码:

  1 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/device.h>
  7 #include <linux/platform_device.h>
  8 #include <linux/ioport.h>
  9 #include <linux/kernel.h>
 10 
 11 
 12 #include <asm/uaccess.h>
 13 #include <asm/io.h>
 14 #include <asm/irq.h>
 15 
 16 /* 定义文件内私有结构体 */
 17 struct led_device {
 18     struct cdev cdev;
 19     int stat;            /* 用于保存LED状态,0为灭,1为亮 */
 20 };
 21 
 22 static int g_major;
 23 module_param(g_major, int, S_IRUGO);
 24 
 25 static struct led_device*    dev;
 26 static struct class*        scls;
 27 static struct device*        sdev;
 28 
 29 static volatile unsigned long *con;
 30 static volatile unsigned long *dat;
 31 
 32 static int pin;
 33 
 34 /* LED write()函数 */
 35 static ssize_t led_write(struct file *filep, const char __user * buf, size_t len, loff_t *ppos)
 36 {
 37     struct led_device *dev = filep->private_data;
 38 
 39     if (copy_from_user(&(dev->stat), buf, 1))
 40         return -EFAULT;
 41 
 42     if (dev->stat == 1)
 43         *dat |= (1 << pin);
 44     else
 45         *dat &= ~(1 << pin);
 46 
 47     return 1;
 48 }
 49 
 50 /* LED open()函数 */
 51 static int led_open(struct inode *inodep, struct file *filep)
 52 {
 53     struct led_device *dev;
 54 
 55     dev = container_of(inodep->i_cdev, struct led_device, cdev);
 56     // 放入私有数据中
 57     filep->private_data = dev;
 58 
 59     // 设为输出引脚,灭灯
 60     *con |= (1 << (4 * pin));
 61     *dat &= ~(1 << pin);
 62 
 63     return 0;
 64 }
 65 
 66 static int led_close(struct inode *inodep, struct file *filep)
 67 {
 68     return 0;
 69 }
 70 
 71 /* 把定义的函数接口集合起来,方便系统调用 */
 72 static const struct file_operations led_fops = {
 73     .write = led_write,
 74     .open  = led_open,
 75     .release = led_close,
 76 };
 77 
 78 static int led_probe(struct platform_device *platdev)
 79 {
 80     printk("%s\n", __FUNCTION__);
 81 
 82     int ret;
 83     dev_t devt;
 84 
 85     /* 1. 申请设备号 */
 86     if (g_major) {
 87         devt = MKDEV(g_major, 0);
 88         ret = register_chrdev_region(devt, 1, "led");
 89     }
 90     else {
 91         ret = alloc_chrdev_region(&devt, 0, 1, "led");
 92         g_major = MAJOR(devt);
 93     }
 94     if (ret)
 95         return ret;
 96 
 97     /* 2. 申请文件内私有结构体 */
 98     dev = kzalloc(sizeof(struct led_device), GFP_KERNEL);
 99     if (dev == NULL) {
100         ret = -ENOMEM;
101         goto fail_malloc;
102     }
103 
104     /* 3. 注册字符设备驱动 */
105     cdev_init(&dev->cdev, &led_fops);    /* 初始化cdev并链接file_operations和cdev */
106     ret = cdev_add(&dev->cdev, devt, 1);    /* 注册cdev */
107     if (ret)
108         return ret;
109 
110     /* 4. 创建类设备,insmod后会生成/dev/led设备文件 */
111     scls = class_create(THIS_MODULE, "led");
112     sdev = device_create(scls, NULL, devt, NULL, "led");
113     
114     struct resource *led_resource;
115     led_resource = platform_get_resource(platdev, IORESOURCE_MEM, 0);
116     con = (volatile unsigned long *)ioremap(led_resource->start, led_resource->end - led_resource->start + 1);
117     dat = con + 1;    // 映射
118 
119     printk("con = %p, dat = %p\n", con, dat);
120 
121     led_resource = platform_get_resource(platdev, IORESOURCE_IRQ, 0);
122     pin = led_resource->start;
123     
124     return 0;
125 
126 fail_malloc:
127     unregister_chrdev_region(devt, 1);
128 
129     return ret;
130 }
131 
132 static int led_remove(struct platform_device *platdev)
133 {
134     printk("%s\n", __FUNCTION__);
135 
136     iounmap(con);
137     
138     /* 镜像注销 */
139     dev_t devt = MKDEV(g_major, 0);
140 
141     device_destroy(scls, devt);
142     class_destroy(scls);
143 
144     cdev_del(&(dev->cdev));
145     kfree(dev);
146 
147     unregister_chrdev_region(devt, 1);
148 
149     return 0;
150 }
151 
152 static struct platform_driver led_platform_drv = {
153     .driver = {
154         .name    = "led",
155         .owner    = THIS_MODULE,
156     },
157     .probe        = led_probe,
158     .remove        = __devexit_p(led_remove),  /* __devexit_p(x) x */
159 };
160 
161 static int __init led_init(void)
162 {
163     return platform_driver_register(&led_platform_drv);
164 }
165  
166 static void __exit led_exit(void)
167 {    
168     platform_driver_unregister(&led_platform_drv);
169 }
170  
171 /* 声明段属性 */
172 module_init(led_init);
173 module_exit(led_exit);
174 
175 MODULE_LICENSE("GPL");
View Code

Makefile:

 1 KERN_DIR = /work/itop4412/tools/linux-3.5
 2 
 3 all:
 4     make -C $(KERN_DIR) M=`pwd` modules 
 5 
 6 clean:
 7     make -C $(KERN_DIR) M=`pwd` modules clean
 8     rm -rf modules.order
 9 
10 obj-m    += device.o driver.o
View Code

测试文件:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <fcntl.h>
 6 #include <string.h>
 7 
 8 int main(int argc, char** argv)
 9 {
10     if (argc != 2) {
11         printf("Usage: \n");
12         printf("%s <on|off>\n", argv[0]);
13         return -1;
14     }
15 
16     int fd;
17     fd = open("/dev/led", O_RDWR);
18     if (fd < 0) {
19         printf("can't open /dev/led\n");
20         return -1;
21     }
22 
23     char stat;
24     if (0 == strcmp(argv[1], "off")) {
25         stat = 0;
26         write(fd, &stat, 1);
27     } else {
28         stat = 1;
29         write(fd, &stat, 1);
30     }
31     close(fd);
32 
33     return 0;
34 }
View Code

 

在rmmod device时,产生如下图错误:

也就是说我们需要提供release()函数,更改platform_device如下:

 1 static void led_release(struct device *dev)
 2 {
 3     /* NULL */
 4 }
 5 
 6 static struct platform_device led_platform_dev = {
 7     .name    = "led",
 8     .id        = 0,
 9     .resource    = led_resource,
10     .num_resources = ARRAY_SIZE(led_resource),
11     .dev = {
12         .release = led_release,
13     },
14 };

 

 

下一章  十、LCD的framebuffer设备驱动

 

转载于:https://www.cnblogs.com/Lioker/p/10893768.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值