summaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/Kconfig8
-rw-r--r--drivers/base/Makefile1
-rw-r--r--drivers/base/arch_topology.c243
-rw-r--r--drivers/base/bus.c47
-rw-r--r--drivers/base/class.c33
-rw-r--r--drivers/base/core.c7
-rw-r--r--drivers/base/dma-mapping.c33
-rw-r--r--drivers/base/firmware_class.c302
-rw-r--r--drivers/base/platform.c11
9 files changed, 491 insertions, 194 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index d718ae4..f046d21 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -339,4 +339,12 @@ config CMA_ALIGNMENT
endif
+config GENERIC_ARCH_TOPOLOGY
+ bool
+ help
+ Enable support for architectures common topology code: e.g., parsing
+ CPU capacity information from DT, usage of such information for
+ appropriate scaling, sysfs interface for changing capacity values at
+ runtime.
+
endmenu
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index f2816f6..397e5c3 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_SOC_BUS) += soc.o
obj-$(CONFIG_PINCTRL) += pinctrl.o
obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o
obj-$(CONFIG_GENERIC_MSI_IRQ_DOMAIN) += platform-msi.o
+obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY) += arch_topology.o
obj-y += test/
diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c
new file mode 100644
index 0000000..d1c33a8
--- /dev/null
+++ b/drivers/base/arch_topology.c
@@ -0,0 +1,243 @@
+/*
+ * Arch specific cpu topology information
+ *
+ * Copyright (C) 2016, ARM Ltd.
+ * Written by: Juri Lelli, ARM Ltd.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Released under the GPLv2 only.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/acpi.h>
+#include <linux/arch_topology.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sched/topology.h>
+
+static DEFINE_MUTEX(cpu_scale_mutex);
+static DEFINE_PER_CPU(unsigned long, cpu_scale) = SCHED_CAPACITY_SCALE;
+
+unsigned long topology_get_cpu_scale(struct sched_domain *sd, int cpu)
+{
+ return per_cpu(cpu_scale, cpu);
+}
+
+void topology_set_cpu_scale(unsigned int cpu, unsigned long capacity)
+{
+ per_cpu(cpu_scale, cpu) = capacity;
+}
+
+static ssize_t cpu_capacity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct cpu *cpu = container_of(dev, struct cpu, dev);
+
+ return sprintf(buf, "%lu\n",
+ topology_get_cpu_scale(NULL, cpu->dev.id));
+}
+
+static ssize_t cpu_capacity_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct cpu *cpu = container_of(dev, struct cpu, dev);
+ int this_cpu = cpu->dev.id;
+ int i;
+ unsigned long new_capacity;
+ ssize_t ret;
+
+ if (!count)
+ return 0;
+
+ ret = kstrtoul(buf, 0, &new_capacity);
+ if (ret)
+ return ret;
+ if (new_capacity > SCHED_CAPACITY_SCALE)
+ return -EINVAL;
+
+ mutex_lock(&cpu_scale_mutex);
+ for_each_cpu(i, &cpu_topology[this_cpu].core_sibling)
+ topology_set_cpu_scale(i, new_capacity);
+ mutex_unlock(&cpu_scale_mutex);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(cpu_capacity);
+
+static int register_cpu_capacity_sysctl(void)
+{
+ int i;
+ struct device *cpu;
+
+ for_each_possible_cpu(i) {
+ cpu = get_cpu_device(i);
+ if (!cpu) {
+ pr_err("%s: too early to get CPU%d device!\n",
+ __func__, i);
+ continue;
+ }
+ device_create_file(cpu, &dev_attr_cpu_capacity);
+ }
+
+ return 0;
+}
+subsys_initcall(register_cpu_capacity_sysctl);
+
+static u32 capacity_scale;
+static u32 *raw_capacity;
+static bool cap_parsing_failed;
+
+void topology_normalize_cpu_scale(void)
+{
+ u64 capacity;
+ int cpu;
+
+ if (!raw_capacity || cap_parsing_failed)
+ return;
+
+ pr_debug("cpu_capacity: capacity_scale=%u\n", capacity_scale);
+ mutex_lock(&cpu_scale_mutex);
+ for_each_possible_cpu(cpu) {
+ pr_debug("cpu_capacity: cpu=%d raw_capacity=%u\n",
+ cpu, raw_capacity[cpu]);
+ capacity = (raw_capacity[cpu] << SCHED_CAPACITY_SHIFT)
+ / capacity_scale;
+ topology_set_cpu_scale(cpu, capacity);
+ pr_debug("cpu_capacity: CPU%d cpu_capacity=%lu\n",
+ cpu, topology_get_cpu_scale(NULL, cpu));
+ }
+ mutex_unlock(&cpu_scale_mutex);
+}
+
+int __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)
+{
+ int ret = 1;
+ u32 cpu_capacity;
+
+ if (cap_parsing_failed)
+ return !ret;
+
+ ret = of_property_read_u32(cpu_node,
+ "capacity-dmips-mhz",
+ &cpu_capacity);
+ if (!ret) {
+ if (!raw_capacity) {
+ raw_capacity = kcalloc(num_possible_cpus(),
+ sizeof(*raw_capacity),
+ GFP_KERNEL);
+ if (!raw_capacity) {
+ pr_err("cpu_capacity: failed to allocate memory for raw capacities\n");
+ cap_parsing_failed = true;
+ return 0;
+ }
+ }
+ capacity_scale = max(cpu_capacity, capacity_scale);
+ raw_capacity[cpu] = cpu_capacity;
+ pr_debug("cpu_capacity: %s cpu_capacity=%u (raw)\n",
+ cpu_node->full_name, raw_capacity[cpu]);
+ } else {
+ if (raw_capacity) {
+ pr_err("cpu_capacity: missing %s raw capacity\n",
+ cpu_node->full_name);
+ pr_err("cpu_capacity: partial information: fallback to 1024 for all CPUs\n");
+ }
+ cap_parsing_failed = true;
+ kfree(raw_capacity);
+ }
+
+ return !ret;
+}
+
+#ifdef CONFIG_CPU_FREQ
+static cpumask_var_t cpus_to_visit;
+static bool cap_parsing_done;
+static void parsing_done_workfn(struct work_struct *work);
+static DECLARE_WORK(parsing_done_work, parsing_done_workfn);
+
+static int
+init_cpu_capacity_callback(struct notifier_block *nb,
+ unsigned long val,
+ void *data)
+{
+ struct cpufreq_policy *policy = data;
+ int cpu;
+
+ if (cap_parsing_failed || cap_parsing_done)
+ return 0;
+
+ switch (val) {
+ case CPUFREQ_NOTIFY:
+ pr_debug("cpu_capacity: init cpu capacity for CPUs [%*pbl] (to_visit=%*pbl)\n",
+ cpumask_pr_args(policy->related_cpus),
+ cpumask_pr_args(cpus_to_visit));
+ cpumask_andnot(cpus_to_visit,
+ cpus_to_visit,
+ policy->related_cpus);
+ for_each_cpu(cpu, policy->related_cpus) {
+ raw_capacity[cpu] = topology_get_cpu_scale(NULL, cpu) *
+ policy->cpuinfo.max_freq / 1000UL;
+ capacity_scale = max(raw_capacity[cpu], capacity_scale);
+ }
+ if (cpumask_empty(cpus_to_visit)) {
+ topology_normalize_cpu_scale();
+ kfree(raw_capacity);
+ pr_debug("cpu_capacity: parsing done\n");
+ cap_parsing_done = true;
+ schedule_work(&parsing_done_work);
+ }
+ }
+ return 0;
+}
+
+static struct notifier_block init_cpu_capacity_notifier = {
+ .notifier_call = init_cpu_capacity_callback,
+};
+
+static int __init register_cpufreq_notifier(void)
+{
+ /*
+ * on ACPI-based systems we need to use the default cpu capacity
+ * until we have the necessary code to parse the cpu capacity, so
+ * skip registering cpufreq notifier.
+ */
+ if (!acpi_disabled || !raw_capacity)
+ return -EINVAL;
+
+ if (!alloc_cpumask_var(&cpus_to_visit, GFP_KERNEL)) {
+ pr_err("cpu_capacity: failed to allocate memory for cpus_to_visit\n");
+ return -ENOMEM;
+ }
+
+ cpumask_copy(cpus_to_visit, cpu_possible_mask);
+
+ return cpufreq_register_notifier(&init_cpu_capacity_notifier,
+ CPUFREQ_POLICY_NOTIFIER);
+}
+core_initcall(register_cpufreq_notifier);
+
+static void parsing_done_workfn(struct work_struct *work)
+{
+ cpufreq_unregister_notifier(&init_cpu_capacity_notifier,
+ CPUFREQ_POLICY_NOTIFIER);
+}
+
+#else
+static int __init free_raw_capacity(void)
+{
+ kfree(raw_capacity);
+
+ return 0;
+}
+core_initcall(free_raw_capacity);
+#endif
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index 6470eb8..e162c9a 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -466,35 +466,6 @@ int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
}
EXPORT_SYMBOL_GPL(bus_for_each_drv);
-static int device_add_attrs(struct bus_type *bus, struct device *dev)
-{
- int error = 0;
- int i;
-
- if (!bus->dev_attrs)
- return 0;
-
- for (i = 0; bus->dev_attrs[i].attr.name; i++) {
- error = device_create_file(dev, &bus->dev_attrs[i]);
- if (error) {
- while (--i >= 0)
- device_remove_file(dev, &bus->dev_attrs[i]);
- break;
- }
- }
- return error;
-}
-
-static void device_remove_attrs(struct bus_type *bus, struct device *dev)
-{
- int i;
-
- if (bus->dev_attrs) {
- for (i = 0; bus->dev_attrs[i].attr.name; i++)
- device_remove_file(dev, &bus->dev_attrs[i]);
- }
-}
-
/**
* bus_add_device - add device to bus
* @dev: device being added
@@ -510,12 +481,9 @@ int bus_add_device(struct device *dev)
if (bus) {
pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
- error = device_add_attrs(bus, dev);
- if (error)
- goto out_put;
error = device_add_groups(dev, bus->dev_groups);
if (error)
- goto out_id;
+ goto out_put;
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
if (error)
@@ -532,8 +500,6 @@ out_subsys:
sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
device_remove_groups(dev, bus->dev_groups);
-out_id:
- device_remove_attrs(bus, dev);
out_put:
bus_put(dev->bus);
return error;
@@ -590,7 +556,6 @@ void bus_remove_device(struct device *dev)
sysfs_remove_link(&dev->kobj, "subsystem");
sysfs_remove_link(&dev->bus->p->devices_kset->kobj,
dev_name(dev));
- device_remove_attrs(dev->bus, dev);
device_remove_groups(dev, dev->bus->dev_groups);
if (klist_node_attached(&dev->p->knode_bus))
klist_del(&dev->p->knode_bus);
@@ -648,10 +613,7 @@ static void remove_probe_files(struct bus_type *bus)
static ssize_t uevent_store(struct device_driver *drv, const char *buf,
size_t count)
{
- enum kobject_action action;
-
- if (kobject_action_type(buf, count, &action) == 0)
- kobject_uevent(&drv->p->kobj, action);
+ kobject_synth_uevent(&drv->p->kobj, buf, count);
return count;
}
static DRIVER_ATTR_WO(uevent);
@@ -868,10 +830,7 @@ static void klist_devices_put(struct klist_node *n)
static ssize_t bus_uevent_store(struct bus_type *bus,
const char *buf, size_t count)
{
- enum kobject_action action;
-
- if (kobject_action_type(buf, count, &action) == 0)
- kobject_uevent(&bus->p->subsys.kobj, action);
+ kobject_synth_uevent(&bus->p->subsys.kobj, buf, count);
return count;
}
static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);
diff --git a/drivers/base/class.c b/drivers/base/class.c
index a2b2896..52eb8e6 100644
--- a/drivers/base/class.c
+++ b/drivers/base/class.c
@@ -119,36 +119,6 @@ static void class_put(struct class *cls)
kset_put(&cls->p->subsys);
}
-static int add_class_attrs(struct class *cls)
-{
- int i;
- int error = 0;
-
- if (cls->class_attrs) {
- for (i = 0; cls->class_attrs[i].attr.name; i++) {
- error = class_create_file(cls, &cls->class_attrs[i]);
- if (error)
- goto error;
- }
- }
-done:
- return error;
-error:
- while (--i >= 0)
- class_remove_file(cls, &cls->class_attrs[i]);
- goto done;
-}
-
-static void remove_class_attrs(struct class *cls)
-{
- int i;
-
- if (cls->class_attrs) {
- for (i = 0; cls->class_attrs[i].attr.name; i++)
- class_remove_file(cls, &cls->class_attrs[i]);
- }
-}
-
static void klist_class_dev_get(struct klist_node *n)
{
struct device *dev = container_of(n, struct device, knode_class);
@@ -217,8 +187,6 @@ int __class_register(struct class *cls, struct lock_class_key *key)
}
error = class_add_groups(class_get(cls), cls->class_groups);
class_put(cls);
- error = add_class_attrs(class_get(cls));
- class_put(cls);
return error;
}
EXPORT_SYMBOL_GPL(__class_register);
@@ -226,7 +194,6 @@ EXPORT_SYMBOL_GPL(__class_register);
void class_unregister(struct class *cls)
{
pr_debug("device class '%s': unregistering\n", cls->name);
- remove_class_attrs(cls);
class_remove_groups(cls, cls->class_groups);
kset_unregister(&cls->p->subsys);
}
diff --git a/drivers/base/core.c b/drivers/base/core.c
index c3064ff..8dde934 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -981,12 +981,9 @@ out:
static ssize_t uevent_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
- enum kobject_action action;
+ if (kobject_synth_uevent(&dev->kobj, buf, count))
+ dev_err(dev, "uevent: failed to send synthetic uevent\n");
- if (kobject_action_type(buf, count, &action) == 0)
- kobject_uevent(&dev->kobj, action);
- else
- dev_err(dev, "uevent: unknown action-string\n");
return count;
}
static DEVICE_ATTR_RW(uevent);
diff --git a/drivers/base/dma-mapping.c b/drivers/base/dma-mapping.c
index f3deb6a..9dbef4d 100644
--- a/drivers/base/dma-mapping.c
+++ b/drivers/base/dma-mapping.c
@@ -275,6 +275,24 @@ int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
EXPORT_SYMBOL(dma_common_mmap);
#ifdef CONFIG_MMU
+static struct vm_struct *__dma_common_pages_remap(struct page **pages,
+ size_t size, unsigned long vm_flags, pgprot_t prot,
+ const void *caller)
+{
+ struct vm_struct *area;
+
+ area = get_vm_area_caller(size, vm_flags, caller);
+ if (!area)
+ return NULL;
+
+ if (map_vm_area(area, prot, pages)) {
+ vunmap(area->addr);
+ return NULL;
+ }
+
+ return area;
+}
+
/*
* remaps an array of PAGE_SIZE pages into another vm_area
* Cannot be used in non-sleeping contexts
@@ -285,17 +303,12 @@ void *dma_common_pages_remap(struct page **pages, size_t size,
{
struct vm_struct *area;
- area = get_vm_area_caller(size, vm_flags, caller);
+ area = __dma_common_pages_remap(pages, size, vm_flags, prot, caller);
if (!area)
return NULL;
area->pages = pages;
- if (map_vm_area(area, prot, pages)) {
- vunmap(area->addr);
- return NULL;
- }
-
return area->addr;
}
@@ -310,7 +323,7 @@ void *dma_common_contiguous_remap(struct page *page, size_t size,
{
int i;
struct page **pages;
- void *ptr;
+ struct vm_struct *area;
pages = kmalloc(sizeof(struct page *) << get_order(size), GFP_KERNEL);
if (!pages)
@@ -319,11 +332,13 @@ void *dma_common_contiguous_remap(struct page *page, size_t size,
for (i = 0; i < (size >> PAGE_SHIFT); i++)
pages[i] = nth_page(page, i);
- ptr = dma_common_pages_remap(pages, size, vm_flags, prot, caller);
+ area = __dma_common_pages_remap(pages, size, vm_flags, prot, caller);
kfree(pages);
- return ptr;
+ if (!area)
+ return NULL;
+ return area->addr;
}
/*
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index ac350c5..b9f907e 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -260,6 +260,38 @@ static int fw_cache_piggyback_on_request(const char *name);
* guarding for corner cases a global lock should be OK */
static DEFINE_MUTEX(fw_lock);
+static bool __enable_firmware = false;
+
+static void enable_firmware(void)
+{
+ mutex_lock(&fw_lock);
+ __enable_firmware = true;
+ mutex_unlock(&fw_lock);
+}
+
+static void disable_firmware(void)
+{
+ mutex_lock(&fw_lock);
+ __enable_firmware = false;
+ mutex_unlock(&fw_lock);
+}
+
+/*
+ * When disabled only the built-in firmware and the firmware cache will be
+ * used to look for firmware.
+ */
+static bool firmware_enabled(void)
+{
+ bool enabled = false;
+
+ mutex_lock(&fw_lock);
+ if (__enable_firmware)
+ enabled = true;
+ mutex_unlock(&fw_lock);
+
+ return enabled;
+}
+
static struct firmware_cache fw_cache;
static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
@@ -523,6 +555,44 @@ static int fw_add_devm_name(struct device *dev, const char *name)
}
#endif
+static int assign_firmware_buf(struct firmware *fw, struct device *device,
+ unsigned int opt_flags)
+{
+ struct firmware_buf *buf = fw->priv;
+
+ mutex_lock(&fw_lock);
+ if (!buf->size || fw_state_is_aborted(&buf->fw_st)) {
+ mutex_unlock(&fw_lock);
+ return -ENOENT;
+ }
+
+ /*
+ * add firmware name into devres list so that we can auto cache
+ * and uncache firmware for device.
+ *
+ * device may has been deleted already, but the problem
+ * should be fixed in devres or driver core.
+ */
+ /* don't cache firmware handled without uevent */
+ if (device && (opt_flags & FW_OPT_UEVENT) &&
+ !(opt_flags & FW_OPT_NOCACHE))
+ fw_add_devm_name(device, buf->fw_id);
+
+ /*
+ * After caching firmware image is started, let it piggyback
+ * on request firmware.
+ */
+ if (!(opt_flags & FW_OPT_NOCACHE) &&
+ buf->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);
+ mutex_unlock(&fw_lock);
+ return 0;
+}
/*
* user-mode helper code
@@ -562,23 +632,19 @@ static void fw_load_abort(struct firmware_priv *fw_priv)
static LIST_HEAD(pending_fw_head);
-/* reboot notifier for avoid deadlock with usermode_lock */
-static int fw_shutdown_notify(struct notifier_block *unused1,
- unsigned long unused2, void *unused3)
+static void kill_pending_fw_fallback_reqs(bool only_kill_custom)
{
+ struct firmware_buf *buf;
+ struct firmware_buf *next;
+
mutex_lock(&fw_lock);
- while (!list_empty(&pending_fw_head))
- __fw_load_abort(list_first_entry(&pending_fw_head,
- struct firmware_buf,
- pending_list));
+ list_for_each_entry_safe(buf, next, &pending_fw_head, pending_list) {
+ if (!buf->need_uevent || !only_kill_custom)
+ __fw_load_abort(buf);
+ }
mutex_unlock(&fw_lock);
- return NOTIFY_DONE;
}
-static struct notifier_block fw_shutdown_nb = {
- .notifier_call = fw_shutdown_notify,
-};
-
static ssize_t timeout_show(struct class *class, struct class_attribute *attr,
char *buf)
{
@@ -1036,46 +1102,56 @@ err_put_dev:
static int fw_load_from_user_helper(struct firmware *firmware,
const char *name, struct device *device,
- unsigned int opt_flags, long timeout)
+ unsigned int opt_flags)
{
struct firmware_priv *fw_priv;
+ long timeout;
+ int ret;
+
+ timeout = firmware_loading_timeout();
+ if (opt_flags & FW_OPT_NOWAIT) {
+ timeout = usermodehelper_read_lock_wait(timeout);
+ if (!timeout) {
+ dev_dbg(device, "firmware: %s loading timed out\n",
+ name);
+ return -EBUSY;
+ }
+ } else {
+ ret = usermodehelper_read_trylock();
+ if (WARN_ON(ret)) {
+ dev_err(device, "firmware: %s will not be loaded\n",
+ name);
+ return ret;
+ }
+ }
fw_priv = fw_create_instance(firmware, name, device, opt_flags);
- if (IS_ERR(fw_priv))
- return PTR_ERR(fw_priv);
+ if (IS_ERR(fw_priv)) {
+ ret = PTR_ERR(fw_priv);
+ goto out_unlock;
+ }
fw_priv->buf = firmware->priv;
- return _request_firmware_load(fw_priv, opt_flags, timeout);
-}
+ ret = _request_firmware_load(fw_priv, opt_flags, timeout);
-#ifdef CONFIG_PM_SLEEP
-/* kill pending requests without uevent to avoid blocking suspend */
-static void kill_requests_without_uevent(void)
-{
- struct firmware_buf *buf;
- struct firmware_buf *next;
+ if (!ret)
+ ret = assign_firmware_buf(firmware, device, opt_flags);
- mutex_lock(&fw_lock);
- list_for_each_entry_safe(buf, next, &pending_fw_head, pending_list) {
- if (!buf->need_uevent)
- __fw_load_abort(buf);
- }
- mutex_unlock(&fw_lock);
+out_unlock:
+ usermodehelper_read_unlock();
+
+ return ret;
}
-#endif
#else /* CONFIG_FW_LOADER_USER_HELPER */
static inline int
fw_load_from_user_helper(struct firmware *firmware, const char *name,
- struct device *device, unsigned int opt_flags,
- long timeout)
+ struct device *device, unsigned int opt_flags)
{
return -ENOENT;
}
-#ifdef CONFIG_PM_SLEEP
-static inline void kill_requests_without_uevent(void) { }
-#endif
+static inline void kill_pending_fw_fallback_reqs(bool only_kill_custom) { }
#endif /* CONFIG_FW_LOADER_USER_HELPER */
@@ -1124,45 +1200,6 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
return 1; /* need to load */
}
-static int assign_firmware_buf(struct firmware *fw, struct device *device,
- unsigned int opt_flags)
-{
- struct firmware_buf *buf = fw->priv;
-
- mutex_lock(&fw_lock);
- if (!buf->size || fw_state_is_aborted(&buf->fw_st)) {
- mutex_unlock(&fw_lock);
- return -ENOENT;
- }
-
- /*
- * add firmware name into devres list so that we can auto cache
- * and uncache firmware for device.
- *
- * device may has been deleted already, but the problem
- * should be fixed in devres or driver core.
- */
- /* don't cache firmware handled without uevent */
- if (device && (opt_flags & FW_OPT_UEVENT) &&
- !(opt_flags & FW_OPT_NOCACHE))
- fw_add_devm_name(device, buf->fw_id);
-
- /*
- * After caching firmware image is started, let it piggyback
- * on request firmware.
- */
- if (!(opt_flags & FW_OPT_NOCACHE) &&
- buf->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);
- mutex_unlock(&fw_lock);
- return 0;
-}
-
/* called from request_firmware() and request_firmware_work_func() */
static int
_request_firmware(const struct firmware **firmware_p, const char *name,
@@ -1170,7 +1207,6 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
unsigned int opt_flags)
{
struct firmware *fw = NULL;
- long timeout;
int ret;
if (!firmware_p)
@@ -1185,23 +1221,10 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
if (ret <= 0) /* error or already assigned */
goto out;
- ret = 0;
- timeout = firmware_loading_timeout();
- if (opt_flags & FW_OPT_NOWAIT) {
- timeout = usermodehelper_read_lock_wait(timeout);
- if (!timeout) {
- dev_dbg(device, "firmware: %s loading timed out\n",
- name);
- ret = -EBUSY;
- goto out;
- }
- } else {
- ret = usermodehelper_read_trylock();
- if (WARN_ON(ret)) {
- dev_err(device, "firmware: %s will not be loaded\n",
- name);
- goto out;
- }
+ if (!firmware_enabled()) {
+ WARN(1, "firmware request while host is not available\n");
+ ret = -EHOSTDOWN;
+ goto out;
}
ret = fw_get_filesystem_firmware(device, fw->priv);
@@ -1213,15 +1236,11 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
if (opt_flags & FW_OPT_USERHELPER) {
dev_warn(device, "Falling back to user helper\n");
ret = fw_load_from_user_helper(fw, name, device,
- opt_flags, timeout);
+ opt_flags);
}
- }
-
- if (!ret)
+ } else
ret = assign_firmware_buf(fw, device, opt_flags);
- usermodehelper_read_unlock();
-
out:
if (ret < 0) {
release_firmware(fw);
@@ -1717,6 +1736,62 @@ static void device_uncache_fw_images_delay(unsigned long delay)
msecs_to_jiffies(delay));
}
+/**
+ * fw_pm_notify - notifier for suspend/resume
+ * @notify_block: unused
+ * @mode: mode we are switching to
+ * @unused: unused
+ *
+ * Used to modify the firmware_class state as we move in between states.
+ * The firmware_class implements a firmware cache to enable device driver
+ * to fetch firmware upon resume before the root filesystem is ready. We
+ * disable API calls which do not use the built-in firmware or the firmware
+ * cache when we know these calls will not work.
+ *
+ * The inner logic behind all this is a bit complex so it is worth summarizing
+ * the kernel's own suspend/resume process with context and focus on how this
+ * can impact the firmware API.
+ *
+ * First a review on how we go to suspend::
+ *
+ * pm_suspend() --> enter_state() -->
+ * sys_sync()
+ * suspend_prepare() -->
+ * __pm_notifier_call_chain(PM_SUSPEND_PREPARE, ...);
+ * suspend_freeze_processes() -->
+ * freeze_processes() -->
+ * __usermodehelper_set_disable_depth(UMH_DISABLED);
+ * freeze all tasks ...
+ * freeze_kernel_threads()
+ * suspend_devices_and_enter() -->
+ * dpm_suspend_start() -->
+ * dpm_prepare()
+ * dpm_suspend()
+ * suspend_enter() -->
+ * platform_suspend_prepare()
+ * dpm_suspend_late()
+ * freeze_enter()
+ * syscore_suspend()
+ *
+ * When we resume we bail out of a loop from suspend_devices_and_enter() and
+ * unwind back out to the caller enter_state() where we were before as follows::
+ *
+ * enter_state() -->
+ * suspend_devices_and_enter() --> (bail from loop)
+ * dpm_resume_end() -->
+ * dpm_resume()
+ * dpm_complete()
+ * suspend_finish() -->
+ * suspend_thaw_processes() -->
+ * thaw_processes() -->
+ * __usermodehelper_set_disable_depth(UMH_FREEZING);
+ * thaw_workqueues();
+ * thaw all processes ...
+ * usermodehelper_enable();
+ * pm_notifier_call_chain(PM_POST_SUSPEND);
+ *
+ * fw_pm_notify() works through pm_notifier_call_chain().
+ */
static int fw_pm_notify(struct notifier_block *notify_block,
unsigned long mode, void *unused)
{
@@ -1724,8 +1799,13 @@ static int fw_pm_notify(struct notifier_block *notify_block,
case PM_HIBERNATION_PREPARE:
case PM_SUSPEND_PREPARE:
case PM_RESTORE_PREPARE:
- kill_requests_without_uevent();
+ /*
+ * kill pending fallback requests with a custom fallback
+ * to avoid stalling suspend.
+ */
+ kill_pending_fw_fallback_reqs(true);
device_cache_fw_images();
+ disable_firmware();
break;
case PM_POST_SUSPEND:
@@ -1738,6 +1818,7 @@ static int fw_pm_notify(struct notifier_block *notify_block,
mutex_lock(&fw_lock);
fw_cache.state = FW_LOADER_NO_CACHE;
mutex_unlock(&fw_lock);
+ enable_firmware();
device_uncache_fw_images_delay(10 * MSEC_PER_SEC);
break;
@@ -1783,11 +1864,29 @@ static void __init fw_cache_init(void)
#endif
}
+static int fw_shutdown_notify(struct notifier_block *unused1,
+ unsigned long unused2, void *unused3)
+{
+ disable_firmware();
+ /*
+ * Kill all pending fallback requests to avoid both stalling shutdown,
+ * and avoid a deadlock with the usermode_lock.
+ */
+ kill_pending_fw_fallback_reqs(false);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block fw_shutdown_nb = {
+ .notifier_call = fw_shutdown_notify,
+};
+
static int __init firmware_class_init(void)
{
+ enable_firmware();
fw_cache_init();
-#ifdef CONFIG_FW_LOADER_USER_HELPER
register_reboot_notifier(&fw_shutdown_nb);
+#ifdef CONFIG_FW_LOADER_USER_HELPER
return class_register(&firmware_class);
#else
return 0;
@@ -1796,12 +1895,13 @@ static int __init firmware_class_init(void)
static void __exit firmware_class_exit(void)
{
+ disable_firmware();
#ifdef CONFIG_PM_SLEEP
unregister_syscore_ops(&fw_syscore_ops);
unregister_pm_notifier(&fw_cache.pm_notify);
#endif
-#ifdef CONFIG_FW_LOADER_USER_HELPER
unregister_reboot_notifier(&fw_shutdown_nb);
+#ifdef CONFIG_FW_LOADER_USER_HELPER
class_unregister(&firmware_class);
#endif
}
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index a102152..97332d0 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -866,7 +866,7 @@ static ssize_t driver_override_store(struct device *dev,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
- char *driver_override, *old = pdev->driver_override, *cp;
+ char *driver_override, *old, *cp;
if (count > PATH_MAX)
return -EINVAL;
@@ -879,12 +879,15 @@ static ssize_t driver_override_store(struct device *dev,
if (cp)
*cp = '\0';
+ device_lock(dev);
+ old = pdev->driver_override;
if (strlen(driver_override)) {
pdev->driver_override = driver_override;
} else {
kfree(driver_override);
pdev->driver_override = NULL;
}
+ device_unlock(dev);
kfree(old);
@@ -895,8 +898,12 @@ static ssize_t driver_override_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
+ ssize_t len;
- return sprintf(buf, "%s\n", pdev->driver_override);
+ device_lock(dev);
+ len = sprintf(buf, "%s\n", pdev->driver_override);
+ device_unlock(dev);
+ return len;
}
static DEVICE_ATTR_RW(driver_override);
OpenPOWER on IntegriCloud