platform device驱动

bus-driver-device

  • bus:
    总线作为主机和外设的连接通道,有些总线是比较规范的,形成了很多协议。如PCI,USB,1394,IIC等。任何设备都可以选择合适的总线连接到主机。当然主机也可能就是CPU本身。
  • driver:
    驱动程序是在CPU运行时,提供操作的软件接口。所有的设备必须有与之配套驱动程序才能正常工作。一个驱动程序可以驱动多个类似或者完全不同的设备。
  • device:
    设备就是连接在总线上的物理实体。设备是有功能之分的。具有相同功能的设备被归到一个类(CLASS中)。如音频设备(和声音相关的都算),输入设备(鼠标,键盘,游戏杆等)
  • 从宏观考虑,任何DEVICE必须要连接到主机才能发挥其作用。一个鼠标离开了电脑主机就不再是鼠标了。提到了连接就必然出现总线BUS。任何设备要正常运行必须有软件支持,所有的设备必须有DRIVER。
  • 在linux驱动管理中,可以将驱动程序中用到的数据和代码分离开来:
    设备是数据
    驱动是代码
  • 这种分离好处是:如果设备的参数变了,或者更改了同类的设备,那么只需要修改数据,二不需要修改代码,就能驱动设备了。
  • 而设备和驱动的关联靠的是总线。

bus总线

  • bus、device、dirver三者的定义在 include/linux/device.h中。
struct bus_type         表示总线
struct device_driver    表示驱动
struct device           表示设备
  • 总线是处理器与设备之间通道,在设备模型中,所有的设备都通过总线相连。
  • 总线上有两条链表klist_devices、klist_drivers;
    • 每次出现一个设备就要向总线注册,添加到总线的klist_devices链表中;
    • 每次出现一个驱动也要向总线注册,添加到总线的klist_drivers中。
    • 比如系统初始化的时候,会扫描连接了哪些设备,并为每一个设备建立起一个struct device的变量,每一次有一个驱动程序,就要准备一个struct device_driver结构的变量。把这些变量统统加入相应的链表,device 插入devices 链表,driver插入drivers链表。这样通过总线就能找到每一个设备,每一个驱动.
    • 假如计算机里只有设备却没有对应的驱动,那么设备无法工作。反过来,倘若只有驱动却没有设备,驱动也起不了任何作用。
  • 链表里的device和driver又是如何联系
    • 用“名字”关联:也就是device中的“name”于driver中的“name”一致。
    • 用 ID 关联:device中的ID于driver中的ID一致,两者关联起来。
  • 驱动核心可以注册多种类型的总线。
    • 每种总线下面可以挂载许多设备。(通过kset devices)
    • 每种总线下可以用很多设备驱动。(通过包含一个kset drivers)}
  • 每个驱动可以处理一组设备。所有的设备都挂载到总线上,当加载驱动时,驱动就支总线上找到自己对应的设备。或者先把驱动加载上,来了一个设备就去总线找驱动。
  • 总线由 bus_type 结构表示, 定义在 <linux/device.h>
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);//驱动匹配函数  
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);// 添加环境变量  
    int (*probe)(struct device *dev);// 驱动探测  
    int (*remove)(struct device *dev);//移除设备  
    void (*shutdown)(struct device *dev);//关机时会调用  
    int (*suspend)(struct device *dev, pm_message_t state);//挂起(投入休眠)时调用。  
    int (*resume)(struct device *dev);//恢复时调用  
    const struct dev_pm_ops *pm;// 设备电源管理  
    struct bus_type_private *p;//私有数据。完全由驱动核心初始化并使用。  
};  
  • 上面的总线的私有数据类型如下:
