嵌入式Linux驅動筆記(五)------學習platform設備驅動

你好!這裏是風箏的博客,

歡迎和我一塊兒交流。


 

設備是設備,驅動是驅動。node

若是把兩個糅合寫一塊兒,當設備發生變化時,勢必要改寫整個文件,這是很是愚蠢的作法。若是把他們分開來,當設備發生變化時,只要改寫設備文件便可,驅動文件巍然不動。linux

從linux2.6內核起,引入一套新的驅動管理和註冊機制:platform_device 和 platform_driver 。Linux 中大部分的設備驅動,均可以使用這套機制,設備用 platform_device 表示;驅動用 platform_driver 進行註冊。app

platform將驅動分爲platform_device (設備文件)和platform_driver(驅動文件),他們會經過platform總線來相配對。當設備註冊到總線時,會經過總線去尋找有沒有相對應的驅動文件,有的話則將他兩配對。同理,當驅動註冊到總線時,會經過總線去尋找有沒有相對應的設備文件,有的話也將他兩進行配對。dom

linux platform driver 機制和傳統的device driver機制(即:經過 driver_register 函數進行註冊)相比,一個十分明顯的優點在於platform機制將設備自己的資源註冊進內核,由內核統一管理,在驅動程序中用使用這些資源時,經過platform device提供的標準接口進行申請並使用。async

 

以kernel 4.8.17爲例,驅動文件:ide

platform_driver_register(&led_drv);  
    ——>__platform_driver_register  
        ——>drv->driver.bus = &platform_bus_type;  
            ——>.match      = platform_match,  
                ——>of_driver_match_device(dev, drv)  
                    ——>of_match_device(drv->of_match_table, dev)  
                        ——>of_match_node(matches, dev->of_node)  
                            ——>__of_match_node(matches, node)  
                                ——>__of_device_is_compatible(node, matches->compatible,matches->type, matches->name)  
  
                ——>acpi_driver_match_device(dev, drv)  
                ——>platform_match_id(pdrv->id_table, pdev)  
                ——>strcmp(pdev->name, drv->name)  

代碼如上,驅動註冊時,會在總線上與設備匹配,有四種匹配方法:函數

1)如5行,經過這個OpenFirmware的匹配方式,匹配name、type、和compatible字符串三個屬性,三者要同時相同(通常name、和type爲空,只比較compatible字符串),compatible這個好像是在設備樹(dts)裏說到,這個以後再討論。若是不匹配,則會進行第二種匹配方式。學習

2)如11行,我也不知道這個acpi_driver_match_device是什麼,反正也是若是不匹配,則會進行第三種匹配方式。this

3)如12行,經過id_table方式匹配,比較設備的名字和id_table裏的名字是否有相同的。這樣在id_table能夠實現一個驅動對應多個設備。若是沒有,則會進行第四種匹配方式了。atom

4)如13行,直接比較設備名字和驅動名字。

即便匹配不成功,也會driver_register(&drv->driver)進行註冊,等帶設備註冊時來與驅動匹配。

若是匹配成功,則會引起驅動的probe()函數執行。

設備文件:

platform_device_register(&led_dev)    
    platform_device_add(pdev)    
        pdev->dev.bus = &platform_bus_type    
            .match      = platform_match    
                /*以後就同樣了*/  

 

那麼他們具體是怎麼操做的呢?咱們來具體分析,以platform_device_register爲例:

int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}

這裏面,先初始化device,其中涉及kset,能夠看看這篇文章:嵌入式Linux驅動學習筆記(十六)------設備驅動模型(kobject、kset、ktype)

而後是platform_device_add函數:

