[PATCH 1/4] firmware: add new driver for SCMI firmwares

Peng Fan peng.fan at nxp.com
Mon Jul 20 04:01:08 CEST 2020


Hi Etienne,

+Sudeep

> Subject: [PATCH 1/4] firmware: add new driver for SCMI firmwares

Thanks for posting this out.

> 
> This change introduces SCMI agent driver in U-Boot in the firmware U-class.
> 
> SCMI agent driver is designed for platforms that embed a SCMI server in a
> firmware hosted for example by a companion co-processor or the secure
> world of the executing processor.
> 
> SCMI protocols allow an SCMI agent to discover and access external
> resources as clock, reset controllers and many more. SCMI agent and server
> communicate following the SCMI specification [1]. SCMI agent complies with
> the DT bindings defined in the Linux kernel source tree regarding SCMI agent
> description since v5.8-rc1.
> 
> These bindings describe 2 supported message transport layer: using mailbox
> uclass devices or using Arm SMC invocation instruction. Both use a piece or
> shared memory for message data exchange.
> 
> In the current state, the SCMI agent driver does not bind to any SCMI protocol
> to a U-Boot device driver. Former changes will implement dedicated driver (i.e.
> an SCMI clock driver or an SCMI reset controller
> driver) and add bind supported SCMI protocols in scmi_agent_bind().
> 
> Links: [1]
> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdevelo
> per.arm.com%2Farchitectures%2Fsystem-architectures%2Fsoftware-standar
> ds%2Fscmi&data=02%7C01%7Cpeng.fan%40nxp.com%7C39c55064be5
> 04bf248a708d82a6775cd%7C686ea1d3bc2b4c6fa92cd99c5c301635%7C0%7
> C0%7C637305971142916174&sdata=5kCgz3kzzk4qHy598u79zz3hV17yV
> zdPxM531sOAnUs%3D&reserved=0
> Signed-off-by: Etienne Carriere <etienne.carriere at linaro.org>
> ---
> 
>  drivers/firmware/Kconfig  |  15 ++
>  drivers/firmware/Makefile |   1 +
>  drivers/firmware/scmi.c   | 439
> ++++++++++++++++++++++++++++++++++++++
>  include/scmi.h            |  82 +++++++
>  4 files changed, 537 insertions(+)
>  create mode 100644 drivers/firmware/scmi.c  create mode 100644
> include/scmi.h
> 
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index
> b70a2063551..f7c7ee7a5aa 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -1,6 +1,21 @@
>  config FIRMWARE
>  	bool "Enable Firmware driver support"
> 
> +config SCMI_FIRMWARE
> +	bool "Enable SCMI support"
> +	select FIRMWARE
> +	select OF_TRANSLATE
> +	depends on DM_MAILBOX || ARM_SMCCC
> +	help
> +	  An SCMI agent communicates with a related SCMI server firmware
> +	  located in another sub-system, as a companion micro controller
> +	  or a companion host in the CPU system.
> +
> +	  Communications between agent (client) and the SCMI server are
> +	  based on message exchange. Messages can be exchange over tranport
> +	  channels as a mailbox device or an Arm SMCCC service with some
> +	  piece of identified shared memory.
> +
>  config SPL_FIRMWARE
>  	bool "Enable Firmware driver support in SPL"
>  	depends on FIRMWARE
> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index
> a0c250a473e..3965838179f 100644
> --- a/drivers/firmware/Makefile
> +++ b/drivers/firmware/Makefile
> @@ -2,4 +2,5 @@ obj-$(CONFIG_FIRMWARE)		+= firmware-uclass.o
>  obj-$(CONFIG_$(SPL_)ARM_PSCI_FW)	+= psci.o
>  obj-$(CONFIG_TI_SCI_PROTOCOL)	+= ti_sci.o
>  obj-$(CONFIG_SANDBOX)		+= firmware-sandbox.o
> +obj-$(CONFIG_SCMI_FIRMWARE) 	+= scmi.o
>  obj-$(CONFIG_ZYNQMP_FIRMWARE)	+= firmware-zynqmp.o
> diff --git a/drivers/firmware/scmi.c b/drivers/firmware/scmi.c new file mode
> 100644 index 00000000000..fa8a91c3f3d
> --- /dev/null
> +++ b/drivers/firmware/scmi.c
> @@ -0,0 +1,439 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights
> reserved.
> + * Copyright (C) 2019-2020 Linaro Limited.
> + */
> +
> +#include <common.h>
> +#include <cpu_func.h>
> +#include <dm.h>
> +#include <errno.h>
> +#include <mailbox.h>
> +#include <memalign.h>
> +#include <scmi.h>
> +#include <asm/system.h>
> +#include <asm/types.h>
> +#include <dm/device-internal.h>
> +#include <dm/devres.h>
> +#include <dm/lists.h>
> +#include <dm/ofnode.h>
> +#include <linux/arm-smccc.h>
> +#include <linux/compat.h>
> +#include <linux/errno.h>
> +#include <linux/io.h>
> +#include <linux/ioport.h>
> +
> +#define TIMEOUT_US_10MS			10000
> +
> +struct error_code {
> +	int scmi;
> +	int errno;
> +};
> +
> +static const struct error_code scmi_linux_errmap[] = {
> +	{ .scmi = SCMI_NOT_SUPPORTED, .errno = -EOPNOTSUPP, },
> +	{ .scmi = SCMI_INVALID_PARAMETERS, .errno = -EINVAL, },
> +	{ .scmi = SCMI_DENIED, .errno = -EACCES, },
> +	{ .scmi = SCMI_NOT_FOUND, .errno = -ENOENT, },
> +	{ .scmi = SCMI_OUT_OF_RANGE, .errno = -ERANGE, },
> +	{ .scmi = SCMI_BUSY, .errno = -EBUSY, },
> +	{ .scmi = SCMI_COMMS_ERROR, .errno = -ECOMM, },
> +	{ .scmi = SCMI_GENERIC_ERROR, .errno = -EIO, },
> +	{ .scmi = SCMI_HARDWARE_ERROR, .errno = -EREMOTEIO, },
> +	{ .scmi = SCMI_PROTOCOL_ERROR, .errno = -EPROTO, }, };
> +
> +int scmi_to_linux_errno(s32 scmi_code)
> +{
> +	int n;
> +
> +	if (scmi_code == 0)

!scmi_code

> +		return 0;
> +
> +	for (n = 0; n < ARRAY_SIZE(scmi_linux_errmap); n++)
> +		if (scmi_code == scmi_linux_errmap[n].scmi)
> +			return scmi_linux_errmap[1].errno;
> +
> +	return -EPROTO;
> +}
> +
> +struct method_ops {
> +	int (*process_msg)(struct udevice *dev, struct scmi_msg *msg);
> +	int (*remove_agent)(struct udevice *dev); };
> +
> +struct scmi_agent {
> +	struct method_ops *method_ops;
> +	void *method_priv;
> +};
> +
> +/*
> + * Shared Memory based Transport (SMT) message buffer management
> + *
> + * SMT uses 28 byte header prior message payload to handle the state of
> + * the communication channel realized by the shared memory area and
> + * to define SCMI protocol information the payload relates to.
> + */
> +struct scmi_smt_header {
> +	__le32 reserved;
> +	__le32 channel_status;
> +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR	BIT(1)
> +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE	BIT(0)
> +	__le32 reserved1[2];
> +	__le32 flags;
> +#define SCMI_SHMEM_FLAG_INTR_ENABLED		BIT(0)
> +	__le32 length;
> +	__le32 msg_header;
> +	u8 msg_payload[0];
> +};
> +
> +#define SMT_HEADER_TOKEN(token)		(((token) << 18) & GENMASK(31,
> 18))
> +#define SMT_HEADER_PROTOCOL_ID(proto)	(((proto) << 10) &
> GENMASK(17, 10))
> +#define SMT_HEADER_MESSAGE_TYPE(type)	(((type) << 18) & GENMASK(9,
> 8))
> +#define SMT_HEADER_MESSAGE_ID(id)	((id) & GENMASK(7, 0))
> +
> +struct scmi_shm_buf {
> +	u8 *buf;
> +	size_t size;
> +};
> +
> +static int get_shm_buffer(struct udevice *dev, struct scmi_shm_buf
> +*shm) {
> +	int rc;
> +	struct ofnode_phandle_args args;
> +	struct resource resource;
> +	fdt32_t faddr;
> +	phys_addr_t paddr;
> +
> +	rc = dev_read_phandle_with_args(dev, "shmem", NULL, 0, 0, &args);
> +	if (rc)
> +		return rc;
> +
> +	rc = ofnode_read_resource(args.node, 0, &resource);
> +	if (rc)
> +		return rc;
> +
> +	faddr = cpu_to_fdt32(resource.start);
> +	paddr = ofnode_translate_address(args.node, &faddr);
> +
> +	shm->size = resource_size(&resource);
> +	if (shm->size < sizeof(struct scmi_smt_header)) {
> +		dev_err(dev, "Shared memory buffer too small\n");
> +		return -EINVAL;
> +	}
> +
> +	shm->buf = devm_ioremap(dev, paddr, shm->size);
> +	if (!shm->buf)
> +		return -ENOMEM;
> +
> +	if (dcache_status())
> +		mmu_set_region_dcache_behaviour((uintptr_t)shm->buf,
> +						shm->size, DCACHE_OFF);


Could dev_read_addr be used to replace the upper code block?

> +
> +	return 0;
> +}
> +
> +static int write_msg_to_smt(struct udevice *dev, struct scmi_shm_buf
> *shm_buf,
> +			    struct scmi_msg *msg)
> +{
> +	struct scmi_smt_header *hdr = (void *)shm_buf->buf;
> +
> +	if ((!msg->in_msg && msg->in_msg_sz) ||
> +	    (!msg->out_msg && msg->out_msg_sz))
> +		return -EINVAL;
> +
> +	if (!(hdr->channel_status &
> SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE)) {
> +		dev_dbg(dev, "Channel busy\n");
> +		return -EBUSY;
> +	}
> +
> +	if (shm_buf->size < (sizeof(*hdr) + msg->in_msg_sz) ||
> +	    shm_buf->size < (sizeof(*hdr) + msg->out_msg_sz)) {
> +		dev_dbg(dev, "Buffer too small\n");
> +		return -ETOOSMALL;
> +	}
> +
> +	/* Load message in shared memory */
> +	hdr->channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE;
> +	hdr->length = msg->in_msg_sz + sizeof(hdr->msg_header);
> +	hdr->msg_header = SMT_HEADER_TOKEN(0) |
> +			  SMT_HEADER_MESSAGE_TYPE(0) |
> +			  SMT_HEADER_PROTOCOL_ID(msg->protocol_id) |
> +			  SMT_HEADER_MESSAGE_ID(msg->message_id);
> +
> +	memcpy(hdr->msg_payload, msg->in_msg, msg->in_msg_sz);

memcpy_toio?

> +
> +	return 0;
> +}
> +
> +static int read_resp_from_smt(struct udevice *dev, struct scmi_shm_buf
> *shm_buf,
> +			      struct scmi_msg *msg)
> +{
> +	struct scmi_smt_header *hdr = (void *)shm_buf->buf;
> +
> +	if (!(hdr->channel_status &
> SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE)) {
> +		dev_err(dev, "Channel unexpectedly busy\n");
> +		return -EBUSY;
> +	}
> +
> +	if (hdr->channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR)
> {
> +		dev_err(dev, "Channel error reported, reset channel\n");
> +		return -ECOMM;
> +	}
> +
> +	if (hdr->length > msg->out_msg_sz + sizeof(hdr->msg_header)) {
> +		dev_err(dev, "Buffer to small\n");
> +		return -ETOOSMALL;
> +	}
> +
> +	/* Get the data */
> +	msg->out_msg_sz = hdr->length - sizeof(hdr->msg_header);
> +	memcpy(msg->out_msg, hdr->msg_payload, msg->out_msg_sz);
> +
> +	return 0;
> +}
> +
> +static void clear_smt_channel(struct scmi_shm_buf *shm_buf) {
> +	struct scmi_smt_header *hdr = (void *)shm_buf->buf;
> +
> +	hdr->channel_status &=
> ~SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR;
> +}
> +
> +struct scmi_mbox_channel {
> +	struct scmi_shm_buf shm_buf;
> +	struct mbox_chan mbox;
> +	ulong timeout_us;
> +};
> +
> +static int mbox_process_msg(struct udevice *dev, struct scmi_msg *msg)
> +{
> +	struct scmi_agent *agent = dev_get_priv(dev);
> +	struct scmi_mbox_channel *chan = agent->method_priv;
> +	int rc;
> +
> +	rc = write_msg_to_smt(dev, &chan->shm_buf, msg);
> +	if (rc)
> +		return rc;
> +
> +	/* Give shm addr to mbox in case it is meaningful */
> +	rc = mbox_send(&chan->mbox, chan->shm_buf.buf);
> +	if (rc) {
> +		dev_err(dev, "Message send failed: %d\n", rc);
> +		goto out;
> +	}
> +
> +	/* Receive the response */
> +	rc = mbox_recv(&chan->mbox, chan->shm_buf.buf, chan->timeout_us);
> +	if (rc) {
> +		dev_err(dev, "Response failed: %d, abort\n", rc);
> +		goto out;
> +	}
> +
> +	rc = read_resp_from_smt(dev, &chan->shm_buf, msg);
> +
> +out:
> +	clear_smt_channel(&chan->shm_buf);
> +
> +	return rc;
> +}
> +
> +struct method_ops mbox_channel_ops = {
> +	.process_msg = mbox_process_msg,
> +};
> +
> +static int probe_mailbox_channel(struct udevice *dev) {
> +	struct scmi_agent *agent = dev_get_priv(dev);
> +	struct scmi_mbox_channel *chan;
> +	int rc;
> +
> +	chan = devm_kzalloc(dev, sizeof(*chan), GFP_KERNEL);
> +	if (!chan)
> +		return -ENOMEM;
> +
> +	chan->timeout_us = TIMEOUT_US_10MS;
> +
> +	rc = mbox_get_by_index(dev, 0, &chan->mbox);
> +	if (rc) {
> +		dev_err(dev, "Failed to find mailbox: %d\n", rc);
> +		goto out;
> +	}
> +
> +	rc = get_shm_buffer(dev, &chan->shm_buf);
> +	if (rc)
> +		dev_err(dev, "Failed to get shm resources: %d\n", rc);
> +
> +out:
> +	if (rc) {
> +		devm_kfree(dev, chan);
> +	} else {
> +		agent->method_ops = &mbox_channel_ops;
> +		agent->method_priv = (void *)chan;
> +	}
> +
> +	return rc;
> +}
> +
> +struct scmi_arm_smc_channel {
> +	ulong func_id;
> +	struct scmi_shm_buf shm_buf;
> +};
> +
> +#define SMCCC_RET_NOT_SUPPORTED         ((unsigned long)-1)
> +
> +static int arm_smc_process_msg(struct udevice *dev, struct scmi_msg
> +*msg) {
> +	struct scmi_agent *agent = dev_get_priv(dev);
> +	struct scmi_arm_smc_channel *chan = agent->method_priv;
> +	struct arm_smccc_res res;
> +	int rc;
> +
> +	rc = write_msg_to_smt(dev, &chan->shm_buf, msg);
> +	if (rc)
> +		return rc;
> +
> +	arm_smccc_smc(chan->func_id, 0, 0, 0, 0, 0, 0, 0, &res);
> +	if (res.a0 == SMCCC_RET_NOT_SUPPORTED)
> +		rc = -EINVAL;
> +	else
> +		rc = read_resp_from_smt(dev, &chan->shm_buf, msg);
> +
> +	clear_smt_channel(&chan->shm_buf);

Should the clear be done in firmware side?

> +
> +	return rc;
> +}
> +
> +struct method_ops arm_smc_channel_ops = {
> +	.process_msg = arm_smc_process_msg,
> +};
> +
> +static int probe_arm_smc_channel(struct udevice *dev) {
> +	struct scmi_agent *agent = dev_get_priv(dev);
> +	struct scmi_arm_smc_channel *chan;
> +	ofnode node = dev_ofnode(dev);
> +	u32 func_id;
> +	int rc;
> +
> +	chan = devm_kzalloc(dev, sizeof(*chan), GFP_KERNEL);
> +	if (!chan)
> +		return -ENOMEM;
> +
> +	if (ofnode_read_u32(node, "arm,smc-id", &func_id)) {
> +		dev_err(dev, "Missing property func-id\n");
> +		return -EINVAL;
> +	}
> +
> +	chan->func_id = func_id;
> +
> +	rc = get_shm_buffer(dev, &chan->shm_buf);
> +	if (rc) {
> +		dev_err(dev, "Failed to get shm resources: %d\n", rc);
> +		return rc;
> +	}
> +
> +	agent->method_ops = &arm_smc_channel_ops;
> +	agent->method_priv = (void *)chan;
> +
> +	return rc;
> +}
> +
> +/*
> + * Exported functions by the SCMI agent  */
> +
> +int scmi_send_and_process_msg(struct udevice *dev, struct scmi_msg
> +*msg) {
> +	struct scmi_agent *agent = dev_get_priv(dev);
> +
> +	return agent->method_ops->process_msg(dev, msg); }
> +
> +static int scmi_remove(struct udevice *dev) {
> +	struct scmi_agent *agent = dev_get_priv(dev);
> +
> +	if (agent->method_ops->remove_agent)
> +		return agent->method_ops->remove_agent(dev);
> +
> +	return 0;
> +}
> +
> +enum scmi_transport_channel {
> +	SCMI_MAILBOX_TRANSPORT,
> +	SCMI_ARM_SMCCC_TRANSPORT,
> +};
> +
> +static int scmi_probe(struct udevice *dev) {
> +	switch (dev_get_driver_data(dev)) {
> +	case SCMI_MAILBOX_TRANSPORT:
> +		if (IS_ENABLED(CONFIG_DM_MAILBOX))
> +			return probe_mailbox_channel(dev);
> +		break;
> +	case SCMI_ARM_SMCCC_TRANSPORT:
> +		if (IS_ENABLED(CONFIG_ARM_SMCCC))
> +			return probe_arm_smc_channel(dev);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int scmi_bind(struct udevice *dev) {
> +	int rc = 0;
> +	ofnode node;
> +	struct driver *drv;
> +
> +	dev_for_each_subnode(node, dev) {
> +		u32 protocol_id;
> +
> +		if (!ofnode_is_available(node))
> +			continue;
> +
> +		if (ofnode_read_u32(node, "reg", &protocol_id))
> +			continue;
> +
> +		switch (protocol_id) {
> +		default:
> +			dev_info(dev, "Ignore unsupported SCMI protocol %u\n",
> +				 protocol_id);
> +			continue;
> +		}
> +
> +		rc = device_bind_ofnode(dev, drv, ofnode_get_name(node),
> +					NULL, node, NULL);
> +		if (rc)
> +			break;
> +	}
> +
> +	if (rc)
> +		device_unbind(dev);
> +
> +	return rc;
> +}
> +
> +static const struct udevice_id scmi_ids[] = { #ifdef CONFIG_DM_MAILBOX
> +	{ .compatible = "arm,scmi", .data = SCMI_MAILBOX_TRANSPORT },
> #endif
> +#ifdef CONFIG_ARM_SMCCC
> +	{ .compatible = "arm,scmi-smc", .data =
> SCMI_ARM_SMCCC_TRANSPORT },
> +#endif

To minimize the code base, how about following Linux kernel to split mailbox
and smc to different files?

Regards,
Peng.

> +	{ }
> +};
> +
> +U_BOOT_DRIVER(scmi) = {
> +	.name		= "scmi",
> +	.id		= UCLASS_FIRMWARE,
> +	.of_match	= scmi_ids,
> +	.priv_auto_alloc_size = sizeof(struct scmi_agent),
> +	.bind		= scmi_bind,
> +	.probe		= scmi_probe,
> +	.remove		= scmi_remove,
> +	.flags		= DM_FLAG_OS_PREPARE,
> +};
> diff --git a/include/scmi.h b/include/scmi.h new file mode 100644 index
> 00000000000..e12e322991d
> --- /dev/null
> +++ b/include/scmi.h
> @@ -0,0 +1,82 @@
> +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */
> +/*
> + * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights
> reserved.
> + * Copyright (C) 2019, Linaro Limited
> + */
> +#ifndef SCMI_H
> +#define SCMI_H
> +
> +#include <asm/types.h>
> +
> +/**
> + * An SCMI agent represent on communication path from a device driver
> +to
> + * the remote SCMI server which driver sends messages to and receives
> + * response messages from.
> + */
> +struct scmi_agent;
> +
> +enum scmi_std_protocol {
> +	SCMI_PROTOCOL_ID_BASE = 0x10,
> +	SCMI_PROTOCOL_ID_POWER_DOMAIN = 0x11,
> +	SCMI_PROTOCOL_ID_SYSTEM = 0x12,
> +	SCMI_PROTOCOL_ID_PERF = 0x13,
> +	SCMI_PROTOCOL_ID_CLOCK = 0x14,
> +	SCMI_PROTOCOL_ID_SENSOR = 0x15,
> +	SCMI_PROTOCOL_ID_RESET_DOMAIN = 0x16,
> +};
> +
> +enum scmi_status_code {
> +	SCMI_SUCCESS =  0,
> +	SCMI_NOT_SUPPORTED = -1,
> +	SCMI_INVALID_PARAMETERS = -2,
> +	SCMI_DENIED = -3,
> +	SCMI_NOT_FOUND = -4,
> +	SCMI_OUT_OF_RANGE = -5,
> +	SCMI_BUSY = -6,
> +	SCMI_COMMS_ERROR = -7,
> +	SCMI_GENERIC_ERROR = -8,
> +	SCMI_HARDWARE_ERROR = -9,
> +	SCMI_PROTOCOL_ERROR = -10,
> +};
> +
> +/*
> + * struct scmi_msg - Context of a SCMI message sent and the response
> +received
> + *
> + * @protocol_id: SCMI protocol ID
> + * @message_id: SCMI message ID for a defined protocol ID
> + * @in_msg: pointer to the message payload sent by the driver
> + * @in_msg_sz: byte size of the message payload sent
> + * @out_msg: pointer to buffer to store response message payload
> + * @out_msg_size: Byte size of the response buffer or payload  */
> +struct scmi_msg {
> +	unsigned int protocol_id;
> +	unsigned int message_id;
> +	u8 *in_msg;
> +	size_t in_msg_sz;
> +	u8 *out_msg;
> +	size_t out_msg_sz;
> +};
> +
> +/**
> + * scmi_send_and_process_msg() - send and process a SCMI message
> + *
> + * Send a message to a SCMI server through a target SCMI agent device.
> + * Caller sets scmi_msg::out_msg_sz to the output message buffer size.
> + * On return, scmi_msg::out_msg_sz stores the response payload size.
> + *
> + * @dev: SCMI agent device
> + * @msg: Message structure reference
> + * @return 0 on success, a negative errno otherwise  */ int
> +scmi_send_and_process_msg(struct udevice *dev, struct scmi_msg *msg);
> +
> +/**
> + * scmi_to_linux_errno() - Convert an SCMI error code into a Linux
> +errno code
> + *
> + * @scmi_errno: SCMI error code value
> + * @return 0 for successful status and a negative errno otherwise  */
> +int scmi_to_linux_errno(s32 scmi_errno);
> +
> +#endif /* SCMI_H */
> --
> 2.17.1



More information about the U-Boot mailing list