diff options
Diffstat (limited to 'drivers/firmware/arm_scmi/bus.c')
| -rw-r--r-- | drivers/firmware/arm_scmi/bus.c | 533 |
1 files changed, 461 insertions, 72 deletions
diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c index 472c88ae1c0f..c7698cfaa4e8 100644 --- a/drivers/firmware/arm_scmi/bus.c +++ b/drivers/firmware/arm_scmi/bus.c @@ -2,104 +2,375 @@ /* * System Control and Management Interface (SCMI) Message Protocol bus layer * - * Copyright (C) 2018 ARM Ltd. + * Copyright (C) 2018-2021 ARM Ltd. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/atomic.h> #include <linux/types.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/device.h> #include "common.h" +#define SCMI_UEVENT_MODALIAS_FMT "%s:%02x:%s" + +BLOCKING_NOTIFIER_HEAD(scmi_requested_devices_nh); +EXPORT_SYMBOL_GPL(scmi_requested_devices_nh); + static DEFINE_IDA(scmi_bus_id); -static DEFINE_IDR(scmi_protocols); -static DEFINE_SPINLOCK(protocol_lock); -static const struct scmi_device_id * -scmi_dev_match_id(struct scmi_device *scmi_dev, struct scmi_driver *scmi_drv) +static DEFINE_IDR(scmi_requested_devices); +/* Protect access to scmi_requested_devices */ +static DEFINE_MUTEX(scmi_requested_devices_mtx); + +struct scmi_requested_dev { + const struct scmi_device_id *id_table; + struct list_head node; +}; + +/* Track globally the creation of SCMI SystemPower related devices */ +static atomic_t scmi_syspower_registered = ATOMIC_INIT(0); + +/** + * scmi_protocol_device_request - Helper to request a device + * + * @id_table: A protocol/name pair descriptor for the device to be created. + * + * This helper let an SCMI driver request specific devices identified by the + * @id_table to be created for each active SCMI instance. + * + * The requested device name MUST NOT be already existent for this protocol; + * at first the freshly requested @id_table is annotated in the IDR table + * @scmi_requested_devices and then the requested device is advertised to any + * registered party via the @scmi_requested_devices_nh notification chain. + * + * Return: 0 on Success + */ +static int scmi_protocol_device_request(const struct scmi_device_id *id_table) { - const struct scmi_device_id *id = scmi_drv->id_table; + int ret = 0; + struct list_head *head, *phead = NULL; + struct scmi_requested_dev *rdev; - if (!id) - return NULL; + pr_debug("Requesting SCMI device (%s) for protocol %x\n", + id_table->name, id_table->protocol_id); - for (; id->protocol_id; id++) - if (id->protocol_id == scmi_dev->protocol_id) - return id; + if (IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT) && + !IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT_COEX)) { + pr_warn("SCMI Raw mode active. Rejecting '%s'/0x%02X\n", + id_table->name, id_table->protocol_id); + return -EINVAL; + } + + /* + * Find the matching protocol rdev list and then search of any + * existent equally named device...fails if any duplicate found. + */ + mutex_lock(&scmi_requested_devices_mtx); + phead = idr_find(&scmi_requested_devices, id_table->protocol_id); + if (phead) { + head = phead; + list_for_each_entry(rdev, head, node) { + if (!strcmp(rdev->id_table->name, id_table->name)) { + pr_err("Ignoring duplicate request [%d] %s\n", + rdev->id_table->protocol_id, + rdev->id_table->name); + ret = -EINVAL; + goto out; + } + } + } + + /* + * No duplicate found for requested id_table, so let's create a new + * requested device entry for this new valid request. + */ + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); + if (!rdev) { + ret = -ENOMEM; + goto out; + } + rdev->id_table = id_table; + + /* + * Append the new requested device table descriptor to the head of the + * related protocol list, eventually creating such head if not already + * there. + */ + if (!phead) { + phead = kzalloc(sizeof(*phead), GFP_KERNEL); + if (!phead) { + kfree(rdev); + ret = -ENOMEM; + goto out; + } + INIT_LIST_HEAD(phead); + + ret = idr_alloc(&scmi_requested_devices, (void *)phead, + id_table->protocol_id, + id_table->protocol_id + 1, GFP_KERNEL); + if (ret != id_table->protocol_id) { + pr_err("Failed to save SCMI device - ret:%d\n", ret); + kfree(rdev); + kfree(phead); + ret = -EINVAL; + goto out; + } + ret = 0; + } + list_add(&rdev->node, phead); + +out: + mutex_unlock(&scmi_requested_devices_mtx); + + if (!ret) + blocking_notifier_call_chain(&scmi_requested_devices_nh, + SCMI_BUS_NOTIFY_DEVICE_REQUEST, + (void *)rdev->id_table); - return NULL; + return ret; } -static int scmi_dev_match(struct device *dev, struct device_driver *drv) +static int scmi_protocol_table_register(const struct scmi_device_id *id_table) { - struct scmi_driver *scmi_drv = to_scmi_driver(drv); - struct scmi_device *scmi_dev = to_scmi_dev(dev); - const struct scmi_device_id *id; + int ret = 0; + const struct scmi_device_id *entry; - id = scmi_dev_match_id(scmi_dev, scmi_drv); - if (id) - return 1; + for (entry = id_table; entry->name && ret == 0; entry++) + ret = scmi_protocol_device_request(entry); + return ret; +} + +/** + * scmi_protocol_device_unrequest - Helper to unrequest a device + * + * @id_table: A protocol/name pair descriptor for the device to be unrequested. + * + * The unrequested device, described by the provided id_table, is at first + * removed from the IDR @scmi_requested_devices and then the removal is + * advertised to any registered party via the @scmi_requested_devices_nh + * notification chain. + */ +static void scmi_protocol_device_unrequest(const struct scmi_device_id *id_table) +{ + struct list_head *phead; + + pr_debug("Unrequesting SCMI device (%s) for protocol %x\n", + id_table->name, id_table->protocol_id); + + mutex_lock(&scmi_requested_devices_mtx); + phead = idr_find(&scmi_requested_devices, id_table->protocol_id); + if (phead) { + struct scmi_requested_dev *victim, *tmp; + + list_for_each_entry_safe(victim, tmp, phead, node) { + if (!strcmp(victim->id_table->name, id_table->name)) { + list_del(&victim->node); + + mutex_unlock(&scmi_requested_devices_mtx); + blocking_notifier_call_chain(&scmi_requested_devices_nh, + SCMI_BUS_NOTIFY_DEVICE_UNREQUEST, + (void *)victim->id_table); + kfree(victim); + mutex_lock(&scmi_requested_devices_mtx); + break; + } + } + + if (list_empty(phead)) { + idr_remove(&scmi_requested_devices, + id_table->protocol_id); + kfree(phead); + } + } + mutex_unlock(&scmi_requested_devices_mtx); +} + +static void +scmi_protocol_table_unregister(const struct scmi_device_id *id_table) +{ + const struct scmi_device_id *entry; + + for (entry = id_table; entry->name; entry++) + scmi_protocol_device_unrequest(entry); +} + +static int scmi_dev_match_by_id_table(struct scmi_device *scmi_dev, + const struct scmi_device_id *id_table) +{ + if (!id_table || !id_table->name) + return 0; + + /* Always skip transport devices from matching */ + for (; id_table->protocol_id && id_table->name; id_table++) + if (id_table->protocol_id == scmi_dev->protocol_id && + strncmp(scmi_dev->name, "__scmi_transport_device", 23) && + !strcmp(id_table->name, scmi_dev->name)) + return 1; return 0; } -static int scmi_protocol_init(int protocol_id, struct scmi_handle *handle) +static int scmi_dev_match_id(struct scmi_device *scmi_dev, + const struct scmi_driver *scmi_drv) +{ + return scmi_dev_match_by_id_table(scmi_dev, scmi_drv->id_table); +} + +static int scmi_dev_match(struct device *dev, const struct device_driver *drv) { - scmi_prot_init_fn_t fn = idr_find(&scmi_protocols, protocol_id); + const struct scmi_driver *scmi_drv = to_scmi_driver(drv); + struct scmi_device *scmi_dev = to_scmi_dev(dev); - if (unlikely(!fn)) - return -EINVAL; - return fn(handle); + return scmi_dev_match_id(scmi_dev, scmi_drv); +} + +static int scmi_match_by_id_table(struct device *dev, const void *data) +{ + struct scmi_device *scmi_dev = to_scmi_dev(dev); + const struct scmi_device_id *id_table = data; + + return scmi_dev_match_by_id_table(scmi_dev, id_table); +} + +static struct scmi_device *scmi_child_dev_find(struct device *parent, + int prot_id, const char *name) +{ + struct scmi_device_id id_table[2] = { 0 }; + struct device *dev; + + id_table[0].protocol_id = prot_id; + id_table[0].name = name; + + dev = device_find_child(parent, &id_table, scmi_match_by_id_table); + if (!dev) + return NULL; + + /* Drop the refcnt bumped implicitly by device_find_child */ + put_device(dev); + + return to_scmi_dev(dev); } static int scmi_dev_probe(struct device *dev) { struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver); struct scmi_device *scmi_dev = to_scmi_dev(dev); - const struct scmi_device_id *id; - int ret; - - id = scmi_dev_match_id(scmi_dev, scmi_drv); - if (!id) - return -ENODEV; if (!scmi_dev->handle) return -EPROBE_DEFER; - ret = scmi_protocol_init(scmi_dev->protocol_id, scmi_dev->handle); - if (ret) - return ret; - return scmi_drv->probe(scmi_dev); } -static int scmi_dev_remove(struct device *dev) +static void scmi_dev_remove(struct device *dev) { struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver); struct scmi_device *scmi_dev = to_scmi_dev(dev); if (scmi_drv->remove) scmi_drv->remove(scmi_dev); +} + +static int scmi_device_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct scmi_device *scmi_dev = to_scmi_dev(dev); + + return add_uevent_var(env, "MODALIAS=" SCMI_UEVENT_MODALIAS_FMT, + dev_name(&scmi_dev->dev), scmi_dev->protocol_id, + scmi_dev->name); +} + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scmi_device *scmi_dev = to_scmi_dev(dev); + + return sysfs_emit(buf, SCMI_UEVENT_MODALIAS_FMT, + dev_name(&scmi_dev->dev), scmi_dev->protocol_id, + scmi_dev->name); +} +static DEVICE_ATTR_RO(modalias); + +static ssize_t protocol_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scmi_device *scmi_dev = to_scmi_dev(dev); + + return sprintf(buf, "0x%02x\n", scmi_dev->protocol_id); +} +static DEVICE_ATTR_RO(protocol_id); + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct scmi_device *scmi_dev = to_scmi_dev(dev); + + return sprintf(buf, "%s\n", scmi_dev->name); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *scmi_device_attributes_attrs[] = { + &dev_attr_protocol_id.attr, + &dev_attr_name.attr, + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(scmi_device_attributes); + +static int scmi_pm_suspend(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + + if (drv && drv->pm && drv->pm->suspend) + return drv->pm->suspend(dev); return 0; } -static struct bus_type scmi_bus_type = { +static int scmi_pm_resume(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + + if (drv && drv->pm && drv->pm->resume) + return drv->pm->resume(dev); + + return 0; +} + +static const struct dev_pm_ops scmi_dev_pm_ops = { + .suspend = pm_sleep_ptr(scmi_pm_suspend), + .resume = pm_sleep_ptr(scmi_pm_resume), +}; + +const struct bus_type scmi_bus_type = { .name = "scmi_protocol", .match = scmi_dev_match, .probe = scmi_dev_probe, .remove = scmi_dev_remove, + .uevent = scmi_device_uevent, + .dev_groups = scmi_device_attributes_groups, + .pm = &scmi_dev_pm_ops, }; +EXPORT_SYMBOL_GPL(scmi_bus_type); int scmi_driver_register(struct scmi_driver *driver, struct module *owner, const char *mod_name) { int retval; + if (!driver->probe) + return -EINVAL; + + retval = scmi_protocol_table_register(driver->id_table); + if (retval) + return retval; + driver->driver.bus = &scmi_bus_type; driver->driver.name = driver->name; driver->driver.owner = owner; @@ -107,7 +378,7 @@ int scmi_driver_register(struct scmi_driver *driver, struct module *owner, retval = driver_register(&driver->driver); if (!retval) - pr_debug("registered new scmi driver %s\n", driver->name); + pr_debug("Registered new scmi driver %s\n", driver->name); return retval; } @@ -116,84 +387,191 @@ EXPORT_SYMBOL_GPL(scmi_driver_register); void scmi_driver_unregister(struct scmi_driver *driver) { driver_unregister(&driver->driver); + scmi_protocol_table_unregister(driver->id_table); } EXPORT_SYMBOL_GPL(scmi_driver_unregister); -struct scmi_device * -scmi_device_create(struct device_node *np, struct device *parent, int protocol) +static void scmi_device_release(struct device *dev) +{ + struct scmi_device *scmi_dev = to_scmi_dev(dev); + + kfree_const(scmi_dev->name); + kfree(scmi_dev); +} + +static void __scmi_device_destroy(struct scmi_device *scmi_dev) +{ + pr_debug("(%pOF) Destroying SCMI device '%s' for protocol 0x%x (%s)\n", + scmi_dev->dev.parent->of_node, + dev_name(&scmi_dev->dev), scmi_dev->protocol_id, + scmi_dev->name); + + if (scmi_dev->protocol_id == SCMI_PROTOCOL_SYSTEM) + atomic_set(&scmi_syspower_registered, 0); + + ida_free(&scmi_bus_id, scmi_dev->id); + device_unregister(&scmi_dev->dev); +} + +static struct scmi_device * +__scmi_device_create(struct device_node *np, struct device *parent, + int protocol, const char *name) { int id, retval; struct scmi_device *scmi_dev; + /* + * If the same protocol/name device already exist under the same parent + * (i.e. SCMI instance) just return the existent device. + * This avoids any race between the SCMI driver, creating devices for + * each DT defined protocol at probe time, and the concurrent + * registration of SCMI drivers. + */ + scmi_dev = scmi_child_dev_find(parent, protocol, name); + if (scmi_dev) + return scmi_dev; + + /* + * Ignore any possible subsequent failures while creating the device + * since we are doomed anyway at that point; not using a mutex which + * spans across this whole function to keep things simple and to avoid + * to serialize all the __scmi_device_create calls across possibly + * different SCMI server instances (parent) + */ + if (protocol == SCMI_PROTOCOL_SYSTEM && + atomic_cmpxchg(&scmi_syspower_registered, 0, 1)) { + dev_warn(parent, + "SCMI SystemPower protocol device must be unique !\n"); + return NULL; + } + scmi_dev = kzalloc(sizeof(*scmi_dev), GFP_KERNEL); if (!scmi_dev) return NULL; - id = ida_simple_get(&scmi_bus_id, 1, 0, GFP_KERNEL); - if (id < 0) - goto free_mem; + scmi_dev->name = kstrdup_const(name ?: "unknown", GFP_KERNEL); + if (!scmi_dev->name) { + kfree(scmi_dev); + return NULL; + } + + id = ida_alloc_min(&scmi_bus_id, 1, GFP_KERNEL); + if (id < 0) { + kfree_const(scmi_dev->name); + kfree(scmi_dev); + return NULL; + } scmi_dev->id = id; scmi_dev->protocol_id = protocol; scmi_dev->dev.parent = parent; - scmi_dev->dev.of_node = np; + device_set_node(&scmi_dev->dev, of_fwnode_handle(np)); scmi_dev->dev.bus = &scmi_bus_type; + scmi_dev->dev.release = scmi_device_release; dev_set_name(&scmi_dev->dev, "scmi_dev.%d", id); retval = device_register(&scmi_dev->dev); if (retval) goto put_dev; + pr_debug("(%pOF) Created SCMI device '%s' for protocol 0x%x (%s)\n", + parent->of_node, dev_name(&scmi_dev->dev), protocol, name); + return scmi_dev; put_dev: put_device(&scmi_dev->dev); - ida_simple_remove(&scmi_bus_id, id); -free_mem: - kfree(scmi_dev); + ida_free(&scmi_bus_id, id); return NULL; } -void scmi_device_destroy(struct scmi_device *scmi_dev) +static struct scmi_device * +_scmi_device_create(struct device_node *np, struct device *parent, + int protocol, const char *name) { - scmi_handle_put(scmi_dev->handle); - device_unregister(&scmi_dev->dev); - ida_simple_remove(&scmi_bus_id, scmi_dev->id); - kfree(scmi_dev); -} + struct scmi_device *sdev; -void scmi_set_handle(struct scmi_device *scmi_dev) -{ - scmi_dev->handle = scmi_handle_get(&scmi_dev->dev); + sdev = __scmi_device_create(np, parent, protocol, name); + if (!sdev) + pr_err("(%pOF) Failed to create device for protocol 0x%x (%s)\n", + parent->of_node, protocol, name); + + return sdev; } -int scmi_protocol_register(int protocol_id, scmi_prot_init_fn_t fn) +/** + * scmi_device_create - A method to create one or more SCMI devices + * + * @np: A reference to the device node to use for the new device(s) + * @parent: The parent device to use identifying a specific SCMI instance + * @protocol: The SCMI protocol to be associated with this device + * @name: The requested-name of the device to be created; this is optional + * and if no @name is provided, all the devices currently known to + * be requested on the SCMI bus for @protocol will be created. + * + * This method can be invoked to create a single well-defined device (like + * a transport device or a device requested by an SCMI driver loaded after + * the core SCMI stack has been probed), or to create all the devices currently + * known to have been requested by the loaded SCMI drivers for a specific + * protocol (typically during SCMI core protocol enumeration at probe time). + * + * Return: The created device (or one of them if @name was NOT provided and + * multiple devices were created) or NULL if no device was created; + * note that NULL indicates an error ONLY in case a specific @name + * was provided: when @name param was not provided, a number of devices + * could have been potentially created for a whole protocol, unless no + * device was found to have been requested for that specific protocol. + */ +struct scmi_device *scmi_device_create(struct device_node *np, + struct device *parent, int protocol, + const char *name) { - int ret; + struct list_head *phead; + struct scmi_requested_dev *rdev; + struct scmi_device *scmi_dev = NULL; + + if (name) + return _scmi_device_create(np, parent, protocol, name); + + mutex_lock(&scmi_requested_devices_mtx); + phead = idr_find(&scmi_requested_devices, protocol); + /* Nothing to do. */ + if (!phead) { + mutex_unlock(&scmi_requested_devices_mtx); + return NULL; + } - spin_lock(&protocol_lock); - ret = idr_alloc(&scmi_protocols, fn, protocol_id, protocol_id + 1, - GFP_ATOMIC); - spin_unlock(&protocol_lock); - if (ret != protocol_id) - pr_err("unable to allocate SCMI idr slot, err %d\n", ret); + /* Walk the list of requested devices for protocol and create them */ + list_for_each_entry(rdev, phead, node) { + struct scmi_device *sdev; - return ret; + sdev = _scmi_device_create(np, parent, + rdev->id_table->protocol_id, + rdev->id_table->name); + if (sdev) + scmi_dev = sdev; + } + + mutex_unlock(&scmi_requested_devices_mtx); + + return scmi_dev; } -EXPORT_SYMBOL_GPL(scmi_protocol_register); +EXPORT_SYMBOL_GPL(scmi_device_create); -void scmi_protocol_unregister(int protocol_id) +void scmi_device_destroy(struct device *parent, int protocol, const char *name) { - spin_lock(&protocol_lock); - idr_remove(&scmi_protocols, protocol_id); - spin_unlock(&protocol_lock); + struct scmi_device *scmi_dev; + + scmi_dev = scmi_child_dev_find(parent, protocol, name); + if (scmi_dev) + __scmi_device_destroy(scmi_dev); } -EXPORT_SYMBOL_GPL(scmi_protocol_unregister); +EXPORT_SYMBOL_GPL(scmi_device_destroy); static int __scmi_devices_unregister(struct device *dev, void *data) { struct scmi_device *scmi_dev = to_scmi_dev(dev); - scmi_device_destroy(scmi_dev); + __scmi_device_destroy(scmi_dev); return 0; } @@ -208,7 +586,9 @@ static int __init scmi_bus_init(void) retval = bus_register(&scmi_bus_type); if (retval) - pr_err("scmi protocol bus register failed (%d)\n", retval); + pr_err("SCMI protocol bus register failed (%d)\n", retval); + + pr_info("SCMI protocol bus registered\n"); return retval; } @@ -216,8 +596,17 @@ subsys_initcall(scmi_bus_init); static void __exit scmi_bus_exit(void) { + /* + * Destroy all remaining devices: just in case the drivers were + * manually unbound and at first and then the modules unloaded. + */ scmi_devices_unregister(); bus_unregister(&scmi_bus_type); ida_destroy(&scmi_bus_id); } module_exit(scmi_bus_exit); + +MODULE_ALIAS("scmi-core"); +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); +MODULE_DESCRIPTION("ARM SCMI protocol bus"); +MODULE_LICENSE("GPL"); |
