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 结构表示, 定义在
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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 


int main(void)
{
    int fd;
    fd = open("/dev/tiny4412_led",O_RDWR);
    
    sleep(5);
    close(fd);
    
    return 0;
}

你可能感兴趣的:(platform device驱动)