diff --git a/Documentation/devicetree/bindings/arm/arm,scmi.txt b/Documentation/devicetree/bindings/arm/arm,scmi.txt index dc102c4e4a78b8..596561a569fe81 100644 --- a/Documentation/devicetree/bindings/arm/arm,scmi.txt +++ b/Documentation/devicetree/bindings/arm/arm,scmi.txt @@ -10,21 +10,44 @@ the SCMI as described in ARM document number ARM DEN 0056A ("ARM System Control and Management Interface Platform Design Document")[0] provide for OSPM in the device tree. +The SCMI agent node with its properties shall be under the /firmware/ node. + +Each protocol supported by an agent be defined by a sub-node in the SCMI +agent node as described in the following sections. + +For a given SCMI agent node, the communication channel properties, as defined +by related SCMI tranport compatible bindings, can be either defined in the +root node of the SCMI device or in the subnode of the related SCMI protocol +supported by the device. In the former case, the communcation propoerties +are shared by all protocol. In the later case, each protocol node must +define all communication configuration expected by the related transport +channel. + +Required properties common to all SCMI agent node: + +- #address-cells : should be '1' if the device has sub-nodes, maps to + protocol identifier for a given sub-node. +- #size-cells : should be '0' as 'reg' property doesn't have any size + associated with it. + +SCMI Agent over Mailbox transport channel +----------------------------------------- + +SCMI messages can be exchange between agent and the SCMI server using a +mailbox device for message notification and a piece of shared memory for +message payload and protocol data transfer. + Required properties: -The scmi node with the following properties shall be under the /firmware/ node. +- compatible : "arm,scmi" -- compatible : shall be "arm,scmi" - mboxes: List of phandle and mailbox channel specifiers. It should contain exactly one or two mailboxes, one for transmitting messages("tx") and another optional for receiving the notifications("rx") if supported. + - shmem : List of phandle pointing to the shared memory(SHM) area as per generic mailbox client binding. -- #address-cells : should be '1' if the device has sub-nodes, maps to - protocol identifier for a given sub-node. -- #size-cells : should be '0' as 'reg' property doesn't have any size - associated with it. Optional properties: @@ -37,11 +60,42 @@ The mailbox is the only permitted method of calling the SCMI firmware. Mailbox doorbell is used as a mechanism to alert the presence of a messages and/or notification. -Each protocol supported shall have a sub-node with corresponding compatible -as described in the following sections. If the platform supports dedicated -communication channel for a particular protocol, the 3 properties namely: -mboxes, mbox-names and shmem shall be present in the sub-node corresponding -to that protocol. +SCMI Agent over Arm SMCCC transport channel +------------------------------------------- + +On Arm based systems that support secure world invocation through secure +monitor, SCMI messages can be exchange between agent and the SCMI server +using an Arm SMC or HVC instruction for message notification and a piece +of shared memory for message payload and protocol data transfer. + +Required properties: + +- compatible : "arm,scmi-smc" + +- shmem : List of phandle pointing to the shared memory(SHM) area as per + generic mailbox client binding. + +- arm,smc-id : function identifier used a argument to the SMC/HVC instruction + as defined in Arm SMCCC specification [6]. + +- method : "smc" or "hvc" + Optional property defining the conduit method for to be used + for invoking the SCMI server in secure world. + "smc" states instruction SMC #0 is used whereas "hvc" states + instruction HVC #0 is used. + +SCMI Agent over on OP-TEE Service +--------------------------------- + +On systems that support OP-TEE, SCMI messages can be exchange between agent +and the SCMI server using an OP-TEE invocation. + +Required properties: + +- compatible : "linaro,scmi-optee" + +- agent-id : agent identifier assigned to the agent/channel and known from + the SCMI server in OP-TEE. Clock/Performance bindings for the clocks/OPPs based on SCMI Message Protocol ------------------------------------------------------------ @@ -104,6 +158,7 @@ Required sub-node properties: [3] Documentation/devicetree/bindings/thermal/thermal.txt [4] Documentation/devicetree/bindings/sram/sram.yaml [5] Documentation/devicetree/bindings/reset/reset.txt +[6] https://developer.arm.com/docs/den0028/latest Example: diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 8007d4aa76dcd6..28903ebfbe6070 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -9,7 +9,7 @@ menu "Firmware Drivers" config ARM_SCMI_PROTOCOL bool "ARM System Control and Management Interface (SCMI) Message Protocol" depends on ARM || ARM64 || COMPILE_TEST - depends on MAILBOX + depends on MAILBOX || HAVE_ARM_SMCCC || OPTEE help ARM System Control and Management Interface (SCMI) protocol is a set of operating system-independent software interfaces that are diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index 6694d0d908d635..1473210c69f2ae 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -2,6 +2,9 @@ obj-y = scmi-bus.o scmi-driver.o scmi-protocols.o scmi-transport.o scmi-bus-y = bus.o scmi-driver-y = driver.o -scmi-transport-y = mailbox.o shmem.o +scmi-transport-y = shmem.o +scmi-transport-$(CONFIG_MAILBOX) += mailbox.o +scmi-transport-$(CONFIG_HAVE_ARM_SMCCC) += smc.o +scmi-transport-$(CONFIG_OPTEE) += optee_service.o scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 5ac06469b01cb3..aacfa14589b200 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -6,6 +6,8 @@ * * Copyright (C) 2018 ARM Ltd. */ +#ifndef ARM_SCMI_COMMON_H +#define ARM_SCMI_COMMON_H #include #include @@ -172,7 +174,6 @@ struct scmi_chan_info { /** * struct scmi_transport_ops - Structure representing a SCMI transport ops * - * @chan_available: Callback to check if channel is available or not * @chan_setup: Callback to allocate and setup a channel * @chan_free: Callback to free a channel * @send_message: Callback to send a message @@ -181,7 +182,6 @@ struct scmi_chan_info { * @poll_done: Callback to poll transfer status */ struct scmi_transport_ops { - bool (*chan_available)(struct device *dev, int idx); int (*chan_setup)(struct scmi_chan_info *cinfo, struct device *dev, bool tx); int (*chan_free)(int id, void *p, void *data); @@ -210,6 +210,8 @@ struct scmi_desc { }; extern const struct scmi_desc scmi_mailbox_desc; +extern const struct scmi_desc scmi_smc_desc; +extern const struct scmi_desc scmi_optee_desc; void scmi_rx_callback(struct scmi_chan_info *cinfo, u32 msg_hdr); void scmi_free_channel(struct scmi_chan_info *cinfo, struct idr *idr, int id); @@ -224,3 +226,5 @@ void shmem_fetch_response(struct scmi_shared_mem __iomem *shmem, struct scmi_xfer *xfer); bool shmem_poll_done(struct scmi_shared_mem __iomem *shmem, struct scmi_xfer *xfer); + +#endif /* ARM_SCMI_COMMON_H */ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index dbec767222e9fc..9fa325560d997e 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -261,13 +261,17 @@ void scmi_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer) #define SCMI_MAX_POLL_TO_NS (100 * NSEC_PER_USEC) -static bool scmi_xfer_done_no_timeout(struct scmi_chan_info *cinfo, - struct scmi_xfer *xfer, ktime_t stop) +static bool scmi_poll_done(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer) { struct scmi_info *info = handle_to_scmi_info(cinfo->handle); - return info->desc->ops->poll_done(cinfo, xfer) || - ktime_after(ktime_get(), stop); + return info->desc->ops->poll_done(cinfo, xfer); +} + +static bool scmi_xfer_done_no_timeout(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer, ktime_t stop) +{ + return scmi_poll_done(cinfo, xfer) || ktime_after(ktime_get(), stop); } /** @@ -297,7 +301,7 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer) xfer->hdr.poll_completion); ret = info->desc->ops->send_message(cinfo, xfer); - if (ret < 0) { + if (ret) { dev_dbg(dev, "Failed to send message %d\n", ret); return ret; } @@ -307,7 +311,7 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer) spin_until_cond(scmi_xfer_done_no_timeout(cinfo, xfer, stop)); - if (ktime_before(ktime_get(), stop)) + if (scmi_poll_done(cinfo, xfer)) info->desc->ops->fetch_response(cinfo, xfer); else ret = -ETIMEDOUT; @@ -566,6 +570,14 @@ static int scmi_xfer_info_init(struct scmi_info *sinfo) return 0; } +/* SCMI root node has no reg property */ +static bool node_is_scmi_root_node(const struct device_node *np) +{ + u32 protocol_id; + + return of_property_read_u32(np, "reg", &protocol_id); +} + static int scmi_chan_setup(struct scmi_info *info, struct device *dev, int prot_id, bool tx) { @@ -582,7 +594,8 @@ static int scmi_chan_setup(struct scmi_info *info, struct device *dev, if (cinfo) return 0; - if (!info->desc->ops->chan_available(dev, idx)) { + /* Channels are defined in SCMI device root node */ + if (!node_is_scmi_root_node(dev->of_node)) { cinfo = idr_find(idr, SCMI_PROTOCOL_BASE); if (unlikely(!cinfo)) /* Possible only if platform has no Rx */ return -EINVAL; @@ -826,7 +839,15 @@ ATTRIBUTE_GROUPS(versions); /* Each compatible listed below must have descriptor associated with it */ static const struct of_device_id scmi_of_match[] = { +#ifdef CONFIG_MAILBOX { .compatible = "arm,scmi", .data = &scmi_mailbox_desc }, +#endif +#ifdef CONFIG_HAVE_ARM_SMCCC + { .compatible = "arm,scmi-smc", .data = &scmi_smc_desc}, +#endif +#ifdef CONFIG_OPTEE + { .compatible = "arm,scmi-optee", .data = &scmi_optee_desc }, +#endif { /* Sentinel */ }, }; diff --git a/drivers/firmware/arm_scmi/mailbox.c b/drivers/firmware/arm_scmi/mailbox.c index 73077bbc4ad9dc..0e3792395cc930 100644 --- a/drivers/firmware/arm_scmi/mailbox.c +++ b/drivers/firmware/arm_scmi/mailbox.c @@ -46,12 +46,6 @@ static void rx_callback(struct mbox_client *cl, void *m) scmi_rx_callback(smbox->cinfo, shmem_read_header(smbox->shmem)); } -static bool mailbox_chan_available(struct device *dev, int idx) -{ - return !of_parse_phandle_with_args(dev->of_node, "mboxes", - "#mbox-cells", idx, NULL); -} - static int mailbox_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, bool tx) { @@ -167,7 +161,6 @@ mailbox_poll_done(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer) } static struct scmi_transport_ops scmi_mailbox_ops = { - .chan_available = mailbox_chan_available, .chan_setup = mailbox_chan_setup, .chan_free = mailbox_chan_free, .send_message = mailbox_send_message, diff --git a/drivers/firmware/arm_scmi/optee_service.c b/drivers/firmware/arm_scmi/optee_service.c new file mode 100644 index 00000000000000..743877dd731277 --- /dev/null +++ b/drivers/firmware/arm_scmi/optee_service.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define DRIVER_NAME "optee-scmi-agent" + +/* + * TA_CMD_CHANNEL_COUNT - Get number of channels supported + * + * param[0] (out value) - value.a: Number of communication channels + * param[1] unused + * param[2] unused + * param[3] unused + * + * Result: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + */ +#define TA_CMD_CHANNEL_COUNT 0x0 + +/* + * TA_CMD_GET_CHANNEL - Get channel identifer for a buffer pool + * + * param[0] (in value) - Message buffer physical start address + * param[1] (in value) - Message buffer byte size + * param[2] (out value) - value.a: Output channel identifier + * param[3] unused + * + * Result: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + */ +#define TA_CMD_GET_CHANNEL 0x1 + + +/* + * TA_CMD_PROCESS_CHANNEL - Process message in SCMI channel + * + * param[0] (in value) - value.a: SCMI channel identifier + * param[1] unused + * param[2] unused + * param[3] unused + * + * Result: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + */ +#define TA_CMD_PROCESS_CHANNEL 0x2 + +/** + * struct optee_scmi_agent - OP-TEE Random Number Generator private data + * @dev: OP-TEE based SCMI server device. + * @ctx: OP-TEE context handler. + * @session_id: SCMI server TA session identifier. + * @agent_count: Count of agent channels supported by the server + */ +struct optee_scmi_agent { + struct device *dev; + struct tee_context *ctx; + u32 session_id; + unsigned int agent_count; +}; + +static struct optee_scmi_agent agent_private; + +static int get_channel_count(void) +{ + int ret = 0; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + + dev_info(agent_private.dev, "count channels\n"); + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + /* Invoke TA_CMD_CHANNEL_COUNT function of Trusted App */ + inv_arg.func = TA_CMD_CHANNEL_COUNT; + inv_arg.session = agent_private.session_id; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; + + ret = tee_client_invoke_func(agent_private.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(agent_private.dev, "Failed to get agent count: 0x%x\n", + inv_arg.ret); + return -ENOTSUPP; + } + + dev_info(agent_private.dev, "count channels: back: %u\n", + (unsigned)param[0].u.value.a); + + agent_private.agent_count = param[0].u.value.a; + + return 0; +} + +static int get_channel(unsigned long pa, size_t length, int *channel_id) +{ + int ret = 0; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + uint64_t addr = pa; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + /* Invoke TA_CMD_GET_CHANNEL function of Trusted App */ + inv_arg.func = TA_CMD_GET_CHANNEL; + inv_arg.session = agent_private.session_id; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + param[0].u.value.a = addr >> 32; + param[0].u.value.b = addr & 0xffffffff; + param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + param[1].u.value.a = length; + param[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT; + + ret = tee_client_invoke_func(agent_private.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(agent_private.dev, "Failed to get channel: 0x%x\n", + inv_arg.ret); + return -ENOTSUPP; + } + + *channel_id = param[2].u.value.a; + + return 0; +} + +static int process_event(unsigned int channel_id) +{ + int ret = 0; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + /* Invoke TA_CMD_PROCESS_CHANNEL function of Trusted App */ + inv_arg.func = TA_CMD_PROCESS_CHANNEL; + inv_arg.session = agent_private.session_id; + inv_arg.num_params = 4; + + /* Fill invoke cmd params */ + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + param[0].u.value.a = channel_id; + + ret = tee_client_invoke_func(agent_private.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(agent_private.dev, "Failed on channel %u: 0x%x\n", + channel_id, inv_arg.ret); + return -EIO; + } + + return 0; +} + +/** + * struct optee_scmi_channel - OP-TEE server assigns channel ID per shmem + * @channle_id: Id provided by OP-TEE for the channel + */ +struct optee_scmi_channel { + uint32_t agent_id; + struct scmi_chan_info *cinfo; + struct scmi_shared_mem __iomem *shmem; +}; + +static int optee_scmi_get_channel(struct device *dev, struct resource *res, + struct optee_scmi_channel *channel) +{ + int ret; + unsigned int id = 0; + + if (!agent_private.ctx) + return -EPROBE_DEFER; + + if (!agent_private.agent_count) + return -ENOENT; + + ret = get_channel(res->start, resource_size(res), &id); + if (ret) + return ret; + + if (channel->agent_id != id) + dev_warn(dev, "Channel ID mismatch\n"); + + return 0; +} + +static int optee_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, + bool tx) +{ + const char *desc = tx ? "Tx" : "Rx"; + int idx = tx ? 0 : 1; + struct device *cdev = cinfo->dev; + struct optee_scmi_channel *channel; + u32 agent_id; + struct device_node *shmem; + resource_size_t size; + struct resource res; + int ret; + + channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + if (of_property_read_u32(dev->of_node, "agent-id", &agent_id)) { + dev_err(dev, "Missing \"agent-id\" property\n"); + return -EINVAL; + } + channel->agent_id = agent_id; + + // TODO: remove this: push buffer in invocation memeref parameter + shmem = of_parse_phandle(cdev->of_node, "shmem", idx); + if (!shmem) { + dev_err(cdev, "OPTEE failed to get SCMI %s shm\n", desc); + return -ENOENT; + } + + ret = of_address_to_resource(shmem, 0, &res); + of_node_put(shmem); + if (ret) { + dev_err(cdev, "failed to get SCMI %s shared memory\n", desc); + return ret; + } + + /* Get channel IDs from shm location */ + ret = optee_scmi_get_channel(dev, &res, channel); + if (ret) { + dev_err(dev, "failed to get OP-TEE channel %d\n", ret); + return ret; + } + + size = resource_size(&res); + channel->shmem = devm_ioremap(dev, res.start, size); + if (!channel->shmem) { + dev_err(dev, "failed to ioremap SCMI %s shared memory\n", desc); + return -EADDRNOTAVAIL; + } + + cinfo->transport_info = channel; + channel->cinfo = cinfo; + + return 0; +} + +static int optee_chan_free(int id, void *p, void *data) +{ + struct scmi_chan_info *cinfo = p; + struct optee_scmi_channel *channel = cinfo->transport_info; + + cinfo->transport_info = NULL; + channel->cinfo = NULL; + + scmi_free_channel(cinfo, data, id); + + return 0; +} + +static int optee_send_message(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer) +{ + struct optee_scmi_channel *channel = cinfo->transport_info; + int ret; + + if (!channel && !agent_private.ctx) + return -EINVAL; + + shmem_tx_prepare(channel->shmem, xfer); + + ret = process_event(channel->agent_id); + if (ret) + return ret; + + scmi_rx_callback(cinfo, shmem_read_header(channel->shmem)); + + return 0; +} + +static void optee_fetch_response(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer) +{ + struct optee_scmi_channel *channel = cinfo->transport_info; + + shmem_fetch_response(channel->shmem, xfer); +} + +static bool optee_poll_done(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer) +{ + struct optee_scmi_channel *channel = cinfo->transport_info; + + return shmem_poll_done(channel->shmem, xfer); +} + +static struct scmi_transport_ops scmi_optee_ops = { + .chan_setup = optee_chan_setup, + .chan_free = optee_chan_free, + .send_message = optee_send_message, + .fetch_response = optee_fetch_response, + .poll_done = optee_poll_done, +}; + +const struct scmi_desc scmi_optee_desc = { + .ops = &scmi_optee_ops, + .max_rx_timeout_ms = 30, /* We may increase this if required */ + .max_msg = 1, + .max_msg_size = 128, +}; + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, + const void *data) +{ + return ver->impl_id == TEE_IMPL_ID_OPTEE; +} + +static int optee_scmi_probe(struct device *dev) +{ + struct tee_client_device *scmi_device = to_tee_client_device(dev); + int ret = 0, err = -ENODEV; + struct tee_ioctl_open_session_arg sess_arg; + + memset(&sess_arg, 0, sizeof(sess_arg)); + + /* Open context with TEE driver */ + agent_private.ctx = tee_client_open_context(NULL, optee_ctx_match, + NULL, NULL); + if (IS_ERR(agent_private.ctx)) + return -ENODEV; + + /* Open session with SCMI server TA */ + memcpy(sess_arg.uuid, scmi_device->id.uuid.b, TEE_IOCTL_UUID_LEN); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; + sess_arg.num_params = 0; + + ret = tee_client_open_session(agent_private.ctx, &sess_arg, NULL); + if ((ret < 0) || (sess_arg.ret != 0)) { + dev_err(dev, "tee_client_open_session failed, err: %x\n", + sess_arg.ret); + err = -EINVAL; + goto out_ctx; + } + agent_private.session_id = sess_arg.session; + + err = get_channel_count(); + if (err) + goto out_sess; + + agent_private.dev = dev; + + dev_info(dev, "OP-TEE SCMI channel probed\n"); + + return 0; + +out_sess: + tee_client_close_session(agent_private.ctx, agent_private.session_id); +out_ctx: + tee_client_close_context(agent_private.ctx); + agent_private.ctx = NULL; + + return err; +} + +static int optee_scmi_remove(struct device *dev) +{ + tee_client_close_session(agent_private.ctx, agent_private.session_id); + tee_client_close_context(agent_private.ctx); + agent_private.ctx = NULL; + + return 0; +} + +static const struct tee_client_device_id optee_scmi_id_table[] = { + { + UUID_INIT(0xa8cfe406, 0xd4f5, 0x4a2e, + 0x9f, 0x8d, 0xa2, 0x5d, 0xc7, 0x54, 0xc0, 0x99) + }, + { } +}; + +MODULE_DEVICE_TABLE(tee, optee_scmi_id_table); + +static struct tee_client_driver optee_scmi_driver = { + .id_table = optee_scmi_id_table, + .driver = { + .name = DRIVER_NAME, + .bus = &tee_bus_type, + .probe = optee_scmi_probe, + .remove = optee_scmi_remove, + }, +}; + +static int __init optee_scmi_init(void) +{ + return driver_register(&optee_scmi_driver.driver); +} + +static void __exit optee_scmi_exit(void) +{ + driver_unregister(&optee_scmi_driver.driver); +} + +module_init(optee_scmi_init); +module_exit(optee_scmi_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Etienne Carriere "); +MODULE_DESCRIPTION("OP-TEE SCMI agent driver"); diff --git a/drivers/firmware/arm_scmi/smc.c b/drivers/firmware/arm_scmi/smc.c new file mode 100644 index 00000000000000..56cff7e5135d39 --- /dev/null +++ b/drivers/firmware/arm_scmi/smc.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Message SMC/HVC + * Transport driver + * + * Copyright 2020 NXP + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +typedef void (scmi_arm_smccc_invoke_fn)(unsigned long, struct arm_smccc_res *); + +/** + * struct scmi_smc - Structure representing a SCMI smc transport + * + * @cinfo: SCMI channel info + * @shmem: Transmit/Receive shared memory area + * @func_id: smc/hvc call function id + */ +struct scmi_smc { + struct scmi_chan_info *cinfo; + struct scmi_shared_mem __iomem *shmem; + scmi_arm_smccc_invoke_fn *invoke_fn; + u32 func_id; +}; + +static DEFINE_MUTEX(smc_mutex); + +/* Simple wrapper functions to be able to use a function pointer */ +static void _smccc_smc(unsigned long func_id, struct arm_smccc_res *res) +{ + arm_smccc_smc(func_id, 0, 0, 0, 0, 0, 0, 0, res); +} + +static void _smccc_hvc(unsigned long func_id, struct arm_smccc_res *res) +{ + arm_smccc_hvc(func_id, 0, 0, 0, 0, 0, 0, 0, res); +} + +static void _smccc_1_1(unsigned long func_id, struct arm_smccc_res *res) +{ + arm_smccc_1_1_invoke(func_id, 0, 0, 0, 0, 0, 0, 0, res); +} + +static scmi_arm_smccc_invoke_fn *get_invoke_function(struct device *dev) +{ + const char *method; + + pr_info("probing for conduit method.\n"); + + if (device_property_read_string(dev, "method", &method)) + return _smccc_1_1; + + if (!strcmp("hvc", method)) + return _smccc_hvc; + + if (!strcmp("smc", method)) + return _smccc_smc; + + dev_err(dev, "Invalid \"method\" property: %s\n", method); + return ERR_PTR(-EINVAL); +} + +static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, + bool tx) +{ + struct device *cdev = cinfo->dev; + struct scmi_smc *scmi_info; + resource_size_t size; + struct resource res; + struct device_node *np; + u32 func_id; + int ret; + + if (!tx) + return -ENODEV; + + scmi_info = devm_kzalloc(dev, sizeof(*scmi_info), GFP_KERNEL); + if (!scmi_info) + return -ENOMEM; + + np = of_parse_phandle(cdev->of_node, "shmem", 0); + if (!np) + np = of_parse_phandle(dev->of_node, "shmem", 0); + ret = of_address_to_resource(np, 0, &res); + of_node_put(np); + if (ret) { + dev_err(cdev, "failed to get SCMI Tx shared memory\n"); + return ret; + } + + size = resource_size(&res); + scmi_info->shmem = devm_ioremap(dev, res.start, size); + if (!scmi_info->shmem) { + dev_err(dev, "failed to ioremap SCMI Tx shared memory\n"); + return -EADDRNOTAVAIL; + } + + ret = of_property_read_u32(dev->of_node, "arm,smc-id", &func_id); + if (ret < 0) + return ret; + + scmi_info->invoke_fn = get_invoke_function(dev); + if (IS_ERR(scmi_info->invoke_fn)) + return PTR_ERR(scmi_info->invoke_fn); + + scmi_info->func_id = func_id; + scmi_info->cinfo = cinfo; + cinfo->transport_info = scmi_info; + + return 0; +} + +static int smc_chan_free(int id, void *p, void *data) +{ + struct scmi_chan_info *cinfo = p; + struct scmi_smc *scmi_info = cinfo->transport_info; + + cinfo->transport_info = NULL; + scmi_info->cinfo = NULL; + + scmi_free_channel(cinfo, data, id); + + return 0; +} + +static int smc_send_message(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer) +{ + struct scmi_smc *scmi_info = cinfo->transport_info; + struct arm_smccc_res res; + + mutex_lock(&smc_mutex); + + shmem_tx_prepare(scmi_info->shmem, xfer); + + scmi_info->invoke_fn(scmi_info->func_id, &res); + + scmi_rx_callback(scmi_info->cinfo, shmem_read_header(scmi_info->shmem)); + + mutex_unlock(&smc_mutex); + + return res.a0 == ~0 ? -EINVAL : 0; +} + +static void smc_mark_txdone(struct scmi_chan_info *cinfo, int ret) +{ +} + +static void smc_fetch_response(struct scmi_chan_info *cinfo, + struct scmi_xfer *xfer) +{ + struct scmi_smc *scmi_info = cinfo->transport_info; + + shmem_fetch_response(scmi_info->shmem, xfer); +} + +static bool +smc_poll_done(struct scmi_chan_info *cinfo, struct scmi_xfer *xfer) +{ + struct scmi_smc *scmi_info = cinfo->transport_info; + + return shmem_poll_done(scmi_info->shmem, xfer); +} + +static struct scmi_transport_ops scmi_smc_ops = { + .chan_setup = smc_chan_setup, + .chan_free = smc_chan_free, + .send_message = smc_send_message, + .mark_txdone = smc_mark_txdone, + .fetch_response = smc_fetch_response, + .poll_done = smc_poll_done, +}; + +const struct scmi_desc scmi_smc_desc = { + .ops = &scmi_smc_ops, + .max_rx_timeout_ms = 30, + .max_msg = 1, + .max_msg_size = 128, +};