struct bus_type_private {  
    struct kset subsys;;/*与该总线相关的子系统*/  
    struct kset *drivers_kset;/*总线驱动程序的kset*/  
    struct kset *devices_kset;/* 挂在该总线的所有设备的kset*/  
    struct klist klist_devices;/*挂接在该总线的设备链表*/  
    struct klist klist_drivers;/*与该总线相关的驱动程序链表*/  
    struct blocking_notifier_head bus_notifier;  
    unsigned int drivers_autoprobe:1;/*处理热插拔、电源管理、探测和移除等事件的方法*/  
    struct bus_type *bus;  
};  
  • struct bus_type类型里面又几个比较重要的成员,如下:
    • match() 匹配函数:

      • 判定设备和驱动是否匹配,是总线体系相关的。驱动核心通过match和probe两个函数来完成匹配每当有设备添加到总线时,驱动核心遍历总线上的驱动链表查找设备驱动;每当有驱动添加到总线时,驱动核心遍历总线上的设备链表查找驱动可操控的设备。
      • 当前,match一般只执行总线特定的匹配处理,而在probe中,通过回调设备驱动probe,完成设备特定的匹配、设备初始化等。
      • match匹配成功则返回1,失配返回0。
    • probe()探测函数:

      • probe执行设备相关的匹配探测、设备初始化、资源分配等。
      • 对于一次遍历匹配而言,如果match和probe均成功,则结束匹配成功;如果match成功而probe失配,继续遍历查找匹配;如果遍历结束而没有找到成功的匹配,对于驱动而言表示没有可操控设备,对于设备而言表示没有适当的驱动。
    • remove()移除设备

    • 设备移除时,调用该方法,完成部分清理工作。如删除设备驱动中,设备链表下的该设备。

    • shutdown()系统关机

      • 系统关机时,调用该方法关闭设备。
    • suspend()设备休眠(挂起)。

      • 设备休眠(挂起)时调用该方法。一般在该方法中设置设备为低耗电状态。
    • resume()设备恢复

      • 设备从休眠中恢复时调用该方法。
    • pm()电源管理

      • 一些设备有电源状态转换。结构体内部提供很多方法实现这个过程

deviece定义

  • struct device结构中比较重要的成员如下:
struct device {  
......
    struct bus_type *bus;   //  标识了该设备链接在哪一个总线上  
    struct device_driver *driver;   // 管理该设备的驱动程序,一个设备只能有一个驱动程序  
    void        *platform_data; //平台的特定数据   
......
};  

device注册的代码分析

  • 将device注册到总线上的函数是device_register();
  • 跟踪这个注册函数的调用如下:
device_register(dev)
 device_add(dev)
  bus_add_device(dev)
    bus_probe_device(dev)
       device_attach(dev);
          bus_for_each_drv(dev->bus, NULL, dev, __device_attach)
  • bus_for_each_drv()函数是一个回调函数,意思是对于bus中的每一个driver,都调用__device_attach这个函数
  • __device_attach函数就是匹配总线上的设备与驱动的函数:
static int __device_attach(struct device_driver *drv, void *data)  
{  
   struct device *dev = data;  
    if (!driver_match_device(drv, dev))//匹配条件检查  
        return 0;  
    return driver_probe_device(drv, dev);//进行真正的匹配操作  
}  
  • bus的match存在就用bus的否则就直接匹配成功.match通常实现为首先扫描driver支持的id设备表,如果为NULL就用名字进行匹配
static inline int driver_match_device(struct device_driver *drv,  
                      struct device *dev){  
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;  
} 
  • 再来看下driver_probe_device(),进行匹配操作:
driver_probe_device(drv,dev)
  really_probe(dev, drv);   //调用really_probe 
    if (dev->bus->probe) {  //利用probe初始化设备  
      ret = dev->bus->probe(dev); //如果bus的probe存在就用bus
    } else if (drv->probe) {//如果bus的不存在driver的存在  
        ret = drv->probe(dev);//再用driver的   
    }  

device_driver的定义

  • struct device_driver结构中比较重要的成员如下:
struct device_driver {  
    const char      *name; //名字  
    struct bus_type     *bus;//其所在的bus   
    struct module       *owner;//表示实现设备驱动程序的模块  
    const char      *mod_name;  //模块名  
    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */    
    int (*probe) (struct device *dev); //匹配成功时可能会调用到的函数  
    int (*remove) (struct device *dev);  
    void (*shutdown) (struct device *dev);  
    int (*suspend) (struct device *dev, pm_message_t state);  
    int (*resume) (struct device *dev);  
    const struct attribute_group **groups;  
    const struct dev_pm_ops *pm;//电源管理方面  
    struct driver_private *p;//私有属性  
};  

device_driver的注册代码分析

  • driver的注册函数也跟device一样,也是注册的函数driver_register,下面来看看这个函数
driver_register(drv)
  bus_add_driver(drv) //把该driver加入所在bus
    driver_attach(drv)//到bus的devices上去匹配设备
  • 匹配总线的驱动与设备的函数如下:
driver_attach(struct device_driver *drv)  
{  
   return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
} 
//遍历bus的设备链表找到合适的设备就调用__driver_attach
  • __driver_attach()函数
__driver_attach(drv, dev)
  driver_match_device(drv, dev)
    driver_probe_device(drv, dev)
      really_probe(dev, drv)

这里真正开始调用用户在 device_driver 中注册的 probe()例程

整体框架

platform总线

  • 在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于总线。
  • 也就是说在嵌入式CPU中,访问外设跟访问内存是一样的。
  • 基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。
  • platform总线的定义如下:


  • platform总线中的match()函数是设备与驱动匹配的函数,我们看看这个函数的实现,就是匹配设备与驱动的名字一不一致。


  • platform_device结构体的定义
struct platform_device {
    const char  *name;//名字,用于匹配driver
    int     id;
    struct device   dev;
    u32     num_resources;      //资源数目
    struct resource * resource; //资源
    const struct platform_device_id *id_entry;
    struct mfd_cell *mfd_cell;
    struct pdev_archdata    archdata;
};
  • 前面提到,设备代表数据,在这里数据由上面的num_resources、resource来指定;
  • Platform驱动的资源一般驱动根据硬件来调整的参数。
    • 主要是中断号和寄存器地址
  • 原来驱动一般把这些资源以硬编码形式写死在驱动源码,如果调整必须修改驱动源码。
  • 使用资源的优点是参数跟源码是分离,一般是在devs.c统一管理资源.而实现在代码写在原来platform驱动里。
  • 常见资源类型
    • I/O 地址(IO一般特指x86,I/O端口)
    • 地址空间(寄存器空间) MEM,中断号,IRQ
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};
#define IORESOURCE_IO       0x00000100
#define IORESOURCE_MEM      0x00000200
#define IORESOURCE_IRQ      0x00000400
#define IORESOURCE_DMA      0x00000800

举个例子:

//引自于devs.c
/* Keypad interface */
static struct resource s3c_keypad_resource[] = {
    [0] = {
        .start = S3C_PA_KEYPAD,
        .end   = S3C_PA_KEYPAD+ S3C_SZ_KEYPAD - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_KEYPAD,
        .end   = IRQ_KEYPAD,
        .flags = IORESOURCE_IRQ,
    }
};

struct platform_device s3c_device_keypad = {
    .name         = "s3c-keypad",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(s3c_keypad_resource),
    .resource     = s3c_keypad_resource,
};
  • 查找资源
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
dev ,设备定立
Type ,资源类型,即resource.flags
num,序号,表示如果有相同的类型,选取的序号,1表取第二个资源
  • 以下查找I/O地址并重映射
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dev = &pdev->dev;   
size = (res->end - res->start) + 1;
base_addr = ioremap(res->start, size);
  • 以下查找查找中断
ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts);
  • 如果某一个platform设备有一个与本设备高度相关的参数。Platform建议把地址入struct device.platform_data。
  • platform_data要求是动态分配的,方便device注销时释放。
  • platform_driver的定义如下:


  • platform_device的注册函数
    int platform_device_register(struct platform_device *);
  • platform_driver的注册函数
    int platform_driver_register(struct platform_driver *);
  • 注册的流程跟前面分析的bus-device-driver一样
    • 注册platform_device时
      将设备添加到platform_bus_type的设备链表中;
      然后匹配设备与驱动的“name”,如果一直调用驱动的probe方法;
    • 注册platform_driver时
      将驱动添加到platform_bus_type的驱动链表中;
      然后匹配设备与驱动的“name”,如果一直调用驱动的probe方法;
  • platform驱动只是一种机制,这种机制就像模块机制一样;
    • 模块机制:
      在插入模块时,调用模块的入口函数(初始化函数)
      在卸载模块时,调用模块的卸载函数
    • bus_device_driver机制:
      当注册device时,匹配driver,如果匹配就调用driver->probe方法;
      当注册driver时,匹配device,如果匹配就调用driver->probe方法;
    • 不管注册设备还是注册驱动,都会调用driver->probe方法;
    • 至于在驱动的probe方法中做什么事情,那是程序员的事情

实战代码

tiny4412_led_dev.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/platform_device.h>

struct led_t {
    char *name;
    unsigned int gpio;
} ;

