Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 288887
b: refs/heads/master
c: d1c3414
h: refs/heads/master
i:
  288885: 0a5eace
  288883: 1f5ef74
  288879: d6a887a
v: v3
  • Loading branch information
Grant Likely authored and Greg Kroah-Hartman committed Mar 8, 2012
1 parent 1aa31e1 commit b468759
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 2 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: fef37e9a47b9927ce2817fe1a0fa8cf40f6eefb6
refs/heads/master: d1c3414c2a9d10ef7f0f7665f5d2947cd088c093
1 change: 1 addition & 0 deletions trunk/drivers/base/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ extern void bus_remove_driver(struct device_driver *drv);

extern void driver_detach(struct device_driver *drv);
extern int driver_probe_device(struct device_driver *drv, struct device *dev);
extern void driver_deferred_probe_del(struct device *dev);
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
Expand Down
2 changes: 2 additions & 0 deletions trunk/drivers/base/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->deferred_probe);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
Expand Down Expand Up @@ -1188,6 +1189,7 @@ void device_del(struct device *dev)
device_remove_file(dev, &uevent_attr);
device_remove_attrs(dev);
bus_remove_device(dev);
driver_deferred_probe_del(dev);

/*
* Some platform devices are driven without driver attached
Expand Down
138 changes: 137 additions & 1 deletion trunk/drivers/base/dd.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,133 @@
#include "base.h"
#include "power/power.h"

/*
* Deferred Probe infrastructure.
*
* Sometimes driver probe order matters, but the kernel doesn't always have
* dependency information which means some drivers will get probed before a
* resource it depends on is available. For example, an SDHCI driver may
* first need a GPIO line from an i2c GPIO controller before it can be
* initialized. If a required resource is not available yet, a driver can
* request probing to be deferred by returning -EPROBE_DEFER from its probe hook
*
* Deferred probe maintains two lists of devices, a pending list and an active
* list. A driver returning -EPROBE_DEFER causes the device to be added to the
* pending list. A successful driver probe will trigger moving all devices
* from the pending to the active list so that the workqueue will eventually
* retry them.
*
* The deferred_probe_mutex must be held any time the deferred_probe_*_list
* of the (struct device*)->deferred_probe pointers are manipulated
*/
static DEFINE_MUTEX(deferred_probe_mutex);
static LIST_HEAD(deferred_probe_pending_list);
static LIST_HEAD(deferred_probe_active_list);
static struct workqueue_struct *deferred_wq;

/**
* deferred_probe_work_func() - Retry probing devices in the active list.
*/
static void deferred_probe_work_func(struct work_struct *work)
{
struct device *dev;
/*
* This block processes every device in the deferred 'active' list.
* Each device is removed from the active list and passed to
* bus_probe_device() to re-attempt the probe. The loop continues
* until every device in the active list is removed and retried.
*
* Note: Once the device is removed from the list and the mutex is
* released, it is possible for the device get freed by another thread
* and cause a illegal pointer dereference. This code uses
* get/put_device() to ensure the device structure cannot disappear
* from under our feet.
*/
mutex_lock(&deferred_probe_mutex);
while (!list_empty(&deferred_probe_active_list)) {
dev = list_first_entry(&deferred_probe_active_list,
typeof(*dev), deferred_probe);
list_del_init(&dev->deferred_probe);

get_device(dev);

/* Drop the mutex while probing each device; the probe path
* may manipulate the deferred list */
mutex_unlock(&deferred_probe_mutex);
dev_dbg(dev, "Retrying from deferred list\n");
bus_probe_device(dev);
mutex_lock(&deferred_probe_mutex);

put_device(dev);
}
mutex_unlock(&deferred_probe_mutex);
}
static DECLARE_WORK(deferred_probe_work, deferred_probe_work_func);