int platform_device_add(struct platform_device *pdev)
{
	int i, ret;

	if (!pdev)
		return -EINVAL;

	if (!pdev->dev.parent)
		pdev->dev.parent = &platform_bus;

	pdev->dev.bus = &platform_bus_type;

	switch (pdev->id) {
	default:
		dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
		break;
	case PLATFORM_DEVID_NONE:
		dev_set_name(&pdev->dev, "%s", pdev->name);
		break;
	case PLATFORM_DEVID_AUTO:
		ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
		if (ret < 0)
			goto err_out;
		pdev->id = ret;
		pdev->id_auto = true;
		dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
		break;
	}

	for (i = 0; i < pdev->num_resources; i++) {
		struct resource *p, *r = &pdev->resource[i];

		if (r->name == NULL)
			r->name = dev_name(&pdev->dev);

		p = r->parent;
		if (!p) {
			if (resource_type(r) == IORESOURCE_MEM)
				p = &iomem_resource;
			else if (resource_type(r) == IORESOURCE_IO)
				p = &ioport_resource;
		}

		if (p && insert_resource(p, r)) {
			dev_err(&pdev->dev, "failed to claim resource %d\n", i);
			ret = -EBUSY;
			goto failed;
		}
	}

	pr_debug("Registering platform device '%s'. Parent at %s\n",
		 dev_name(&pdev->dev), dev_name(pdev->dev.parent));

	ret = device_add(&pdev->dev);
	if (ret == 0)
		return ret;

/*省略部分代碼*/
}


這裏面,這是了所屬總線,填充好名字,就會調用device_add函數了,

 

這個函數也很複雜,我放在嵌入式Linux驅動筆記(十六)------設備驅動模型(kobject、kset、ktype)這裏講了

可是,複雜的那些細節咱們咱們先能夠不看,咱們看到device_add函數裏調用bus_probe_device函數,這一個探測函數:

void bus_probe_device(struct device *dev)
{
	struct bus_type *bus = dev->bus;
	struct subsys_interface *sif;

	if (!bus)
		return;

	if (bus->p->drivers_autoprobe)//設置了自動匹配初始化那麼就開始匹配 
		device_initial_probe(dev);

	mutex_lock(&bus->p->mutex);
	list_for_each_entry(sif, &bus->p->interfaces, node)
		if (sif->add_dev)
			sif->add_dev(dev, sif);
	mutex_unlock(&bus->p->mutex);
}

 

這裏面,調用了device_initial_probe函數,device_initial_probe又調用了__device_attach函數,咱們繼續看看:

static int __device_attach(struct device *dev, bool allow_async)
{
	int ret = 0;

	device_lock(dev);
	if (dev->driver) {
		if (device_is_bound(dev)) {
			ret = 1;
			goto out_unlock;
		}
		ret = device_bind_driver(dev);
		if (ret == 0)
			ret = 1;
		else {
			dev->driver = NULL;
			ret = 0;
		}
	} else {
		struct device_attach_data data = {
			.dev = dev,
			.check_async = allow_async,
			.want_async = false,
		};

		if (dev->parent)
			pm_runtime_get_sync(dev->parent);

		ret = bus_for_each_drv(dev->bus, NULL, &data,
					__device_attach_driver);
		if (!ret && allow_async && data.have_async) {
			dev_dbg(dev, "scheduling asynchronous probe\n");
			get_device(dev);
			async_schedule(__device_attach_async_helper, dev);
		} else {
			pm_request_idle(dev);
		}

		if (dev->parent)
			pm_runtime_put(dev->parent);
	}
out_unlock:
	device_unlock(dev);
	return ret;
}


函數一開始,先檢查device是否綁定過了,接着調用device_bind_driver對device和driver進行綁定:

int device_bind_driver(struct device *dev)
{
	int ret;

	ret = driver_sysfs_add(dev);//將driver和dev使用link,連接到一塊兒,使他們真正相關 
	if (!ret)
		driver_bound(dev);//將私有成員的driver節點掛到了driver的設備鏈表 
	else if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_DRIVER_NOT_BOUND, dev);//通知bus上全部設備bound消息 
	return ret;
}

 

