編輯:關於Android編程
前面寫了兩個博文,一個是Android下,一個是Linux下led控制,但是Linux下那個寫的有很多漏洞和不清楚的地方。這裡寫一篇作為補充,也是我在學習中理解的深入。當然這個可能也會有很多漏洞,如果我有更深入的了解,繼續進行補充。我的開發板是全志科技的CQA83T,成都啟劃公司出的擴展板。
先貼出來驅動源程序的代碼,此代碼的位置在lichee\linux-3.4\drivers\char\led.c:
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LED_IOCTL_SET_ON 1 #define LED_IOCTL_SET_OFF 0 static script_item_u led_val[5]; static script_item_value_type_e led_type; static struct semaphore lock; //led_open static int led_open(struct inode *inode, struct file *file) { if (!down_trylock(&lock)) return 0; else return -EBUSY; } //led_close static int led_close(struct inode *inode, struct file *file) { up(&lock); return 0; } //led_ioctl static long led_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { unsigned int n; n = (unsigned int)arg; switch (cmd) { case LED_IOCTL_SET_ON: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 1); printk("led%d on !\n", n); } break; case LED_IOCTL_SET_OFF: default: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 0); printk("led%d off !\n", n); } break; } return 0; } //led_gpio static int __devinit led_gpio(void) { int i = 0; char gpio_num[10]; for(i =1 ; i < 6; i++) { sprintf(gpio_num, "led_gpio%d", i); led_type= script_get_item("led_para", gpio_num, &led_val[i-1]); if(SCIRPT_ITEM_VALUE_TYPE_PIO != led_type) { printk("led_gpio type fail !"); // gpio_free(led_val[i-1].gpio.gpio); led_val[i-1].gpio.gpio = -1; continue; } if(0 != gpio_request(led_val[i-1].gpio.gpio, NULL)) { printk("led_gpio gpio_request fail !"); led_val[i-1].gpio.gpio = -1; continue; } if (0 != gpio_direction_output(led_val[i-1].gpio.gpio, 0)) { printk("led_gpio gpio_direction_output fail !"); // gpio_free(led_val[i-1].gpio.gpio); led_val[i-1].gpio.gpio = -1; continue; } } return 0; } //file_operations static struct file_operations leds_ops = { .owner = THIS_MODULE, .open = led_open, .release = led_close, .unlocked_ioctl = led_ioctl, }; //miscdevice static struct miscdevice leds_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "led", .fops = &leds_ops, }; //led_remove static int __devexit led_remove(struct platform_device *pdev) { return 0; } //led_probe static int __devinit led_probe(struct platform_device *pdev) { int led_used; script_item_u val; script_item_value_type_e type; int err; printk("led_para!\n"); type = script_get_item("led_para", "led_used", &val); if (SCIRPT_ITEM_VALUE_TYPE_INT != type) { printk("%s script_get_item \"led_para\" led_used = %d\n", __FUNCTION__, val.val); return -1; } led_used = val.val; printk("%s script_get_item \"led_para\" led_used = %d\n", __FUNCTION__, val.val); if(!led_used) { printk("%s led_used is not used in config, led_used=%d\n", __FUNCTION__,led_used); return -1; } err = led_gpio(); if (err) return -1; sema_init(&lock, 1); err = misc_register(&leds_dev); printk("======= cqa83 led initialized ================\n"); return err; } //platform_device struct platform_device led_device = { .name = "led", }; //platform_driver static struct platform_driver led_driver = { .probe = led_probe, .remove = __devexit_p(led_remove), .driver = { .name = "led", .owner = THIS_MODULE, }, }; //led_init static int __init led_init(void) { if (platform_device_register(&led_device)) { printk("%s: register gpio device failed\n", __func__); } if (platform_driver_register(&led_driver)) { printk("%s: register gpio driver failed\n", __func__); } return 0; } //led_exit static void __exit led_exit(void) { platform_driver_unregister(&led_driver); } module_init(led_init); module_exit(led_exit); MODULE_DESCRIPTION("Led Driver"); MODULE_LICENSE("GPL v2");
1、這是一個Linux驅動程序,一個字符驅動,一個雜項字符驅動。從err = misc_register(&leds_dev);可以知道是雜項字符驅動。
2、這裡使用到了Linux的GPIO驅動模型。
3、這個驅動是基於platform機制的。
第一,我們先說一說platform機制。
platform機制是Linux2.6引入的一套新的驅動管理和注冊機制,Linux大部分設備驅動中都能使用這套機制。platform是一種虛擬總線,主要用來管理CPU的片上資源具有很好的移植性。platform機制本身的使用並不復雜,由platform_device(總是設備)和platform_driver(總線驅動)兩部分組成,設備用platform_device表示,驅動用platform_driver注冊。系統首先會初始化platform總線,當platform設備想要掛載到總線上時,定義platform_device和platform_driver,然後使用函數platform_device_register注冊platform_device,再使用platform_driver_register函數注冊platform_driver驅動,這裡要記住,platform_device_register它一定要在platform_driver_register之前。也就是一定要先注冊設備再注冊驅動,因為在驅動注冊是,要先查找與之對應的設備,如果能夠找到並匹配成功才能注冊驅動。具體細節下面會分析。
下面,我們來說一下platform總線,platform總線相關的代碼都在內核linux-3.4\drivers\base\platform.c裡面。既然platform總線是在內核啟動時初始化,那麼先列出初始化函數的調用過程,asmlinkagevoid __init start_kernel(void)[linux-3.4\init\main.c] -->static noinline void __init_refok rest_init(void)[linux-3.4\init\main.c] -->static int __init kernel_init(void * unused)[linux-3.4\init\main.c] -->static void __init do_basic_setup(void) [linux-3.4\init\main.c]-->void __init driver_init(void) [linux-3.4\drivers\base\init.c]-->int __init platform_bus_init(void) [linux-3.4\drivers\base\platform.c] ,中括號裡是文件位置,函數platform_bus_init就是platform的總線初始化函數。
來看platform總線初始化函數platform_bus_init,位於linux-3.4\drivers\base\platform.c中:
int __init platform_bus_init(void) { int error; early_platform_cleanup(); error = device_register(&platform_bus); if (error) return error; error = bus_register(&platform_bus_type); if (error) device_unregister(&platform_bus); return error; }按圖索骥,繼續深入platform總線初始化函數來看early_platform_cleanup函數,這個函數從名字上就可以看出是一個清理函數。我們來看一下這個函數的源碼,位於linux-3.4\drivers\base\platform.c中:
/** * early_platform_cleanup - clean up early platform code */ void __init early_platform_cleanup(void) { struct platform_device *pd, *pd2; /* clean up the devres list used to chain devices */ list_for_each_entry_safe(pd, pd2, &early_platform_device_list, dev.devres_head) { list_del(&pd->dev.devres_head); memset(&pd->dev.devres_head, 0, sizeof(pd->dev.devres_head)); } }從注釋可以看出,這個函數是清除早期的platform設備鏈表,list_for_each_entry_safe的作用是遍歷先前的platform設備鏈表early_platform_device_list
下面繼續platform總線初始化函數中的device_register(&platform_bus)的函數,該函數是將platform總線作為設備進行注冊。我們先看參數plat_bus,位於linux-3.4\drivers\base\platform.c中:
struct device platform_bus = { .init_name = "platform", }; EXPORT_SYMBOL_GPL(platform_bus);參數platform_bus是一個device類型的結構體,下面EXPORT_SYMBOL_GPL是宏,這個宏說明其參數所指向的函數只給有GPL認證的模塊使用。下面來看一下device結構體,位於linux-3.4\include\linux\device.h中:
struct device { struct device *parent; struct device_private *p; struct kobject kobj; const char *init_name; /* initial name of the device */ const struct device_type *type; struct mutex mutex; /* mutex to synchronize calls to * its driver. */ struct bus_type *bus; /* type of bus device is on */ struct device_driver *driver; /* which driver has allocated this device */ void *platform_data; /* Platform specific data, device core doesn't touch it */ struct dev_pm_info power; struct dev_pm_domain *pm_domain; #ifdef CONFIG_NUMA int numa_node; /* NUMA node this device is close to */ #endif u64 *dma_mask; /* dma mask (if dma'able device) */ u64 coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit addresses for consistent allocations such descriptors. */ struct device_dma_parameters *dma_parms; struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */ #ifdef CONFIG_CMA struct cma *cma_area; /* contiguous memory area for dma allocations */ #endif /* arch specific additions */ struct dev_archdata archdata; struct device_node *of_node; /* associated device tree node */ dev_t devt; /* dev_t, creates the sysfs "dev" */ u32 id; /* device instance */ spinlock_t devres_lock; struct list_head devres_head; struct klist_node knode_class; struct class *class; const struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev); };
int device_register(struct device *dev) { device_initialize(dev); return device_add(dev); }這個函數作用是向系統注冊一個設備,它首先使用函數device_initialize對設備進行初始化,然後使用device_add添加設備。下面分別來看一下這倆函數,但這裡不做解釋,這倆函數都是位於linux-3.4\drivers\base\core.c中:
void device_initialize(struct device *dev) { dev->kobj.kset = devices_kset; kobject_init(&dev->kobj, &device_ktype); INIT_LIST_HEAD(&dev->dma_pools); mutex_init(&dev->mutex); lockdep_set_novalidate_class(&dev->mutex); spin_lock_init(&dev->devres_lock); INIT_LIST_HEAD(&dev->devres_head); device_pm_init(dev); set_dev_node(dev, -1); }
int device_add(struct device *dev) { struct device *parent = NULL; struct kobject *kobj; struct class_interface *class_intf; int error = -EINVAL; dev = get_device(dev); if (!dev) goto done; if (!dev->p) { error = device_private_init(dev); if (error) goto done; } /* * for statically allocated devices, which should all be converted * some day, we need to initialize the name. We prevent reading back * the name, and force the use of dev_name() */ if (dev->init_name) { dev_set_name(dev, "%s", dev->init_name); dev->init_name = NULL; } /* subsystems can specify simple device enumeration */ if (!dev_name(dev) && dev->bus && dev->bus->dev_name) dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id); if (!dev_name(dev)) { error = -EINVAL; goto name_error; } pr_debug("device: '%s': %s\n", dev_name(dev), __func__); parent = get_device(dev->parent); kobj = get_device_parent(dev, parent); if (kobj) dev->kobj.parent = kobj; /* use parent numa_node */ if (parent) set_dev_node(dev, dev_to_node(parent)); /* first, register with generic layer. */ /* we require the name to be set before, and pass NULL */ error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); if (error) goto Error; /* notify platform of device entry */ if (platform_notify) platform_notify(dev); error = device_create_file(dev, &uevent_attr); if (error) goto attrError; if (MAJOR(dev->devt)) { error = device_create_file(dev, &devt_attr); if (error) goto ueventattrError; error = device_create_sys_dev_entry(dev); if (error) goto devtattrError; devtmpfs_create_node(dev); } error = device_add_class_symlinks(dev); if (error) goto SymlinkError; error = device_add_attrs(dev); if (error) goto AttrsError; error = bus_add_device(dev); if (error) goto BusError; error = dpm_sysfs_add(dev); if (error) goto DPMError; device_pm_add(dev); /* Notify clients of device addition. This call must come * after dpm_sysfs_add() and before kobject_uevent(). */ if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev); kobject_uevent(&dev->kobj, KOBJ_ADD); bus_probe_device(dev); if (parent) klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children); if (dev->class) { mutex_lock(&dev->class->p->mutex); /* tie the class to the device */ klist_add_tail(&dev->knode_class, &dev->class->p->klist_devices); /* notify any interfaces that the device is here */ list_for_each_entry(class_intf, &dev->class->p->interfaces, node) if (class_intf->add_dev) class_intf->add_dev(dev, class_intf); mutex_unlock(&dev->class->p->mutex); } done: put_device(dev); return error; DPMError: bus_remove_device(dev); BusError: device_remove_attrs(dev); AttrsError: device_remove_class_symlinks(dev); SymlinkError: if (MAJOR(dev->devt)) devtmpfs_delete_node(dev); if (MAJOR(dev->devt)) device_remove_sys_dev_entry(dev); devtattrError: if (MAJOR(dev->devt)) device_remove_file(dev, &devt_attr); ueventattrError: device_remove_file(dev, &uevent_attr); attrError: kobject_uevent(&dev->kobj, KOBJ_REMOVE); kobject_del(&dev->kobj); Error: cleanup_device_parent(dev); if (parent) put_device(parent); name_error: kfree(dev->p); dev->p = NULL; goto done; }
下面我們繼續回到platform總線初始化函數platform_bus_init中,來看總線注冊函數bus_register(&platform_bus_type),先看一下參數platfor_bus_type,位於linux-3.4\drivers\base\platform.c中:
struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, }; EXPORT_SYMBOL_GPL(platform_bus_type);
struct bus_type { const char *name; const char *dev_name; struct device *dev_root; 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 iommu_ops *iommu_ops; struct subsys_private *p; };這是一個設備總線類型結構體,成員變量指出了總線名稱,子設備前綴名(像"foo%u", dev->id),被用作父設備的默認設備,總線屬性,設備屬性,驅動屬性以及一些回調函數。
下面來看一下總線注冊函數bus_register,位於linux-3.4\include\linux\device.h中,這是一個宏定義:
/* This is a #define to keep the compiler from merging different * instances of the __key variable */ #define bus_register(subsys) \ ({ \ static struct lock_class_key __key; \ __bus_register(subsys, &__key); \ })
int __bus_register(struct bus_type *bus, struct lock_class_key *key) { int retval; struct subsys_private *priv; priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); if (!priv) return -ENOMEM; priv->bus = bus; bus->p = priv; BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); if (retval) goto out; priv->subsys.kobj.kset = bus_kset; priv->subsys.kobj.ktype = &bus_ktype; priv->drivers_autoprobe = 1; retval = kset_register(&priv->subsys); if (retval) goto out; retval = bus_create_file(bus, &bus_attr_uevent); if (retval) goto bus_uevent_fail; priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj); if (!priv->devices_kset) { retval = -ENOMEM; goto bus_devices_fail; } priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj); if (!priv->drivers_kset) { retval = -ENOMEM; goto bus_drivers_fail; } INIT_LIST_HEAD(&priv->interfaces); __mutex_init(&priv->mutex, "subsys mutex", key); klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); klist_init(&priv->klist_drivers, NULL, NULL); retval = add_probe_files(bus); if (retval) goto bus_probe_files_fail; retval = bus_add_attrs(bus); if (retval) goto bus_attrs_fail; pr_debug("bus: '%s': registered\n", bus->name); return 0; bus_attrs_fail: remove_probe_files(bus); bus_probe_files_fail: kset_unregister(bus->p->drivers_kset); bus_drivers_fail: kset_unregister(bus->p->devices_kset); bus_devices_fail: bus_remove_file(bus, &bus_attr_uevent); bus_uevent_fail: kset_unregister(&bus->p->subsys); out: kfree(bus->p); bus->p = NULL; return retval; } EXPORT_SYMBOL_GPL(__bus_register);這個函數進行了返回檢測,如果注冊識別,則進行與注冊相反的操作注銷device_unregister。這個函數位於linux-3.4\drivers\base\core.c中:
/** * device_unregister - unregister device from system. * @dev: device going away. * * We do this in two parts, like we do device_register(). First, * we remove it from all the subsystems with device_del(), then * we decrement the reference count via put_device(). If that * is the final reference count, the device will be cleaned up * via device_release() above. Otherwise, the structure will * stick around until the final reference to the device is dropped. */ void device_unregister(struct device *dev) { pr_debug("device: '%s': %s\n", dev_name(dev), __func__); device_del(dev); put_device(dev); }
到這裡呢,platform總線初始化就結束了,沒有做更多的解釋,主要原因是我也在學習,還有就是每個函數的作用無論是見名知意還是查看函數說明,這個函數的功能是很明確的。
現在呢,platform總線已經初始化完成,下面就是把platform設備和驅動掛載到platform總線上了。
現在我們還是回到開始led驅動函數led.c中,了解過驅動的人都知道,驅動被加載到內核第一次被調用的函數就是其初始化函數,在led.c中:
module_init(led_init);初始化函數led_init的源碼再寫一遍,如下:
static int __init led_init(void) { if (platform_device_register(&led_device)) { printk("%s: register gpio device failed\n", __func__); } if (platform_driver_register(&led_driver)) { printk("%s: register gpio driver failed\n", __func__); } return 0; }
struct platform_device led_device = { .name = "led", };
struct platform_device { const char * name;//設備名 int id;//設備id struct device dev;//包含設備結構體 u32 num_resources;//資源個數 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_size_t start;//資源起始地址 resource_size_t end;//資源結束地址 const char *name;//定義資源名稱 unsigned long flags;//定義資源類型 struct resource *parent, *sibling, *child;//資源樹 };
platform_device結構體包含了device結構體,device結構體描述了設備的詳細情況,在面向對象編程中device是所有設備的基類。device結構體在platform總線初始化的時候已經說過了,這裡就不說了。
然後我們來看一下platform_device_register 函數,其位於linux-3.4\drivers\base\platform.c中:
/** * platform_device_register - add a platform-level device * @pdev: platform device we're adding */ int platform_device_register(struct platform_device *pdev) { device_initialize(&pdev->dev); arch_setup_pdev_archdata(pdev); return platform_device_add(pdev); } EXPORT_SYMBOL_GPL(platform_device_register);這裡首先對設備使用函數device_initalize進行初始化,這個函數在上面platform總線初始化的時候已經說過,這裡不說了。在這是不是可以發散一下,只要設備注冊進內核,無論是總線設備也好,其他設備也好,內核都把他們看成設備,使用同樣的方式初始化。
然後使用arch_setup_pdev_archdata(pdev),這個函數位於linux-3.4\drivers\base\platform.c中:
/** * arch_setup_pdev_archdata - Allow manipulation of archdata before its used * @pdev: platform device * * This is called before platform_device_add() such that any pdev_archdata may * be setup before the platform_notifier is called. So if a user needs to * manipulate any relevant information in the pdev_archdata they can do: * * platform_devic_alloc() * ... manipulate ... * platform_device_add() * * And if they don't care they can just call platform_device_register() and * everything will just work out. */ void __weak arch_setup_pdev_archdata(struct platform_device *pdev) { }
/** * platform_device_add - add a platform device to device hierarchy * @pdev: platform device we're adding * * This is part 2 of platform_device_register(), though may be called * separately _iff_ pdev was allocated by platform_device_alloc(). */ int platform_device_add(struct platform_device *pdev) { int i, ret = 0; if (!pdev) return -EINVAL; if (!pdev->dev.parent) pdev->dev.parent = &platform_bus; pdev->dev.bus = &platform_bus_type; if (pdev->id != -1) dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); else dev_set_name(&pdev->dev, "%s", pdev->name); 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)) { printk(KERN_ERR "%s: failed to claim resource %d\n", dev_name(&pdev->dev), 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; failed: while (--i >= 0) { struct resource *r = &pdev->resource[i]; unsigned long type = resource_type(r); if (type == IORESOURCE_MEM || type == IORESOURCE_IO) release_resource(r); } return ret; } EXPORT_SYMBOL_GPL(platform_device_add);
下面我們回到led_init函數繼續往下看驅動注冊函數platform_driver_register(&led_driver),還是先看參數led_driver:
static struct platform_driver led_driver = { .probe = led_probe, .remove = __devexit_p(led_remove), .driver = { .name = "led", .owner = THIS_MODULE, }, };
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; };該結構體主要包含了設備操作的一些函數,並且包含了device_driver結構體,用面向對象的思想說明platform_driver繼承了device_driver結構體。也即是device_driver結構體派生了platform_driver結構體,device_driver是platform_driver的基類。結構體device_driver位於linux-3.4\include\linux中:
struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table; 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; };該結構體包含了設備驅動的相關數據,比如設備驅動名稱,總線類型,擁有者,操作函數等等。
我們接下來看platform設備驅動注冊函數platform_driver_register,該函數位於linux-3.4\drivers\base\platform.c中:
/** * platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure */ int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } EXPORT_SYMBOL_GPL(platform_driver_register);
/** * driver_register - register driver with bus * @drv: driver to register * * We pass off most of the work to the bus_add_driver() call, * since most of the things we have to do deal with the bus * structures. */ int driver_register(struct device_driver *drv) { int ret; struct device_driver *other; BUG_ON(!drv->bus->p); if ((drv->bus->probe && drv->probe) || (drv->bus->remove && drv->remove) || (drv->bus->shutdown && drv->shutdown)) printk(KERN_WARNING "Driver '%s' needs updating - please use " "bus_type methods\n", drv->name); other = driver_find(drv->name, drv->bus); if (other) { printk(KERN_ERR "Error: Driver '%s' is already registered, " "aborting...\n", drv->name); return -EBUSY; } ret = bus_add_driver(drv); if (ret) return ret; ret = driver_add_groups(drv, drv->groups); if (ret) bus_remove_driver(drv); return ret; } EXPORT_SYMBOL_GPL(driver_register);
下面來看一下bus_add_driver函數,這個函數位於linux-3.4\drivers\base\bus.c中:
/** * bus_add_driver - Add a driver to the bus. * @drv: driver. */ int bus_add_driver(struct device_driver *drv) { struct bus_type *bus; struct driver_private *priv; int error = 0; bus = bus_get(drv->bus); if (!bus) return -EINVAL; pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name); priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) { error = -ENOMEM; goto out_put_bus; } klist_init(&priv->klist_devices, NULL, NULL); priv->driver = drv; drv->p = priv; priv->kobj.kset = bus->p->drivers_kset; error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name); if (error) goto out_unregister; if (drv->bus->p->drivers_autoprobe) { error = driver_attach(drv); if (error) goto out_unregister; } klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); module_add_driver(drv->owner, drv); error = driver_create_file(drv, &driver_attr_uevent); if (error) { printk(KERN_ERR "%s: uevent attr (%s) failed\n", __func__, drv->name); } error = driver_add_attrs(bus, drv); if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n", __func__, drv->name); } if (!drv->suppress_bind_attrs) { error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed\n", __func__, drv->name); } } kobject_uevent(&priv->kobj, KOBJ_ADD); return 0; out_unregister: kobject_put(&priv->kobj); kfree(drv->p); drv->p = NULL; out_put_bus: bus_put(bus); return error; }
從上面的紅線部分,如果驅動是自動probe的話,將調用driver_attach來綁定設備和驅動。函數driver_attach位於linux-3.4\drivers\base\dd.c中:
/** * driver_attach - try to bind driver to devices. * @drv: driver. * * Walk the list of devices that the bus has on it and try to * match the driver with each one. If driver_probe_device() * returns 0 and the @dev->driver is set, we've found a * compatible pair. */ int driver_attach(struct device_driver *drv) { return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); } EXPORT_SYMBOL_GPL(driver_attach);
/** * bus_for_each_dev - device iterator. * @bus: bus type. * @start: device to start iterating from. * @data: data for the callback. * @fn: function to be called for each device. * * Iterate over @bus's list of devices, and call @fn for each, * passing it @data. If @start is not NULL, we use that device to * begin iterating from. * * We check the return of @fn each time. If it returns anything * other than 0, we break out and return that value. * * NOTE: The device that returns a non-zero value is not retained * in any way, nor is its refcount incremented. If the caller needs * to retain this data, it should do so, and increment the reference * count in the supplied callback. */ int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *)) { struct klist_iter i; struct device *dev; int error = 0; if (!bus || !bus->p) return -EINVAL; klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL)); while ((dev = next_device(&i)) && !error) error = fn(dev, data); klist_iter_exit(&i); return error; } EXPORT_SYMBOL_GPL(bus_for_each_dev);
static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv = data; /* * Lock device and try to bind to it. We drop the error * here and always return 0, because we need to keep trying * to bind to devices and some drivers will return an error * simply if it didn't support the device. * * driver_probe_device() will spit a warning if there * is an error. */ if (!driver_match_device(drv, dev)) return 0; if (dev->parent) /* Needed for USB */ device_lock(dev->parent); device_lock(dev); if (!dev->driver) driver_probe_device(drv, dev); device_unlock(dev); if (dev->parent) device_unlock(dev->parent); return 0; }
這裡首先是driver 匹配device,然後調用了driver_probe_device函數,該函數位於linux-3.4\drivers\base\dd.c中:
/** * driver_probe_device - attempt to bind device & driver together * @drv: driver to bind a device to * @dev: device to try to bind to the driver * * This function returns -ENODEV if the device is not registered, * 1 if the device is bound successfully and 0 otherwise. * * This function must be called with @dev lock held. When called for a * USB interface, @dev->parent lock must be held as well. */ 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); pm_runtime_get_noresume(dev); pm_runtime_barrier(dev); ret = really_probe(dev, drv); pm_runtime_put_sync(dev); return ret; }
static int really_probe(struct device *dev, struct device_driver *drv) { int ret = 0; 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 (driver_sysfs_add(dev)) { printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n", __func__, dev_name(dev)); goto probe_failed; } if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) goto probe_failed; } else if (drv->probe) { ret = drv->probe(dev); if (ret) goto probe_failed; } driver_bound(dev); 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; probe_failed: devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; if (ret == -EPROBE_DEFER) { /* Driver requested deferred probing */ dev_info(dev, "Driver %s requests probe deferral\n", drv->name); driver_deferred_probe_add(dev); } else if (ret != -ENODEV && ret != -ENXIO) { /* driver matched but the probe failed */ printk(KERN_WARNING "%s: probe of %s failed with error %d\n", drv->name, dev_name(dev), ret); } else { pr_debug("%s: probe of %s rejects match %d\n", drv->name, dev_name(dev), ret); } /* * Ignore errors returned by ->probe so that the next driver can try * its luck. */ ret = 0; done: atomic_dec(&probe_count); wake_up(&probe_waitqueue); return ret; }
//led_probe static int __devinit led_probe(struct platform_device *pdev) { int led_used; script_item_u val; script_item_value_type_e type; int err; printk("led_para!\n"); type = script_get_item("led_para", "led_used", &val); if (SCIRPT_ITEM_VALUE_TYPE_INT != type) { printk("%s script_get_item \"led_para\" led_used = %d\n", __FUNCTION__, val.val); return -1; } led_used = val.val; printk("%s script_get_item \"led_para\" led_used = %d\n", __FUNCTION__, val.val); if(!led_used) { printk("%s led_used is not used in config, led_used=%d\n", __FUNCTION__,led_used); return -1; } err = led_gpio(); if (err) return -1; sema_init(&lock, 1); err = misc_register(&leds_dev); printk("======= cqa83 led initialized ================\n"); return err; }到這裡,platform設備的設備和驅動初始化和綁定,探測就結束了,其實也意味著驅動已經設備和注冊成功了。
我覺得有一點還是要說一下,那就是設備和驅動的匹配,在驅動注冊是會回調總線注冊的匹配函數platform_match,該函數位於linux-3.4\drivers\base\platform.c中:
/** * platform_match - bind platform device to platform driver. * @dev: device. * @drv: driver. * * Platform device IDs are assumed to be encoded like this: * "", where is a short description of the type of * device, like "pci" or "floppy", and is the enumerated * instance of the device, like '0' or '42'. Driver IDs are simply * " ". So, extract the from the platform_device structure, * and compare it against the name of the driver. Return whether they match * or not. */ static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv); /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) return 1; /* Then try to match against the id table */ if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL; /* fall-back to driver name match */ return (strcmp(pdev->name, drv->name) == 0); }
在這裡簡單總結一下,platform設備加載驅動的過程。首先有一個platform總線,這個總線呢會在系統初始化的時候對其進行初始化。在總線初始化完成之後,如果你想要往總線上掛載platform設備,那麼這個要分為兩部分,一是設備,二是驅動,也即是片platform_device和platform_driver,這兩個都是要掛載到platform總線上。但是掛載有一個順序,一定要先掛載設備,再掛載驅動,因為驅動是遍歷總線上所有的設備節點來匹配的。那麼這倆東西靠什麼來匹配呢?他們靠的是其結構體下的name成員變量,如果名字一樣才能匹配成功,這也就是為什麼要求platform_device和platform_driver的名字要一樣的原因了。
platform_device結構體提供的是資源,而platform_driver結構體提供的是操作,也就是驅動操作設備。platform_driver主要完成了設備的注冊和初始化,還有移除是的資源釋放等。在驅動led.c中很容易可以看出來,led_probe調用了led_gpio函數。到platform設備驅動加載完成,其實是在目錄/dev/platform下會出現你的設備。然而這並不能做什麼,但是Linux裡有一句話“一切皆文件”,設備也是文件。那麼這些完成之後,下面就是文件操作了。
一個問題是,我們的應用程序如何去使用驅動程序中的函數?比如打開設備,關閉設備,使用設備等等。這裡就是說對應用程序來說需要一個入口,一個可以通過驅動程序控制設備的入口。這裡就引入了一個重要的數據結構file_operrations,這個結構體包含了一組函數指針,這些指針所指向的函數就是用來操作設備的。
這個結構體位於linux-3.4\include\linux\fs.h中:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); };
//file_operations static struct file_operations leds_ops = { .owner = THIS_MODULE, .open = led_open, .release = led_close, .unlocked_ioctl = led_ioctl, };
這裡只定義了open函數,release函數,unlocked_ioctl函數,並且定義了其擁有者是THIS_MODULE。這幾個函數的源代碼分別是:
//led_open static int led_open(struct inode *inode, struct file *file) { if (!down_trylock(&lock)) return 0; else return -EBUSY; }
led_open函數是開始時對設備加鎖,防止多應用程序訪問。
//led_close static int led_close(struct inode *inode, struct file *file) { up(&lock); return 0; }
led_close函數是設備使用完之後對設備進行解鎖方便其他程序使用。
//led_ioctl static long led_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { unsigned int n; n = (unsigned int)arg; switch (cmd) { case LED_IOCTL_SET_ON: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 1); printk("led%d on !\n", n); } break; case LED_IOCTL_SET_OFF: default: if (n < 1) return -EINVAL; if(led_val[n-1].gpio.gpio != -1) { __gpio_set_value(led_val[n-1].gpio.gpio, 0); printk("led%d off !\n", n); } break; } return 0; }
到此呢,驅動也加載了,應用程序也有了入口,但是還有一個重要問題沒有說,那就是它是何時加載到驅動的呢?
我們知道Linux系統驅動加載一般是兩種方式,一個是編譯成ko模塊加載,一個是編譯進內核,系統啟動時自動加載。模塊加載有兩種方式,一個是手動加載,一個是使用腳本在系統啟動時加載,但是這兩種方式都會使用到mknod,insmod命令等。
那麼這裡是怎麼加載的呢?我們先查看其系統啟動的配置文件init.sun8i.rc,裡面關於led的啟動設置是這樣的
# led chmod 777 /dev/led而不像lcd,lcd是這樣的:
# lcd insmod /system/vendor/modules/disp.ko insmod /system/vendor/modules/hdmi.ko
然而其關於led的只有:
obj-$(CONFIG_SUNXI_LED) += led.o
其實我們再回到led.c代碼中的led_probe函數:
//led_probe static int __devinit led_probe(struct platform_device *pdev) { int led_used; script_item_u val; script_item_value_type_e type; int err; printk("led_para!\n"); type = script_get_item("led_para", "led_used", &val); if (SCIRPT_ITEM_VALUE_TYPE_INT != type) { printk("%s script_get_item \"led_para\" led_used = %d\n", __FUNCTION__, val.val); return -1; } led_used = val.val; printk("%s script_get_item \"led_para\" led_used = %d\n", __FUNCTION__, val.val); if(!led_used) { printk("%s led_used is not used in config, led_used=%d\n", __FUNCTION__,led_used); return -1; } err = led_gpio(); if (err) return -1; sema_init(&lock, 1); err = misc_register(&leds_dev); printk("======= cqa83 led initialized ================\n"); return err; }
//miscdevice static struct miscdevice leds_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "led", .fops = &leds_ops, };
這個led設備驅動呢是一個雜項字符驅動,這就是我開始說的那三點中的一點。那這個有什麼關系呢?
misc_device是特殊字符設備。注冊驅動程序時采用misc_register函數注冊,此函數中會自動創建設備節點,即設備文件。無需mknod指令創建設備文件。
因為misc_register()會調用class_device_creat或者device_creat().
關於雜項字符設備網上有很多資料,大家可以查一下。我會在後面的博文中寫一篇來說雜項字符設備。
到這裡我們解決了沒有mknod的疑問,但是我們是如何配置才能把驅動編譯進內核呢?
可以這樣做,在內核源代碼目錄下執行make menuconfig命令,這會彈出一個對話界面。找到對應的驅動,然後用空格把前面的尖括號裡變為*號,然後保存退出。編譯系統就可以了。由於我的電腦不能截屏,就不能給大家上圖了,抱歉。不過網上有很多資料。我下面會給出連接。
到這裡這個驅動的分析基本上就完成了。但是還有倆問題需要另外來寫一下,一個是LinuxGPIO驅動模型,一個是雜項字符設備。
這是谷歌官方給我們提供的一個兼容低版本安卓設備的軟件包,裡面包囊了只有在安卓3.0以上可以使用的api。而viewpager就是其中之一利用它,我們可以做很多事情,從最簡
MIUI 8是2016年5月10日小米科技發布的全新MIUI 8手機操作系統。小米公司官方此次推出的全新MIUI8系統,在各個方面都進行了大深度的優化。米迷
上節中我們是手動拼接xml文件,但是上節中那樣的做法會有一個問題,比如: //插入消息的內容sBuffer.append(); sBuffer.append(s
綜述對於MVP (Model View Presenter)架構是從著名的MVC(Model View Controller)架構演變而來的。而對於Android應用的開