firmware: scmi: mailbox/smt agent device

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.carriere@linaro.org>
Cc: Simon Glass <sjg@chromium.org>
Cc: Peng Fan <peng.fan@nxp.com>
Cc: Sudeep Holla <sudeep.holla@arm.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Etienne Carriere 2020-09-09 18:44:01 +02:00 committed by Tom Rini
parent 358599efd8
commit 240720e905
5 changed files with 333 additions and 2 deletions

View file

@ -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.

View file

@ -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

View file

@ -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,
};

139
drivers/firmware/scmi/smt.c Normal file
View file

@ -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;
}

View file

@ -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 */