static int driver_sysfs_add(struct device *dev)
{
	int ret;

	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_BIND_DRIVER, dev);

	ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
			  kobject_name(&dev->kobj));//驅動目錄下dev->kobj目錄連接到dev->kobj 
	if (ret == 0) {
		ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
					"driver");//在dev->kobj目錄下的driver目錄連接到其驅動目錄
		if (ret)
			sysfs_remove_link(&dev->driver->p->kobj,
					kobject_name(&dev->kobj));
	}
	return ret;
}


接着調用__device_attach_driver進行match:

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
	struct device_attach_data *data = _data;
	struct device *dev = data->dev;
	bool async_allowed;
	int ret;

	/*
	 * Check if device has already been claimed. This may
	 * happen with driver loading, device discovery/registration,
	 * and deferred probe processing happens all at once with
	 * multiple threads.
	 */
	if (dev->driver)
		return -EBUSY;

	ret = driver_match_device(drv, dev);
	if (ret == 0) {
		/* no match */
		return 0;
	} else if (ret == -EPROBE_DEFER) {
		dev_dbg(dev, "Device match requests probe deferral\n");
		driver_deferred_probe_add(dev);
	} else if (ret < 0) {
		dev_dbg(dev, "Bus failed to match device: %d", ret);
		return ret;
	} /* ret > 0 means positive match */

	async_allowed = driver_allows_async_probing(drv);

	if (async_allowed)
		data->have_async = true;

	if (data->check_async && async_allowed != data->want_async)
		return 0;

	return driver_probe_device(drv, dev);
}


這裏面,先調用driver_match_device進行各類關鍵字match:

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函數觸發probe函數:

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;
	
	if (!device_is_registered(dev))
		return -ENODEV;
	
	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);

	if (dev->parent)
		pm_runtime_get_sync(dev->parent);

	pm_runtime_barrier(dev);
	ret = really_probe(dev, drv);//調用really_probe 
	pm_request_idle(dev);

	if (dev->parent)
		pm_runtime_put(dev->parent);

	return ret;
}


函數一開始檢查device是否註冊過,若是註冊過,直接return。

 

不然,則調用really_probe函數:

static int really_probe(struct device *dev, struct device_driver *drv)
{
	int ret = -EPROBE_DEFER;
	int local_trigger_count = atomic_read(&deferred_trigger_count);

	if (defer_all_probes) {
		/*
		 * Value of defer_all_probes can be set only by
		 * device_defer_all_probes_enable() which, in turn, will call
		 * wait_for_device_probe() right after that to avoid any races.
		 */
		dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);
		driver_deferred_probe_add(dev);
		return ret;
	}

	atomic_inc(&probe_count);
	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
		 drv->bus->name, __func__, drv->name, dev_name(dev));
	WARN_ON(!list_empty(&dev->devres_head));

	dev->driver = drv;

	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto pinctrl_bind_failed;

	if (driver_sysfs_add(dev)) {//驅動目錄下創建一個到設備的同名連接,而且在設備目錄下創建一個名爲 driver.到驅動的連接
		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
			__func__, dev_name(dev));
		goto probe_failed;
	}

	if (dev->pm_domain && dev->pm_domain->activate) {
		ret = dev->pm_domain->activate(dev);
		if (ret)
			goto probe_failed;
	}

	/*
	 * Ensure devices are listed in devices_kset in correct order
	 * It's important to move Dev to the end of devices_kset before
	 * calling .probe, because it could be recursive and parent Dev
	 * should always go first
	 */
	devices_kset_move_last(dev);

	if (dev->bus->probe) {
		ret = dev->bus->probe(dev);//若是bus的probe存在就用bus的
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {//若是bus的不存在driver的存在
		ret = drv->probe(dev);//再用driver的
		if (ret)
			goto probe_failed;
	}

	pinctrl_init_done(dev);

	if (dev->pm_domain && dev->pm_domain->sync)
		dev->pm_domain->sync(dev);

	driver_bound(dev);//調用driver_bound進行綁定
	ret = 1;
	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);
	goto done;
	/*省略部分代碼*/
}


這裏,先調用driver_sysfs_add函數,把drivers添加到sysfs中:

