This change implements a mailbox transport using SMT format for SCMI
exchanges. This implementation follows the Linux kernel and
SCP-firmware [1] as references implementation for SCMI message
processing using SMT format for communication channel meta-data.

Use of mailboxes in SCMI FDT bindings are defined in the Linux kernel
DT bindings since v4.17.

Links: [1] https://github.com/ARM-software/SCP-firmware
Signed-off-by: Etienne Carriere <etienne.carri...@linaro.org>
Cc: Simon Glass <s...@chromium.org>
Cc: Peng Fan <peng....@nxp.com>
Cc: Sudeep Holla <sudeep.ho...@arm.com>
---

Changes in v4:
- Replace __arm__ with CONFIG_ARM.
- Remove cast for priv reference hence remove helper scmi_mbox_get_priv().

Changes in v3:
- This is a followup of the SCMI agent patches posted in
  https://patchwork.ozlabs.org/project/uboot/list/?series=196253
  The v3 splits commits and introduces a new uclass as requested.
- This patch implements the same mailbox SCMI agent proposed in v2
  but split over few source files.
---
 drivers/firmware/scmi/Kconfig         |   6 +-
 drivers/firmware/scmi/Makefile        |   2 +
 drivers/firmware/scmi/mailbox_agent.c | 102 +++++++++++++++++++
 drivers/firmware/scmi/smt.c           | 139 ++++++++++++++++++++++++++
 drivers/firmware/scmi/smt.h           |  86 ++++++++++++++++
 5 files changed, 333 insertions(+), 2 deletions(-)
 create mode 100644 drivers/firmware/scmi/mailbox_agent.c
 create mode 100644 drivers/firmware/scmi/smt.c
 create mode 100644 drivers/firmware/scmi/smt.h

diff --git a/drivers/firmware/scmi/Kconfig b/drivers/firmware/scmi/Kconfig
index 57e2ebbe42..c501bf4943 100644
--- a/drivers/firmware/scmi/Kconfig
+++ b/drivers/firmware/scmi/Kconfig
@@ -2,7 +2,7 @@ config SCMI_FIRMWARE
        bool "Enable SCMI support"
        select FIRMWARE
        select OF_TRANSLATE
-       depends on SANDBOX
+       depends on SANDBOX || DM_MAILBOX
        help
          System Control and Management Interface (SCMI) is a communication
          protocol that defines standard interfaces for power, performance
@@ -14,4 +14,6 @@ config SCMI_FIRMWARE
          or a companion host in the CPU system.
 
          Communications between agent (client) and the SCMI server are
-         based on message exchange.
+         based on message exchange. Messages can be exchange over tranport
+         channels as a mailbox device with some piece of identified shared
+         memory.
diff --git a/drivers/firmware/scmi/Makefile b/drivers/firmware/scmi/Makefile
index 336ea1f2a3..d22f53efe7 100644
--- a/drivers/firmware/scmi/Makefile
+++ b/drivers/firmware/scmi/Makefile
@@ -1,2 +1,4 @@
 obj-y  += scmi_agent-uclass.o
+obj-y  += smt.o
+obj-$(CONFIG_DM_MAILBOX)       += mailbox_agent.o
 obj-$(CONFIG_SANDBOX)          += sandbox-scmi_agent.o
