summaryrefslogtreecommitdiff
path: root/drivers/firmware
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware')
-rw-r--r--drivers/firmware/arm_scmi/bus.c89
-rw-r--r--drivers/firmware/arm_scmi/common.h2
-rw-r--r--drivers/firmware/arm_scmi/driver.c77
-rw-r--r--drivers/firmware/arm_scmi/notify.c21
-rw-r--r--drivers/firmware/arm_scmi/notify.h1
-rw-r--r--drivers/firmware/arm_scmi/transports/mailbox.c26
-rw-r--r--drivers/firmware/arm_scmi/transports/smc.c15
-rw-r--r--drivers/firmware/google/coreboot_table.c26
-rw-r--r--drivers/firmware/qcom/Makefile1
-rw-r--r--drivers/firmware/qcom/qcom_scm-smc.c12
-rw-r--r--drivers/firmware/qcom/qcom_scm.c180
-rw-r--r--drivers/firmware/qcom/qcom_scm.h3
-rw-r--r--drivers/firmware/qcom/qcom_scm_trace.h143
-rw-r--r--drivers/firmware/qcom/qcom_tzmem.c13
14 files changed, 501 insertions, 108 deletions
diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c
index c7698cfaa4e8..290047b46d51 100644
--- a/drivers/firmware/arm_scmi/bus.c
+++ b/drivers/firmware/arm_scmi/bus.c
@@ -136,17 +136,6 @@ out:
return ret;
}
-static int scmi_protocol_table_register(const struct scmi_device_id *id_table)
-{
- int ret = 0;
- const struct scmi_device_id *entry;
-
- 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
*
@@ -159,6 +148,7 @@ static int scmi_protocol_table_register(const struct scmi_device_id *id_table)
*/
static void scmi_protocol_device_unrequest(const struct scmi_device_id *id_table)
{
+ struct scmi_requested_dev *rdev, *victim = NULL;
struct list_head *phead;
pr_debug("Unrequesting SCMI device (%s) for protocol %x\n",
@@ -167,29 +157,48 @@ static void scmi_protocol_device_unrequest(const struct scmi_device_id *id_table
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);
+ list_for_each_entry(rdev, phead, node) {
+ if (!strcmp(rdev->id_table->name, id_table->name)) {
+ victim = rdev;
+ list_del(&rdev->node);
break;
}
}
- if (list_empty(phead)) {
+ if (victim && list_empty(phead)) {
idr_remove(&scmi_requested_devices,
id_table->protocol_id);
kfree(phead);
}
}
mutex_unlock(&scmi_requested_devices_mtx);
+
+ if (victim) {
+ blocking_notifier_call_chain(&scmi_requested_devices_nh,
+ SCMI_BUS_NOTIFY_DEVICE_UNREQUEST,
+ (void *)victim->id_table);
+ kfree(victim);
+ }
+}
+
+static int scmi_protocol_table_register(const struct scmi_device_id *id_table)
+{
+ const struct scmi_device_id *entry;
+ int ret;
+
+ for (entry = id_table; entry->name; entry++) {
+ ret = scmi_protocol_device_request(entry);
+ if (ret)
+ goto err_unrequest;
+ }
+
+ return 0;
+
+err_unrequest:
+ while (entry != id_table)
+ scmi_protocol_device_unrequest(--entry);
+
+ return ret;
}
static void
@@ -201,21 +210,33 @@ scmi_protocol_table_unregister(const struct scmi_device_id *id_table)
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)
+static bool scmi_device_is_transport(const struct scmi_device *scmi_dev)
+{
+ return !strncmp(scmi_dev->name, SCMI_TRANSPORT_DEVNAME_PREFIX,
+ strlen(SCMI_TRANSPORT_DEVNAME_PREFIX));
+}
+
+static int __scmi_dev_match_by_id_table(struct scmi_device *scmi_dev,
+ const struct scmi_device_id *id_table,
+ bool skip_transport)
{
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) &&
+ !(skip_transport && scmi_device_is_transport(scmi_dev)) &&
!strcmp(id_table->name, scmi_dev->name))
return 1;
return 0;
}
+static int scmi_dev_match_by_id_table(struct scmi_device *scmi_dev,
+ const struct scmi_device_id *id_table)
+{
+ return __scmi_dev_match_by_id_table(scmi_dev, id_table, true);
+}
+
static int scmi_dev_match_id(struct scmi_device *scmi_dev,
const struct scmi_driver *scmi_drv)
{
@@ -235,7 +256,7 @@ 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);
+ return __scmi_dev_match_by_id_table(scmi_dev, id_table, false);
}
static struct scmi_device *scmi_child_dev_find(struct device *parent,
@@ -377,10 +398,14 @@ int scmi_driver_register(struct scmi_driver *driver, struct module *owner,
driver->driver.mod_name = mod_name;
retval = driver_register(&driver->driver);
- if (!retval)
- pr_debug("Registered new scmi driver %s\n", driver->name);
+ if (retval) {
+ scmi_protocol_table_unregister(driver->id_table);
+ return retval;
+ }
- return retval;
+ pr_debug("Registered new scmi driver %s\n", driver->name);
+
+ return 0;
}
EXPORT_SYMBOL_GPL(scmi_driver_register);
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 7c35c95fddba..ef803e1d25e1 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -33,6 +33,8 @@
#define SCMI_SHMEM_MAX_PAYLOAD_SIZE 104
+#define SCMI_TRANSPORT_DEVNAME_PREFIX "__scmi_transport_device"
+
enum scmi_error_codes {
SCMI_SUCCESS = 0, /* Success */
SCMI_ERR_SUPPORT = -1, /* Not supported */
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 5caa9191a8d1..5a9d6df50679 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -32,6 +32,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/processor.h>
+#include <linux/rcupdate.h>
#include <linux/refcount.h>
#include <linux/slab.h>
#include <linux/xarray.h>
@@ -2540,21 +2541,31 @@ static int scmi_handle_put(const struct scmi_handle *handle)
return 0;
}
-static void scmi_device_link_add(struct device *consumer,
+static bool scmi_device_link_add(struct device *consumer,
struct device *supplier)
{
struct device_link *link;
link = device_link_add(consumer, supplier, DL_FLAG_AUTOREMOVE_CONSUMER);
- WARN_ON(!link);
+ return !WARN_ON(!link);
+}
+
+static void scmi_clear_handle(struct scmi_device *scmi_dev)
+{
+ if (!scmi_dev->handle)
+ return;
+
+ scmi_handle_put(scmi_dev->handle);
+ scmi_dev->handle = NULL;
}
static void scmi_set_handle(struct scmi_device *scmi_dev)
{
scmi_dev->handle = scmi_handle_get(&scmi_dev->dev);
- if (scmi_dev->handle)
- scmi_device_link_add(&scmi_dev->dev, scmi_dev->handle->dev);
+ if (scmi_dev->handle &&
+ !scmi_device_link_add(&scmi_dev->dev, scmi_dev->handle->dev))
+ scmi_clear_handle(scmi_dev);
}
static int __scmi_xfer_info_init(struct scmi_info *sinfo,
@@ -2663,6 +2674,9 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
idx = tx ? 0 : 1;
idr = tx ? &info->tx_idr : &info->rx_idr;
+ if (idr_find(idr, prot_id))
+ return -EEXIST;
+
if (!info->desc->ops->chan_available(of_node, idx)) {
cinfo = idr_find(idr, SCMI_PROTOCOL_BASE);
if (unlikely(!cinfo)) /* Possible only if platform has no Rx */
@@ -2679,7 +2693,7 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
cinfo->max_msg_size = info->desc->max_msg_size;
/* Create a unique name for this transport device */
- snprintf(name, 32, "__scmi_transport_device_%s_%02X",
+ snprintf(name, sizeof(name), SCMI_TRANSPORT_DEVNAME_PREFIX "_%s_%02X",
idx ? "rx" : "tx", prot_id);
/* Create a uniquely named, dedicated transport device for this chan */
tdev = scmi_device_create(of_node, info->dev, prot_id, name);
@@ -2693,6 +2707,7 @@ static int scmi_chan_setup(struct scmi_info *info, struct device_node *of_node,
cinfo->id = prot_id;
cinfo->dev = &tdev->dev;
+ cinfo->handle = &info->handle;
ret = info->desc->ops->chan_setup(cinfo, info->dev, tx);
if (ret) {
of_node_put(of_node);
@@ -2718,6 +2733,7 @@ idr_alloc:
"unable to allocate SCMI idr slot err %d\n", ret);
/* Destroy channel and device only if created by this call. */
if (tdev) {
+ info->desc->ops->chan_free(prot_id, cinfo, idr);
of_node_put(of_node);
scmi_device_destroy(info->dev, prot_id, name);
devm_kfree(info->dev, cinfo);
@@ -2725,7 +2741,6 @@ idr_alloc:
return ret;
}
- cinfo->handle = &info->handle;
return 0;
}
@@ -2783,9 +2798,11 @@ static int scmi_channels_setup(struct scmi_info *info)
if (of_property_read_u32(child, "reg", &prot_id))
continue;
- if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id))
+ if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id)) {
dev_err(info->dev,
"Out of range protocol %d\n", prot_id);
+ continue;
+ }
ret = scmi_txrx_setup(info, child, prot_id);
if (ret)
@@ -2795,7 +2812,7 @@ static int scmi_channels_setup(struct scmi_info *info)
return 0;
}
-static int scmi_chan_destroy(int id, void *p, void *idr)
+static int scmi_chan_destroy(int id, void *p, void *data)
{
struct scmi_chan_info *cinfo = p;
@@ -2804,12 +2821,10 @@ static int scmi_chan_destroy(int id, void *p, void *idr)
struct scmi_device *sdev = to_scmi_dev(cinfo->dev);
of_node_put(cinfo->dev->of_node);
- scmi_device_destroy(info->dev, id, sdev->name);
+ scmi_device_destroy(info->dev, cinfo->id, sdev->name);
cinfo->dev = NULL;
}
- idr_remove(idr, id);
-
return 0;
}
@@ -2836,6 +2851,7 @@ static int scmi_bus_notifier(struct notifier_block *nb,
{
struct scmi_info *info = bus_nb_to_scmi_info(nb);
struct scmi_device *sdev = to_scmi_dev(data);
+ const char *status;
/* Skip devices of different SCMI instances */
if (sdev->dev.parent != info->dev)
@@ -2845,18 +2861,22 @@ static int scmi_bus_notifier(struct notifier_block *nb,
case BUS_NOTIFY_BIND_DRIVER:
/* setup handle now as the transport is ready */
scmi_set_handle(sdev);
+ status = "about to be BOUND.";
+ break;
+ case BUS_NOTIFY_DRIVER_NOT_BOUND:
+ scmi_clear_handle(sdev);
+ status = "NOT BOUND.";
break;
case BUS_NOTIFY_UNBOUND_DRIVER:
- scmi_handle_put(sdev->handle);
- sdev->handle = NULL;
+ scmi_clear_handle(sdev);
+ status = "UNBOUND.";
break;
default:
return NOTIFY_DONE;
}
dev_dbg(info->dev, "Device %s (%s) is now %s\n", dev_name(&sdev->dev),
- sdev->name, action == BUS_NOTIFY_BIND_DRIVER ?
- "about to be BOUND." : "UNBOUND.");
+ sdev->name, status);
return NOTIFY_OK;
}
@@ -2868,7 +2888,9 @@ static int scmi_device_request_notifier(struct notifier_block *nb,
struct scmi_device_id *id_table = data;
struct scmi_info *info = req_nb_to_scmi_info(nb);
+ rcu_read_lock();
np = idr_find(&info->active_protocols, id_table->protocol_id);
+ rcu_read_unlock();
if (!np)
return NOTIFY_DONE;
@@ -3171,7 +3193,7 @@ static int scmi_probe(struct platform_device *pdev)
ret = scmi_channels_setup(info);
if (ret) {
err_str = "failed to setup channels\n";
- goto clear_ida;
+ goto clear_txrx_setup;
}
ret = bus_register_notifier(&scmi_bus_type, &info->bus_nb);
@@ -3233,7 +3255,7 @@ static int scmi_probe(struct platform_device *pdev)
dev_err(dev, "%s", err_str);
return 0;
}
- goto notification_exit;
+ goto raw_mode_cleanup;
}
mutex_lock(&scmi_list_mutex);
@@ -3248,8 +3270,10 @@ static int scmi_probe(struct platform_device *pdev)
if (of_property_read_u32(child, "reg", &prot_id))
continue;
- if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id))
+ if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id)) {
dev_err(dev, "Out of range protocol %d\n", prot_id);
+ continue;
+ }
if (!scmi_is_protocol_implemented(handle, prot_id)) {
dev_err(dev, "SCMI protocol %d not implemented\n",
@@ -3275,18 +3299,18 @@ static int scmi_probe(struct platform_device *pdev)
return 0;
-notification_exit:
+raw_mode_cleanup:
if (IS_ENABLED(CONFIG_ARM_SCMI_RAW_MODE_SUPPORT))
scmi_raw_mode_cleanup(info->raw);
- scmi_notification_exit(&info->handle);
clear_dev_req_notifier:
blocking_notifier_chain_unregister(&scmi_requested_devices_nh,
&info->dev_req_nb);
clear_bus_notifier:
bus_unregister_notifier(&scmi_bus_type, &info->bus_nb);
clear_txrx_setup:
+ scmi_notification_quiesce(&info->handle);
scmi_cleanup_txrx_channels(info);
-clear_ida:
+ scmi_notification_exit(&info->handle);
ida_free(&scmi_id, info->id);
out_err:
@@ -3309,6 +3333,12 @@ static void scmi_remove(struct platform_device *pdev)
list_del(&info->node);
mutex_unlock(&scmi_list_mutex);
+ blocking_notifier_chain_unregister(&scmi_requested_devices_nh,
+ &info->dev_req_nb);
+
+ /* Stop transport callbacks before tearing down notifications. */
+ scmi_notification_quiesce(&info->handle);
+ scmi_cleanup_txrx_channels(info);
scmi_notification_exit(&info->handle);
mutex_lock(&info->protocols_mtx);
@@ -3319,13 +3349,8 @@ static void scmi_remove(struct platform_device *pdev)
of_node_put(child);
idr_destroy(&info->active_protocols);
- blocking_notifier_chain_unregister(&scmi_requested_devices_nh,
- &info->dev_req_nb);
bus_unregister_notifier(&scmi_bus_type, &info->bus_nb);
- /* Safe to free channels since no more users */
- scmi_cleanup_txrx_channels(info);
-
ida_free(&scmi_id, info->id);
}
diff --git a/drivers/firmware/arm_scmi/notify.c b/drivers/firmware/arm_scmi/notify.c
index 9bf7f43ab868..672c91197a68 100644
--- a/drivers/firmware/arm_scmi/notify.c
+++ b/drivers/firmware/arm_scmi/notify.c
@@ -1707,6 +1707,25 @@ err:
}
/**
+ * scmi_notification_quiesce() - Stop notification late initialization
+ * @handle: The handle identifying the platform instance to quiesce
+ *
+ * Prevent new late-init work from being queued and wait for any already queued
+ * or running late-init work to complete before transport channels are torn
+ * down.
+ */
+void scmi_notification_quiesce(struct scmi_handle *handle)
+{
+ struct scmi_notify_instance *ni;
+
+ ni = scmi_notification_instance_data_get(handle);
+ if (!ni)
+ return;
+
+ disable_work_sync(&ni->init_work);
+}
+
+/**
* scmi_notification_exit() - Shutdown and clean Notification core
* @handle: The handle identifying the platform instance to shutdown
*/
@@ -1717,6 +1736,8 @@ void scmi_notification_exit(struct scmi_handle *handle)
ni = scmi_notification_instance_data_get(handle);
if (!ni)
return;
+
+ scmi_notification_quiesce(handle);
scmi_notification_instance_data_set(handle, NULL);
/* Destroy while letting pending work complete */
diff --git a/drivers/firmware/arm_scmi/notify.h b/drivers/firmware/arm_scmi/notify.h
index 76758a736cf4..f18f98c5ab3b 100644
--- a/drivers/firmware/arm_scmi/notify.h
+++ b/drivers/firmware/arm_scmi/notify.h
@@ -82,6 +82,7 @@ struct scmi_protocol_events {
};
int scmi_notification_init(struct scmi_handle *handle);
+void scmi_notification_quiesce(struct scmi_handle *handle);
void scmi_notification_exit(struct scmi_handle *handle);
int scmi_register_protocol_events(const struct scmi_handle *handle, u8 proto_id,
const struct scmi_protocol_handle *ph,
diff --git a/drivers/firmware/arm_scmi/transports/mailbox.c b/drivers/firmware/arm_scmi/transports/mailbox.c
index ae0f67e6cc45..308736c3ead9 100644
--- a/drivers/firmware/arm_scmi/transports/mailbox.c
+++ b/drivers/firmware/arm_scmi/transports/mailbox.c
@@ -211,13 +211,18 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
cl->tx_block = false;
cl->knows_txdone = tx;
+ cinfo->transport_info = smbox;
+ smbox->cinfo = cinfo;
+ mutex_init(&smbox->chan_lock);
+
smbox->chan = mbox_request_channel(cl, tx ? 0 : p2a_chan);
if (IS_ERR(smbox->chan)) {
ret = PTR_ERR(smbox->chan);
+ smbox->chan = NULL;
if (ret != -EPROBE_DEFER)
dev_err(cdev,
"failed to request SCMI %s mailbox\n", desc);
- return ret;
+ goto err_clear_cinfo;
}
/* Additional unidirectional channel for TX if needed */
@@ -225,9 +230,10 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
smbox->chan_receiver = mbox_request_channel(cl, a2p_rx_chan);
if (IS_ERR(smbox->chan_receiver)) {
ret = PTR_ERR(smbox->chan_receiver);
+ smbox->chan_receiver = NULL;
if (ret != -EPROBE_DEFER)
dev_err(cdev, "failed to request SCMI Tx Receiver mailbox\n");
- return ret;
+ goto err_free_chan;
}
}
@@ -235,17 +241,23 @@ static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
smbox->chan_platform_receiver = mbox_request_channel(cl, p2a_rx_chan);
if (IS_ERR(smbox->chan_platform_receiver)) {
ret = PTR_ERR(smbox->chan_platform_receiver);
+ smbox->chan_platform_receiver = NULL;
if (ret != -EPROBE_DEFER)
dev_err(cdev, "failed to request SCMI P2A Receiver mailbox\n");
- return ret;
+ goto err_free_chan;
}
}
- cinfo->transport_info = smbox;
- smbox->cinfo = cinfo;
- mutex_init(&smbox->chan_lock);
-
return 0;
+
+err_free_chan:
+ mbox_free_channel(smbox->chan);
+err_clear_cinfo:
+ cinfo->transport_info = NULL;
+ smbox->cinfo = NULL;
+ devm_iounmap(dev, smbox->shmem);
+ devm_kfree(dev, smbox);
+ return ret;
}
static int mailbox_chan_free(int id, void *p, void *data)
diff --git a/drivers/firmware/arm_scmi/transports/smc.c b/drivers/firmware/arm_scmi/transports/smc.c
index 21abb571e4f2..1fce3ccdeb7f 100644
--- a/drivers/firmware/arm_scmi/transports/smc.c
+++ b/drivers/firmware/arm_scmi/transports/smc.c
@@ -172,6 +172,13 @@ static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
scmi_info->param_page = SHMEM_PAGE(res.start);
scmi_info->param_offset = SHMEM_OFFSET(res.start);
}
+
+ scmi_info->func_id = func_id;
+ scmi_info->cap_id = cap_id;
+ scmi_info->cinfo = cinfo;
+ smc_channel_lock_init(scmi_info);
+ cinfo->transport_info = scmi_info;
+
/*
* If there is an interrupt named "a2p", then the service and
* completion of a message is signaled by an interrupt rather than by
@@ -183,18 +190,14 @@ static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
IRQF_NO_SUSPEND, dev_name(dev), scmi_info);
if (ret) {
dev_err(dev, "failed to setup SCMI smc irq\n");
+ cinfo->transport_info = NULL;
+ scmi_info->cinfo = NULL;
return ret;
}
} else {
cinfo->no_completion_irq = true;
}
- scmi_info->func_id = func_id;
- scmi_info->cap_id = cap_id;
- scmi_info->cinfo = cinfo;
- smc_channel_lock_init(scmi_info);
- cinfo->transport_info = scmi_info;
-
return 0;
}
diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c
index 882db32e51be..2f1122635098 100644
--- a/drivers/firmware/google/coreboot_table.c
+++ b/drivers/firmware/google/coreboot_table.c
@@ -101,16 +101,20 @@ void coreboot_driver_unregister(struct coreboot_driver *driver)
}
EXPORT_SYMBOL(coreboot_driver_unregister);
-static int coreboot_table_populate(struct device *dev, void *ptr)
+static int coreboot_table_populate(struct device *dev, void *ptr, resource_size_t len)
{
int i, ret;
void *ptr_entry;
struct coreboot_device *device;
struct coreboot_table_entry *entry;
struct coreboot_table_header *header = ptr;
+ void *ptr_end;
+ ptr_end = ptr + len;
ptr_entry = ptr + header->header_bytes;
for (i = 0; i < header->table_entries; i++) {
+ if (ptr_entry + sizeof(*entry) > ptr_end)
+ return -EINVAL;
entry = ptr_entry;
if (entry->size < sizeof(*entry)) {
@@ -118,6 +122,9 @@ static int coreboot_table_populate(struct device *dev, void *ptr)
return -EINVAL;
}
+ if (ptr_entry + entry->size > ptr_end)
+ return -EINVAL;
+
device = kzalloc(sizeof(device->dev) + entry->size, GFP_KERNEL);
if (!device)
return -ENOMEM;
@@ -152,6 +159,7 @@ static int coreboot_table_populate(struct device *dev, void *ptr)
static int coreboot_table_probe(struct platform_device *pdev)
{
resource_size_t len;
+ resource_size_t table_span;
struct coreboot_table_header *header;
struct resource *res;
struct device *dev = &pdev->dev;
@@ -163,7 +171,7 @@ static int coreboot_table_probe(struct platform_device *pdev)
return -EINVAL;
len = resource_size(res);
- if (!res->start || !len)
+ if (!res->start || len < sizeof(*header))
return -EINVAL;
/* Check just the header first to make sure things are sane */
@@ -171,19 +179,27 @@ static int coreboot_table_probe(struct platform_device *pdev)
if (!header)
return -ENOMEM;
- len = header->header_bytes + header->table_bytes;
ret = strncmp(header->signature, "LBIO", sizeof(header->signature));
+
+ if (!ret &&
+ (header->header_bytes < sizeof(*header) ||
+ check_add_overflow((resource_size_t)header->header_bytes,
+ (resource_size_t)header->table_bytes,
+ &table_span) ||
+ table_span > len))
+ ret = -EINVAL;
+
memunmap(header);
if (ret) {
dev_warn(dev, "coreboot table missing or corrupt!\n");
return -ENODEV;
}
- ptr = memremap(res->start, len, MEMREMAP_WB);
+ ptr = memremap(res->start, table_span, MEMREMAP_WB);
if (!ptr)
return -ENOMEM;
- ret = coreboot_table_populate(dev, ptr);
+ ret = coreboot_table_populate(dev, ptr, table_span);
memunmap(ptr);
diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile
index 0be40a1abc13..b679d3fc2c26 100644
--- a/drivers/firmware/qcom/Makefile
+++ b/drivers/firmware/qcom/Makefile
@@ -5,6 +5,7 @@
obj-$(CONFIG_QCOM_SCM) += qcom-scm.o
qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
+CFLAGS_qcom_scm-smc.o := -I$(src)
obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o
obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
diff --git a/drivers/firmware/qcom/qcom_scm-smc.c b/drivers/firmware/qcom/qcom_scm-smc.c
index 574930729ddd..127365ab11fc 100644
--- a/drivers/firmware/qcom/qcom_scm-smc.c
+++ b/drivers/firmware/qcom/qcom_scm-smc.c
@@ -24,6 +24,9 @@ struct arm_smccc_args {
unsigned long args[8];
};
+#define CREATE_TRACE_POINTS
+#include "qcom_scm_trace.h"
+
static DEFINE_MUTEX(qcom_scm_lock);
#define QCOM_SCM_EBUSY_WAIT_MS 30
@@ -44,6 +47,7 @@ static void __scm_smc_do_quirk(const struct arm_smccc_args *smc,
quirk.state.a6 = 0;
do {
+ trace_scm_smc_request(a0, smc);
arm_smccc_smc_quirk(a0, smc->args[1], smc->args[2],
smc->args[3], smc->args[4], smc->args[5],
quirk.state.a6, smc->args[7], res, &quirk);
@@ -83,6 +87,7 @@ int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending)
if (ret)
return ret;
+ trace_scm_waitq_get_wq_ctx(get_wq_res.a1, get_wq_res.a2, get_wq_res.a3);
*wq_ctx = get_wq_res.a1;
*flags = get_wq_res.a2;
*more_pending = get_wq_res.a3;
@@ -105,10 +110,12 @@ static int __scm_smc_do_quirk_handle_waitq(struct device *dev, struct arm_smccc_
wq_ctx = res->a1;
smc_call_ctx = res->a2;
- ret = qcom_scm_wait_for_wq_completion(wq_ctx);
+ trace_scm_waitq_sleep(wq_ctx, smc_call_ctx);
+ ret = qcom_scm_wait_for_wq_completion(dev, wq_ctx);
if (ret)
return ret;
+ trace_scm_waitq_resume(smc_call_ctx);
fill_wq_resume_args(&resume, smc_call_ctx);
smc = &resume;
}
@@ -201,6 +208,9 @@ int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
}
ret = __scm_smc_do(dev, &smc, &smc_res, atomic);
+
+ trace_scm_smc_done(ret, smc.args[0], &smc_res);
+
if (ret)
return ret;
diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
index 3379607eaf94..226265b311fe 100644
--- a/drivers/firmware/qcom/qcom_scm.c
+++ b/drivers/firmware/qcom/qcom_scm.c
@@ -30,18 +30,25 @@
#include <linux/sizes.h>
#include <linux/types.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
#include "qcom_scm.h"
#include "qcom_tzmem.h"
static u32 download_mode;
+#define GIC_SPI_BASE 32
+#define GIC_MAX_SPI 1019 // SPIs in GICv3 spec range from 32..1019
+#define GIC_ESPI_BASE 4096
+#define GIC_MAX_ESPI 5119 // ESPIs in GICv3 spec range from 4096..5119
+
struct qcom_scm {
struct device *dev;
struct clk *core_clk;
struct clk *iface_clk;
struct clk *bus_clk;
struct icc_path *path;
- struct completion waitq_comp;
+ struct completion *waitq_comps;
struct reset_controller_dev reset;
/* control access to the interconnect path */
@@ -51,6 +58,7 @@ struct qcom_scm {
u64 dload_mode_addr;
struct qcom_tzmem_pool *mempool;
+ unsigned int wq_cnt;
};
struct qcom_scm_current_perm_info {
@@ -130,6 +138,8 @@ static const u8 qcom_scm_cpu_warm_bits[QCOM_SCM_BOOT_MAX_CPUS] = {
#define QCOM_DLOAD_MINIDUMP 2
#define QCOM_DLOAD_BOTHDUMP 3
+#define QCOM_SCM_DEFAULT_WAITQ_COUNT 1
+
static const char * const qcom_scm_convention_names[] = {
[SMC_CONVENTION_UNKNOWN] = "unknown",
[SMC_CONVENTION_ARM_32] = "smc arm 32",
@@ -559,6 +569,40 @@ static void qcom_scm_set_download_mode(u32 dload_mode)
}
/**
+ * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service
+ * context for a given peripheral
+ *
+ * PAS context is device-resource managed, so the caller does not need
+ * to worry about freeing the context memory.
+ *
+ * @dev: PAS firmware device
+ * @pas_id: peripheral authentication service id
+ * @mem_phys: Subsystem reserve memory start address
+ * @mem_size: Subsystem reserve memory size
+ *
+ * Returns: The new PAS context, or ERR_PTR() on failure.
+ */
+struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev,
+ u32 pas_id,
+ phys_addr_t mem_phys,
+ size_t mem_size)
+{
+ struct qcom_scm_pas_context *ctx;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->dev = dev;
+ ctx->pas_id = pas_id;
+ ctx->mem_phys = mem_phys;
+ ctx->mem_size = mem_size;
+
+ return ctx;
+}
+EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc);
+
+/**
* qcom_scm_pas_init_image() - Initialize peripheral authentication service
* state machine for a given peripheral, using the
* metadata
@@ -2223,42 +2267,104 @@ bool qcom_scm_is_available(void)
}
EXPORT_SYMBOL_GPL(qcom_scm_is_available);
-static int qcom_scm_assert_valid_wq_ctx(u32 wq_ctx)
+static int qcom_scm_fill_irq_fwspec_params(struct irq_fwspec *fwspec, u32 hwirq)
{
- /* FW currently only supports a single wq_ctx (zero).
- * TODO: Update this logic to include dynamic allocation and lookup of
- * completion structs when FW supports more wq_ctx values.
- */
- if (wq_ctx != 0) {
- dev_err(__scm->dev, "Firmware unexpectedly passed non-zero wq_ctx\n");
- return -EINVAL;
+ if (hwirq >= GIC_SPI_BASE && hwirq <= GIC_MAX_SPI) {
+ fwspec->param[0] = GIC_SPI;
+ fwspec->param[1] = hwirq - GIC_SPI_BASE;
+ } else if (hwirq >= GIC_ESPI_BASE && hwirq <= GIC_MAX_ESPI) {
+ fwspec->param[0] = GIC_ESPI;
+ fwspec->param[1] = hwirq - GIC_ESPI_BASE;
+ } else {
+ WARN(1, "Unexpected hwirq: %d\n", hwirq);
+ return -ENXIO;
}
+ fwspec->param[2] = IRQ_TYPE_EDGE_RISING;
+ fwspec->param_count = 3;
+
return 0;
}
-int qcom_scm_wait_for_wq_completion(u32 wq_ctx)
+static int qcom_scm_query_waitq_count(struct qcom_scm *scm)
{
+ struct qcom_scm_desc desc = {
+ .svc = QCOM_SCM_SVC_WAITQ,
+ .cmd = QCOM_SCM_WAITQ_GET_INFO,
+ .owner = ARM_SMCCC_OWNER_SIP
+ };
+ struct qcom_scm_res res;
int ret;
- ret = qcom_scm_assert_valid_wq_ctx(wq_ctx);
+ ret = qcom_scm_call_atomic(scm->dev, &desc, &res);
if (ret)
return ret;
- wait_for_completion(&__scm->waitq_comp);
-
- return 0;
+ return res.result[0] & GENMASK(7, 0);
}
-static int qcom_scm_waitq_wakeup(unsigned int wq_ctx)
+static int qcom_scm_get_waitq_irq(struct qcom_scm *scm)
{
+ struct qcom_scm_desc desc = {
+ .svc = QCOM_SCM_SVC_WAITQ,
+ .cmd = QCOM_SCM_WAITQ_GET_INFO,
+ .owner = ARM_SMCCC_OWNER_SIP
+ };
+ struct device_node *parent_irq_node;
+ struct irq_fwspec fwspec;
+ struct qcom_scm_res res;
+ u32 hwirq;
int ret;
- ret = qcom_scm_assert_valid_wq_ctx(wq_ctx);
+ ret = qcom_scm_call_atomic(scm->dev, &desc, &res);
+ if (ret)
+ return ret;
+
+ hwirq = res.result[1] & GENMASK(15, 0);
+ ret = qcom_scm_fill_irq_fwspec_params(&fwspec, hwirq);
if (ret)
return ret;
- complete(&__scm->waitq_comp);
+ parent_irq_node = of_irq_find_parent(scm->dev->of_node);
+ if (!parent_irq_node)
+ return -ENODEV;
+
+ fwspec.fwnode = of_fwnode_handle(parent_irq_node);
+
+ return irq_create_fwspec_mapping(&fwspec);
+}
+
+static struct completion *qcom_scm_get_completion(struct qcom_scm *scm, u32 wq_ctx)
+{
+ if (WARN_ON_ONCE(wq_ctx >= scm->wq_cnt))
+ return ERR_PTR(-EINVAL);
+
+ return &scm->waitq_comps[wq_ctx];
+}
+
+int qcom_scm_wait_for_wq_completion(struct device *dev, u32 wq_ctx)
+{
+ struct qcom_scm *scm = dev_get_drvdata(dev);
+ struct completion *wq;
+
+ wq = qcom_scm_get_completion(scm, wq_ctx);
+ if (IS_ERR(wq))
+ return PTR_ERR(wq);
+
+ wait_for_completion(wq);
+
+ return 0;
+}
+
+static int qcom_scm_waitq_wakeup(struct qcom_scm *scm, unsigned int wq_ctx)
+{
+ struct completion *wq;
+
+ wq = qcom_scm_get_completion(scm, wq_ctx);
+ if (IS_ERR(wq))
+ return PTR_ERR(wq);
+
+ complete(wq);
return 0;
}
@@ -2281,7 +2387,7 @@ static irqreturn_t qcom_scm_irq_handler(int irq, void *data)
goto out;
}
- ret = qcom_scm_waitq_wakeup(wq_ctx);
+ ret = qcom_scm_waitq_wakeup(scm, wq_ctx);
if (ret)
goto out;
} while (more_pending);
@@ -2334,17 +2440,18 @@ static int qcom_scm_probe(struct platform_device *pdev)
struct qcom_tzmem_pool_config pool_config;
struct qcom_scm *scm;
int irq, ret;
+ int i;
scm = devm_kzalloc(&pdev->dev, sizeof(*scm), GFP_KERNEL);
if (!scm)
return -ENOMEM;
scm->dev = &pdev->dev;
+ platform_set_drvdata(pdev, scm);
ret = qcom_scm_find_dload_address(&pdev->dev, &scm->dload_mode_addr);
if (ret < 0)
return ret;
- init_completion(&scm->waitq_comp);
mutex_init(&scm->scm_bw_lock);
scm->path = devm_of_icc_get(&pdev->dev, NULL);
@@ -2382,9 +2489,11 @@ static int qcom_scm_probe(struct platform_device *pdev)
"Failed to setup the reserved memory region for TZ mem\n");
ret = qcom_tzmem_enable(scm->dev);
- if (ret)
- return dev_err_probe(scm->dev, ret,
- "Failed to enable the TrustZone memory allocator\n");
+ if (ret) {
+ ret = dev_err_probe(scm->dev, ret,
+ "Failed to enable the TrustZone memory allocator\n");
+ goto err_rmem;
+ }
memset(&pool_config, 0, sizeof(pool_config));
pool_config.initial_size = 0;
@@ -2392,11 +2501,26 @@ static int qcom_scm_probe(struct platform_device *pdev)
pool_config.max_size = SZ_256K;
scm->mempool = devm_qcom_tzmem_pool_new(scm->dev, &pool_config);
- if (IS_ERR(scm->mempool))
- return dev_err_probe(scm->dev, PTR_ERR(scm->mempool),
- "Failed to create the SCM memory pool\n");
+ if (IS_ERR(scm->mempool)) {
+ ret = dev_err_probe(scm->dev, PTR_ERR(scm->mempool),
+ "Failed to create the SCM memory pool\n");
+ goto err_rmem;
+ }
+
+ ret = qcom_scm_query_waitq_count(scm);
+ scm->wq_cnt = ret < 0 ? QCOM_SCM_DEFAULT_WAITQ_COUNT : ret;
+ scm->waitq_comps = devm_kcalloc(&pdev->dev, scm->wq_cnt, sizeof(*scm->waitq_comps),
+ GFP_KERNEL);
+ if (!scm->waitq_comps)
+ return -ENOMEM;
+
+ for (i = 0; i < scm->wq_cnt; i++)
+ init_completion(&scm->waitq_comps[i]);
+
+ irq = qcom_scm_get_waitq_irq(scm);
+ if (irq < 0)
+ irq = platform_get_irq_optional(pdev, 0);
- irq = platform_get_irq_optional(pdev, 0);
if (irq < 0) {
if (irq != -ENXIO)
return irq;
@@ -2449,6 +2573,10 @@ static int qcom_scm_probe(struct platform_device *pdev)
qcom_scm_qtee_init(scm);
return 0;
+
+err_rmem:
+ of_reserved_mem_device_release(scm->dev);
+ return ret;
}
static void qcom_scm_shutdown(struct platform_device *pdev)
diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_scm.h
index a56c8212cc0c..2111188f9fad 100644
--- a/drivers/firmware/qcom/qcom_scm.h
+++ b/drivers/firmware/qcom/qcom_scm.h
@@ -66,7 +66,7 @@ struct qcom_scm_res {
u64 result[MAX_QCOM_SCM_RETS];
};
-int qcom_scm_wait_for_wq_completion(u32 wq_ctx);
+int qcom_scm_wait_for_wq_completion(struct device *dev, u32 wq_ctx);
int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending);
#define SCM_SMC_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF))
@@ -152,6 +152,7 @@ int qcom_scm_shm_bridge_enable(struct device *scm_dev);
#define QCOM_SCM_SVC_WAITQ 0x24
#define QCOM_SCM_WAITQ_RESUME 0x02
#define QCOM_SCM_WAITQ_GET_WQ_CTX 0x03
+#define QCOM_SCM_WAITQ_GET_INFO 0x04
#define QCOM_SCM_SVC_GPU 0x28
#define QCOM_SCM_SVC_GPU_INIT_REGS 0x01
diff --git a/drivers/firmware/qcom/qcom_scm_trace.h b/drivers/firmware/qcom/qcom_scm_trace.h
new file mode 100644
index 000000000000..6c911124fc56
--- /dev/null
+++ b/drivers/firmware/qcom/qcom_scm_trace.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM qcom_scm
+
+#if !defined(_TRACE_SCM_SMC_INTERFACE_H) || defined(TRACE_HEADER_MULTI_READ)
+
+#define _TRACE_SCM_SMC_INTERFACE_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(scm_smc_request,
+
+ TP_PROTO(unsigned long a0, const struct arm_smccc_args *smc),
+
+ TP_ARGS(a0, smc),
+
+ TP_STRUCT__entry(
+ __field(u64, smc_id)
+ __field(u8, svc_id)
+ __field(u8, cmd_id)
+ __field(u8, args_cnt)
+ __dynamic_array(unsigned long, args,
+ min_t(u8, (smc->args[1] & 0xF), (u8)6))
+ ),
+
+ TP_fast_assign(
+ __entry->smc_id = a0;
+ __entry->svc_id = (smc->args[0] >> 8) & 0xFF;
+ __entry->cmd_id = smc->args[0] & 0xFF;
+ u8 n = min_t(u8, (smc->args[1] & 0xF), (u8)6);
+
+ __entry->args_cnt = n;
+
+ unsigned long *dst = __get_dynamic_array(args);
+
+ for (int i = 0; i < n; i++)
+ dst[i] = smc->args[2 + i];
+ ),
+
+ TP_printk("smc_id:0x%08llx svc_id:0x%02x cmd_id:0x%02x args_cnt:%u args:%s",
+ __entry->smc_id, __entry->svc_id, __entry->cmd_id, __entry->args_cnt,
+ __print_dynamic_array(args, sizeof(unsigned long)))
+);
+
+TRACE_EVENT(scm_waitq_sleep,
+
+ TP_PROTO(u32 wq_ctx, u32 smc_ctx),
+
+ TP_ARGS(wq_ctx, smc_ctx),
+
+ TP_STRUCT__entry(
+ __field(u32, wq_ctx)
+ __field(u32, smc_call_ctx)
+ ),
+
+ TP_fast_assign(
+ __entry->wq_ctx = wq_ctx;
+ __entry->smc_call_ctx = smc_ctx;
+ ),
+
+ TP_printk("wq_ctx:%u, smc_call_ctx:%u", __entry->wq_ctx, __entry->smc_call_ctx)
+);
+
+TRACE_EVENT(scm_waitq_resume,
+
+ TP_PROTO(u32 smc_ctx),
+
+ TP_ARGS(smc_ctx),
+
+ TP_STRUCT__entry(
+ __field(u32, smc_call_ctx)
+ ),
+
+ TP_fast_assign(
+ __entry->smc_call_ctx = smc_ctx;
+ ),
+
+ TP_printk("smc_call_ctx:%u", __entry->smc_call_ctx)
+);
+
+TRACE_EVENT(scm_waitq_get_wq_ctx,
+
+ TP_PROTO(u32 wq_ctx, u32 flags, u32 pending),
+
+ TP_ARGS(wq_ctx, flags, pending),
+
+ TP_STRUCT__entry(
+ __field(u32, wq_ctx)
+ __field(u32, flags)
+ __field(u32, more_pending)
+ ),
+
+ TP_fast_assign(
+ __entry->wq_ctx = wq_ctx;
+ __entry->flags = flags;
+ __entry->more_pending = pending;
+ ),
+
+ TP_printk("wq_ctx:%u, flags:%u, more_pending:%u",
+ __entry->wq_ctx, __entry->flags, __entry->more_pending)
+);
+
+TRACE_EVENT(scm_smc_done,
+
+ TP_PROTO(int ret, u64 smc_id, struct arm_smccc_res *smc_res),
+
+ TP_ARGS(ret, smc_id, smc_res),
+
+ TP_STRUCT__entry(
+ __field(int, ret)
+ __field(u64, smc_id)
+ __field(unsigned long, res)
+ __field(unsigned long, res0)
+ __field(unsigned long, res1)
+ __field(unsigned long, res2)
+ ),
+
+ TP_fast_assign(
+ __entry->ret = ret;
+ __entry->smc_id = smc_id;
+ __entry->res = smc_res->a0;
+ __entry->res0 = smc_res->a1;
+ __entry->res1 = smc_res->a2;
+ __entry->res2 = smc_res->a3;
+ ),
+
+ TP_printk("smc_id:0x%08llx, ret:%d res_to_callee:0x%lx res0:0x%lx res1:0x%lx res2:0x%lx",
+ __entry->smc_id, __entry->ret, __entry->res,
+ __entry->res0, __entry->res1, __entry->res2)
+);
+
+#endif /* _TRACE_SCM_SMC_INTERFACE_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE qcom_scm_trace
+
+#include <trace/define_trace.h>
+
diff --git a/drivers/firmware/qcom/qcom_tzmem.c b/drivers/firmware/qcom/qcom_tzmem.c
index 9f232e53115e..b867b9d8122a 100644
--- a/drivers/firmware/qcom/qcom_tzmem.c
+++ b/drivers/firmware/qcom/qcom_tzmem.c
@@ -15,6 +15,7 @@
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mm.h>
+#include <linux/once.h>
#include <linux/radix-tree.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@@ -508,14 +509,18 @@ phys_addr_t qcom_tzmem_to_phys(void *vaddr)
}
EXPORT_SYMBOL_GPL(qcom_tzmem_to_phys);
+static void qcom_tzmem_do_init(int *result)
+{
+ *result = qcom_tzmem_init();
+}
+
int qcom_tzmem_enable(struct device *dev)
{
- if (qcom_tzmem_dev)
- return -EBUSY;
+ static int result;
qcom_tzmem_dev = dev;
-
- return qcom_tzmem_init();
+ DO_ONCE(qcom_tzmem_do_init, &result);
+ return result;
}
EXPORT_SYMBOL_GPL(qcom_tzmem_enable);