static int driver_sysfs_add(struct device *dev)
{
	int ret;

	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_BIND_DRIVER, dev);

	ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
			  kobject_name(&dev->kobj));//驅動目錄下dev->kobj目錄連接到dev->kobj 
	if (ret == 0) {
		ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
					"driver");//在dev->kobj目錄下的driver目錄連接到其驅動目錄
		if (ret)
			sysfs_remove_link(&dev->driver->p->kobj,
					kobject_name(&dev->kobj));
	}
	return ret;
}


最後,也是咱們指望看到的,probe函數的觸發:

if (dev->bus->probe) {
		ret = dev->bus->probe(dev);//若是bus的probe存在就用bus的
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {//若是bus的不存在driver的存在
		ret = drv->probe(dev);//再用driver的
		if (ret)
			goto probe_failed;
	}

 

咱們的platform總線是不自帶probe的,因此這裏對觸發drv->probe,好了,分析到這裏,就大功告成了!

platform_driver_register函數也是同樣的分析方法,就很少累述了。

 

因此咱們主要仍是 構造好這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;    
    bool prevent_deferred_probe;    
};   

 

設備的結構體爲:

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;    
    char *driver_override; /* Driver name to force a match */    
    
    /* MFD cell pointer */    
    struct mfd_cell *mfd_cell;    
    
    /* arch specific additions */    
    struct pdev_archdata    archdata;    
};   

 

其中,有個重要的參數:resource(資源),結構體以下 :

struct resource {    
    resource_size_t start;/*資源的起始地址*/    
    resource_size_t end;/*資源的結束地址*/    
    const char *name;/*資源的名字*/    
    unsigned long flags;/*資源的類型*/    
    unsigned long desc;    
    struct resource *parent, *sibling, *child;    
};  

flags類型的可選參數有:

IORESOURCE_TYPE_BITS

IORESOURCE_IO/*IO地址空間*/

IORESOURCE_MEM/*屬於外設或者用於和設備通信的支持直接尋址的地址空間*/

IORESOURCE_REG/*寄存器偏移*/

IORESOURCE_IRQ

IORESOURCE_DMA

IORESOURCE_BUS

start、end的含義會隨着flags而變動,如:

當flags爲IORESOURCE_MEM時,start、end分別表示該platform_device佔據的內存的開始地址和結束地址;

當flags爲IORESOURCE_IRQ時,start、end分別表示該platform_device使用的中斷號的開始值和結束值,若是隻使用了1箇中斷號,開始和結束值相同。

對於同種類型的資源而言,能夠有多份,譬如說某設備佔據了2個內存區域,則能夠定義2個IORESOURCE_MEM資源。

下面給出完整程序參考,摘抄自韋東山驅動視頻。

驅動文件:

/* 分配/設置/註冊一個platform_driver */  
#include <linux/module.h>  
#include <linux/version.h>  
  
#include <linux/init.h>  
#include <linux/fs.h>  
#include <linux/interrupt.h>  
#include <linux/irq.h>  
#include <linux/sched.h>  
#include <linux/pm.h>  
#include <linux/sysctl.h>  
#include <linux/proc_fs.h>  
#include <linux/delay.h>  
#include <linux/platform_device.h>  
#include <linux/input.h>  
#include <linux/irq.h>  
#include <asm/uaccess.h>  
#include <asm/io.h>  
  
static int major;  
static struct class *cls;  
static volatile unsigned long *gpio_con;  
static volatile unsigned long *gpio_dat;  
static int pin;  
  
static int led_open(struct inode *inode, struct file *file)  
{  
    /* 配置爲輸出 */  
    *gpio_con &= ~(0x3<<(pin*2));  
    *gpio_con |= (0x1<<(pin*2));  
    return 0;     
}  
  
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)  
{  
    int val;  
  
    copy_from_user(&val, buf, count); //    copy_to_user();  
  
    if (val == 1)  
    {  
        // 點燈  
        *gpio_dat &= ~(1<<pin);  
    }  
    else  
    {  
        // 滅燈  
        *gpio_dat |= (1<<pin);  
    }  
      
    return 0;  
}  
  