diff --git a/drivers/firmware/scmi/mailbox_agent.c 
b/drivers/firmware/scmi/mailbox_agent.c
new file mode 100644
index 0000000000..7d9fb3622e
--- /dev/null
+++ b/drivers/firmware/scmi/mailbox_agent.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 Linaro Limited.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <mailbox.h>
+#include <scmi_agent.h>
+#include <scmi_agent-uclass.h>
+#include <dm/devres.h>
+#include <linux/compat.h>
+
+#include "smt.h"
+
+#define TIMEOUT_US_10MS                        10000
+
+/**
+ * struct scmi_mbox_channel - Description of an SCMI mailbox transport
+ * @smt:       Shared memory buffer
+ * @mbox:      Mailbox channel description
+ * @timeout_us:        Timeout in microseconds for the mailbox transfer
+ */
+struct scmi_mbox_channel {
+       struct scmi_smt smt;
+       struct mbox_chan mbox;
+       ulong timeout_us;
+};
+
+static int scmi_mbox_process_msg(struct udevice *dev, struct scmi_msg *msg)
+{
+       struct scmi_mbox_channel *chan = dev_get_priv(dev);
+       int ret;
+
+       ret = scmi_write_msg_to_smt(dev, &chan->smt, msg);
+       if (ret)
+               return ret;
+
+       /* Give shm addr to mbox in case it is meaningful */
+       ret = mbox_send(&chan->mbox, chan->smt.buf);
+       if (ret) {
+               dev_err(dev, "Message send failed: %d\n", ret);
+               goto out;
+       }
+
+       /* Receive the response */
+       ret = mbox_recv(&chan->mbox, chan->smt.buf, chan->timeout_us);
+       if (ret) {
+               dev_err(dev, "Response failed: %d, abort\n", ret);
+               goto out;
+       }
+
+       ret = scmi_read_resp_from_smt(dev, &chan->smt, msg);
+
+out:
+       scmi_clear_smt_channel(&chan->smt);
+
+       return ret;
+}
+
+int scmi_mbox_probe(struct udevice *dev)
+{
+       struct scmi_mbox_channel *chan = dev_get_priv(dev);
+       int ret;
+
+       chan->timeout_us = TIMEOUT_US_10MS;
+
+       ret = mbox_get_by_index(dev, 0, &chan->mbox);
+       if (ret) {
+               dev_err(dev, "Failed to find mailbox: %d\n", ret);
+               goto out;
+       }
+
+       ret = scmi_dt_get_smt_buffer(dev, &chan->smt);
+       if (ret)
+               dev_err(dev, "Failed to get shm resources: %d\n", ret);
+
+out:
+       if (ret)
+               devm_kfree(dev, chan);
+
+       return ret;
+}
+
+static const struct udevice_id scmi_mbox_ids[] = {
+       { .compatible = "arm,scmi" },
+       { }
+};
+
+static const struct scmi_agent_ops scmi_mbox_ops = {
+       .process_msg = scmi_mbox_process_msg,
+};
+
+U_BOOT_DRIVER(scmi_mbox) = {
+       .name           = "scmi-over-mailbox",
+       .id             = UCLASS_SCMI_AGENT,
+       .of_match       = scmi_mbox_ids,
+       .priv_auto_alloc_size = sizeof(struct scmi_mbox_channel),
+       .probe          = scmi_mbox_probe,
+       .ops            = &scmi_mbox_ops,
+};
diff --git a/drivers/firmware/scmi/smt.c b/drivers/firmware/scmi/smt.c
new file mode 100644
index 0000000000..ce8fe49939
--- /dev/null
+++ b/drivers/firmware/scmi/smt.c
@@ -0,0 +1,139 @@
+// 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 <scmi_agent.h>
+#include <asm/cache.h>
+#include <asm/system.h>
+#include <dm/ofnode.h>
+#include <linux/compat.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+
+#include "smt.h"
+
+/**
+ * Get shared memory configuration defined by the referred DT phandle
+ * Return with a errno compliant value.
+ */
+int scmi_dt_get_smt_buffer(struct udevice *dev, struct scmi_smt *smt)
+{
+       int ret;
+       struct ofnode_phandle_args args;
+       struct resource resource;
+       fdt32_t faddr;
+       phys_addr_t paddr;
+
+       ret = dev_read_phandle_with_args(dev, "shmem", NULL, 0, 0, &args);
+       if (ret)
+               return ret;
+
+       ret = ofnode_read_resource(args.node, 0, &resource);
+       if (ret)
+               return ret;
+
+       faddr = cpu_to_fdt32(resource.start);
+       paddr = ofnode_translate_address(args.node, &faddr);
+
+       smt->size = resource_size(&resource);
+       if (smt->size < sizeof(struct scmi_smt_header)) {
+               dev_err(dev, "Shared memory buffer too small\n");
+               return -EINVAL;
+       }
+
+       smt->buf = devm_ioremap(dev, paddr, smt->size);
+       if (!smt->buf)
+               return -ENOMEM;
+
+#ifdef CONFIG_ARM
+       if (dcache_status())
+               mmu_set_region_dcache_behaviour((uintptr_t)smt->buf,
+                                               smt->size, DCACHE_OFF);
+#endif
+
+       return 0;
+}
+
+/**
+ * Write SCMI message @msg into a SMT shared buffer @smt.
+ * Return 0 on success and with a negative errno in case of error.
+ */
+int scmi_write_msg_to_smt(struct udevice *dev, struct scmi_smt *smt,
+                         struct scmi_msg *msg)
+{
+       struct scmi_smt_header *hdr = (void *)smt->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 (smt->size < (sizeof(*hdr) + msg->in_msg_sz) ||
+           smt->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_toio(hdr->msg_payload, msg->in_msg, msg->in_msg_sz);
+
+       return 0;
+}
+
+/**
+ * Read SCMI message from a SMT shared buffer @smt and copy it into @msg.
+ * Return 0 on success and with a negative errno in case of error.
+ */
+int scmi_read_resp_from_smt(struct udevice *dev, struct scmi_smt *smt,
+                           struct scmi_msg *msg)
+{
+       struct scmi_smt_header *hdr = (void *)smt->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_fromio(msg->out_msg, hdr->msg_payload, msg->out_msg_sz);
+
+       return 0;
+}
+
+/**
+ * Clear SMT flags in shared buffer to allow further message exchange
+ */
+void scmi_clear_smt_channel(struct scmi_smt *smt)
+{
+       struct scmi_smt_header *hdr = (void *)smt->buf;
+
+       hdr->channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR;
+}
diff --git a/drivers/firmware/scmi/smt.h b/drivers/firmware/scmi/smt.h
new file mode 100644
index 0000000000..a8c0987bd3
--- /dev/null
+++ b/drivers/firmware/scmi/smt.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved.
+ * Copyright (C) 2019-2020 Linaro Limited.
+ */
+#ifndef SCMI_SMT_H
+#define SCMI_SMT_H
+
+#include <asm/types.h>
+
+/**
+ * struct scmi_smt_header - Description of the shared memory message buffer
+ *
+ * SMT stands for Shared Memory based Transport.
+ * 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_smt - Description of a SMT memory buffer
+ * @buf:       Shared memory base address
+ * @size:      Shared memory byte size
+ */
+struct scmi_smt {
+       u8 *buf;
+       size_t size;
+};
+
+static inline bool scmi_smt_channel_is_free(struct scmi_smt *smt)
+{
+       struct scmi_smt_header *hdr = (void *)smt->buf;
+
+       return hdr->channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE;
+}
+
+static inline bool scmi_smt_channel_reports_error(struct scmi_smt *smt)
+{
+       struct scmi_smt_header *hdr = (void *)smt->buf;
+
+       return hdr->channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR;
+}
+
+static inline void scmi_smt_get_channel(struct scmi_smt *smt)
+{
+       struct scmi_smt_header *hdr = (void *)smt->buf;
+
+       hdr->channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE;
+}
+
+static inline void scmi_smt_put_channel(struct scmi_smt *smt)
+{
+       struct scmi_smt_header *hdr = (void *)smt->buf;
+
+       hdr->channel_status |= SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE;
+       hdr->channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR;
+}
+
+int scmi_dt_get_smt_buffer(struct udevice *dev, struct scmi_smt *smt);
+
+int scmi_write_msg_to_smt(struct udevice *dev, struct scmi_smt *smt,
+                         struct scmi_msg *msg);
+
+int scmi_read_resp_from_smt(struct udevice *dev, struct scmi_smt *smt,
+                           struct scmi_msg *msg);
+
+void scmi_clear_smt_channel(struct scmi_smt *smt);
+
+#endif /* SCMI_SMT_H */
-- 
2.17.1

Reply via email to