static void driver_deferred_probe_add(struct device *dev)
{
mutex_lock(&deferred_probe_mutex);
if (list_empty(&dev->deferred_probe)) {
dev_dbg(dev, "Added to deferred list\n");
list_add(&dev->deferred_probe, &deferred_probe_pending_list);
}
mutex_unlock(&deferred_probe_mutex);
}

void driver_deferred_probe_del(struct device *dev)
{
mutex_lock(&deferred_probe_mutex);
if (!list_empty(&dev->deferred_probe)) {
dev_dbg(dev, "Removed from deferred list\n");
list_del_init(&dev->deferred_probe);
}
mutex_unlock(&deferred_probe_mutex);
}

static bool driver_deferred_probe_enable = false;
/**
* driver_deferred_probe_trigger() - Kick off re-probing deferred devices
*
* This functions moves all devices from the pending list to the active
* list and schedules the deferred probe workqueue to process them. It
* should be called anytime a driver is successfully bound to a device.
*/
static void driver_deferred_probe_trigger(void)
{
if (!driver_deferred_probe_enable)
return;

/* A successful probe means that all the devices in the pending list
* should be triggered to be reprobed. Move all the deferred devices
* into the active list so they can be retried by the workqueue */
mutex_lock(&deferred_probe_mutex);
list_splice_tail_init(&deferred_probe_pending_list,
&deferred_probe_active_list);
mutex_unlock(&deferred_probe_mutex);

/* Kick the re-probe thread. It may already be scheduled, but
* it is safe to kick it again. */
queue_work(deferred_wq, &deferred_probe_work);
}

/**
* deferred_probe_initcall() - Enable probing of deferred devices
*
* We don't want to get in the way when the bulk of drivers are getting probed.
* Instead, this initcall makes sure that deferred probing is delayed until
* late_initcall time.
*/
static int deferred_probe_initcall(void)
{
deferred_wq = create_singlethread_workqueue("deferwq");
if (WARN_ON(!deferred_wq))
return -ENOMEM;

driver_deferred_probe_enable = true;
driver_deferred_probe_trigger();
return 0;
}
late_initcall(deferred_probe_initcall);

static void driver_bound(struct device *dev)
{
Expand All @@ -42,6 +169,11 @@ static void driver_bound(struct device *dev)

klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);

/* Make sure the device is no longer in one of the deferred lists
* and kick off retrying all pending devices */
driver_deferred_probe_del(dev);
driver_deferred_probe_trigger();

if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_BOUND_DRIVER, dev);
Expand Down Expand Up @@ -142,7 +274,11 @@ static int really_probe(struct device *dev, struct device_driver *drv)
driver_sysfs_remove(dev);
dev->driver = NULL;

if (ret != -ENODEV && ret != -ENXIO) {
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",
Expand Down
5 changes: 5 additions & 0 deletions trunk/include/linux/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,10 @@ struct device_dma_parameters {
* @mutex: Mutex to synchronize calls to its driver.
* @bus: Type of bus device is on.
* @driver: Which driver has allocated this
* @deferred_probe: entry in deferred_probe_list which is used to retry the
* binding of drivers which were unable to get all the resources
* needed by the device; typically because it depends on another
* driver getting probed first.
* @platform_data: Platform data specific to the device.
* Example: For devices on custom boards, as typical of embedded
* and SOC based hardware, Linux often uses platform_data to point
Expand Down Expand Up @@ -644,6 +648,7 @@ struct device {
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
struct list_head deferred_probe;
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
Expand Down
1 change: 1 addition & 0 deletions trunk/include/linux/errno.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#define ERESTARTNOHAND 514 /* restart if no handler.. */
#define ENOIOCTLCMD 515 /* No ioctl command */
#define ERESTART_RESTARTBLOCK 516 /* restart by calling sys_restart_syscall */
#define EPROBE_DEFER 517 /* Driver requests probe retry */

/* Defined for the NFSv3 protocol */
#define EBADHANDLE 521 /* Illegal NFS file handle */
Expand Down

0 comments on commit b468759

Please sign in to comment.