static struct file_operations led_fops = {  
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動建立的__this_module變量 */  
    .open   =   led_open,       
    .write  =   led_write,       
};  
  
static int led_probe(struct platform_device *pdev)  
{  
    struct resource     *res;  
  
    /* 根據platform_device的資源進行ioremap */  
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
    gpio_con = ioremap(res->start, res->end - res->start + 1);  
    gpio_dat = gpio_con + 1;  
  
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  
    pin = res->start;  
  
    /* 註冊字符設備驅動程序 */  
  
    printk("led_probe, found led\n");  
  
    major = register_chrdev(0, "myled", &led_fops);  
  
    cls = class_create(THIS_MODULE, "myled");  
  
    //class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */  
    device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */  
      
    return 0;  
}  
  
static int led_remove(struct platform_device *pdev)  
{  
    /* 卸載字符設備驅動程序 */  
    /* iounmap */  
    printk("led_remove, remove led\n");  
  
    //class_device_destroy(cls, MKDEV(major, 0));  
    device_destroy(cls, MKDEV(major, 0));  
    class_destroy(cls);  
    unregister_chrdev(major, "myled");  
    iounmap(gpio_con);  
      
    return 0;  
}  
  
struct platform_driver led_drv = {  
    .probe      = led_probe,  
    .remove     = led_remove,  
    .driver     = {  
        .name   = "myled",  
    }  
};  
  
static int led_drv_init(void)  
{  
    platform_driver_register(&led_drv);  
    return 0;  
}  
  
static void led_drv_exit(void)  
{  
    platform_driver_unregister(&led_drv);  
}  
  
module_init(led_drv_init);  
module_exit(led_drv_exit);  
  
MODULE_LICENSE("GPL");  

 

設備文件:

/* 分配/設置/註冊一個platform_device */  
#include <linux/module.h>  
#include <linux/version.h>  
  
#include <linux/init.h>  
  
#include <linux/kernel.h>  
#include <linux/types.h>  
#include <linux/interrupt.h>  
#include <linux/list.h>  
#include <linux/timer.h>  
#include <linux/init.h>  
#include <linux/serial_core.h>  
#include <linux/platform_device.h>  
  
static struct resource led_resource[] = {  
    [0] = {  
        .start = 0x56000050,  
        .end   = 0x56000050 + 8 - 1,  
        .flags = IORESOURCE_MEM,/*指的是屬於外設或者用於和設備通信的支持直接尋址的地址空間*/  
    },  
    [1] = {  
        .start = 5,  
        .end   = 5,  
        .flags = IORESOURCE_IRQ,  
    }  
};  
  
static void led_release(struct device * dev)  
{  
}  
  
static struct platform_device led_dev = {  
    .name               = "myled",  
    .id                 = -1,  
    .num_resources      = ARRAY_SIZE(led_resource),/*資源個數*/  
    .resource           = led_resource,  
    .dev                = {   
                            .release = led_release,   
                    },  
};  
  
static int led_dev_init(void)  
{  
    platform_device_register(&led_dev);  
    return 0;  
}  
  
static void led_dev_exit(void)  
{  
    platform_device_unregister(&led_dev);  
}  
  
module_init(led_dev_init);  
module_exit(led_dev_exit);  
  
MODULE_LICENSE("GPL");  

 

須要注意的是:platform_driver 和 platform_device 中的 name 變量的值必須是相同的 。這樣在 platform_driver_register() 註冊時,會將當前註冊的 platform_driver 中的 name 變量的值和已註冊的全部 platform_device 中的 name 變量的值進行比較,只有找到具備相同名稱的 platform_device 才能註冊成功。當註冊成功時,會調用 platform_driver 結構元素 probe 函數指針,運行.probe進行初始化。

 

最後,結合十六節文章分析口味更佳!

嵌入式Linux驅動學習筆記(十六)------設備驅動模型(kobject、kset、ktype)