static struct led_t tiny4412leds[] ={
        {"LED1",EXYNOS4X12_GPM4(0)},
        {"LED2",EXYNOS4X12_GPM4(1)},
        {"LED3",EXYNOS4X12_GPM4(2)},
        {"LED4",EXYNOS4X12_GPM4(3)},
};

static void led_release(struct device *dev)
{
}

static struct platform_device leddev = {
    .name = "tiny4412_led",
    .dev = {
        .release = led_release,
        .platform_data = (void *)tiny4412leds,
    },
};


static int led_init(void)
{
    platform_device_register(&leddev);
    return 0;
}

static void led_exit(void)
{
    platform_device_unregister(&leddev);
}


module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

tiny4412_led_dev.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/platform_device.h>

struct led_t {
    char *name;
    unsigned int gpio;
} ;

static struct led_t *led_p;
static int tiny4412_led_open(struct inode *_inode, struct file *fp)
{
    int i;
    for(i=0;i<4;i++){
        s3c_gpio_cfgpin(led_p[i].gpio, S3C_GPIO_OUTPUT);
        gpio_set_value(led_p[i].gpio,0);
    }
    
    return 0;
}

static int tiny4412_led_release(struct inode *_inode, struct file *fp)
{
    int i;
    for(i=0;i<4;i++){
        gpio_set_value(led_p[i].gpio,1);
    }
    return 0;
}

static char value = 0xff;

static ssize_t tiny4412_led_read(struct file *fp, char __user *buf, size_t size, loff_t *off)
{
    copy_to_user(buf, &value, sizeof(char));
    return sizeof(char);
}

static ssize_t tiny4412_led_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)
{
    copy_from_user(&value, buf, sizeof(char));

    if(value &  (0x1<<0))    gpio_set_value(led_p[0].gpio,1);
    else    gpio_set_value(led_p[0].gpio,0);

    if(value &  (0x1<<1))    gpio_set_value(led_p[1].gpio,1);
    else    gpio_set_value(led_p[1].gpio,0);

    if(value &  (0x1<<2))    gpio_set_value(led_p[2].gpio,1);
    else    gpio_set_value(led_p[2].gpio,0);

    if(value &  (0x1<<3))    gpio_set_value(led_p[3].gpio,1);
    else    gpio_set_value(led_p[3].gpio,0);
    
    return sizeof(char);
}

static struct file_operations fops={
    .owner = THIS_MODULE,
    .open = tiny4412_led_open,
    .release = tiny4412_led_release,
    .read = tiny4412_led_read,
    .write = tiny4412_led_write,
};

#define TINY4412_LED_NAME "tiny4412_led"
static int major = 0;
static struct cdev *led_cdev;
static struct class *led_class;

static int led_probe(struct platform_device *pdev)
{
    printk("my probe\n");
    /*1 dev number*/
    led_p = (struct led_t *)pdev->dev.platform_data;
    
    dev_t devnum = 0;
    alloc_chrdev_region(&devnum, 0, 1, TINY4412_LED_NAME);
    major = MAJOR(devnum);
    
    /*2 init cdev */
    led_cdev = cdev_alloc();
    cdev_init(led_cdev,&fops);
    led_cdev->owner = THIS_MODULE;
    
    /*3 add cdev */
    cdev_add(led_cdev, devnum,1);
    
    /*4 create dev file */
    led_class=class_create(THIS_MODULE, TINY4412_LED_NAME);
    device_create(led_class, NULL, devnum,NULL, TINY4412_LED_NAME); //  /dev/tiny4412_led
    return 0;
    return 0;
}
static int led_remove(struct platform_device *pdev)
{
    printk("my remove\n");
    device_destroy(led_class, MKDEV(major,0));
    class_destroy(led_class);

    cdev_del(led_cdev);

    unregister_chrdev_region(MKDEV(major,0), 1);
    return 0;
}

static struct platform_driver leddrv = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "tiny4412_led",
    },
};


static int led_init(void)
{
    platform_driver_register(&leddrv);
    return 0;
}

static void led_exit(void)
{
    platform_driver_unregister(&leddrv);
}


module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

makefile

obj-m += tiny4412_led_dev.o tiny4412_led_drv.o
all:
    make -C /home/sice/linux-3.5 M=`pwd` modules
clean:
    make -C /home/sice/linux-3.5 M=`pwd` clean

app.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>


int main(void)
{
    int fd;
    fd = open("/dev/tiny4412_led",O_RDWR);
    
    sleep(5);
    close(fd);
    
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容