summaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-10-01 12:10:44 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2012-10-01 12:10:44 -0700
commit06d2fe153b9b35e57221e35831a26918f462db68 (patch)
treef77cb72dfba7f2a47ceb93e120abd9399a24a1b9 /drivers/base
parent3aebd34b1200a902a8662da8845824a02f00772e (diff)
parente0f21e6d52cc245e7d4f7e02ca4b7b6571660ec2 (diff)
downloadlinux-06d2fe153b9b35e57221e35831a26918f462db68.tar.bz2
Merge tag 'driver-core-3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core
Pull driver core merge from Greg Kroah-Hartman: "Here is the big driver core update for 3.7-rc1. A number of firmware_class.c updates (as you saw a month or so ago), and some hyper-v updates and some printk fixes as well. All patches that are outside of the drivers/base area have been acked by the respective maintainers, and have all been in the linux-next tree for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" * tag 'driver-core-3.6' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (95 commits) memory: tegra{20,30}-mc: Fix reading incorrect register in mc_readl() device.h: Add missing inline to #ifndef CONFIG_PRINTK dev_vprintk_emit memory: emif: Add ifdef CONFIG_DEBUG_FS guard for emif_debugfs_[init|exit] Documentation: Fixes some translation error in Documentation/zh_CN/gpio.txt Documentation: Remove 3 byte redundant code at the head of the Documentation/zh_CN/arm/booting Documentation: Chinese translation of Documentation/video4linux/omap3isp.txt device and dynamic_debug: Use dev_vprintk_emit and dev_printk_emit dev: Add dev_vprintk_emit and dev_printk_emit netdev_printk/netif_printk: Remove a superfluous logging colon netdev_printk/dynamic_netdev_dbg: Directly call printk_emit dev_dbg/dynamic_debug: Update to use printk_emit, optimize stack driver-core: Shut up dev_dbg_reatelimited() without DEBUG tools/hv: Parse /etc/os-release tools/hv: Check for read/write errors tools/hv: Fix exit() error code tools/hv: Fix file handle leak Tools: hv: Implement the KVP verb - KVP_OP_GET_IP_INFO Tools: hv: Rename the function kvp_get_ip_address() Tools: hv: Implement the KVP verb - KVP_OP_SET_IP_INFO Tools: hv: Add an example script to configure an interface ...
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/core.c105
-rw-r--r--drivers/base/devres.c42
-rw-r--r--drivers/base/firmware_class.c838
-rw-r--r--drivers/base/platform.c41
-rw-r--r--drivers/base/power/main.c22
5 files changed, 904 insertions, 144 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 5e6e00bc1652..abea76c36a4b 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -184,6 +184,17 @@ static void device_release(struct kobject *kobj)
struct device *dev = kobj_to_dev(kobj);
struct device_private *p = dev->p;
+ /*
+ * Some platform devices are driven without driver attached
+ * and managed resources may have been acquired. Make sure
+ * all resources are released.
+ *
+ * Drivers still can add resources into device after device
+ * is deleted but alive, so release devres here to avoid
+ * possible memory leak.
+ */
+ devres_release_all(dev);
+
if (dev->release)
dev->release(dev);
else if (dev->type && dev->type->release)
@@ -1196,13 +1207,6 @@ void device_del(struct device *dev)
bus_remove_device(dev);
driver_deferred_probe_del(dev);
- /*
- * Some platform devices are driven without driver attached
- * and managed resources may have been acquired. Make sure
- * all resources are released.
- */
- devres_release_all(dev);
-
/* Notify the platform of the removal, in case they
* need to do anything...
*/
@@ -1861,26 +1865,20 @@ void device_shutdown(void)
*/
#ifdef CONFIG_PRINTK
-int __dev_printk(const char *level, const struct device *dev,
- struct va_format *vaf)
+static int
+create_syslog_header(const struct device *dev, char *hdr, size_t hdrlen)
{
- char dict[128];
- const char *level_extra = "";
- size_t dictlen = 0;
const char *subsys;
-
- if (!dev)
- return printk("%s(NULL device *): %pV", level, vaf);
+ size_t pos = 0;
if (dev->class)
subsys = dev->class->name;
else if (dev->bus)
subsys = dev->bus->name;
else
- goto skip;
+ return 0;
- dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen,
- "SUBSYSTEM=%s", subsys);
+ pos += snprintf(hdr + pos, hdrlen - pos, "SUBSYSTEM=%s", subsys);
/*
* Add device identifier DEVICE=:
@@ -1896,32 +1894,63 @@ int __dev_printk(const char *level, const struct device *dev,
c = 'b';
else
c = 'c';
- dictlen++;
- dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen,
- "DEVICE=%c%u:%u",
- c, MAJOR(dev->devt), MINOR(dev->devt));
+ pos++;
+ pos += snprintf(hdr + pos, hdrlen - pos,
+ "DEVICE=%c%u:%u",
+ c, MAJOR(dev->devt), MINOR(dev->devt));
} else if (strcmp(subsys, "net") == 0) {
struct net_device *net = to_net_dev(dev);
- dictlen++;
- dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen,
- "DEVICE=n%u", net->ifindex);
+ pos++;
+ pos += snprintf(hdr + pos, hdrlen - pos,
+ "DEVICE=n%u", net->ifindex);
} else {
- dictlen++;
- dictlen += snprintf(dict + dictlen, sizeof(dict) - dictlen,
- "DEVICE=+%s:%s", subsys, dev_name(dev));
+ pos++;
+ pos += snprintf(hdr + pos, hdrlen - pos,
+ "DEVICE=+%s:%s", subsys, dev_name(dev));
}
-skip:
- if (level[2])
- level_extra = &level[2]; /* skip past KERN_SOH "L" */
- return printk_emit(0, level[1] - '0',
- dictlen ? dict : NULL, dictlen,
- "%s %s: %s%pV",
- dev_driver_string(dev), dev_name(dev),
- level_extra, vaf);
+ return pos;
+}
+EXPORT_SYMBOL(create_syslog_header);
+
+int dev_vprintk_emit(int level, const struct device *dev,
+ const char *fmt, va_list args)
+{
+ char hdr[128];
+ size_t hdrlen;
+
+ hdrlen = create_syslog_header(dev, hdr, sizeof(hdr));
+
+ return vprintk_emit(0, level, hdrlen ? hdr : NULL, hdrlen, fmt, args);
+}
+EXPORT_SYMBOL(dev_vprintk_emit);
+
+int dev_printk_emit(int level, const struct device *dev, const char *fmt, ...)
+{
+ va_list args;
+ int r;
+
+ va_start(args, fmt);
+
+ r = dev_vprintk_emit(level, dev, fmt, args);
+
+ va_end(args);
+
+ return r;
+}
+EXPORT_SYMBOL(dev_printk_emit);
+
+static int __dev_printk(const char *level, const struct device *dev,
+ struct va_format *vaf)
+{
+ if (!dev)
+ return printk("%s(NULL device *): %pV", level, vaf);
+
+ return dev_printk_emit(level[1] - '0', dev,
+ "%s %s: %pV",
+ dev_driver_string(dev), dev_name(dev), vaf);
}
-EXPORT_SYMBOL(__dev_printk);
int dev_printk(const char *level, const struct device *dev,
const char *fmt, ...)
@@ -1936,6 +1965,7 @@ int dev_printk(const char *level, const struct device *dev,
vaf.va = &args;
r = __dev_printk(level, dev, &vaf);
+
va_end(args);
return r;
@@ -1955,6 +1985,7 @@ int func(const struct device *dev, const char *fmt, ...) \
vaf.va = &args; \
\
r = __dev_printk(kern_level, dev, &vaf); \
+ \
va_end(args); \
\
return r; \
diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index 2360adb7a58f..8731979d668a 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -144,6 +144,48 @@ EXPORT_SYMBOL_GPL(devres_alloc);
#endif
/**
+ * devres_for_each_res - Resource iterator
+ * @dev: Device to iterate resource from
+ * @release: Look for resources associated with this release function
+ * @match: Match function (optional)
+ * @match_data: Data for the match function
+ * @fn: Function to be called for each matched resource.
+ * @data: Data for @fn, the 3rd parameter of @fn
+ *
+ * Call @fn for each devres of @dev which is associated with @release
+ * and for which @match returns 1.
+ *
+ * RETURNS:
+ * void
+ */
+void devres_for_each_res(struct device *dev, dr_release_t release,
+ dr_match_t match, void *match_data,
+ void (*fn)(struct device *, void *, void *),
+ void *data)
+{
+ struct devres_node *node;
+ struct devres_node *tmp;
+ unsigned long flags;
+
+ if (!fn)
+ return;
+
+ spin_lock_irqsave(&dev->devres_lock, flags);
+ list_for_each_entry_safe_reverse(node, tmp,
+ &dev->devres_head, entry) {
+ struct devres *dr = container_of(node, struct devres, node);
+
+ if (node->release != release)
+ continue;
+ if (match && !match(dev, dr->data, match_data))
+ continue;
+ fn(dev, dr->data, data);
+ }
+ spin_unlock_irqrestore(&dev->devres_lock, flags);
+}
+EXPORT_SYMBOL_GPL(devres_for_each_res);
+
+/**
* devres_free - Free device resource data
* @res: Pointer to devres data to free
*
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 803cfc1597a9..6e210802c37b 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -21,6 +21,13 @@
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/async.h>
+#include <linux/pm.h>
+#include <linux/suspend.h>
+#include <linux/syscore_ops.h>
+
+#include "base.h"
MODULE_AUTHOR("Manuel Estrada Sainz");
MODULE_DESCRIPTION("Multi purpose firmware loading support");
@@ -85,23 +92,168 @@ static inline long firmware_loading_timeout(void)
return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT;
}
-/* fw_lock could be moved to 'struct firmware_priv' but since it is just
- * guarding for corner cases a global lock should be OK */
-static DEFINE_MUTEX(fw_lock);
+struct firmware_cache {
+ /* firmware_buf instance will be added into the below list */
+ spinlock_t lock;
+ struct list_head head;
+ int state;
+
+#ifdef CONFIG_PM_SLEEP
+ /*
+ * Names of firmware images which have been cached successfully
+ * will be added into the below list so that device uncache
+ * helper can trace which firmware images have been cached
+ * before.
+ */
+ spinlock_t name_lock;
+ struct list_head fw_names;
+
+ wait_queue_head_t wait_queue;
+ int cnt;
+ struct delayed_work work;
+
+ struct notifier_block pm_notify;
+#endif
+};
-struct firmware_priv {
+struct firmware_buf {
+ struct kref ref;
+ struct list_head list;
struct completion completion;
- struct firmware *fw;
+ struct firmware_cache *fwc;
unsigned long status;
+ void *data;
+ size_t size;
struct page **pages;
int nr_pages;
int page_array_size;
+ char fw_id[];
+};
+
+struct fw_cache_entry {
+ struct list_head list;
+ char name[];
+};
+
+struct firmware_priv {
struct timer_list timeout;
- struct device dev;
bool nowait;
- char fw_id[];
+ struct device dev;
+ struct firmware_buf *buf;
+ struct firmware *fw;
+};
+
+struct fw_name_devm {
+ unsigned long magic;
+ char name[];
};
+#define to_fwbuf(d) container_of(d, struct firmware_buf, ref)
+
+#define FW_LOADER_NO_CACHE 0
+#define FW_LOADER_START_CACHE 1
+
+static int fw_cache_piggyback_on_request(const char *name);
+
+/* fw_lock could be moved to 'struct firmware_priv' but since it is just
+ * guarding for corner cases a global lock should be OK */
+static DEFINE_MUTEX(fw_lock);
+
+static struct firmware_cache fw_cache;
+
+static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
+ struct firmware_cache *fwc)
+{
+ struct firmware_buf *buf;
+
+ buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1 , GFP_ATOMIC);
+
+ if (!buf)
+ return buf;
+
+ kref_init(&buf->ref);
+ strcpy(buf->fw_id, fw_name);
+ buf->fwc = fwc;
+ init_completion(&buf->completion);
+
+ pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf);
+
+ return buf;
+}
+
+static struct firmware_buf *__fw_lookup_buf(const char *fw_name)
+{
+ struct firmware_buf *tmp;
+ struct firmware_cache *fwc = &fw_cache;
+
+ list_for_each_entry(tmp, &fwc->head, list)
+ if (!strcmp(tmp->fw_id, fw_name))
+ return tmp;
+ return NULL;
+}
+
+static int fw_lookup_and_allocate_buf(const char *fw_name,
+ struct firmware_cache *fwc,
+ struct firmware_buf **buf)
+{
+ struct firmware_buf *tmp;
+
+ spin_lock(&fwc->lock);
+ tmp = __fw_lookup_buf(fw_name);
+ if (tmp) {
+ kref_get(&tmp->ref);
+ spin_unlock(&fwc->lock);
+ *buf = tmp;
+ return 1;
+ }
+ tmp = __allocate_fw_buf(fw_name, fwc);
+ if (tmp)
+ list_add(&tmp->list, &fwc->head);
+ spin_unlock(&fwc->lock);
+
+ *buf = tmp;
+
+ return tmp ? 0 : -ENOMEM;
+}
+
+static struct firmware_buf *fw_lookup_buf(const char *fw_name)
+{
+ struct firmware_buf *tmp;
+ struct firmware_cache *fwc = &fw_cache;
+
+ spin_lock(&fwc->lock);
+ tmp = __fw_lookup_buf(fw_name);
+ spin_unlock(&fwc->lock);
+
+ return tmp;
+}
+
+static void __fw_free_buf(struct kref *ref)
+{
+ struct firmware_buf *buf = to_fwbuf(ref);
+ struct firmware_cache *fwc = buf->fwc;
+ int i;
+
+ pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
+ __func__, buf->fw_id, buf, buf->data,
+ (unsigned int)buf->size);
+
+ spin_lock(&fwc->lock);
+ list_del(&buf->list);
+ spin_unlock(&fwc->lock);
+
+ vunmap(buf->data);
+ for (i = 0; i < buf->nr_pages; i++)
+ __free_page(buf->pages[i]);
+ kfree(buf->pages);
+ kfree(buf);
+}
+
+static void fw_free_buf(struct firmware_buf *buf)
+{
+ kref_put(&buf->ref, __fw_free_buf);
+}
+
static struct firmware_priv *to_firmware_priv(struct device *dev)
{
return container_of(dev, struct firmware_priv, dev);
@@ -109,9 +261,10 @@ static struct firmware_priv *to_firmware_priv(struct device *dev)
static void fw_load_abort(struct firmware_priv *fw_priv)
{
- set_bit(FW_STATUS_ABORT, &fw_priv->status);
- wmb();
- complete(&fw_priv->completion);
+ struct firmware_buf *buf = fw_priv->buf;
+
+ set_bit(FW_STATUS_ABORT, &buf->status);
+ complete_all(&buf->completion);
}
static ssize_t firmware_timeout_show(struct class *class,
@@ -154,11 +307,7 @@ static struct class_attribute firmware_class_attrs[] = {
static void fw_dev_release(struct device *dev)
{
struct firmware_priv *fw_priv = to_firmware_priv(dev);
- int i;
- for (i = 0; i < fw_priv->nr_pages; i++)
- __free_page(fw_priv->pages[i]);
- kfree(fw_priv->pages);
kfree(fw_priv);
module_put(THIS_MODULE);
@@ -168,7 +317,7 @@ static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct firmware_priv *fw_priv = to_firmware_priv(dev);
- if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->fw_id))
+ if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->buf->fw_id))
return -ENOMEM;
if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout))
return -ENOMEM;
@@ -189,20 +338,16 @@ static ssize_t firmware_loading_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct firmware_priv *fw_priv = to_firmware_priv(dev);
- int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status);
+ int loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status);
return sprintf(buf, "%d\n", loading);
}
+/* firmware holds the ownership of pages */
static void firmware_free_data(const struct firmware *fw)
{
- int i;
- vunmap(fw->data);
- if (fw->pages) {
- for (i = 0; i < PFN_UP(fw->size); i++)
- __free_page(fw->pages[i]);
- kfree(fw->pages);
- }
+ WARN_ON(!fw->priv);
+ fw_free_buf(fw->priv);
}
/* Some architectures don't have PAGE_KERNEL_RO */
@@ -227,45 +372,33 @@ static ssize_t firmware_loading_store(struct device *dev,
const char *buf, size_t count)
{
struct firmware_priv *fw_priv = to_firmware_priv(dev);
+ struct firmware_buf *fw_buf = fw_priv->buf;
int loading = simple_strtol(buf, NULL, 10);
int i;
mutex_lock(&fw_lock);
- if (!fw_priv->fw)
+ if (!fw_buf)
goto out;
switch (loading) {
case 1:
- firmware_free_data(fw_priv->fw);
- memset(fw_priv->fw, 0, sizeof(struct firmware));
- /* If the pages are not owned by 'struct firmware' */
- for (i = 0; i < fw_priv->nr_pages; i++)
- __free_page(fw_priv->pages[i]);
- kfree(fw_priv->pages);
- fw_priv->pages = NULL;
- fw_priv->page_array_size = 0;
- fw_priv->nr_pages = 0;
- set_bit(FW_STATUS_LOADING, &fw_priv->status);
+ /* discarding any previous partial load */
+ if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) {
+ for (i = 0; i < fw_buf->nr_pages; i++)
+ __free_page(fw_buf->pages[i]);
+ kfree(fw_buf->pages);
+ fw_buf->pages = NULL;
+ fw_buf->page_array_size = 0;
+ fw_buf->nr_pages = 0;
+ set_bit(FW_STATUS_LOADING, &fw_buf->status);
+ }
break;
case 0:
- if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) {
- vunmap(fw_priv->fw->data);
- fw_priv->fw->data = vmap(fw_priv->pages,
- fw_priv->nr_pages,
- 0, PAGE_KERNEL_RO);
- if (!fw_priv->fw->data) {
- dev_err(dev, "%s: vmap() failed\n", __func__);
- goto err;
- }
- /* Pages are now owned by 'struct firmware' */
- fw_priv->fw->pages = fw_priv->pages;
- fw_priv->pages = NULL;
-
- fw_priv->page_array_size = 0;
- fw_priv->nr_pages = 0;
- complete(&fw_priv->completion);
- clear_bit(FW_STATUS_LOADING, &fw_priv->status);
+ if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) {
+ set_bit(FW_STATUS_DONE, &fw_buf->status);
+ clear_bit(FW_STATUS_LOADING, &fw_buf->status);
+ complete_all(&fw_buf->completion);
break;
}
/* fallthrough */
@@ -273,7 +406,6 @@ static ssize_t firmware_loading_store(struct device *dev,
dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading);
/* fallthrough */
case -1:
- err:
fw_load_abort(fw_priv);
break;
}
@@ -290,21 +422,21 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
{
struct device *dev = kobj_to_dev(kobj);
struct firmware_priv *fw_priv = to_firmware_priv(dev);
- struct firmware *fw;
+ struct firmware_buf *buf;
ssize_t ret_count;
mutex_lock(&fw_lock);
- fw = fw_priv->fw;
- if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
+ buf = fw_priv->buf;
+ if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
ret_count = -ENODEV;
goto out;
}
- if (offset > fw->size) {
+ if (offset > buf->size) {
ret_count = 0;
goto out;
}
- if (count > fw->size - offset)
- count = fw->size - offset;
+ if (count > buf->size - offset)
+ count = buf->size - offset;
ret_count = count;
@@ -314,11 +446,11 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj,
int page_ofs = offset & (PAGE_SIZE-1);
int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);
- page_data = kmap(fw_priv->pages[page_nr]);
+ page_data = kmap(buf->pages[page_nr]);
memcpy(buffer, page_data + page_ofs, page_cnt);
- kunmap(fw_priv->pages[page_nr]);
+ kunmap(buf->pages[page_nr]);
buffer += page_cnt;
offset += page_cnt;
count -= page_cnt;
@@ -330,12 +462,13 @@ out:
static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
{
+ struct firmware_buf *buf = fw_priv->buf;
int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT;
/* If the array of pages is too small, grow it... */
- if (fw_priv->page_array_size < pages_needed) {
+ if (buf->page_array_size < pages_needed) {
int new_array_size = max(pages_needed,
- fw_priv->page_array_size * 2);
+ buf->page_array_size * 2);
struct page **new_pages;
new_pages = kmalloc(new_array_size * sizeof(void *),
@@ -344,24 +477,24 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
fw_load_abort(fw_priv);
return -ENOMEM;
}
- memcpy(new_pages, fw_priv->pages,
- fw_priv->page_array_size * sizeof(void *));
- memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) *
- (new_array_size - fw_priv->page_array_size));
- kfree(fw_priv->pages);
- fw_priv->pages = new_pages;
- fw_priv->page_array_size = new_array_size;
+ memcpy(new_pages, buf->pages,
+ buf->page_array_size * sizeof(void *));
+ memset(&new_pages[buf->page_array_size], 0, sizeof(void *) *
+ (new_array_size - buf->page_array_size));
+ kfree(buf->pages);
+ buf->pages = new_pages;
+ buf->page_array_size = new_array_size;
}
- while (fw_priv->nr_pages < pages_needed) {
- fw_priv->pages[fw_priv->nr_pages] =
+ while (buf->nr_pages < pages_needed) {
+ buf->pages[buf->nr_pages] =
alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
- if (!fw_priv->pages[fw_priv->nr_pages]) {
+ if (!buf->pages[buf->nr_pages]) {
fw_load_abort(fw_priv);
return -ENOMEM;
}
- fw_priv->nr_pages++;
+ buf->nr_pages++;
}
return 0;
}
@@ -384,18 +517,19 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
{
struct device *dev = kobj_to_dev(kobj);
struct firmware_priv *fw_priv = to_firmware_priv(dev);
- struct firmware *fw;
+ struct firmware_buf *buf;
ssize_t retval;
if (!capable(CAP_SYS_RAWIO))
return -EPERM;
mutex_lock(&fw_lock);
- fw = fw_priv->fw;
- if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
+ buf = fw_priv->buf;
+ if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) {
retval = -ENODEV;
goto out;
}
+
retval = fw_realloc_buffer(fw_priv, offset + count);
if (retval)
goto out;
@@ -408,17 +542,17 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
int page_ofs = offset & (PAGE_SIZE - 1);
int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);
- page_data = kmap(fw_priv->pages[page_nr]);
+ page_data = kmap(buf->pages[page_nr]);
memcpy(page_data + page_ofs, buffer, page_cnt);
- kunmap(fw_priv->pages[page_nr]);
+ kunmap(buf->pages[page_nr]);
buffer += page_cnt;
offset += page_cnt;
count -= page_cnt;
}
- fw->size = max_t(size_t, offset, fw->size);
+ buf->size = max_t(size_t, offset, buf->size);
out:
mutex_unlock(&fw_lock);
return retval;
@@ -445,35 +579,120 @@ fw_create_instance(struct firmware *firmware, const char *fw_name,
struct firmware_priv *fw_priv;
struct device *f_dev;
- fw_priv = kzalloc(sizeof(*fw_priv) + strlen(fw_name) + 1 , GFP_KERNEL);
+ fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL);
if (!fw_priv) {
dev_err(device, "%s: kmalloc failed\n", __func__);
- return ERR_PTR(-ENOMEM);
+ fw_priv = ERR_PTR(-ENOMEM);
+ goto exit;
}
- fw_priv->fw = firmware;
fw_priv->nowait = nowait;
- strcpy(fw_priv->fw_id, fw_name);
- init_completion(&fw_priv->completion);
+ fw_priv->fw = firmware;
setup_timer(&fw_priv->timeout,
firmware_class_timeout, (u_long) fw_priv);
f_dev = &fw_priv->dev;
device_initialize(f_dev);
- dev_set_name(f_dev, "%s", dev_name(device));
+ dev_set_name(f_dev, "%s", fw_name);
f_dev->parent = device;
f_dev->class = &firmware_class;
-
+exit:
return fw_priv;
}
+/* one pages buffer is mapped/unmapped only once */
+static int fw_map_pages_buf(struct firmware_buf *buf)
+{
+ buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO);
+ if (!buf->data)
+ return -ENOMEM;
+ return 0;
+}
+
+/* store the pages buffer info firmware from buf */
+static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
+{
+ fw->priv = buf;
+ fw->pages = buf->pages;
+ fw->size = buf->size;
+ fw->data = buf->data;
+
+ pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
+ __func__, buf->fw_id, buf, buf->data,
+ (unsigned int)buf->size);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void fw_name_devm_release(struct device *dev, void *res)
+{
+ struct fw_name_devm *fwn = res;
+
+ if (fwn->magic == (unsigned long)&fw_cache)
+ pr_debug("%s: fw_name-%s devm-%p released\n",
+ __func__, fwn->name, res);
+}
+
+static int fw_devm_match(struct device *dev, void *res,
+ void *match_data)
+{
+ struct fw_name_devm *fwn = res;
+
+ return (fwn->magic == (unsigned long)&fw_cache) &&
+ !strcmp(fwn->name, match_data);
+}
+
+static struct fw_name_devm *fw_find_devm_name(struct device *dev,
+ const char *name)
+{
+ struct fw_name_devm *fwn;
+
+ fwn = devres_find(dev, fw_name_devm_release,
+ fw_devm_match, (void *)name);
+ return fwn;
+}
+
+/* add firmware name into devres list */
+static int fw_add_devm_name(struct device *dev, const char *name)
+{
+ struct fw_name_devm *fwn;
+
+ fwn = fw_find_devm_name(dev, name);
+ if (fwn)
+ return 1;
+
+ fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm) +
+ strlen(name) + 1, GFP_KERNEL);
+ if (!fwn)
+ return -ENOMEM;
+
+ fwn->magic = (unsigned long)&fw_cache;
+ strcpy(fwn->name, name);
+ devres_add(dev, fwn);
+
+ return 0;
+}
+#else
+static int fw_add_devm_name(struct device *dev, const char *name)
+{
+ return 0;
+}
+#endif
+
+static void _request_firmware_cleanup(const struct firmware **firmware_p)
+{
+ release_firmware(*firmware_p);
+ *firmware_p = NULL;
+}
+
static struct firmware_priv *
_request_firmware_prepare(const struct firmware **firmware_p, const char *name,
struct device *device, bool uevent, bool nowait)
{
struct firmware *firmware;
- struct firmware_priv *fw_priv;
+ struct firmware_priv *fw_priv = NULL;
+ struct firmware_buf *buf;
+ int ret;
if (!firmware_p)
return ERR_PTR(-EINVAL);
@@ -490,18 +709,46 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name,
return NULL;
}
- fw_priv = fw_create_instance(firmware, name, device, uevent, nowait);
- if (IS_ERR(fw_priv)) {
- release_firmware(firmware);
+ ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
+ if (!ret)
+ fw_priv = fw_create_instance(firmware, name, device,
+ uevent, nowait);
+
+ if (IS_ERR(fw_priv) || ret < 0) {
+ kfree(firmware);
*firmware_p = NULL;
+ return ERR_PTR(-ENOMEM);
+ } else if (fw_priv) {
+ fw_priv->buf = buf;
+
+ /*
+ * bind with 'buf' now to avoid warning in failure path
+ * of requesting firmware.
+ */
+ firmware->priv = buf;
+ return fw_priv;
}
- return fw_priv;
-}
-static void _request_firmware_cleanup(const struct firmware **firmware_p)
-{
- release_firmware(*firmware_p);
- *firmware_p = NULL;
+ /* share the cached buf, which is inprogessing or completed */
+ check_status:
+ mutex_lock(&fw_lock);
+ if (test_bit(FW_STATUS_ABORT, &buf->status)) {
+ fw_priv = ERR_PTR(-ENOENT);
+ firmware->priv = buf;
+ _request_firmware_cleanup(firmware_p);
+ goto exit;
+ } else if (test_bit(FW_STATUS_DONE, &buf->status)) {
+ fw_priv = NULL;
+ fw_set_page_data(buf, firmware);
+ goto exit;
+ }
+ mutex_unlock(&fw_lock);
+ wait_for_completion(&buf->completion);
+ goto check_status;
+
+exit:
+ mutex_unlock(&fw_lock);
+ return fw_priv;
}
static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
@@ -509,6 +756,8 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
{
int retval = 0;
struct device *f_dev = &fw_priv->dev;
+ struct firmware_buf *buf = fw_priv->buf;
+ struct firmware_cache *fwc = &fw_cache;
dev_set_uevent_suppress(f_dev, true);
@@ -535,7 +784,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
if (uevent) {
dev_set_uevent_suppress(f_dev, false);
- dev_dbg(f_dev, "firmware: requesting %s\n", fw_priv->fw_id);
+ dev_dbg(f_dev, "firmware: requesting %s\n", buf->fw_id);
if (timeout != MAX_SCHEDULE_TIMEOUT)
mod_timer(&fw_priv->timeout,
round_jiffies_up(jiffies + timeout));
@@ -543,15 +792,40 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD);
}
- wait_for_completion(&fw_priv->completion);
+ wait_for_completion(&buf->completion);
- set_bit(FW_STATUS_DONE, &fw_priv->status);
del_timer_sync(&fw_priv->timeout);
mutex_lock(&fw_lock);
- if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status))
+ if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status))
retval = -ENOENT;
- fw_priv->fw = NULL;
+
+ /*
+ * add firmware name into devres list so that we can auto cache
+ * and uncache firmware for device.
+ *
+ * f_dev->parent may has been deleted already, but the problem
+ * should be fixed in devres or driver core.
+ */
+ if (!retval && f_dev->parent)
+ fw_add_devm_name(f_dev->parent, buf->fw_id);
+
+ if (!retval)
+ retval = fw_map_pages_buf(buf);
+
+ /*
+ * After caching firmware image is started, let it piggyback
+ * on request firmware.
+ */
+ if (!retval && fwc->state == FW_LOADER_START_CACHE) {
+ if (fw_cache_piggyback_on_request(buf->fw_id))
+ kref_get(&buf->ref);
+ }
+
+ /* pass the pages buffer to driver at the last minute */
+ fw_set_page_data(buf, fw_priv->fw);
+
+ fw_priv->buf = NULL;
mutex_unlock(&fw_lock);
device_remove_file(f_dev, &dev_attr_loading);
@@ -578,6 +852,8 @@ err_put_dev:
* @name will be used as $FIRMWARE in the uevent environment and
* should be distinctive enough not to be confused with any other
* firmware image for this or any other device.
+ *
+ * Caller must hold the reference count of @device.
**/
int
request_firmware(const struct firmware **firmware_p, const char *name,
@@ -659,6 +935,7 @@ static void request_firmware_work_func(struct work_struct *work)
out:
fw_work->cont(fw, fw_work->context);
+ put_device(fw_work->device);
module_put(fw_work->module);
kfree(fw_work);
@@ -677,9 +954,15 @@ static void request_firmware_work_func(struct work_struct *work)
* @cont: function will be called asynchronously when the firmware
* request is over.
*
- * Asynchronous variant of request_firmware() for user contexts where
- * it is not possible to sleep for long time. It can't be called
- * in atomic contexts.
+ * Caller must hold the reference count of @device.
+ *
+ * Asynchronous variant of request_firmware() for user contexts:
+ * - sleep for as small periods as possible since it may
+ * increase kernel boot time of built-in device drivers
+ * requesting firmware in their ->probe() methods, if
+ * @gfp is GFP_KERNEL.
+ *
+ * - can't sleep at all if @gfp is GFP_ATOMIC.
**/
int
request_firmware_nowait(
@@ -705,18 +988,363 @@ request_firmware_nowait(
return -EFAULT;
}
+ get_device(fw_work->device);
INIT_WORK(&fw_work->work, request_firmware_work_func);
schedule_work(&fw_work->work);
return 0;
}
+/**
+ * cache_firmware - cache one firmware image in kernel memory space
+ * @fw_name: the firmware image name
+ *
+ * Cache firmware in kernel memory so that drivers can use it when
+ * system isn't ready for them to request firmware image from userspace.
+ * Once it returns successfully, driver can use request_firmware or its
+ * nowait version to get the cached firmware without any interacting
+ * with userspace
+ *
+ * Return 0 if the firmware image has been cached successfully
+ * Return !0 otherwise
+ *
+ */
+int cache_firmware(const char *fw_name)
+{
+ int ret;
+ const struct firmware *fw;
+
+ pr_debug("%s: %s\n", __func__, fw_name);
+
+ ret = request_firmware(&fw, fw_name, NULL);
+ if (!ret)
+ kfree(fw);
+
+ pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret);
+
+ return ret;
+}
+
+/**
+ * uncache_firmware - remove one cached firmware image
+ * @fw_name: the firmware image name
+ *
+ * Uncache one firmware image which has been cached successfully
+ * before.
+ *
+ * Return 0 if the firmware cache has been removed successfully
+ * Return !0 otherwise
+ *
+ */
+int uncache_firmware(const char *fw_name)
+{
+ struct firmware_buf *buf;
+ struct firmware fw;
+
+ pr_debug("%s: %s\n", __func__, fw_name);
+
+ if (fw_get_builtin_firmware(&fw, fw_name))
+ return 0;
+
+ buf = fw_lookup_buf(fw_name);
+ if (buf) {
+ fw_free_buf(buf);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static struct fw_cache_entry *alloc_fw_cache_entry(const char *name)
+{
+ struct fw_cache_entry *fce;
+
+ fce = kzalloc(sizeof(*fce) + strlen(name) + 1, GFP_ATOMIC);
+ if (!fce)
+ goto exit;
+
+ strcpy(fce->name, name);
+exit:
+ return fce;
+}
+
+static int fw_cache_piggyback_on_request(const char *name)
+{
+ struct firmware_cache *fwc = &fw_cache;
+ struct fw_cache_entry *fce;
+ int ret = 0;
+
+ spin_lock(&fwc->name_lock);
+ list_for_each_entry(fce, &fwc->fw_names, list) {
+ if (!strcmp(fce->name, name))
+ goto found;
+ }
+
+ fce = alloc_fw_cache_entry(name);
+ if (fce) {
+ ret = 1;
+ list_add(&fce->list, &fwc->fw_names);
+ pr_debug("%s: fw: %s\n", __func__, name);
+ }
+found:
+ spin_unlock(&fwc->name_lock);
+ return ret;
+}
+
+static void free_fw_cache_entry(struct fw_cache_entry *fce)
+{
+ kfree(fce);
+}
+
+static void __async_dev_cache_fw_image(void *fw_entry,
+ async_cookie_t cookie)
+{
+ struct fw_cache_entry *fce = fw_entry;
+ struct firmware_cache *fwc = &fw_cache;
+ int ret;
+
+ ret = cache_firmware(fce->name);
+ if (ret) {
+ spin_lock(&fwc->name_lock);
+ list_del(&fce->list);
+ spin_unlock(&fwc->name_lock);
+
+ free_fw_cache_entry(fce);
+ }
+
+ spin_lock(&fwc->name_lock);
+ fwc->cnt--;
+ spin_unlock(&fwc->name_lock);
+
+ wake_up(&fwc->wait_queue);
+}
+
+/* called with dev->devres_lock held */
+static void dev_create_fw_entry(struct device *dev, void *res,
+ void *data)
+{
+ struct fw_name_devm *fwn = res;
+ const char *fw_name = fwn->name;
+ struct list_head *head = data;
+ struct fw_cache_entry *fce;
+
+ fce = alloc_fw_cache_entry(fw_name);
+ if (fce)
+ list_add(&fce->list, head);
+}
+
+static int devm_name_match(struct device *dev, void *res,
+ void *match_data)
+{
+ struct fw_name_devm *fwn = res;
+ return (fwn->magic == (unsigned long)match_data);
+}
+
+static void dev_cache_fw_image(struct device *dev, void *data)
+{
+ LIST_HEAD(todo);
+ struct fw_cache_entry *fce;
+ struct fw_cache_entry *fce_next;
+ struct firmware_cache *fwc = &fw_cache;
+
+ devres_for_each_res(dev, fw_name_devm_release,
+ devm_name_match, &fw_cache,
+ dev_create_fw_entry, &todo);
+
+ list_for_each_entry_safe(fce, fce_next, &todo, list) {
+ list_del(&fce->list);
+
+ spin_lock(&fwc->name_lock);
+ fwc->cnt++;
+ list_add(&fce->list, &fwc->fw_names);
+ spin_unlock(&fwc->name_lock);
+
+ async_schedule(__async_dev_cache_fw_image, (void *)fce);
+ }
+}
+
+static void __device_uncache_fw_images(void)
+{
+ struct firmware_cache *fwc = &fw_cache;
+ struct fw_cache_entry *fce;
+
+ spin_lock(&fwc->name_lock);
+ while (!list_empty(&fwc->fw_names)) {
+ fce = list_entry(fwc->fw_names.next,
+ struct fw_cache_entry, list);
+ list_del(&fce->list);
+ spin_unlock(&fwc->name_lock);
+
+ uncache_firmware(fce->name);
+ free_fw_cache_entry(fce);
+
+ spin_lock(&fwc->name_lock);
+ }
+ spin_unlock(&fwc->name_lock);
+}
+
+/**
+ * device_cache_fw_images - cache devices' firmware
+ *
+ * If one device called request_firmware or its nowait version
+ * successfully before, the firmware names are recored into the
+ * device's devres link list, so device_cache_fw_images can call
+ * cache_firmware() to cache these firmwares for the device,
+ * then the device driver can load its firmwares easily at
+ * time when system is not ready to complete loading firmware.
+ */
+static void device_cache_fw_images(void)
+{
+ struct firmware_cache *fwc = &fw_cache;
+ int old_timeout;
+ DEFINE_WAIT(wait);
+
+ pr_debug("%s\n", __func__);
+
+ /*
+ * use small loading timeout for caching devices' firmware
+ * because all these firmware images have been loaded
+ * successfully at lease once, also system is ready for
+ * completing firmware loading now. The maximum size of
+ * firmware in current distributions is about 2M bytes,
+ * so 10 secs should be enough.
+ */
+ old_timeout = loading_timeout;
+ loading_timeout = 10;
+
+ mutex_lock(&fw_lock);
+ fwc->state = FW_LOADER_START_CACHE;
+ dpm_for_each_dev(NULL, dev_cache_fw_image);
+ mutex_unlock(&fw_lock);
+
+ /* wait for completion of caching firmware for all devices */
+ spin_lock(&fwc->name_lock);
+ for (;;) {
+ prepare_to_wait(&fwc->wait_queue, &wait,
+ TASK_UNINTERRUPTIBLE);
+ if (!fwc->cnt)
+ break;
+
+ spin_unlock(&fwc->name_lock);
+
+ schedule();
+
+ spin_lock(&fwc->name_lock);
+ }
+ spin_unlock(&fwc->name_lock);
+ finish_wait(&fwc->wait_queue, &wait);
+
+ loading_timeout = old_timeout;
+}
+
+/**
+ * device_uncache_fw_images - uncache devices' firmware
+ *
+ * uncache all firmwares which have been cached successfully
+ * by device_uncache_fw_images earlier
+ */
+static void device_uncache_fw_images(void)
+{
+ pr_debug("%s\n", __func__);
+ __device_uncache_fw_images();
+}
+
+static void device_uncache_fw_images_work(struct work_struct *work)
+{
+ device_uncache_fw_images();
+}
+
+/**
+ * device_uncache_fw_images_delay - uncache devices firmwares
+ * @delay: number of milliseconds to delay uncache device firmwares
+ *
+ * uncache all devices's firmwares which has been cached successfully
+ * by device_cache_fw_images after @delay milliseconds.
+ */
+static void device_uncache_fw_images_delay(unsigned long delay)
+{
+ schedule_delayed_work(&fw_cache.work,
+ msecs_to_jiffies(delay));
+}
+
+static int fw_pm_notify(struct notifier_block *notify_block,
+ unsigned long mode, void *unused)
+{
+ switch (mode) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ device_cache_fw_images();
+ break;
+
+ case PM_POST_SUSPEND:
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ /*
+ * In case that system sleep failed and syscore_suspend is
+ * not called.
+ */
+ mutex_lock(&fw_lock);
+ fw_cache.state = FW_LOADER_NO_CACHE;
+ mutex_unlock(&fw_lock);
+
+ device_uncache_fw_images_delay(10 * MSEC_PER_SEC);
+ break;
+ }
+
+ return 0;
+}
+
+/* stop caching firmware once syscore_suspend is reached */
+static int fw_suspend(void)
+{
+ fw_cache.state = FW_LOADER_NO_CACHE;
+ return 0;
+}
+
+static struct syscore_ops fw_syscore_ops = {
+ .suspend = fw_suspend,
+};
+#else
+static int fw_cache_piggyback_on_request(const char *name)
+{
+ return 0;
+}
+#endif
+
+static void __init fw_cache_init(void)
+{
+ spin_lock_init(&fw_cache.lock);
+ INIT_LIST_HEAD(&fw_cache.head);
+ fw_cache.state = FW_LOADER_NO_CACHE;
+
+#ifdef CONFIG_PM_SLEEP
+ spin_lock_init(&fw_cache.name_lock);
+ INIT_LIST_HEAD(&fw_cache.fw_names);
+ fw_cache.cnt = 0;
+
+ init_waitqueue_head(&fw_cache.wait_queue);
+ INIT_DELAYED_WORK(&fw_cache.work,
+ device_uncache_fw_images_work);
+
+ fw_cache.pm_notify.notifier_call = fw_pm_notify;
+ register_pm_notifier(&fw_cache.pm_notify);
+
+ register_syscore_ops(&fw_syscore_ops);
+#endif
+}
+
static int __init firmware_class_init(void)
{
+ fw_cache_init();
return class_register(&firmware_class);
}
static void __exit firmware_class_exit(void)
{
+#ifdef CONFIG_PM_SLEEP
+ unregister_syscore_ops(&fw_syscore_ops);
+ unregister_pm_notifier(&fw_cache.pm_notify);
+#endif
class_unregister(&firmware_class);
}
@@ -726,3 +1354,5 @@ module_exit(firmware_class_exit);
EXPORT_SYMBOL(release_firmware);
EXPORT_SYMBOL(request_firmware);
EXPORT_SYMBOL(request_firmware_nowait);
+EXPORT_SYMBOL_GPL(cache_firmware);
+EXPORT_SYMBOL_GPL(uncache_firmware);
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index a1a722502587..ddeca142293c 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -20,9 +20,13 @@
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
+#include <linux/idr.h>
#include "base.h"
+/* For automatically allocated device IDs */
+static DEFINE_IDA(platform_devid_ida);
+
#define to_platform_driver(drv) (container_of((drv), struct platform_driver, \
driver))
@@ -99,6 +103,9 @@ struct resource *platform_get_resource_byname(struct platform_device *dev,
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
+ if (unlikely(!r->name))
+ continue;
+
if (type == resource_type(r) && !strcmp(r->name, name))
return r;
}
@@ -263,7 +270,7 @@ EXPORT_SYMBOL_GPL(platform_device_add_data);
*/
int platform_device_add(struct platform_device *pdev)
{
- int i, ret = 0;
+ int i, ret;
if (!pdev)
return -EINVAL;
@@ -273,10 +280,27 @@ int platform_device_add(struct platform_device *pdev)
pdev->dev.bus = &platform_bus_type;
- if (pdev->id != -1)
+ switch (pdev->id) {
+ default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
- else
+ break;
+ case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name);
+ break;
+ case PLATFORM_DEVID_AUTO:
+ /*
+ * Automatically allocated device ID. We mark it as such so
+ * that we remember it must be freed, and we append a suffix
+ * to avoid namespace collision with explicit IDs.
+ */
+ 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];
@@ -309,6 +333,11 @@ int platform_device_add(struct platform_device *pdev)
return ret;
failed:
+ if (pdev->id_auto) {
+ ida_simple_remove(&platform_devid_ida, pdev->id);
+ pdev->id = PLATFORM_DEVID_AUTO;
+ }
+
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
@@ -317,6 +346,7 @@ int platform_device_add(struct platform_device *pdev)
release_resource(r);
}
+ err_out:
return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);
@@ -336,6 +366,11 @@ void platform_device_del(struct platform_device *pdev)
if (pdev) {
device_del(&pdev->dev);
+ if (pdev->id_auto) {
+ ida_simple_remove(&platform_devid_ida, pdev->id);
+ pdev->id = PLATFORM_DEVID_AUTO;
+ }
+
for (i = 0; i < pdev->num_resources; i++) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 0113adc310dc..b0b072a88f5f 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -1324,3 +1324,25 @@ int device_pm_wait_for_dev(struct device *subordinate, struct device *dev)
return async_error;
}
EXPORT_SYMBOL_GPL(device_pm_wait_for_dev);
+
+/**
+ * dpm_for_each_dev - device iterator.
+ * @data: data for the callback.
+ * @fn: function to be called for each device.
+ *
+ * Iterate over devices in dpm_list, and call @fn for each device,
+ * passing it @data.
+ */
+void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *))
+{
+ struct device *dev;
+
+ if (!fn)
+ return;
+
+ device_pm_lock();
+ list_for_each_entry(dev, &dpm_list, power.entry)
+ fn(dev, data);
+ device_pm_unlock();
+}
+EXPORT_SYMBOL_GPL(dpm_for_each_dev);