This RFC series adds the RISC-V RPMI TEE service group transport [1], which provides RISC-V systems a mechanism for Linux to communicate with TEE endpoints. Linux and a TEE act as endpoints of the RPMI TEE service group, while the RPMI framework in machine-mode firmware mediates communication between them over an SBI MPXY [2] mailbox channel.
The series is layered as follows:
- Two mailbox patches add a direct synchronous send mode (mbox_send_message_sync()) and its implementation for RPMI MPXY channels, needed because RPMI TEE requests must complete synchronously in the calling context.
- The RPMI TEE bus registers one device per discovered TEE endpoint and service UUID pair, following the device-per-service model, so individual service drivers can bind independently.
- The RPMI TEE transport core binds to the mailbox channel and validates the RPMI and TEE service-group versions before any discovery or service traffic is attempted.
- System-information parsing and discovery walk the firmware-provided descriptor tables to find physical TEE endpoints and the services they expose, registering a bus device for each.
- Memory parcel operations (lend, share, reclaim) let a consumer driver share memory with a TEE endpoint. Linux creates a parcel and the parcel identifier is then used by the consumer's own protocol to refer to that memory.
- Signal buses let a consumer driver exchange asynchronous notifications with its TEE endpoint in both directions.
This series only establishes the transport, bus, and discovery layer. A consumer driver - an OP-TEE backend mapped onto these services, analogous to drivers/tee/optee/ffa_abi.c — is intended to follow in a later series once this transport is reviewed.
Feedback on the overall architecture, the bus/device model, and the memory-parcel and signal-bus abstractions is especially welcome at this stage in this RFC series.
[1] https://github.com/riscv-non-isa/riscv-rpmi/commits/main/src/srvgrp-tee.adoc [2] https://github.com/riscv-non-isa/riscv-sbi-doc/releases
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- Amirreza Zarrabi (10): mailbox: add direct synchronous send support mailbox: mpxy: add direct synchronous send firmware: add RPMI TEE bus support dt-bindings: firmware: add RISC-V RPMI TEE transport firmware: add RPMI TEE transport core firmware: riscv: rpmi-tee: parse system information tables firmware: riscv: rpmi-tee: discover TEE services firmware: riscv: rpmi-tee: cache TEE capabilities firmware: riscv: rpmi-tee: add memory parcel operations firmware: riscv: rpmi-tee: add signal bus support
.../bindings/firmware/riscv,rpmi-tee.yaml | 35 + drivers/firmware/Kconfig | 2 + drivers/firmware/Makefile | 1 + drivers/firmware/riscv_rpmi_tee/Kconfig | 8 + drivers/firmware/riscv_rpmi_tee/Makefile | 8 + drivers/firmware/riscv_rpmi_tee/bus.c | 202 +++ drivers/firmware/riscv_rpmi_tee/driver.c | 1816 ++++++++++++++++++++ drivers/firmware/riscv_rpmi_tee/sysinfo.c | 385 +++++ drivers/firmware/riscv_rpmi_tee/sysinfo.h | 244 +++ drivers/mailbox/mailbox.c | 72 +- drivers/mailbox/riscv-sbi-mpxy-mbox.c | 196 ++- include/linux/mailbox/riscv-rpmi-message.h | 13 + include/linux/mailbox_client.h | 3 + include/linux/mailbox_controller.h | 10 + include/linux/rpmi_tee.h | 175 ++ 15 files changed, 3098 insertions(+), 72 deletions(-) --- base-commit: 6375e61c01e93e35ee7acd336a689ac1fae4b509 change-id: 20260928-riscv-rpmi-tee-abi-603e9a3b4399
Best regards,
Some mailbox controllers can complete a transaction entirely within the calling context, without going through the queued TX state machine or a TX-done interrupt. Add a synchronous send path so their clients can request one and wait for the result directly.
Controllers advertise support via the new send_data_sync() op and clients opt in by setting tx_sync when requesting the channel.
Channels bound this way must not call mbox_chan_txdone() or mbox_client_txdone(), and mbox_send_message() and mbox_flush() now reject them, since only mbox_send_message_sync() is a valid transmit path. The client remains responsible for serializing calls to mbox_send_message_sync() against mbox_free_channel().
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/mailbox/mailbox.c | 72 ++++++++++++++++++++++++++++++++++++-- include/linux/mailbox_client.h | 3 ++ include/linux/mailbox_controller.h | 10 ++++++ 3 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c index efacd24a085d..c640b19de608 100644 --- a/drivers/mailbox/mailbox.c +++ b/drivers/mailbox/mailbox.c @@ -166,6 +166,12 @@ EXPORT_SYMBOL_GPL(mbox_chan_received_data); */ void mbox_chan_txdone(struct mbox_chan *chan, int r) { + if (unlikely(chan->txdone_method & MBOX_TXDONE_BY_RETURN)) { + dev_err(chan->mbox->dev, + "TX-done notification on direct synchronous channel\n"); + return; + } + if (unlikely(!(chan->txdone_method & MBOX_TXDONE_BY_IRQ))) { dev_err(chan->mbox->dev, "Controller can't run the TX ticker\n"); @@ -187,6 +193,12 @@ EXPORT_SYMBOL_GPL(mbox_chan_txdone); */ void mbox_client_txdone(struct mbox_chan *chan, int r) { + if (unlikely(chan->txdone_method & MBOX_TXDONE_BY_RETURN)) { + dev_err(chan->mbox->dev, + "TX-done notification on direct synchronous channel\n"); + return; + } + if (unlikely(!(chan->txdone_method & MBOX_TXDONE_BY_ACK))) { dev_err(chan->mbox->dev, "Client can't run the TX ticker\n"); return; @@ -278,6 +290,9 @@ int mbox_send_message(struct mbox_chan *chan, void *mssg) if (!chan || !chan->cl || mssg == MBOX_NO_MSG) return -EINVAL;
+ if (chan->txdone_method & MBOX_TXDONE_BY_RETURN) + return -EOPNOTSUPP; + t = add_to_rbuf(chan, mssg); if (t < 0) { dev_err(chan->mbox->dev, "Try increasing MBOX_TX_QUEUE_LEN\n"); @@ -308,6 +323,43 @@ int mbox_send_message(struct mbox_chan *chan, void *mssg) } EXPORT_SYMBOL_GPL(mbox_send_message);
+/** + * mbox_send_message_sync - Send data and wait for transaction completion + * @chan: Mailbox channel assigned to this client + * @mssg: Client specific message typecasted + * + * For a channel bound with tx_sync, ask the controller to transmit @mssg and + * only return on completion. This function may sleep and must not be called + * from atomic context. @mssg must remain valid until this function returns. + * + * The direct synchronous path does not queue @mssg, does not use active_req, + * and does not use a TX-done notification. The client must serialize this + * function against mbox_free_channel(). + * + * Return: 0 on success or a negative error code. + */ +int mbox_send_message_sync(struct mbox_chan *chan, void *mssg) +{ + int ret; + + if (!chan || !chan->cl || mssg == MBOX_NO_MSG) + return -EINVAL; + + if (!(chan->txdone_method & MBOX_TXDONE_BY_RETURN)) + return -EOPNOTSUPP; + + if (chan->cl->tx_prepare) + chan->cl->tx_prepare(chan->cl, mssg); + /* Try to submit a message to the MBOX controller synchonously */ + ret = chan->mbox->ops->send_data_sync(chan, mssg); + + if (chan->cl->tx_done) + chan->cl->tx_done(chan->cl, mssg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(mbox_send_message_sync); + /** * mbox_flush - flush a mailbox channel * @chan: mailbox channel to flush @@ -326,8 +378,11 @@ int mbox_flush(struct mbox_chan *chan, unsigned long timeout) { int ret;
+ if (chan->txdone_method & MBOX_TXDONE_BY_RETURN) + return -EOPNOTSUPP; + if (!chan->mbox->ops->flush) - return -ENOTSUPP; + return -EOPNOTSUPP;
ret = chan->mbox->ops->flush(chan, timeout); if (ret < 0) @@ -343,7 +398,9 @@ static void mbox_clean_and_put_channel(struct mbox_chan *chan) scoped_guard(spinlock_irqsave, &chan->lock) { chan->cl = NULL; chan->active_req = MBOX_NO_MSG; - if (chan->txdone_method == MBOX_TXDONE_BY_ACK) + if (chan->txdone_method & MBOX_TXDONE_BY_RETURN) + chan->txdone_method &= ~MBOX_TXDONE_BY_RETURN; + else if (chan->txdone_method == MBOX_TXDONE_BY_ACK) chan->txdone_method = MBOX_TXDONE_BY_POLL; }
@@ -355,6 +412,14 @@ static int __mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl) struct device *dev = cl->dev; int ret;
+ if (cl->tx_sync) { + if (!chan->mbox->ops->send_data_sync) + return -EOPNOTSUPP; + + if (cl->tx_block || cl->tx_tout || cl->knows_txdone) + return -EINVAL; + } + if (chan->cl || !try_module_get(chan->mbox->dev->driver->owner)) { dev_err(dev, "%s: mailbox not free\n", __func__); return -EBUSY; @@ -380,6 +445,9 @@ static int __mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl) } }
+ if (cl->tx_sync) + chan->txdone_method |= MBOX_TXDONE_BY_RETURN; + return 0; }
diff --git a/include/linux/mailbox_client.h b/include/linux/mailbox_client.h index e5997120f45c..32b1d5ad3bfa 100644 --- a/include/linux/mailbox_client.h +++ b/include/linux/mailbox_client.h @@ -21,6 +21,7 @@ struct mbox_chan; * @knows_txdone: If the client could run the TX state machine. Usually * if the client receives some ACK packet for transmission. * Unused if the controller already has TX_Done/RTR IRQ. + * @tx_sync: Bind the channel for mbox_send_message_sync(). * @rx_callback: Atomic callback to provide client the data received * @tx_prepare: Atomic callback to ask client to prepare the payload * before initiating the transmission if required. @@ -31,6 +32,7 @@ struct mbox_client { bool tx_block; unsigned long tx_tout; bool knows_txdone; + bool tx_sync;
void (*rx_callback)(struct mbox_client *cl, void *mssg); void (*tx_prepare)(struct mbox_client *cl, void *mssg); @@ -42,6 +44,7 @@ struct mbox_chan *mbox_request_channel_byname(struct mbox_client *cl, const char *name); struct mbox_chan *mbox_request_channel(struct mbox_client *cl, int index); int mbox_send_message(struct mbox_chan *chan, void *mssg); +int mbox_send_message_sync(struct mbox_chan *chan, void *mssg); int mbox_flush(struct mbox_chan *chan, unsigned long timeout); void mbox_client_txdone(struct mbox_chan *chan, int r); /* atomic */ bool mbox_client_peek_data(struct mbox_chan *chan); /* atomic */ diff --git a/include/linux/mailbox_controller.h b/include/linux/mailbox_controller.h index 26a238a6f941..c7dc098324ef 100644 --- a/include/linux/mailbox_controller.h +++ b/include/linux/mailbox_controller.h @@ -18,6 +18,7 @@ struct mbox_chan; #define MBOX_TXDONE_BY_IRQ BIT(0) /* controller has remote RTR irq */ #define MBOX_TXDONE_BY_POLL BIT(1) /* controller can read status of last TX */ #define MBOX_TXDONE_BY_ACK BIT(2) /* S/W ACK received by Client ticks the TX */ +#define MBOX_TXDONE_BY_RETURN BIT(3) /* TX completes by function return */
/** * struct mbox_chan_ops - methods to control mailbox channels @@ -28,6 +29,14 @@ struct mbox_chan; * transmission of data is reported by the controller via * mbox_chan_txdone (if it has some TX ACK irq). It must not * sleep. + * @send_data_sync: The API asks the MBOX controller driver, in non-atomic + * context, to transmit a message on the bus and wait for the + * transaction to complete. It returns 0 if the transaction + * completed successfully or a negative error code otherwise. + * The controller must not call mbox_chan_txdone() or + * mbox_client_txdone() for this operation. Concurrent calls for one + * controller must support them, serialize them internally, or + * return -EBUSY for a conflicting transaction. * @flush: Called when a client requests transmissions to be blocking but * the context doesn't allow sleeping. Typically the controller * will implement a busy loop waiting for the data to flush out. @@ -53,6 +62,7 @@ struct mbox_chan; */ struct mbox_chan_ops { int (*send_data)(struct mbox_chan *chan, void *data); + int (*send_data_sync)(struct mbox_chan *chan, void *data); int (*flush)(struct mbox_chan *chan, unsigned long timeout); int (*startup)(struct mbox_chan *chan); void (*shutdown)(struct mbox_chan *chan);
Implement send_data_sync() for RPMI MPXY channels, reusing the existing RPMI message dispatch and returning its status directly instead of going through mbox_chan_txdone().
Factor per-hart shared-memory acquisition into mpxy_shmem_get()/ mpxy_shmem_put() so both the queued and the new synchronous path pin the CPU around the same per-hart buffer, keeping a request and its response on one hart. Calls on separate harts remain independent, subject to firmware support.
Add rpmi_mbox_send_message_sync() as the RPMI counterpart to rpmi_mbox_send_message(), for use by the upcoming TEE transport.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/mailbox/riscv-sbi-mpxy-mbox.c | 196 ++++++++++++++++++----------- include/linux/mailbox/riscv-rpmi-message.h | 13 ++ 2 files changed, 139 insertions(+), 70 deletions(-)
diff --git a/drivers/mailbox/riscv-sbi-mpxy-mbox.c b/drivers/mailbox/riscv-sbi-mpxy-mbox.c index ea69c6b6b4f9..5ca1b6d87f5c 100644 --- a/drivers/mailbox/riscv-sbi-mpxy-mbox.c +++ b/drivers/mailbox/riscv-sbi-mpxy-mbox.c @@ -125,54 +125,83 @@ static DEFINE_PER_CPU(struct mpxy_local, mpxy_local); static unsigned long mpxy_shmem_size; static bool mpxy_shmem_init_done;
+static int mpxy_shmem_get(struct mpxy_local **out) +{ + struct mpxy_local *mpxy; + + get_cpu(); + mpxy = this_cpu_ptr(&mpxy_local); + if (!mpxy->shmem_active) { + put_cpu(); + return -ENODEV; + } + + *out = mpxy; + return 0; +} + +static void mpxy_shmem_put(void) +{ + put_cpu(); +} + static int mpxy_get_channel_count(u32 *channel_count) { - struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); - struct sbi_mpxy_channel_ids_data *sdata = mpxy->shmem; + struct mpxy_local *mpxy; + struct sbi_mpxy_channel_ids_data *sdata; u32 remaining, returned; struct sbiret sret; + int rc;
- if (!mpxy->shmem_active) - return -ENODEV; if (!channel_count) return -EINVAL;
- get_cpu(); + rc = mpxy_shmem_get(&mpxy); + if (rc) + return rc; + sdata = mpxy->shmem;
/* Get the remaining and returned fields to calculate total */ sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_CHANNEL_IDS, 0, 0, 0, 0, 0, 0); - if (sret.error) - goto err_put_cpu; + if (sret.error) { + rc = sbi_err_map_linux_errno(sret.error); + goto out; + }
remaining = le32_to_cpu(sdata->remaining); returned = le32_to_cpu(sdata->returned); *channel_count = remaining + returned; + rc = 0;
-err_put_cpu: - put_cpu(); - return sbi_err_map_linux_errno(sret.error); +out: + mpxy_shmem_put(); + return rc; }
static int mpxy_get_channel_ids(u32 channel_count, u32 *channel_ids) { - struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); - struct sbi_mpxy_channel_ids_data *sdata = mpxy->shmem; + struct mpxy_local *mpxy; + struct sbi_mpxy_channel_ids_data *sdata; u32 remaining, returned, count, start_index = 0; struct sbiret sret; + int rc;
- if (!mpxy->shmem_active) - return -ENODEV; if (!channel_count || !channel_ids) return -EINVAL;
- get_cpu(); + rc = mpxy_shmem_get(&mpxy); + if (rc) + return rc; + sdata = mpxy->shmem;
do { sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_CHANNEL_IDS, start_index, 0, 0, 0, 0, 0); - if (sret.error) - goto err_put_cpu; + if (sret.error) { + rc = sbi_err_map_linux_errno(sret.error); + goto out; + }
remaining = le32_to_cpu(sdata->remaining); returned = le32_to_cpu(sdata->returned); @@ -182,55 +211,61 @@ static int mpxy_get_channel_ids(u32 channel_count, u32 *channel_ids) memcpy_from_le32(&channel_ids[start_index], sdata->channel_array, count); start_index += count; } while (remaining && start_index < channel_count); + rc = 0;
-err_put_cpu: - put_cpu(); - return sbi_err_map_linux_errno(sret.error); +out: + mpxy_shmem_put(); + return rc; }
static int mpxy_read_attrs(u32 channel_id, u32 base_attrid, u32 attr_count, u32 *attrs_buf) { - struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct mpxy_local *mpxy; struct sbiret sret; + int rc;
- if (!mpxy->shmem_active) - return -ENODEV; if (!attr_count || !attrs_buf) return -EINVAL;
- get_cpu(); + rc = mpxy_shmem_get(&mpxy); + if (rc) + return rc;
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_READ_ATTRS, channel_id, base_attrid, attr_count, 0, 0, 0); - if (sret.error) - goto err_put_cpu; + if (sret.error) { + rc = sbi_err_map_linux_errno(sret.error); + goto out; + }
memcpy_from_le32(attrs_buf, (__le32 *)mpxy->shmem, attr_count); + rc = 0;
-err_put_cpu: - put_cpu(); - return sbi_err_map_linux_errno(sret.error); +out: + mpxy_shmem_put(); + return rc; }
static int mpxy_write_attrs(u32 channel_id, u32 base_attrid, u32 attr_count, u32 *attrs_buf) { - struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct mpxy_local *mpxy; struct sbiret sret; + int rc;
- if (!mpxy->shmem_active) - return -ENODEV; if (!attr_count || !attrs_buf) return -EINVAL;
- get_cpu(); + rc = mpxy_shmem_get(&mpxy); + if (rc) + return rc;
memcpy_to_le32((__le32 *)mpxy->shmem, attrs_buf, attr_count); sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_WRITE_ATTRS, channel_id, base_attrid, attr_count, 0, 0, 0);
- put_cpu(); + mpxy_shmem_put(); return sbi_err_map_linux_errno(sret.error); }
@@ -239,16 +274,17 @@ static int mpxy_send_message_with_resp(u32 channel_id, u32 msg_id, void *rx, unsigned long max_rx_len, unsigned long *rx_len) { - struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct mpxy_local *mpxy; unsigned long rx_bytes; struct sbiret sret; + int rc;
- if (!mpxy->shmem_active) - return -ENODEV; if (!tx && tx_len) return -EINVAL;
- get_cpu(); + rc = mpxy_shmem_get(&mpxy); + if (rc) + return rc;
/* Message protocols allowed to have no data in messages */ if (tx_len) @@ -259,8 +295,8 @@ static int mpxy_send_message_with_resp(u32 channel_id, u32 msg_id, if (rx && !sret.error) { rx_bytes = sret.value; if (rx_bytes > max_rx_len) { - put_cpu(); - return -ENOSPC; + rc = -ENOSPC; + goto out; }
memcpy(rx, mpxy->shmem, rx_bytes); @@ -268,22 +304,25 @@ static int mpxy_send_message_with_resp(u32 channel_id, u32 msg_id, *rx_len = rx_bytes; }
- put_cpu(); - return sbi_err_map_linux_errno(sret.error); + rc = sbi_err_map_linux_errno(sret.error); +out: + mpxy_shmem_put(); + return rc; }
static int mpxy_send_message_without_resp(u32 channel_id, u32 msg_id, void *tx, unsigned long tx_len) { - struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct mpxy_local *mpxy; struct sbiret sret; + int rc;
- if (!mpxy->shmem_active) - return -ENODEV; if (!tx && tx_len) return -EINVAL;
- get_cpu(); + rc = mpxy_shmem_get(&mpxy); + if (rc) + return rc;
/* Message protocols allowed to have no data in messages */ if (tx_len) @@ -292,40 +331,45 @@ static int mpxy_send_message_without_resp(u32 channel_id, u32 msg_id, sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_SEND_MSG_WITHOUT_RESP, channel_id, msg_id, tx_len, 0, 0, 0);
- put_cpu(); - return sbi_err_map_linux_errno(sret.error); + rc = sbi_err_map_linux_errno(sret.error); + mpxy_shmem_put(); + return rc; }
static int mpxy_get_notifications(u32 channel_id, struct sbi_mpxy_notification_data *notif_data, unsigned long *events_data_len) { - struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local); + struct mpxy_local *mpxy; struct sbiret sret; + int rc;
- if (!mpxy->shmem_active) - return -ENODEV; if (!notif_data || !events_data_len) return -EINVAL;
- get_cpu(); + rc = mpxy_shmem_get(&mpxy); + if (rc) + return rc;
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_NOTIFICATION_EVENTS, channel_id, 0, 0, 0, 0, 0); - if (sret.error) - goto err_put_cpu; + if (sret.error) { + rc = sbi_err_map_linux_errno(sret.error); + goto out; + } if (sret.value < 0 || mpxy_shmem_size < sizeof(*notif_data) || sret.value > mpxy_shmem_size - sizeof(*notif_data)) { - put_cpu(); - return -EOVERFLOW; + rc = -EOVERFLOW; + goto out; }
memcpy(notif_data, mpxy->shmem, sret.value + sizeof(*notif_data)); *events_data_len = sret.value;
-err_put_cpu: - put_cpu(); - return sbi_err_map_linux_errno(sret.error); + rc = sbi_err_map_linux_errno(sret.error); +out: + mpxy_shmem_put(); + return rc; }
static int mpxy_get_shmem_size(unsigned long *shmem_size) @@ -402,8 +446,8 @@ struct mpxy_mbox {
/* ====== MPXY RPMI processing ====== */
-static void mpxy_mbox_send_rpmi_data(struct mpxy_mbox_channel *mchan, - struct rpmi_mbox_message *msg) +static int mpxy_mbox_send_rpmi_data(struct mpxy_mbox_channel *mchan, + struct rpmi_mbox_message *msg) { msg->error = 0; switch (msg->type) { @@ -474,6 +518,8 @@ static void mpxy_mbox_send_rpmi_data(struct mpxy_mbox_channel *mchan, msg->error = -EOPNOTSUPP; break; } + + return msg->error; }
static void mpxy_mbox_peek_rpmi_data(struct mbox_chan *chan, @@ -516,12 +562,21 @@ static int mpxy_mbox_send_data(struct mbox_chan *chan, void *data) { struct mpxy_mbox_channel *mchan = chan->con_priv;
- if (mchan->attrs.msg_proto_id == SBI_MPXY_MSGPROTO_RPMI_ID) { - mpxy_mbox_send_rpmi_data(mchan, data); - return 0; - } + if (mchan->attrs.msg_proto_id != SBI_MPXY_MSGPROTO_RPMI_ID) + return -EOPNOTSUPP; + + mpxy_mbox_send_rpmi_data(mchan, data); + return 0; +} + +static int mpxy_mbox_send_data_sync(struct mbox_chan *chan, void *data) +{ + struct mpxy_mbox_channel *mchan = chan->con_priv; + + if (mchan->attrs.msg_proto_id != SBI_MPXY_MSGPROTO_RPMI_ID) + return -EOPNOTSUPP;
- return -EOPNOTSUPP; + return mpxy_mbox_send_rpmi_data(mchan, data); }
static bool mpxy_mbox_peek_data(struct mbox_chan *chan) @@ -713,10 +768,11 @@ static void mpxy_mbox_shutdown(struct mbox_chan *chan) }
static const struct mbox_chan_ops mpxy_mbox_ops = { - .send_data = mpxy_mbox_send_data, - .peek_data = mpxy_mbox_peek_data, - .startup = mpxy_mbox_startup, - .shutdown = mpxy_mbox_shutdown, + .send_data = mpxy_mbox_send_data, + .send_data_sync = mpxy_mbox_send_data_sync, + .peek_data = mpxy_mbox_peek_data, + .startup = mpxy_mbox_startup, + .shutdown = mpxy_mbox_shutdown, };
/* ====== MPXY platform driver ===== */ diff --git a/include/linux/mailbox/riscv-rpmi-message.h b/include/linux/mailbox/riscv-rpmi-message.h index d5362b5821f9..16fa85f65d80 100644 --- a/include/linux/mailbox/riscv-rpmi-message.h +++ b/include/linux/mailbox/riscv-rpmi-message.h @@ -251,4 +251,17 @@ static inline int rpmi_mbox_send_message(struct mbox_chan *chan, return ret; }
+static inline int rpmi_mbox_send_message_sync(struct mbox_chan *chan, + struct rpmi_mbox_message *msg) +{ + int ret; + + /* Send message for the underlying mailbox channel synchronously */ + ret = mbox_send_message_sync(chan, msg); + if (ret) + return ret; + + return msg->error; +} + #endif /* _LINUX_RISCV_RPMI_MESSAGE_H_ */
Add a bus for RISC-V RPMI TEE services. Each service exposed by a TEE endpoint is registered as its own device on this bus, identified by a UUID, so that a single physical TEE can host multiple independently-bindable service drivers.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/firmware/Kconfig | 2 + drivers/firmware/Makefile | 1 + drivers/firmware/riscv_rpmi_tee/Kconfig | 8 ++ drivers/firmware/riscv_rpmi_tee/Makefile | 5 + drivers/firmware/riscv_rpmi_tee/bus.c | 202 +++++++++++++++++++++++++++++++ include/linux/rpmi_tee.h | 83 +++++++++++++ 6 files changed, 301 insertions(+)
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index c183d98c1e8f..46297332288f 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -6,6 +6,8 @@
menu "Firmware Drivers"
+source "drivers/firmware/riscv_rpmi_tee/Kconfig" + source "drivers/firmware/arm_scmi/Kconfig"
config ARM_SCPI_PROTOCOL diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index a855d3696173..772fe024baab 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_TURRIS_MOX_RWTM) += turris-mox-rwtm.o
obj-y += arm_ffa/ obj-y += arm_scmi/ +obj-y += riscv_rpmi_tee/ obj-y += broadcom/ obj-y += cirrus/ obj-y += meson/ diff --git a/drivers/firmware/riscv_rpmi_tee/Kconfig b/drivers/firmware/riscv_rpmi_tee/Kconfig new file mode 100644 index 000000000000..b0396183b87d --- /dev/null +++ b/drivers/firmware/riscv_rpmi_tee/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config RISCV_RPMI_TEE_TRANSPORT + tristate "RISC-V RPMI TEE service group transport" + depends on RISCV && OF && RISCV_SBI_MPXY_MBOX + help + Say Y or M here to enable the transport for services provided by the + RISC-V RPMI TEE service group. diff --git a/drivers/firmware/riscv_rpmi_tee/Makefile b/drivers/firmware/riscv_rpmi_tee/Makefile new file mode 100644 index 000000000000..d80ab5bc9dc4 --- /dev/null +++ b/drivers/firmware/riscv_rpmi_tee/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +rpmi-tee-bus-y = bus.o +rpmi-tee-core-objs := $(rpmi-tee-bus-y) +obj-$(CONFIG_RISCV_RPMI_TEE_TRANSPORT) = rpmi-tee-core.o diff --git a/drivers/firmware/riscv_rpmi_tee/bus.c b/drivers/firmware/riscv_rpmi_tee/bus.c new file mode 100644 index 000000000000..32f555c2f1f2 --- /dev/null +++ b/drivers/firmware/riscv_rpmi_tee/bus.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RISC-V RPMI TEE bus + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/device.h> +#include <linux/ida.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/rpmi_tee.h> +#include <linux/slab.h> + +#define RPMI_TEE_UEVENT_MODALIAS_FMT "rpmi_tee:%pUb" + +static DEFINE_IDA(rpmi_tee_bus_id); + +static int rpmi_tee_device_match(struct device *dev, + const struct device_driver *drv) +{ + const struct rpmi_tee_device_id *id_table; + struct rpmi_tee_device *rdev = to_rpmi_tee_dev(dev); + + id_table = to_rpmi_tee_drv(drv)->id_table; + if (!id_table) + return 0; + + while (!uuid_is_null(&id_table->uuid)) { + if (uuid_equal(&rdev->uuid, &id_table->uuid)) + return 1; + id_table++; + } + + return 0; +} + +static int rpmi_tee_device_probe(struct device *dev) +{ + struct rpmi_tee_driver *rdrv = to_rpmi_tee_drv(dev->driver); + + return rdrv->probe(to_rpmi_tee_dev(dev)); +} + +static void rpmi_tee_device_remove(struct device *dev) +{ + struct rpmi_tee_driver *rdrv = to_rpmi_tee_drv(dev->driver); + + if (rdrv->remove) + rdrv->remove(to_rpmi_tee_dev(dev)); +} + +static int rpmi_tee_device_uevent(const struct device *dev, + struct kobj_uevent_env *env) +{ + const struct rpmi_tee_device *rdev = to_rpmi_tee_dev(dev); + + return add_uevent_var(env, "MODALIAS=" RPMI_TEE_UEVENT_MODALIAS_FMT, + &rdev->uuid); +} + +static ssize_t endpoint_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rpmi_tee_device *rdev = to_rpmi_tee_dev(dev); + + return sysfs_emit(buf, "0x%x\n", rdev->endpoint_id); +} +static DEVICE_ATTR_RO(endpoint_id); + +static ssize_t uuid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rpmi_tee_device *rdev = to_rpmi_tee_dev(dev); + + return sysfs_emit(buf, "%pUb\n", &rdev->uuid); +} +static DEVICE_ATTR_RO(uuid); + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rpmi_tee_device *rdev = to_rpmi_tee_dev(dev); + + return sysfs_emit(buf, RPMI_TEE_UEVENT_MODALIAS_FMT, &rdev->uuid); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *rpmi_tee_device_attrs[] = { + &dev_attr_endpoint_id.attr, + &dev_attr_uuid.attr, + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(rpmi_tee_device); + +const struct bus_type rpmi_tee_bus_type = { + .name = "rpmi_tee", + .match = rpmi_tee_device_match, + .probe = rpmi_tee_device_probe, + .remove = rpmi_tee_device_remove, + .uevent = rpmi_tee_device_uevent, + .dev_groups = rpmi_tee_device_groups, +}; +EXPORT_SYMBOL_GPL(rpmi_tee_bus_type); + +int rpmi_tee_driver_register(struct rpmi_tee_driver *driver, + struct module *owner, const char *mod_name) +{ + if (!driver->probe || !driver->id_table) + return -EINVAL; + + driver->driver.bus = &rpmi_tee_bus_type; + driver->driver.name = driver->name; + driver->driver.owner = owner; + driver->driver.mod_name = mod_name; + + return driver_register(&driver->driver); +} +EXPORT_SYMBOL_GPL(rpmi_tee_driver_register); + +void rpmi_tee_driver_unregister(struct rpmi_tee_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL_GPL(rpmi_tee_driver_unregister); + +static void rpmi_tee_device_release(struct device *dev) +{ + struct rpmi_tee_device *rdev = to_rpmi_tee_dev(dev); + + ida_free(&rpmi_tee_bus_id, rdev->id); + kfree(rdev); +} + +struct rpmi_tee_device * +rpmi_tee_device_register(const uuid_t *uuid, u32 endpoint_id, + const struct rpmi_tee_ops *ops, struct device *parent) +{ + struct rpmi_tee_device *rdev; + int id; + int ret; + + if (!uuid || !ops) + return ERR_PTR(-EINVAL); + + id = ida_alloc_min(&rpmi_tee_bus_id, 1, GFP_KERNEL); + if (id < 0) + return ERR_PTR(id); + + rdev = kzalloc_obj(*rdev, GFP_KERNEL); + if (!rdev) { + ida_free(&rpmi_tee_bus_id, id); + return ERR_PTR(-ENOMEM); + } + + rdev->dev.parent = parent; + rdev->dev.bus = &rpmi_tee_bus_type; + rdev->dev.release = rpmi_tee_device_release; + dev_set_name(&rdev->dev, "rpmi-tee-%d", id); + + rdev->id = id; + rdev->endpoint_id = endpoint_id; + rdev->ops = ops; + uuid_copy(&rdev->uuid, uuid); + + ret = device_register(&rdev->dev); + if (ret) { + put_device(&rdev->dev); + return ERR_PTR(ret); + } + + return rdev; +} +EXPORT_SYMBOL_GPL(rpmi_tee_device_register); + +void rpmi_tee_device_unregister(struct rpmi_tee_device *rdev) +{ + if (rdev) + device_unregister(&rdev->dev); +} +EXPORT_SYMBOL_GPL(rpmi_tee_device_unregister); + +static int __init rpmi_tee_bus_init(void) +{ + return bus_register(&rpmi_tee_bus_type); +} + +subsys_initcall(rpmi_tee_bus_init); + +static void __exit rpmi_tee_bus_exit(void) +{ + bus_unregister(&rpmi_tee_bus_type); + ida_destroy(&rpmi_tee_bus_id); +} + +module_exit(rpmi_tee_bus_exit); + +MODULE_DESCRIPTION("RISC-V RPMI TEE bus"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/rpmi_tee.h b/include/linux/rpmi_tee.h new file mode 100644 index 000000000000..c499f0427833 --- /dev/null +++ b/include/linux/rpmi_tee.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * RISC-V RPMI TEE transport interface + */ + +#ifndef _LINUX_RPMI_TEE_H +#define _LINUX_RPMI_TEE_H + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/uuid.h> + +struct rpmi_tee_ops; + +struct rpmi_tee_device { + struct device dev; + u32 id; + u32 endpoint_id; /* RPMI endpoint identifier of the TEE. */ + uuid_t uuid; /* UUID identifying the TEE service. */ + const struct rpmi_tee_ops *ops; +}; + +#define to_rpmi_tee_dev(d) container_of(d, struct rpmi_tee_device, dev) + +struct rpmi_tee_device_id { + uuid_t uuid; +}; + +struct rpmi_tee_driver { + const char *name; + int (*probe)(struct rpmi_tee_device *rdev); + void (*remove)(struct rpmi_tee_device *rdev); + /* NULL-UUID-terminated list of supported service UUIDs. */ + const struct rpmi_tee_device_id *id_table; + struct device_driver driver; +}; + +#define to_rpmi_tee_drv(d) \ + container_of_const(d, struct rpmi_tee_driver, driver) + +extern const struct bus_type rpmi_tee_bus_type; + +#if IS_REACHABLE(CONFIG_RISCV_RPMI_TEE_TRANSPORT) +struct rpmi_tee_device * +rpmi_tee_device_register(const uuid_t *uuid, u32 endpoint_id, + const struct rpmi_tee_ops *ops, struct device *parent); +void rpmi_tee_device_unregister(struct rpmi_tee_device *rdev); +int rpmi_tee_driver_register(struct rpmi_tee_driver *driver, + struct module *owner, const char *mod_name); +void rpmi_tee_driver_unregister(struct rpmi_tee_driver *driver); +#else +static inline struct rpmi_tee_device * +rpmi_tee_device_register(const uuid_t *uuid, u32 endpoint_id, + const struct rpmi_tee_ops *ops, struct device *parent) +{ + return NULL; +} + +static inline void rpmi_tee_device_unregister(struct rpmi_tee_device *rdev) +{ +} + +static inline int rpmi_tee_driver_register(struct rpmi_tee_driver *driver, + struct module *owner, + const char *mod_name) +{ + return -EOPNOTSUPP; +} + +static inline void rpmi_tee_driver_unregister(struct rpmi_tee_driver *driver) +{ +} +#endif + +#define rpmi_tee_register(driver) \ + rpmi_tee_driver_register(driver, THIS_MODULE, KBUILD_MODNAME) +#define rpmi_tee_unregister(driver) \ + rpmi_tee_driver_unregister(driver) + +#endif /* _LINUX_RPMI_TEE_H */
Document the platform device that carries the RISC-V RPMI TEE service group over a single SBI MPXY mailbox channel.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- .../bindings/firmware/riscv,rpmi-tee.yaml | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+)
diff --git a/Documentation/devicetree/bindings/firmware/riscv,rpmi-tee.yaml b/Documentation/devicetree/bindings/firmware/riscv,rpmi-tee.yaml new file mode 100644 index 000000000000..32287f27a625 --- /dev/null +++ b/Documentation/devicetree/bindings/firmware/riscv,rpmi-tee.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/firmware/riscv,rpmi-tee.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: RISC-V RPMI TEE service group transport + +maintainers: + - Amirreza Zarrabi amrzar@gmail.com + +description: | + The RISC-V Platform Management Interface TEE service group is carried over + an SBI MPXY RPMI mailbox channel. The transport discovers TEE services and + creates child devices for their endpoint and UUID pairs. + +properties: + compatible: + const: riscv,rpmi-tee + + mboxes: + maxItems: 1 + +required: + - compatible + - mboxes + +additionalProperties: false + +examples: + - | + rpmi-tee { + compatible = "riscv,rpmi-tee"; + mboxes = <&mpxy_mbox 0x3000 0x0>; + };
Add the RISC-V RPMI TEE platform driver and bind it to a direct synchronous mailbox channel via tx_sync.
Before retaining the channel, validate the RPMI spec version, the TEE service-group identifier and version, and cache the negotiated maximum message data size, so later discovery, memory, and notification support can rely on a checked transport.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/firmware/riscv_rpmi_tee/Makefile | 3 + drivers/firmware/riscv_rpmi_tee/driver.c | 144 +++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+)
diff --git a/drivers/firmware/riscv_rpmi_tee/Makefile b/drivers/firmware/riscv_rpmi_tee/Makefile index d80ab5bc9dc4..8984127535ed 100644 --- a/drivers/firmware/riscv_rpmi_tee/Makefile +++ b/drivers/firmware/riscv_rpmi_tee/Makefile @@ -3,3 +3,6 @@ rpmi-tee-bus-y = bus.o rpmi-tee-core-objs := $(rpmi-tee-bus-y) obj-$(CONFIG_RISCV_RPMI_TEE_TRANSPORT) = rpmi-tee-core.o +rpmi-tee-driver-y = driver.o +rpmi-tee-module-objs := $(rpmi-tee-driver-y) +obj-$(CONFIG_RISCV_RPMI_TEE_TRANSPORT) += rpmi-tee-module.o diff --git a/drivers/firmware/riscv_rpmi_tee/driver.c b/drivers/firmware/riscv_rpmi_tee/driver.c new file mode 100644 index 000000000000..633dba116584 --- /dev/null +++ b/drivers/firmware/riscv_rpmi_tee/driver.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RISC-V RPMI TEE transport + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/mailbox_client.h> +#include <linux/mailbox/riscv-rpmi-message.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/rpmi_tee.h> + +#define RPMI_SRVGRP_TEE 0x10 + +struct rpmi_tee_mbox { + struct mbox_client client; + struct mbox_chan *chan; + u32 max_msg_data_size; +}; + +struct rpmi_tee_transport { + struct device *dev; + struct rpmi_tee_mbox mbox; +}; + +/** + * rpmi_tee_get_attr() - Get an RPMI mailbox attribute + * @priv: RPMI TEE transport + * @id: Attribute identifier + * @value: Returned attribute value + * + * Return: 0 on success, or a negative error code on failure. + */ +static int rpmi_tee_get_attr(struct rpmi_tee_transport *priv, + enum rpmi_mbox_attribute_id id, u32 *value) +{ + struct rpmi_mbox_message msg; + int ret; + + rpmi_mbox_init_get_attribute(&msg, id); + ret = rpmi_mbox_send_message_sync(priv->mbox.chan, &msg); + if (ret) + return ret; + + *value = msg.attr.value; + + return 0; +} + +/* Validate the RPMI mailbox transport and cache its message size. */ +static int rpmi_tee_check_transport(struct rpmi_tee_transport *priv) +{ + u32 value; + int ret; + + ret = rpmi_tee_get_attr(priv, RPMI_MBOX_ATTR_SPEC_VERSION, &value); + if (ret) + return ret; + if (value < RPMI_MKVER(1, 0)) + return -EPROTONOSUPPORT; + + ret = rpmi_tee_get_attr(priv, RPMI_MBOX_ATTR_SERVICEGROUP_ID, &value); + if (ret) + return ret; + if (value != RPMI_SRVGRP_TEE) + return -ENODEV; + + ret = rpmi_tee_get_attr(priv, RPMI_MBOX_ATTR_SERVICEGROUP_VERSION, + &value); + if (ret) + return ret; + if (value < RPMI_MKVER(1, 0)) + return -EPROTONOSUPPORT; + + ret = rpmi_tee_get_attr(priv, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE, &value); + if (ret) + return ret; + + priv->mbox.max_msg_data_size = value; + + return 0; +} + +static int rpmi_tee_transport_probe(struct platform_device *pdev) +{ + struct rpmi_tee_transport *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + platform_set_drvdata(pdev, priv); + priv->mbox.client.dev = &pdev->dev; + priv->mbox.client.tx_sync = true; + priv->mbox.chan = mbox_request_channel(&priv->mbox.client, 0); + if (IS_ERR(priv->mbox.chan)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->mbox.chan), + "failed to request mailbox channel\n"); + + ret = rpmi_tee_check_transport(priv); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "invalid RPMI TEE mailbox channel\n"); + goto out_failed; + } + + return 0; + +out_failed: + mbox_free_channel(priv->mbox.chan); + + return ret; +} + +static void rpmi_tee_transport_remove(struct platform_device *pdev) +{ + struct rpmi_tee_transport *priv = platform_get_drvdata(pdev); + + mbox_free_channel(priv->mbox.chan); +} + +static const struct of_device_id rpmi_tee_transport_match[] = { + { .compatible = "riscv,rpmi-tee" }, + { } +}; +MODULE_DEVICE_TABLE(of, rpmi_tee_transport_match); + +static struct platform_driver rpmi_tee_transport_driver = { + .probe = rpmi_tee_transport_probe, + .remove = rpmi_tee_transport_remove, + .driver = { + .name = "riscv-rpmi-tee", + .of_match_table = rpmi_tee_transport_match, + }, +}; + +module_platform_driver(rpmi_tee_transport_driver); + +MODULE_DESCRIPTION("RISC-V RPMI TEE service group transport"); +MODULE_LICENSE("GPL");
Add parsing for the RPMI TEE descriptor-table SYSINFO format used during discovery. Validate the V1 header, the table directory, each table's byte range, endpoint role flags, and endpoint-to-service ownership before any record is trusted.
Support both the Self SYSINFO response, which identifies the local REE endpoint, and the Whole-system SYSINFO response, which enumerates the physical TEE endpoints and their service UUIDs.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/firmware/riscv_rpmi_tee/Makefile | 2 +- drivers/firmware/riscv_rpmi_tee/sysinfo.c | 385 ++++++++++++++++++++++++++++++ drivers/firmware/riscv_rpmi_tee/sysinfo.h | 244 +++++++++++++++++++ 3 files changed, 630 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/riscv_rpmi_tee/Makefile b/drivers/firmware/riscv_rpmi_tee/Makefile index 8984127535ed..90cc6c39b273 100644 --- a/drivers/firmware/riscv_rpmi_tee/Makefile +++ b/drivers/firmware/riscv_rpmi_tee/Makefile @@ -3,6 +3,6 @@ rpmi-tee-bus-y = bus.o rpmi-tee-core-objs := $(rpmi-tee-bus-y) obj-$(CONFIG_RISCV_RPMI_TEE_TRANSPORT) = rpmi-tee-core.o -rpmi-tee-driver-y = driver.o +rpmi-tee-driver-y = driver.o sysinfo.o rpmi-tee-module-objs := $(rpmi-tee-driver-y) obj-$(CONFIG_RISCV_RPMI_TEE_TRANSPORT) += rpmi-tee-module.o diff --git a/drivers/firmware/riscv_rpmi_tee/sysinfo.c b/drivers/firmware/riscv_rpmi_tee/sysinfo.c new file mode 100644 index 000000000000..5829ab6db042 --- /dev/null +++ b/drivers/firmware/riscv_rpmi_tee/sysinfo.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * RPMI TEE system-information descriptor table parsing + */ + +#include <linux/errno.h> +#include <linux/overflow.h> +#include <linux/string.h> +#include <linux/unaligned.h> + +#include "sysinfo.h" + +struct rpmi_tee_sysinfo_table { + const u8 *data; + u32 count; + u16 desc_size; + bool present; +}; + +/** + * struct rpmi_tee_sysinfo - Validated SYSINFO descriptor-table state + * @data: Start of the complete SYSINFO response buffer. + * @len: Size of @data in bytes. + * @tables: Table views indexed by &enum rpmi_tee_sysinfo_table_type. + * + * Each table view is populated only after the header, directory, table + * range, and descriptor size have been validated. + */ +struct rpmi_tee_sysinfo { + const u8 *data; + size_t len; + struct rpmi_tee_sysinfo_table tables[RPMI_TEE_SYSINFO_TABLE_MAX]; +}; + +/* Assign table record @i in @t to a typed pointer @ptr. */ +#define rpmi_tee_sysinfo_entry_at(t, i, ptr) \ + do { \ + typeof(t) table = (t); \ + (ptr) = (typeof(ptr))(table->data + \ + (size_t)(i) * table->desc_size); \ + } while (0) + +/** + * rpmi_tee_sysinfo_buffer_range() - Validate a serialized SYSINFO range + * @total: Total SYSINFO buffer size in bytes. + * @offset: Byte offset of the first record. + * @count: Number of records. + * @size: Size of one record in bytes. + * @start: Optional returned byte offset of the range. + * + * Check that @count records of @size bytes beginning at @offset neither + * overflow nor extend beyond @total. If non-NULL, @start receives the byte + * offset of the validated range. + * + * Return: 0 on success or -EINVAL if the range is invalid. + */ +static int rpmi_tee_sysinfo_buffer_range(size_t total, u32 offset, u32 count, + u16 size, size_t *start) +{ + size_t bytes, end; + + if (check_mul_overflow((size_t)count, (size_t)size, &bytes) || + check_add_overflow((size_t)offset, bytes, &end) || end > total) + return -EINVAL; + + if (start) + *start = offset; + + return 0; +} + +/* Validate a directory descriptor and record its table range in @info. */ +static int rpmi_tee_sysinfo_get_table(struct rpmi_tee_sysinfo *info, + const struct rpmi_tee_sysinfo_table_desc *desc) +{ + u16 desc_size = get_unaligned_le16(&desc->desc_size); + u32 offset = get_unaligned_le32(&desc->offset); + u32 count = get_unaligned_le32(&desc->count); + u16 type = get_unaligned_le16(&desc->type); + struct rpmi_tee_sysinfo_table *table; + size_t start; + + /* Validate table buffer. */ + if (rpmi_tee_sysinfo_buffer_range(info->len, offset, count, desc_size, + &start)) + return -EINVAL; + + switch (type) { + case RPMI_TEE_SYSINFO_TABLE_ENDPOINT: + if (desc_size < sizeof(struct rpmi_tee_sysinfo_endpoint)) + return -EINVAL; + break; + case RPMI_TEE_SYSINFO_TABLE_SERVICE: + if (desc_size < sizeof(struct rpmi_tee_sysinfo_service)) + return -EINVAL; + break; + case RPMI_TEE_SYSINFO_TABLE_PARCEL: + if (desc_size < sizeof(struct rpmi_tee_sysinfo_parcel)) + return -EINVAL; + break; + case RPMI_TEE_SYSINFO_TABLE_PARCEL_RECEIVER: + if (desc_size < sizeof(struct rpmi_tee_sysinfo_parcel_receiver)) + return -EINVAL; + break; + case RPMI_TEE_SYSINFO_TABLE_MEMORY_BLOCK: + if (desc_size < sizeof(struct rpmi_tee_sysinfo_memory_block)) + return -EINVAL; + break; + case RPMI_TEE_SYSINFO_TABLE_BLOB: + /* BLOB table is defined as raw bytes. */ + if (desc_size != 1) + return -EINVAL; + break; + default: + return -EOPNOTSUPP; + } + + table = &info->tables[type]; + if (table->present) + return -EINVAL; + + table->data = info->data + start; + table->count = count; + table->desc_size = desc_size; + table->present = true; + + return 0; +} + +/* Validate the header and directory, then populate table views. */ +static int rpmi_tee_sysinfo_init(struct rpmi_tee_sysinfo *info, + const void *data, size_t len) +{ + const struct rpmi_tee_sysinfo_header *header = data; + u32 i, table_count, dir_off; + u16 header_size, dir_size; + + memset(info, 0, sizeof(*info)); + + if (len < sizeof(struct rpmi_tee_sysinfo_header)) + return -EINVAL; + + if (get_unaligned_le32(&header->magic) != RPMI_TEE_SYSINFO_MAGIC || + get_unaligned_le16(&header->major) != RPMI_TEE_SYSINFO_VERSION_MAJOR || + get_unaligned_le32(&header->total_size) != len) + return -EINVAL; + + header_size = get_unaligned_le16(&header->header_size); + dir_size = get_unaligned_le16(&header->table_desc_size); + table_count = get_unaligned_le32(&header->table_count); + dir_off = get_unaligned_le32(&header->table_dir_offset); + + if (header_size < sizeof(struct rpmi_tee_sysinfo_header) || + dir_size < sizeof(struct rpmi_tee_sysinfo_table_desc) || + /* Directory can not overlap with the header. */ + dir_off < header_size) + return -EINVAL; + + /* Validate directory buffer. */ + if (rpmi_tee_sysinfo_buffer_range(len, dir_off, table_count, dir_size, + NULL)) + return -EINVAL; + + info->data = data; + info->len = len; + for (i = 0; i < table_count; i++) { + const struct rpmi_tee_sysinfo_table_desc *desc; + int ret; + + desc = rpmi_tee_sysinfo_desc_at(header, i); + ret = rpmi_tee_sysinfo_get_table(info, desc); + if (ret) + return ret; + } + + return 0; +} + +/** + * rpmi_tee_sysinfo_table_check() - Check a table index range + * @table: Validated table view. + * @offset: First record index, or first byte index for a BLOB table. + * @count: Number of records, or bytes for a BLOB table. + * + * An empty range @count = 0 is valid. + * A non-empty range must fit wholly within @table. + * + * Return: %true if the range is valid or %false otherwise. + */ +static bool +rpmi_tee_sysinfo_table_check(const struct rpmi_tee_sysinfo_table *table, + u32 offset, u32 count) +{ + size_t end; + + if (!count) + return true; + /* For table->present = false or empty table, table->count is zero. */ + return !check_add_overflow((size_t)offset, (size_t)count, &end) && + end <= table->count; +} + +/* Validate endpoint records and their service ranges. */ +static int rpmi_tee_sysinfo_validate_endpoints(struct rpmi_tee_sysinfo *info, + bool require_single_ree, + u32 *self_ep_id) +{ + const struct rpmi_tee_sysinfo_table *service_table; + const struct rpmi_tee_sysinfo_table *ep_table; + const struct rpmi_tee_sysinfo_endpoint *ep; + u32 ree_count = 0, ep_idx; + + ep_table = &info->tables[RPMI_TEE_SYSINFO_TABLE_ENDPOINT]; + service_table = &info->tables[RPMI_TEE_SYSINFO_TABLE_SERVICE]; + /* Endpoint table should exist and not empty; service table can be empty. */ + if (!ep_table->present || !ep_table->count || !service_table->present) + return -EINVAL; + + for (ep_idx = 0; ep_idx < ep_table->count; ep_idx++) { + rpmi_tee_sysinfo_entry_at(ep_table, ep_idx, ep); + + /* struct rpmi_tee_sysinfo_endpoint. */ + u32 ep_id = get_unaligned_le32(&ep->id); + u32 parent_id = get_unaligned_le32(&ep->parent_id); + u32 ep_flags = get_unaligned_le32(&ep->flags); + u32 service_first = get_unaligned_le32(&ep->service_first); + u32 service_range_count = + get_unaligned_le32(&ep->service_count); + + bool is_physical = + ep_flags & RPMI_TEE_SYSINFO_ENDPOINT_F_PHYSICAL; + bool is_ree = ep_flags & RPMI_TEE_SYSINFO_ENDPOINT_F_REE; + /* Require exactly one endpoint location and security role. */ + if (is_physical == + !!(ep_flags & RPMI_TEE_SYSINFO_ENDPOINT_F_PROXIED) || + is_ree == !!(ep_flags & RPMI_TEE_SYSINFO_ENDPOINT_F_TEE) || + ep_flags & ~RPMI_TEE_SYSINFO_ENDPOINT_F_MASK) + return -EINVAL; + + if (!rpmi_tee_sysinfo_table_check(service_table, service_first, + service_range_count)) + return -EINVAL; + + if (is_physical) { + if (parent_id != RPMI_TEE_SYSINFO_ENDPOINT_NO_PARENT) + return -EINVAL; + + if (is_ree) { + ree_count++; + if (self_ep_id) + *self_ep_id = ep_id; + } + } + } + + if (require_single_ree && ree_count != 1) + return -EINVAL; + + return 0; +} + +/** + * rpmi_tee_sysinfo_parse_self() - Parse a Self SYSINFO table + * @data: SYSINFO response buffer. + * @len: Size of @data in bytes. + * @self_ep_id: Returned local REE endpoint identifier. + * + * Return: 0 on success or a negative error code. + */ +int rpmi_tee_sysinfo_parse_self(const void *data, size_t len, u32 *self_ep_id) +{ + struct rpmi_tee_sysinfo info; + int ret; + + if (!self_ep_id) + return -EINVAL; + + ret = rpmi_tee_sysinfo_init(&info, data, len); + if (ret) + return ret; + /* Expect single endpoint in a self SYSINFO response. */ + if (info.tables[RPMI_TEE_SYSINFO_TABLE_ENDPOINT].count != 1) + return -EINVAL; + + return rpmi_tee_sysinfo_validate_endpoints(&info, true, self_ep_id); +} + +/** + * rpmi_tee_sysinfo_parse_system() - Parse a Whole-system SYSINFO table + * @data: SYSINFO response buffer. + * @len: Size of @data in bytes. + * @self_ep_id: Local REE endpoint identifier from the Self table. + * @system: Caller-provided discovery-result storage and returned counts. + * + * The parser records the number of required endpoint and service entries in + * @system. If either caller-provided array is too small, it fills the entries + * that fit, returns -ENOSPC, and reports the required counts. The caller may + * then allocate the reported capacities and call this function again. + * + * Return: 0 on success, -ENOSPC if an output array is too small, or a + * negative error code. + */ +int rpmi_tee_sysinfo_parse_system(const void *data, size_t len, u32 self_ep_id, + struct rpmi_tee_sysinfo_system *system) +{ + const struct rpmi_tee_sysinfo_table *service_table; + const struct rpmi_tee_sysinfo_table *ep_table; + const struct rpmi_tee_sysinfo_endpoint *ep; + size_t ep_count = 0, service_count = 0; + struct rpmi_tee_sysinfo info; + bool self_ep_found = false; + u32 ep_idx, service_idx; + int ret; + + if ((system->ep_capacity && !system->eps) || + (system->service_capacity && !system->services)) + return -EINVAL; + + ret = rpmi_tee_sysinfo_init(&info, data, len); + if (ret) + return ret; + + ret = rpmi_tee_sysinfo_validate_endpoints(&info, false, NULL); + if (ret) + return ret; + + ep_table = &info->tables[RPMI_TEE_SYSINFO_TABLE_ENDPOINT]; + service_table = &info->tables[RPMI_TEE_SYSINFO_TABLE_SERVICE]; + + for (ep_idx = 0; ep_idx < ep_table->count; ep_idx++) { + rpmi_tee_sysinfo_entry_at(ep_table, ep_idx, ep); + + /* struct rpmi_tee_sysinfo_endpoint. */ + u32 ep_flags = get_unaligned_le32(&ep->flags); + u32 ep_id = get_unaligned_le32(&ep->id); + u32 service_first = get_unaligned_le32(&ep->service_first); + u32 service_range_count = + get_unaligned_le32(&ep->service_count); + + /* Make sure self_ep_id exists in the list of endpoints. */ + if ((ep_flags & (RPMI_TEE_SYSINFO_ENDPOINT_F_PHYSICAL | + RPMI_TEE_SYSINFO_ENDPOINT_F_REE)) == + (RPMI_TEE_SYSINFO_ENDPOINT_F_PHYSICAL | + RPMI_TEE_SYSINFO_ENDPOINT_F_REE) && ep_id == self_ep_id) + self_ep_found = true; + + if (!(ep_flags & RPMI_TEE_SYSINFO_ENDPOINT_F_PHYSICAL) || + !(ep_flags & RPMI_TEE_SYSINFO_ENDPOINT_F_TEE)) + continue; + + if (ep_count < system->ep_capacity) + system->eps[ep_count].endpoint_id = ep_id; + + ep_count++; + + /* Extract per-endpoint services. */ + for (service_idx = service_first; + service_idx < service_first + service_range_count; + service_idx++) { + const struct rpmi_tee_sysinfo_service *service; + + if (service_count < system->service_capacity) { + rpmi_tee_sysinfo_entry_at(service_table, + service_idx, service); + system->services[service_count].endpoint_id = ep_id; + import_uuid(&system->services[service_count].uuid, + service->uuid); + } + + if (check_add_overflow(service_count, 1, &service_count)) + return -EOVERFLOW; + } + } + + system->ep_count = ep_count; + system->service_count = service_count; + + if (!self_ep_found) + return -EINVAL; + + return ep_count > system->ep_capacity || + service_count > system->service_capacity ? -ENOSPC : 0; +} diff --git a/drivers/firmware/riscv_rpmi_tee/sysinfo.h b/drivers/firmware/riscv_rpmi_tee/sysinfo.h new file mode 100644 index 000000000000..f275a612f720 --- /dev/null +++ b/drivers/firmware/riscv_rpmi_tee/sysinfo.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __RISC_V_RPMI_TEE_SYSINFO_H__ +#define __RISC_V_RPMI_TEE_SYSINFO_H__ + +#include <linux/bitops.h> +#include <linux/limits.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/uuid.h> + +/* RPMI_TEE_FEATURE_SYSINFO_FORMAT. */ +#define RPMI_TEE_SYSINFO_FORMAT_CBOR BIT(31) /* CBOR format. */ +#define RPMI_TEE_SYSINFO_FORMAT_TABLE BIT(30) /* Descriptor-table format. */ + +/* "SYSINFO descriptor-table format". */ + +#define RPMI_TEE_SYSINFO_MAGIC 0x49535452 /* Table magic ("RTSI"). */ +#define RPMI_TEE_SYSINFO_VERSION_MAJOR 1 /* Supported major version. */ + +/** + * struct rpmi_tee_sysinfo_header - SYSINFO descriptor-table header + * @magic: RPMI_TEE_SYSINFO_MAGIC. + * @major: Major format version. + * @minor: Minor format version. + * @header_size: Header size, including compatible extensions. + * @table_desc_size: Size of one table-directory descriptor. + * @total_size: Total response size in bytes. + * @table_count: Number of table-directory descriptors. + * @table_dir_offset: Byte offset of the table directory. + * @flags: Ignored by version 1 Linux. + * @reserved: Ignored by version 1 Linux. + */ +struct rpmi_tee_sysinfo_header { + __le32 magic; + __le16 major; + __le16 minor; + __le16 header_size; + __le16 table_desc_size; + __le32 total_size; + __le32 table_count; + __le32 table_dir_offset; + __le32 flags; + __le32 reserved; +} __packed; + +enum rpmi_tee_sysinfo_table_type { + RPMI_TEE_SYSINFO_TABLE_ENDPOINT = 0, /* Endpoint records. */ + RPMI_TEE_SYSINFO_TABLE_SERVICE, /* Service UUID records. */ + RPMI_TEE_SYSINFO_TABLE_PARCEL, /* Memory parcel records. */ + RPMI_TEE_SYSINFO_TABLE_PARCEL_RECEIVER, /* Parcel receiver records. */ + RPMI_TEE_SYSINFO_TABLE_MEMORY_BLOCK, /* Memory block records. */ + RPMI_TEE_SYSINFO_TABLE_BLOB, /* Arbitrary byte data. */ + RPMI_TEE_SYSINFO_TABLE_MAX, /* One past the last table type. */ +}; + +/** + * struct rpmi_tee_sysinfo_table_desc - SYSINFO table-directory descriptor + * @type: Table type from &enum rpmi_tee_sysinfo_table_type. + * @desc_size: Size of one record in this table. + * @count: Number of records in the table. + * @offset: Byte offset of the first record. + * @flags: Ignored by version 1 Linux. + */ +struct rpmi_tee_sysinfo_table_desc { + __le16 type; + __le16 desc_size; + __le32 count; + __le32 offset; + __le32 flags; +} __packed; + +/** + * rpmi_tee_sysinfo_desc_at() - Get a table-directory descriptor + * @header: SYSINFO response header at the start of the response buffer. + * @index: Table-directory descriptor index. + * + * The caller must validate the table-directory range and @index before using + * the returned descriptor. + * + * Return: Pointer to the descriptor's known prefix. + */ +static inline const struct rpmi_tee_sysinfo_table_desc * +rpmi_tee_sysinfo_desc_at(const struct rpmi_tee_sysinfo_header *header, + u32 index) +{ + u32 offset = get_unaligned_le32(&header->table_dir_offset); + u16 stride = get_unaligned_le16(&header->table_desc_size); + + return (const void *)((const u8 *)header + offset + + (size_t)index * stride); +} + +/* Records. */ + +#define RPMI_TEE_SYSINFO_ENDPOINT_NO_PARENT U32_MAX /* No parent endpoint. */ + +#define RPMI_TEE_SYSINFO_ENDPOINT_F_PHYSICAL BIT(0) /* Physical endpoint. */ +#define RPMI_TEE_SYSINFO_ENDPOINT_F_PROXIED BIT(1) /* Proxied endpoint. */ +#define RPMI_TEE_SYSINFO_ENDPOINT_F_REE BIT(2) /* REE endpoint. */ +#define RPMI_TEE_SYSINFO_ENDPOINT_F_TEE BIT(3) /* TEE endpoint. */ +#define RPMI_TEE_SYSINFO_ENDPOINT_F_PERSISTENT BIT(4) /* Persistent endpoint. */ +#define RPMI_TEE_SYSINFO_ENDPOINT_F_MASK \ + (RPMI_TEE_SYSINFO_ENDPOINT_F_PHYSICAL | \ + RPMI_TEE_SYSINFO_ENDPOINT_F_PROXIED | \ + RPMI_TEE_SYSINFO_ENDPOINT_F_REE | \ + RPMI_TEE_SYSINFO_ENDPOINT_F_TEE | \ + RPMI_TEE_SYSINFO_ENDPOINT_F_PERSISTENT) + +/** + * struct rpmi_tee_sysinfo_endpoint - Endpoint table record + * @id: Endpoint identifier. + * @parent_id: Parent endpoint identifier, if proxied. + * @flags: RPMI_TEE_SYSINFO_ENDPOINT_F_* flags. + * @service_first: First service record owned by the endpoint. + * @service_count: Number of service records owned by the endpoint. + * @parcel_first: First parcel record owned by the endpoint. + * @parcel_count: Number of parcel records owned by the endpoint. + * @name_offset: Byte offset of the endpoint name in the blob table. + * @name_length: Endpoint name length in bytes. + * @metadata_offset: Byte offset of endpoint metadata in the blob table. + * @metadata_length: Endpoint metadata length in bytes. + */ +struct rpmi_tee_sysinfo_endpoint { + __le32 id; + __le32 parent_id; + __le32 flags; + __le32 service_first; + __le32 service_count; + __le32 parcel_first; + __le32 parcel_count; + __le32 name_offset; + __le32 name_length; + __le32 metadata_offset; + __le32 metadata_length; +} __packed; + +/** + * struct rpmi_tee_sysinfo_service - Service table record + * @uuid: Service UUID. + */ +struct rpmi_tee_sysinfo_service { + u8 uuid[16]; +} __packed; + +/** + * struct rpmi_tee_sysinfo_parcel - Memory parcel table record + * @id: Parcel identifier. + * @residual_access: Residual access permissions after relinquish. + * @receiver_first: First parcel receiver record. + * @receiver_count: Number of parcel receiver records. + * @block_first: First memory block record. + * @block_count: Number of memory block records. + * @label_offset: Byte offset of the parcel label in the blob table. + * @label_length: Parcel label length in bytes. + * @flags: Parcel flags. + * @reserved: Must be zero. + */ +struct rpmi_tee_sysinfo_parcel { + __le32 id; + __le32 residual_access; + __le32 receiver_first; + __le32 receiver_count; + __le32 block_first; + __le32 block_count; + __le32 label_offset; + __le32 label_length; + __le32 flags; + __le32 reserved; +} __packed; + +/** + * struct rpmi_tee_sysinfo_parcel_receiver - Parcel receiver table record + * @endpoint_id: Receiver endpoint identifier. + * @access: Access permissions granted to the receiver. + * @flags: Receiver flags. + * @reserved: Must be zero. + */ +struct rpmi_tee_sysinfo_parcel_receiver { + __le32 endpoint_id; + __le32 access; + __le32 flags; + __le32 reserved; +} __packed; + +/** + * struct rpmi_tee_sysinfo_memory_block - Memory block table record + * @address: Physical base address. + * @size: Block size in bytes. + */ +struct rpmi_tee_sysinfo_memory_block { + __le64 address; + __le64 size; +} __packed; + +/** + * struct rpmi_tee_sysinfo_service_info - Discovered TEE service + * @endpoint_id: Owning TEE endpoint identifier. + * @uuid: Service UUID. + */ +struct rpmi_tee_sysinfo_service_info { + u32 endpoint_id; + uuid_t uuid; +}; + +/** + * struct rpmi_tee_sysinfo_endpoint_info - Discovered physical TEE endpoint + * @endpoint_id: TEE endpoint identifier. + */ +struct rpmi_tee_sysinfo_endpoint_info { + u32 endpoint_id; +}; + +/** + * struct rpmi_tee_sysinfo_system - Parsed Whole-system discovery result + * @eps: Caller-provided array of physical TEE endpoints. + * @ep_capacity: Number of entries available in @eps. + * @ep_count: Number of discovered physical TEE endpoints. + * @services: Caller-provided array of TEE services. + * @service_capacity: Number of entries available in @services. + * @service_count: Number of discovered TEE services. + * + * The caller supplies storage and capacities. A sizing call with zero + * capacities reports the required counts and returns -ENOSPC for a nonempty + * result. + */ +struct rpmi_tee_sysinfo_system { + struct rpmi_tee_sysinfo_endpoint_info *eps; + size_t ep_capacity; + size_t ep_count; + struct rpmi_tee_sysinfo_service_info *services; + size_t service_capacity; + size_t service_count; +}; + +int rpmi_tee_sysinfo_parse_self(const void *data, size_t len, u32 *self_ep_id); +int rpmi_tee_sysinfo_parse_system(const void *data, size_t len, + u32 self_ep_id, + struct rpmi_tee_sysinfo_system *system); + +#endif /* __RISC_V_RPMI_TEE_SYSINFO_H__ */
Add the RPMI TEE service-group message layer: TEE_PROBE_FEATURES and TEE_PROBE_SYSTEM requests, and TEE_CALL for invoking a service on a discovered endpoint. Use TEE_PROBE_SYSTEM to retrieve the Self and Whole-system SYSINFO tables during transport probe, and register one RPMI TEE device for every physical TEE service UUID discovered.
Implement the msg_ops.call operation on top of TEE_CALL, so consumer drivers bound to the registered devices can issue service requests without depending on the RPMI wire format.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/firmware/riscv_rpmi_tee/driver.c | 504 +++++++++++++++++++++++++++++++ include/linux/rpmi_tee.h | 10 + 2 files changed, 514 insertions(+)
diff --git a/drivers/firmware/riscv_rpmi_tee/driver.c b/drivers/firmware/riscv_rpmi_tee/driver.c index 633dba116584..db3dbab53078 100644 --- a/drivers/firmware/riscv_rpmi_tee/driver.c +++ b/drivers/firmware/riscv_rpmi_tee/driver.c @@ -7,24 +7,188 @@
#include <linux/mailbox_client.h> #include <linux/mailbox/riscv-rpmi-message.h> +#include <linux/cleanup.h> +#include <linux/list.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/overflow.h> #include <linux/platform_device.h> #include <linux/rpmi_tee.h> +#include <linux/slab.h> +#include <linux/unaligned.h> + +#include "sysinfo.h"
#define RPMI_SRVGRP_TEE 0x10
+#define RPMI_TEE_SRV_PROBE_FEATURES 0x02 +#define RPMI_TEE_SRV_PROBE_SYSTEM 0x03 +#define RPMI_TEE_SRV_CALL 0x13 + +#define RPMI_TEE_FEATURE_SYSINFO_FORMAT 6 + +#define RPMI_TEE_SYSTEM_WHOLE 0 +#define RPMI_TEE_SYSTEM_SELF 3 + +/** + * struct rpmi_tee_probe_features_req - TEE_PROBE_FEATURES request + * @feature_id: TEE feature identifier to query. + */ +struct rpmi_tee_probe_features_req { + __le32 feature_id; +} __packed; + +/** + * struct rpmi_tee_probe_features_resp - TEE_PROBE_FEATURES response + * @status: RPMI completion status. + * @value: Feature-specific value. + */ +struct rpmi_tee_probe_features_resp { + __le32 status; + __le32 value; +} __packed; + +/** + * struct rpmi_tee_probe_system_req - TEE_PROBE_SYSTEM request + * @format: Requested system-information format. + * @target_type: Requested system-information target type. + * @target_len: Target-specific request data length. + * @target: Target-specific request data. + */ +struct rpmi_tee_probe_system_req { + __le32 format; + __le32 target_type; + __le32 target_len; + u8 target[]; +} __packed; + +/** + * struct rpmi_tee_probe_system_resp - TEE_PROBE_SYSTEM response prefix + * @status: RPMI completion status. + * @data_len: Length of the following system-information data. + * @data: System-information data in the requested format. + */ +struct rpmi_tee_probe_system_resp { + __le32 status; + __le32 data_len; + u8 data[]; +} __packed; + +/** + * struct rpmi_tee_call_req - TEE_CALL request prefix + * @sender_id: Calling REE endpoint identifier. + * @target_id: Destination TEE endpoint identifier. + * @service: UUID of the target service. + * @service_data_len: Length of @service_data in bytes. + * @service_data: Service-defined request data. + */ +struct rpmi_tee_call_req { + __le32 sender_id; + __le32 target_id; + u8 service[UUID_SIZE]; + __le32 service_data_len; + u8 service_data[]; +} __packed; + +/** + * struct rpmi_tee_call_resp - TEE_CALL response prefix + * @status: RPMI completion status. + * @service_data_len: Length of @service_data in bytes. + * @service_data: Service-defined response data. + */ +struct rpmi_tee_call_resp { + __le32 status; + __le32 service_data_len; + u8 service_data[]; +} __packed; + struct rpmi_tee_mbox { struct mbox_client client; struct mbox_chan *chan; u32 max_msg_data_size; };
+struct rpmi_tee_child { + struct list_head node; + struct rpmi_tee_device *rdev; +}; + struct rpmi_tee_transport { struct device *dev; struct rpmi_tee_mbox mbox; + u32 self_id; + struct list_head devices; };
+/* __rpmi_tee_send() - Send an RPMI TEE service request. */ +static int __rpmi_tee_send(struct rpmi_tee_transport *priv, u32 service_id, + const void *req, size_t req_len, void *resp, + size_t *resp_len, s32 *status) +{ + size_t max_resp_len = *resp_len; + struct rpmi_mbox_message msg; + int ret; + + if (req_len > priv->mbox.max_msg_data_size || + max_resp_len > priv->mbox.max_msg_data_size) + return -EMSGSIZE; + + rpmi_mbox_init_send_with_response(&msg, service_id, (void *)req, + req_len, resp, max_resp_len); + ret = rpmi_mbox_send_message_sync(priv->mbox.chan, &msg); + if (ret) + return ret; + /* At least STATUS word should be present. */ + if (msg.data.out_response_len < sizeof(__le32)) + return -EPROTO; + + *resp_len = msg.data.out_response_len; + *status = (s32)get_unaligned_le32(resp); + + return 0; +} + +/** + * rpmi_tee_send() - Send an RPMI TEE service request + * @priv: RPMI TEE transport + * @service_id: RPMI TEE service identifier + * @req: Request data + * @req_len: Request data length + * @resp: Response data buffer, or %NULL for a status-only response + * @resp_len: On entry, response buffer capacity; on success, response length + * + * Pass both @resp and @resp_len as %NULL when the service has no response + * payload beyond the mandatory RPMI status word. + * + * Return: 0 on success, or a negative error code. + */ +static int rpmi_tee_send(struct rpmi_tee_transport *priv, u32 service_id, + const void *req, size_t req_len, void *resp, + size_t *resp_len) +{ + __le32 status_resp; + size_t status_resp_len = sizeof(status_resp); + s32 status; + int ret; + + if (!resp && !resp_len) { + resp = &status_resp; + resp_len = &status_resp_len; + } else if (!resp || !resp_len) { + return -EINVAL; + } + + ret = __rpmi_tee_send(priv, service_id, req, req_len, resp, resp_len, + &status); + if (ret) + return ret; + + if (status == RPMI_ERR_NO_DATA) + return -ENODATA; + + return rpmi_to_linux_error(status); +} + /** * rpmi_tee_get_attr() - Get an RPMI mailbox attribute * @priv: RPMI TEE transport @@ -77,12 +241,343 @@ static int rpmi_tee_check_transport(struct rpmi_tee_transport *priv) ret = rpmi_tee_get_attr(priv, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE, &value); if (ret) return ret; + /* The mandatory TEE_CALL request and response must fit the mailbox. */ + if (value < sizeof(struct rpmi_tee_call_req) || + value < sizeof(struct rpmi_tee_call_resp)) + return -EMSGSIZE;
priv->mbox.max_msg_data_size = value;
return 0; }
+/* RPMI TEE SERVICE GRP API. */ + +/* TEE_PROBE_FEATURES. */ +static int rpmi_tee_probe_features(struct rpmi_tee_transport *priv, + u32 feature_id, u32 *value) +{ + struct rpmi_tee_probe_features_req req = { + .feature_id = cpu_to_le32(feature_id), + }; + struct rpmi_tee_probe_features_resp resp; + size_t resp_len = sizeof(resp); + int ret; + + ret = rpmi_tee_send(priv, RPMI_TEE_SRV_PROBE_FEATURES, &req, + sizeof(req), &resp, &resp_len); + if (ret) + return ret; + if (resp_len != sizeof(resp)) + return -EPROTO; + + *value = get_unaligned_le32(&resp.value); + + return 0; +} + +/** + * rpmi_tee_probe_system - retrieve a TEE system-information description + * @priv: RPMI TEE transport. + * @target_type: System-information target type. + * @data: Returns an allocated system-information buffer. + * @data_len: Returns the size of @data in bytes. + * + * Supports only the Whole-system and Self targets, which have no target data. + * The caller owns the returned buffer in @data and must free them with kfree(). + * + * Return: 0 on success, or a negative error code. + */ +static int rpmi_tee_probe_system(struct rpmi_tee_transport *priv, + u32 target_type, void **data, size_t *data_len) +{ + struct rpmi_tee_probe_system_req req = { + .format = cpu_to_le32(RPMI_TEE_SYSINFO_FORMAT_TABLE), + .target_type = cpu_to_le32(target_type), + /* Whole-system and Self probes have no target data. */ + .target_len = 0, + }; + size_t resp_len = priv->mbox.max_msg_data_size; + u32 len; + int ret; + + if (target_type != RPMI_TEE_SYSTEM_WHOLE && + target_type != RPMI_TEE_SYSTEM_SELF) + return -EINVAL; + + struct rpmi_tee_probe_system_resp *resp __free(kfree) = + kzalloc(resp_len, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + ret = rpmi_tee_send(priv, RPMI_TEE_SRV_PROBE_SYSTEM, &req, sizeof(req), + resp, &resp_len); + if (ret) + return ret; + + if (resp_len < sizeof(*resp)) + return -EPROTO; + len = get_unaligned_le32(&resp->data_len); + if (len != resp_len - sizeof(*resp)) + return -EPROTO; + + /* Keep the system data. */ + *data = kmemdup(resp->data, len, GFP_KERNEL); + if (!*data) + return -ENOMEM; + + *data_len = len; + + return 0; +} + +/* Retrieve the local REE endpoint identifier. */ +static int rpmi_tee_parse_self(struct rpmi_tee_transport *priv, u32 *self_id) +{ + void *data __free(kfree) = NULL; + size_t data_len; + int ret; + + ret = rpmi_tee_probe_system(priv, RPMI_TEE_SYSTEM_SELF, &data, + &data_len); + if (ret) + return ret; + + return rpmi_tee_sysinfo_parse_self(data, data_len, self_id); +} + +/** + * rpmi_tee_parse_system() - Retrieve and parse Whole-system SYSINFO + * @priv: RPMI TEE transport. + * @system: Returned endpoint and service discovery records. + * + * Retrieve the Whole-system SYSINFO description. The caller owns the arrays + * in @system on success and must free them with kfree(). + * + * Return: 0 on success, or a negative error code. + */ +static int rpmi_tee_parse_system(struct rpmi_tee_transport *priv, + struct rpmi_tee_sysinfo_system *system) +{ + void *data __free(kfree) = NULL; + size_t data_len; + int ret; + + /* Start with no output storage to obtain the required record counts. */ + *system = (struct rpmi_tee_sysinfo_system) {}; + + ret = rpmi_tee_probe_system(priv, RPMI_TEE_SYSTEM_WHOLE, &data, + &data_len); + if (ret) + return ret; + + ret = rpmi_tee_sysinfo_parse_system(data, data_len, priv->self_id, + system); + if (ret != -ENOSPC) + return ret; + + if (system->ep_count) { + system->ep_capacity = system->ep_count; + system->eps = kcalloc(system->ep_capacity, sizeof(*system->eps), + GFP_KERNEL); + if (!system->eps) + return -ENOMEM; + } + + if (system->service_count) { + system->service_capacity = system->service_count; + system->services = kcalloc(system->service_capacity, + sizeof(*system->services), GFP_KERNEL); + if (!system->services) { + kfree(system->eps); + return -ENOMEM; + } + } + + ret = rpmi_tee_sysinfo_parse_system(data, data_len, priv->self_id, + system); + if (ret) { + kfree(system->services); + kfree(system->eps); + } + + return ret; +} + +/** + * rpmi_tee_op_call - Invoke a service offered by a TEE endpoint + * @rdev: TEE service device. + * @req: Service-defined request data. + * @req_len: Length of @req in bytes. + * @resp: Buffer for service-defined response data. + * @resp_len: On entry, capacity of @resp; on success, response length. + * + * MPXY can return -ENOSPC after the TEE has processed the request when the + * response exceeds the supplied buffer. Callers must not blindly retry a + * non-idempotent request in that case. + * + * Return: 0 on success, or a negative error code. + */ +/* Return the transport that owns @rdev. */ +static struct rpmi_tee_transport * +rpmi_tee_device_to_transport(struct rpmi_tee_device *rdev) +{ + return dev_get_drvdata(rdev->dev.parent); +} + +static int rpmi_tee_op_call(struct rpmi_tee_device *rdev, const void *req, + size_t req_len, void *resp, size_t *resp_len) +{ + struct rpmi_tee_transport *priv = rpmi_tee_device_to_transport(rdev); + size_t call_req_len, call_resp_len; + u32 service_data_len; + int ret; + + if (!resp_len || (!req && req_len) || (!resp && *resp_len)) + return -EINVAL; + + /* TEE_CALL payload must fit the mailbox. */ + if (req_len > priv->mbox.max_msg_data_size - + sizeof(struct rpmi_tee_call_req) || + *resp_len > priv->mbox.max_msg_data_size - + sizeof(struct rpmi_tee_call_resp)) + return -EMSGSIZE; + + call_req_len = sizeof(struct rpmi_tee_call_req) + req_len; + call_resp_len = sizeof(struct rpmi_tee_call_resp) + *resp_len; + + struct rpmi_tee_call_req *call_req __free(kfree) = + kzalloc(call_req_len, GFP_KERNEL); + if (!call_req) + return -ENOMEM; + + struct rpmi_tee_call_resp *call_resp __free(kfree) = + kzalloc(call_resp_len, GFP_KERNEL); + if (!call_resp) + return -ENOMEM; + + call_req->sender_id = cpu_to_le32(priv->self_id); + call_req->target_id = cpu_to_le32(rdev->endpoint_id); + export_uuid(call_req->service, &rdev->uuid); + call_req->service_data_len = cpu_to_le32(req_len); + if (req_len) + memcpy(call_req->service_data, req, req_len); + /* Make TEE CALL. */ + ret = rpmi_tee_send(priv, RPMI_TEE_SRV_CALL, call_req, call_req_len, + call_resp, &call_resp_len); + if (ret) + return ret; + + if (call_resp_len < sizeof(*call_resp)) + return -EPROTO; + service_data_len = get_unaligned_le32(&call_resp->service_data_len); + if (service_data_len != call_resp_len - sizeof(*call_resp)) + return -EPROTO; + + if (service_data_len) + memcpy(resp, call_resp->service_data, service_data_len); + *resp_len = service_data_len; + + return 0; +} + +static const struct rpmi_tee_msg_ops rpmi_tee_msg_ops = { + .call = rpmi_tee_op_call, +}; + +static const struct rpmi_tee_ops rpmi_tee_ops = { + .msg_ops = &rpmi_tee_msg_ops, +}; + +static void rpmi_tee_unregister_devices(struct rpmi_tee_transport *priv) +{ + struct rpmi_tee_child *child, *tmp; + + list_for_each_entry_safe(child, tmp, &priv->devices, node) { + list_del(&child->node); + rpmi_tee_device_unregister(child->rdev); + kfree(child); + } +} + +static struct rpmi_tee_device * +rpmi_tee_find_device(struct rpmi_tee_transport *priv, const uuid_t *uuid, + u32 endpoint_id) +{ + struct rpmi_tee_child *child; + + list_for_each_entry(child, &priv->devices, node) { + if (child->rdev->endpoint_id == endpoint_id && + uuid_equal(&child->rdev->uuid, uuid)) + return child->rdev; + } + + return NULL; +} + +static int +rpmi_tee_register_devices(struct rpmi_tee_transport *priv, + const struct rpmi_tee_sysinfo_service_info *services, + size_t service_count) +{ + size_t i; + int ret; + + for (i = 0; i < service_count; i++) { + const struct rpmi_tee_sysinfo_service_info *service = &services[i]; + + /* Discard duplicate devices in the same endpoint. */ + if (rpmi_tee_find_device(priv, &service->uuid, + service->endpoint_id)) + continue; + + struct rpmi_tee_child *child __free(kfree) = + kzalloc_obj(*child, GFP_KERNEL); + if (!child) { + ret = -ENOMEM; + goto err_unregister; + } + child->rdev = rpmi_tee_device_register(&service->uuid, + service->endpoint_id, + &rpmi_tee_ops, + priv->dev); + if (IS_ERR(child->rdev)) { + ret = PTR_ERR(child->rdev); + goto err_unregister; + } + + list_add_tail(&no_free_ptr(child)->node, &priv->devices); + } + + return 0; + +err_unregister: + rpmi_tee_unregister_devices(priv); + + return ret; +} + +static int rpmi_tee_setup_endpoints(struct rpmi_tee_transport *priv) +{ + struct rpmi_tee_sysinfo_system system; + int ret; + + ret = rpmi_tee_parse_self(priv, &priv->self_id); + if (ret) + return ret; + + ret = rpmi_tee_parse_system(priv, &system); + if (ret) + return ret; + + ret = rpmi_tee_register_devices(priv, system.services, + system.service_count); + kfree(system.services); + kfree(system.eps); + + return ret; +} + static int rpmi_tee_transport_probe(struct platform_device *pdev) { struct rpmi_tee_transport *priv; @@ -94,6 +589,7 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev)
priv->dev = &pdev->dev; platform_set_drvdata(pdev, priv); + INIT_LIST_HEAD(&priv->devices); priv->mbox.client.dev = &pdev->dev; priv->mbox.client.tx_sync = true; priv->mbox.chan = mbox_request_channel(&priv->mbox.client, 0); @@ -108,6 +604,13 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev) goto out_failed; }
+ ret = rpmi_tee_setup_endpoints(priv); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "failed to discover RPMI TEE services\n"); + goto out_failed; + } + return 0;
out_failed: @@ -120,6 +623,7 @@ static void rpmi_tee_transport_remove(struct platform_device *pdev) { struct rpmi_tee_transport *priv = platform_get_drvdata(pdev);
+ rpmi_tee_unregister_devices(priv); mbox_free_channel(priv->mbox.chan); }
diff --git a/include/linux/rpmi_tee.h b/include/linux/rpmi_tee.h index c499f0427833..57c1843537da 100644 --- a/include/linux/rpmi_tee.h +++ b/include/linux/rpmi_tee.h @@ -41,6 +41,16 @@ struct rpmi_tee_driver { #define to_rpmi_tee_drv(d) \ container_of_const(d, struct rpmi_tee_driver, driver)
+struct rpmi_tee_msg_ops { + int (*call)(struct rpmi_tee_device *rdev, const void *req, + size_t req_len, void *resp, size_t *resp_len); +}; + +/* RPMI TEE transport operation groups. */ +struct rpmi_tee_ops { + const struct rpmi_tee_msg_ops *msg_ops; +}; + extern const struct bus_type rpmi_tee_bus_type;
#if IS_REACHABLE(CONFIG_RISCV_RPMI_TEE_TRANSPORT)
Query TEE features during discovery and cache memory, multisegment, and signal-bus capabilities in the transport.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/firmware/riscv_rpmi_tee/driver.c | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
diff --git a/drivers/firmware/riscv_rpmi_tee/driver.c b/drivers/firmware/riscv_rpmi_tee/driver.c index db3dbab53078..7b06c1f96ba8 100644 --- a/drivers/firmware/riscv_rpmi_tee/driver.c +++ b/drivers/firmware/riscv_rpmi_tee/driver.c @@ -25,8 +25,16 @@ #define RPMI_TEE_SRV_PROBE_SYSTEM 0x03 #define RPMI_TEE_SRV_CALL 0x13
+#define RPMI_TEE_FEATURE_MEMORY_LEND 2 +#define RPMI_TEE_FEATURE_MEMORY_SHARE 3 +#define RPMI_TEE_FEATURE_SIGNAL_BUS 4 +#define RPMI_TEE_FEATURE_MULTISEGMENT_OPS 5 #define RPMI_TEE_FEATURE_SYSINFO_FORMAT 6
+#define RPMI_TEE_MEMORY_FEATURE_UNSUPPORTED 0 +#define RPMI_TEE_MEMORY_FEATURE_TEE_ONLY 1 +#define RPMI_TEE_MEMORY_FEATURE_FULLY_SUPPORTED 2 + #define RPMI_TEE_SYSTEM_WHOLE 0 #define RPMI_TEE_SYSTEM_SELF 3
@@ -581,6 +589,7 @@ static int rpmi_tee_setup_endpoints(struct rpmi_tee_transport *priv) static int rpmi_tee_transport_probe(struct platform_device *pdev) { struct rpmi_tee_transport *priv; + u32 value; int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); @@ -604,6 +613,37 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev) goto out_failed; }
+ ret = rpmi_tee_probe_features(priv, RPMI_TEE_FEATURE_SYSINFO_FORMAT, + &value); + if (ret) + goto out_failed; + if (!(value & RPMI_TEE_SYSINFO_FORMAT_TABLE)) { + ret = -EOPNOTSUPP; + goto out_failed; + } + + ret = rpmi_tee_probe_features(priv, RPMI_TEE_FEATURE_MEMORY_LEND, + &value); + if (ret) + goto out_failed; + priv->mem.lend_ok = value == RPMI_TEE_MEMORY_FEATURE_FULLY_SUPPORTED; + + ret = rpmi_tee_probe_features(priv, RPMI_TEE_FEATURE_MEMORY_SHARE, + &value); + if (ret) + goto out_failed; + priv->mem.share_ok = value == RPMI_TEE_MEMORY_FEATURE_FULLY_SUPPORTED; + + ret = rpmi_tee_probe_features(priv, RPMI_TEE_FEATURE_MULTISEGMENT_OPS, + &priv->mem.multisegment_max); + if (ret) + goto out_failed; + + ret = rpmi_tee_probe_features(priv, RPMI_TEE_FEATURE_SIGNAL_BUS, + &priv->notif.feature); + if (ret) + goto out_failed; + ret = rpmi_tee_setup_endpoints(priv); if (ret) { dev_err_probe(&pdev->dev, ret,
Implement lend, share, and reclaim of RPMI memory parcels for public memory endpoints. Convert scatterlists into RPMI page blocks and transparently split large parcels across multiple requests when they do not fit a single mailbox message, subject to the per-endpoint multisegment limit reported by firmware.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/firmware/riscv_rpmi_tee/driver.c | 497 +++++++++++++++++++++++++++++++ include/linux/rpmi_tee.h | 58 ++++ 2 files changed, 555 insertions(+)
diff --git a/drivers/firmware/riscv_rpmi_tee/driver.c b/drivers/firmware/riscv_rpmi_tee/driver.c index 7b06c1f96ba8..0004f54c7d89 100644 --- a/drivers/firmware/riscv_rpmi_tee/driver.c +++ b/drivers/firmware/riscv_rpmi_tee/driver.c @@ -14,6 +14,7 @@ #include <linux/overflow.h> #include <linux/platform_device.h> #include <linux/rpmi_tee.h> +#include <linux/scatterlist.h> #include <linux/slab.h> #include <linux/unaligned.h>
@@ -38,6 +39,25 @@ #define RPMI_TEE_SYSTEM_WHOLE 0 #define RPMI_TEE_SYSTEM_SELF 3
+/* Memory service IDs. */ +#define RPMI_TEE_SRV_MEMORY_PARCEL_CREATE 0x09 +#define RPMI_TEE_SRV_MEMORY_PARCEL_RECLAIM 0x0c +#define RPMI_TEE_SRV_MEMORY_SEGMENT_SEND 0x0d + +/* TEE_CALL memory-access flags and block format. */ +#define RPMI_TEE_ACCESS_READ BIT(29) +#define RPMI_TEE_ACCESS_WRITE BIT(30) +#define RPMI_TEE_ACCESS_EXEC BIT(31) + +#define RPMI_TEE_MEM_PAGE_SHIFT 12 +#define RPMI_TEE_MEM_PAGE_SIZE BIT(RPMI_TEE_MEM_PAGE_SHIFT) +#define RPMI_TEE_BLOCK_MAX_PAGES 4096 + +/* TEE_MEMORY_PARCEL_CREATE flags. */ +#define RPMI_TEE_PARCEL_MULTI_SEGMENT BIT(31) + +/* TEE_MEMORY_SEGMENT_SEND flags. */ +#define RPMI_TEE_SEGMENT_LAST BIT(31) /** * struct rpmi_tee_probe_features_req - TEE_PROBE_FEATURES request * @feature_id: TEE feature identifier to query. @@ -110,12 +130,116 @@ struct rpmi_tee_call_resp { u8 service_data[]; } __packed;
+/** + * struct rpmi_tee_parcel_create_req - MEMORY_PARCEL_CREATE request prefix + * @creator_id: Endpoint identifier creating the parcel. + * @creator_access: Creator's residual access permissions. + * @receiver_count: Number of receiver endpoint and access pairs in @data. + * @flags: Parcel creation flags. + * @nonce: Caller-provided parcel nonce. + * @block_count: Total number of memory blocks in the parcel. + * @label: Caller-provided parcel label. + * @data: Receiver endpoint IDs, receiver access values, then memory blocks. + */ +struct rpmi_tee_parcel_create_req { + __le32 creator_id; + __le32 creator_access; + __le32 receiver_count; + __le32 flags; + __le32 nonce; + __le32 block_count; + u8 label[16]; + u8 data[]; +} __packed; + +#define RPMI_TEE_PARCEL_CREATE_SIZE \ + (sizeof(struct rpmi_tee_parcel_create_req)) +/* Size of one RECEIVER_ID[] and ACCESS[] entry pair. */ +#define RPMI_TEE_PARCEL_CREATE_RECEIVER_INFO_SIZE (2 * sizeof(__le32)) +/* Size of one BLOCK_HIGH[] and BLOCK_LOW[] entry pair. */ +#define RPMI_TEE_PARCEL_CREATE_BLOCK_SIZE (2 * sizeof(__le32)) + +/* Store one receiver in adjacent RECEIVER_ID[] and ACCESS[] arrays at @data. */ +static inline void rpmi_tee_put_receiver(u8 *data, u32 count, u32 index, + u32 id, u32 access) +{ + put_unaligned_le32(id, data + index * sizeof(__le32)); + put_unaligned_le32(access, data + (count + index) * sizeof(__le32)); +} + +/* Store one block in adjacent BLOCK_HIGH[] and BLOCK_LOW[] arrays at @data. */ +static inline void rpmi_tee_put_block(u8 *data, u32 count, u32 index, + u32 high, u32 low) +{ + put_unaligned_le32(high, data + index * sizeof(__le32)); + put_unaligned_le32(low, data + (count + index) * sizeof(__le32)); +} + +/** + * struct rpmi_tee_parcel_create_resp - MEMORY_PARCEL_CREATE response + * @status: RPMI completion status. + * @parcel_id: Identifier assigned to the new parcel. + */ +struct rpmi_tee_parcel_create_resp { + __le32 status; + __le32 parcel_id; +} __packed; + +/** + * struct rpmi_tee_segment_send_req - MEMORY_SEGMENT_SEND request prefix + * @parcel_id: Identifier of the partially created parcel. + * @flags: Segment flags. + * @block_count: Number of memory blocks in @data. + * @data: Memory block address and size pairs. + */ +struct rpmi_tee_segment_send_req { + __le32 parcel_id; + __le32 flags; + __le32 block_count; + u8 data[]; +} __packed; + +#define RPMI_TEE_SEGMENT_SEND_SIZE \ + (sizeof(struct rpmi_tee_segment_send_req)) +/* Size of one BLOCK_HIGH[] and BLOCK_LOW[] entry pair. */ +#define RPMI_TEE_SEGMENT_SEND_BLOCK_SIZE (2 * sizeof(__le32)) + +/** + * struct rpmi_tee_parcel_reclaim_req - MEMORY_PARCEL_RECLAIM request + * @parcel_id: Identifier of the parcel to reclaim. + */ +struct rpmi_tee_parcel_reclaim_req { + __le32 parcel_id; +} __packed; + +/** + * struct rpmi_tee_parcel_reclaim_resp - MEMORY_PARCEL_RECLAIM response + * @status: RPMI completion status. + * @flags: Reclaim result flags. + */ +struct rpmi_tee_parcel_reclaim_resp { + __le32 status; + __le32 flags; +} __packed; + +struct rpmi_tee_notif_state { + u32 feature; +}; + struct rpmi_tee_mbox { struct mbox_client client; struct mbox_chan *chan; u32 max_msg_data_size; };
+struct rpmi_tee_mem_state { + struct mutex lock; + u32 multisegment_max; + u32 multisegment_active; + bool lend_ok; + bool share_ok; +}; + struct rpmi_tee_child { struct list_head node; struct rpmi_tee_device *rdev; @@ -126,6 +250,21 @@ struct rpmi_tee_transport { struct rpmi_tee_mbox mbox; u32 self_id; struct list_head devices; + struct rpmi_tee_mem_state mem; + struct rpmi_tee_notif_state notif; +}; + +struct rpmi_tee_block_iter { + struct scatterlist *sg; + phys_addr_t address; + size_t length; +}; + +struct rpmi_tee_parcel_xfer { + struct rpmi_tee_block_iter iter; + u32 parcel_id; + u32 block_count; + u32 next_block; };
/* __rpmi_tee_send() - Send an RPMI TEE service request. */ @@ -489,12 +628,369 @@ static int rpmi_tee_op_call(struct rpmi_tee_device *rdev, const void *req, return 0; }
+/* MEMORY_PARCEL_RECLAIM. */ +static int rpmi_tee_memory_reclaim(struct rpmi_tee_transport *priv, + u32 parcel_id) +{ + struct rpmi_tee_parcel_reclaim_req req = { + .parcel_id = cpu_to_le32(parcel_id), + }; + struct rpmi_tee_parcel_reclaim_resp resp; + size_t resp_len = sizeof(resp); + int ret; + + ret = rpmi_tee_send(priv, RPMI_TEE_SRV_MEMORY_PARCEL_RECLAIM, &req, + sizeof(req), &resp, &resp_len); + if (ret) + return ret; + + if (resp_len != sizeof(resp)) + return -EPROTO; + + return 0; +} + +static int rpmi_tee_op_memory_reclaim(struct rpmi_tee_device *rdev, + u32 parcel_id) +{ + return rpmi_tee_memory_reclaim(rpmi_tee_device_to_transport(rdev), + parcel_id); +} + +/** + * rpmi_tee_count_blocks_sg - Count RPMI memory blocks in an SG list + * @sg: First SG entry describing the memory to share or lend. + * @count_out: Returns the number of RPMI memory blocks. + * + * Validates that every entry represents one or more whole 4 KiB pages. An + * RPMI memory block represents at most @RPMI_TEE_BLOCK_MAX_PAGES pages. + * + * Return: 0 on success, or a negative error code. + */ +static int rpmi_tee_count_blocks_sg(struct scatterlist *sg, u32 *count_out) +{ + struct scatterlist *entry; + u32 count = 0; + + if (!sg) + return -EINVAL; + + for (entry = sg; entry; entry = sg_next(entry)) { + phys_addr_t address = sg_phys(entry); + size_t blocks; + + if (!entry->length || + !IS_ALIGNED(address, RPMI_TEE_MEM_PAGE_SIZE) || + !IS_ALIGNED(entry->length, RPMI_TEE_MEM_PAGE_SIZE)) + return -EINVAL; + + /* One RPMI block describes at most 4096 pages. */ + blocks = DIV_ROUND_UP(entry->length >> RPMI_TEE_MEM_PAGE_SHIFT, + RPMI_TEE_BLOCK_MAX_PAGES); + if (blocks > U32_MAX - count) + return -EOVERFLOW; + + count += blocks; + } + + *count_out = count; + + return 0; +} + +static void rpmi_tee_block_iter_init(struct rpmi_tee_block_iter *iter, + struct scatterlist *sg) +{ + iter->sg = sg; + iter->address = 0; + iter->length = 0; +} + +/* Encode the next RPMI memory block from an SG iterator. */ +static bool rpmi_tee_block_iter_next(struct rpmi_tee_block_iter *iter, + u32 *high, u32 *low) +{ + u32 pages; + + if (!iter->length) { + if (!iter->sg) + return false; + /* Next SG. */ + iter->address = sg_phys(iter->sg); + iter->length = iter->sg->length; + iter->sg = sg_next(iter->sg); + } + + /* RPMI memory block represents at most @RPMI_TEE_BLOCK_MAX_PAGES pages. */ + pages = min_t(size_t, iter->length >> RPMI_TEE_MEM_PAGE_SHIFT, + RPMI_TEE_BLOCK_MAX_PAGES); + + *high = upper_32_bits(iter->address >> RPMI_TEE_MEM_PAGE_SHIFT); + *low = lower_32_bits(iter->address >> RPMI_TEE_MEM_PAGE_SHIFT) << 12 | + (pages - 1); + + iter->address += (phys_addr_t)pages << RPMI_TEE_MEM_PAGE_SHIFT; + iter->length -= (size_t)pages << RPMI_TEE_MEM_PAGE_SHIFT; + + return true; +} + +static int rpmi_tee_fill_blocks(struct rpmi_tee_block_iter *iter, u8 *data, + u32 count) +{ + u32 high, low, i; + + for (i = 0; i < count; i++) { + if (!rpmi_tee_block_iter_next(iter, &high, &low)) + return -EINVAL; + + rpmi_tee_put_block(data, count, i, high, low); + } + + return 0; +} + +/* Convert memory access flags RPMI_TEE_MEM_ACCESS_* to RPMI_TEE_ACCESS_*. */ +static u32 rpmi_tee_access(u32 mem_access) +{ + u32 tee_access = 0; + + if (mem_access & RPMI_TEE_MEM_ACCESS_READ) + tee_access |= RPMI_TEE_ACCESS_READ; + if (mem_access & RPMI_TEE_MEM_ACCESS_WRITE) + tee_access |= RPMI_TEE_ACCESS_WRITE; + if (mem_access & RPMI_TEE_MEM_ACCESS_EXEC) + tee_access |= RPMI_TEE_ACCESS_EXEC; + + return tee_access; +} + +static int rpmi_tee_reserve_segment_slot(struct rpmi_tee_transport *priv) +{ + int ret = 0; + + guard(mutex)(&priv->mem.lock); + if (priv->mem.multisegment_active == priv->mem.multisegment_max) + ret = -EBUSY; + else + priv->mem.multisegment_active++; + + return ret; +} + +static void rpmi_tee_release_segment_slot(struct rpmi_tee_transport *priv) +{ + guard(mutex)(&priv->mem.lock); + priv->mem.multisegment_active--; +} + +/* Send the remaining blocks of a segmented memory parcel. */ +static int rpmi_tee_parcel_send_segments(struct rpmi_tee_transport *priv, + struct rpmi_tee_parcel_xfer *xfer) +{ + while (xfer->next_block < xfer->block_count) { + size_t req_len; + u32 count; + int ret; + + count = min_t(u32, xfer->block_count - xfer->next_block, + (priv->mbox.max_msg_data_size - + RPMI_TEE_SEGMENT_SEND_SIZE) / + RPMI_TEE_SEGMENT_SEND_BLOCK_SIZE); + if (!count) + return -EMSGSIZE; + + req_len = RPMI_TEE_SEGMENT_SEND_SIZE + + RPMI_TEE_SEGMENT_SEND_BLOCK_SIZE * count; + + struct rpmi_tee_segment_send_req *req __free(kfree) = + kzalloc(req_len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + /* INIT request. */ + req->parcel_id = cpu_to_le32(xfer->parcel_id); + req->flags = cpu_to_le32(xfer->next_block + count == + xfer->block_count ? + RPMI_TEE_SEGMENT_LAST : 0); + req->block_count = cpu_to_le32(count); + ret = rpmi_tee_fill_blocks(&xfer->iter, req->data, count); + if (ret) + return ret; + + ret = rpmi_tee_send(priv, RPMI_TEE_SRV_MEMORY_SEGMENT_SEND, + req, req_len, NULL, NULL); + if (ret) + return ret; + + xfer->next_block += count; + } + + return 0; +} + +/* Create a memory parcel after the caller has validated its operation. */ +static int rpmi_tee_parcel_create(struct rpmi_tee_transport *priv, + struct rpmi_tee_mem_args *args) +{ + struct rpmi_tee_parcel_create_resp resp; + struct rpmi_tee_parcel_xfer xfer; + size_t blk_off, req_len, resp_len; + bool segmented; + int ret; + u32 i; + + ret = rpmi_tee_count_blocks_sg(args->sg, &xfer.block_count); + if (ret) + return ret; + + /* BLOCK_HIGH[] follows the request header and receiver arrays. */ + blk_off = RPMI_TEE_PARCEL_CREATE_SIZE + args->receiver_count * + RPMI_TEE_PARCEL_CREATE_RECEIVER_INFO_SIZE; + + /* Limit the initial request to the parcel's actual block count. */ + xfer.next_block = min((priv->mbox.max_msg_data_size - blk_off) / + RPMI_TEE_PARCEL_CREATE_BLOCK_SIZE, + xfer.block_count); + + segmented = xfer.next_block < xfer.block_count; + if (segmented) { + if (!priv->mem.multisegment_max) + return -EOPNOTSUPP; + /* Reserve a slot against the firmware's advertised limit. */ + ret = rpmi_tee_reserve_segment_slot(priv); + if (ret) + return ret; + } + + req_len = blk_off + RPMI_TEE_PARCEL_CREATE_BLOCK_SIZE * xfer.next_block; + + struct rpmi_tee_parcel_create_req *req __free(kfree) = + kzalloc(req_len, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out_release_slot; + } + + rpmi_tee_block_iter_init(&xfer.iter, args->sg); + + /* INIT request. */ + req->creator_id = cpu_to_le32(priv->self_id); + req->creator_access = cpu_to_le32(rpmi_tee_access(args->creator_access)); + req->receiver_count = cpu_to_le32(args->receiver_count); + req->flags = cpu_to_le32(segmented ? RPMI_TEE_PARCEL_MULTI_SEGMENT : 0); + req->nonce = cpu_to_le32(args->nonce); + req->block_count = cpu_to_le32(xfer.block_count); + memcpy(req->label, args->label, sizeof(req->label)); + + for (i = 0; i < args->receiver_count; i++) { + u32 tee_access = rpmi_tee_access(args->receivers[i].access); + /* Store RECEIVER_ID[i] and ACCESS[i]. */ + rpmi_tee_put_receiver(req->data, args->receiver_count, i, + args->receivers[i].endpoint_id, + tee_access); + } + + ret = rpmi_tee_fill_blocks(&xfer.iter, + req->data + 8 * args->receiver_count, + xfer.next_block); + if (ret) + goto out_release_slot; + + resp_len = sizeof(resp); + ret = rpmi_tee_send(priv, RPMI_TEE_SRV_MEMORY_PARCEL_CREATE, + req, req_len, &resp, &resp_len); + if (ret) + goto out_release_slot; + if (resp_len != sizeof(resp)) + return -EPROTO; + + xfer.parcel_id = get_unaligned_le32(&resp.parcel_id); + /* Send remaining blocks as segments. */ + ret = rpmi_tee_parcel_send_segments(priv, &xfer); + if (ret) { + /* On error, retain the slot as firmware may still hold it. */ + if (rpmi_tee_memory_reclaim(priv, xfer.parcel_id)) { + dev_warn(priv->dev, "failed to abort parcel %#x\n", + xfer.parcel_id); + + return ret; + } + } else { + args->parcel_id = xfer.parcel_id; + } + +out_release_slot: + if (segmented) + rpmi_tee_release_segment_slot(priv); + + return ret; +} + +/* MEMORY_PARCEL_CREATE. */ +static int rpmi_tee_op_parcel_create(struct rpmi_tee_device *rdev, + struct rpmi_tee_mem_args *args, bool lend) +{ + struct rpmi_tee_transport *priv = rpmi_tee_device_to_transport(rdev); + u32 i; + + if (!args || !args->receivers || !args->receiver_count) + return -EINVAL; + + /* LEND relinquishes creator access, whereas SHARE retains it. */ + if (lend ? args->creator_access : !args->creator_access) + return -EINVAL; + + if (args->creator_access & ~RPMI_TEE_MEM_ACCESS_MASK) + return -EINVAL; + for (i = 0; i < args->receiver_count; i++) { + if (args->receivers[i].access & ~RPMI_TEE_MEM_ACCESS_MASK) + return -EINVAL; + } + + if (lend ? !priv->mem.lend_ok : !priv->mem.share_ok) + return -EOPNOTSUPP; + + /* A parcel must contain at least one BLOCK_HIGH/BLOCK_LOW pair. */ + if (priv->mbox.max_msg_data_size < RPMI_TEE_PARCEL_CREATE_SIZE + + RPMI_TEE_PARCEL_CREATE_BLOCK_SIZE) + return -EMSGSIZE; + + /* Check if receiver's info fit after reserving room for one block. */ + if (args->receiver_count > + (priv->mbox.max_msg_data_size - RPMI_TEE_PARCEL_CREATE_SIZE - + RPMI_TEE_PARCEL_CREATE_BLOCK_SIZE) / + RPMI_TEE_PARCEL_CREATE_RECEIVER_INFO_SIZE) + return -EMSGSIZE; + + return rpmi_tee_parcel_create(priv, args); +} + +static int rpmi_tee_op_memory_lend(struct rpmi_tee_device *rdev, + struct rpmi_tee_mem_args *args) +{ + return rpmi_tee_op_parcel_create(rdev, args, true); +} + +static int rpmi_tee_op_memory_share(struct rpmi_tee_device *rdev, + struct rpmi_tee_mem_args *args) +{ + return rpmi_tee_op_parcel_create(rdev, args, false); +} + static const struct rpmi_tee_msg_ops rpmi_tee_msg_ops = { .call = rpmi_tee_op_call, };
+static const struct rpmi_tee_mem_ops rpmi_tee_mem_ops = { + .memory_lend = rpmi_tee_op_memory_lend, + .memory_share = rpmi_tee_op_memory_share, + .memory_reclaim = rpmi_tee_op_memory_reclaim, +}; + static const struct rpmi_tee_ops rpmi_tee_ops = { .msg_ops = &rpmi_tee_msg_ops, + .mem_ops = &rpmi_tee_mem_ops, };
static void rpmi_tee_unregister_devices(struct rpmi_tee_transport *priv) @@ -599,6 +1095,7 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev) priv->dev = &pdev->dev; platform_set_drvdata(pdev, priv); INIT_LIST_HEAD(&priv->devices); + mutex_init(&priv->mem.lock); priv->mbox.client.dev = &pdev->dev; priv->mbox.client.tx_sync = true; priv->mbox.chan = mbox_request_channel(&priv->mbox.client, 0); diff --git a/include/linux/rpmi_tee.h b/include/linux/rpmi_tee.h index 57c1843537da..c2dd99293481 100644 --- a/include/linux/rpmi_tee.h +++ b/include/linux/rpmi_tee.h @@ -10,6 +10,7 @@
#include <linux/device.h> #include <linux/module.h> +#include <linux/scatterlist.h> #include <linux/types.h> #include <linux/uuid.h>
@@ -46,9 +47,66 @@ struct rpmi_tee_msg_ops { size_t req_len, void *resp, size_t *resp_len); };
+/* Access permissions used by memory parcel operations. */ +#define RPMI_TEE_MEM_ACCESS_READ BIT(0) +#define RPMI_TEE_MEM_ACCESS_WRITE BIT(1) +#define RPMI_TEE_MEM_ACCESS_EXEC BIT(2) +#define RPMI_TEE_MEM_ACCESS_MASK (RPMI_TEE_MEM_ACCESS_READ | \ + RPMI_TEE_MEM_ACCESS_WRITE | \ + RPMI_TEE_MEM_ACCESS_EXEC) + +/* One receiver's access rights for a memory parcel. */ +struct rpmi_tee_mem_receiver { + /* RPMI endpoint identifier of the receiver. */ + u32 endpoint_id; + /* Bitwise OR of RPMI_TEE_MEM_ACCESS_* permissions. */ + u32 access; +}; + +/* Arguments used to create a memory parcel. */ +struct rpmi_tee_mem_args { + /* Scatterlist describing whole, 4 KiB-aligned memory pages. */ + struct scatterlist *sg; + /* Array of @receiver_count parcel receivers. */ + const struct rpmi_tee_mem_receiver *receivers; + /* Number of entries in @receivers. */ + u32 receiver_count; + /* Creator permissions retained by a SHARE operation. */ + u32 creator_access; + /* Implementation-defined value carried in the parcel descriptor. */ + u32 nonce; + /* Implementation-defined 16-byte parcel label. */ + u8 label[16]; + /* Returned firmware-assigned parcel identifier on success. */ + u32 parcel_id; +}; + +/* RPMI TEE memory-parcel operations. */ +struct rpmi_tee_mem_ops { + /** + * @memory_lend: Lend the pages described by @args to its receivers. The + * creator must not retain access, so @args->creator_access must be zero. + */ + int (*memory_lend)(struct rpmi_tee_device *rdev, + struct rpmi_tee_mem_args *args); + /** + * @memory_share: Share the pages described by @args with its receivers. + * The creator retains the nonzero permissions in + * @args->creator_access. + */ + int (*memory_share)(struct rpmi_tee_device *rdev, + struct rpmi_tee_mem_args *args); + /** + * @memory_reclaim: Reclaim the parcel identified by @parcel_id after all + * receivers have relinquished it. + */ + int (*memory_reclaim)(struct rpmi_tee_device *rdev, u32 parcel_id); +}; + /* RPMI TEE transport operation groups. */ struct rpmi_tee_ops { const struct rpmi_tee_msg_ops *msg_ops; + const struct rpmi_tee_mem_ops *mem_ops; };
extern const struct bus_type rpmi_tee_bus_type;
Add support for RPMI TEE signal buses, letting service devices exchange asynchronous notifications with their TEE endpoint in both directions. Clients can request and relinquish specific incoming notifications, and raise outgoing ones.
Expose this through a new rpmi_tee_notifier_ops, and tear it down cleanly on removal before the service devices themselves are unregistered.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/firmware/riscv_rpmi_tee/driver.c | 631 +++++++++++++++++++++++++++++++ include/linux/rpmi_tee.h | 24 ++ 2 files changed, 655 insertions(+)
diff --git a/drivers/firmware/riscv_rpmi_tee/driver.c b/drivers/firmware/riscv_rpmi_tee/driver.c index 0004f54c7d89..cc0a42d471e3 100644 --- a/drivers/firmware/riscv_rpmi_tee/driver.c +++ b/drivers/firmware/riscv_rpmi_tee/driver.c @@ -8,15 +8,21 @@ #include <linux/mailbox_client.h> #include <linux/mailbox/riscv-rpmi-message.h> #include <linux/cleanup.h> +#include <linux/bitfield.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> #include <linux/list.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_irq.h> #include <linux/overflow.h> #include <linux/platform_device.h> #include <linux/rpmi_tee.h> #include <linux/scatterlist.h> #include <linux/slab.h> #include <linux/unaligned.h> +#include <linux/workqueue.h> +#include <linux/xarray.h>
#include "sysinfo.h"
@@ -39,6 +45,12 @@ #define RPMI_TEE_SYSTEM_WHOLE 0 #define RPMI_TEE_SYSTEM_SELF 3
+/* Signal service IDs. */ +#define RPMI_TEE_SRV_SIGNAL_BUS_SETUP 0x05 +#define RPMI_TEE_SRV_SIGNAL_BUS_TEARDOWN 0x06 +#define RPMI_TEE_SRV_SIGNAL_RAISE 0x07 +#define RPMI_TEE_SRV_SIGNAL_RETRIEVE 0x08 + /* Memory service IDs. */ #define RPMI_TEE_SRV_MEMORY_PARCEL_CREATE 0x09 #define RPMI_TEE_SRV_MEMORY_PARCEL_RECLAIM 0x0c @@ -58,6 +70,15 @@
/* TEE_MEMORY_SEGMENT_SEND flags. */ #define RPMI_TEE_SEGMENT_LAST BIT(31) + +/* TEE_SIGNAL_BUS_SETUP feature value and TEE_SIGNAL_RETRIEVE flags. */ +#define RPMI_TEE_SIGNAL_MODE_MASK GENMASK(1, 0) +#define RPMI_TEE_SIGNAL_WIDTH_MASK GENMASK(11, 2) +#define RPMI_TEE_SIGNAL_INDEX_MASK GENMASK(31, 12) + +#define RPMI_TEE_SIGNAL_MODE_SYSTEM_MSI 1 +#define RPMI_TEE_SIGNAL_MORE_AVAILABLE BIT(31) + /** * struct rpmi_tee_probe_features_req - TEE_PROBE_FEATURES request * @feature_id: TEE feature identifier to query. @@ -222,8 +243,110 @@ struct rpmi_tee_parcel_reclaim_resp { __le32 flags; } __packed;
+/** + * struct rpmi_tee_signal_bus_setup_req - SIGNAL_BUS_SETUP request + * @target_id: TEE endpoint identifier that owns the signal bus. + * @bus_width: Total number of signal IDs in the bus. + * @sender_signals: Number of signal IDs reserved for the endpoint sender. + */ +struct rpmi_tee_signal_bus_setup_req { + __le32 target_id; + __le32 bus_width; + __le32 sender_signals; +} __packed; + +/** + * struct rpmi_tee_signal_bus_teardown_req - SIGNAL_BUS_TEARDOWN request + * @target_id: TEE endpoint identifier that owns the signal bus. + */ +struct rpmi_tee_signal_bus_teardown_req { + __le32 target_id; +} __packed; + +/** + * struct rpmi_tee_signal_raise_req - SIGNAL_RAISE request prefix + * @target_id: TEE endpoint identifier that owns the signal bus. + * @signal_count: Number of signal IDs in @signals. + * @signals: Signal IDs to raise. + */ +struct rpmi_tee_signal_raise_req { + __le32 target_id; + __le32 signal_count; + __le32 signals[]; +} __packed; + +/** + * struct rpmi_tee_signal_raise_one_req - Single-signal SIGNAL_RAISE request + * @target_id: TEE endpoint identifier that owns the signal bus. + * @signal_count: Must be one. + * @signal: Signal ID to raise. + */ +struct rpmi_tee_signal_raise_one_req { + __le32 target_id; + __le32 signal_count; + __le32 signal; +} __packed; + +/** + * struct rpmi_tee_signal_retrieve_resp - SIGNAL_RETRIEVE response prefix + * @status: RPMI completion status. + * @flags: Response flags. + * @target_id: TEE endpoint identifier that owns the signal bus. + * @signal_count: Number of signal IDs in @signals. + * @signals: Retrieved signal IDs. + */ +struct rpmi_tee_signal_retrieve_resp { + __le32 status; + __le32 flags; + __le32 target_id; + __le32 signal_count; + __le32 signals[]; +} __packed; + +enum rpmi_tee_signal_state { + RPMI_TEE_SIGNAL_ACTIVE, + RPMI_TEE_SIGNAL_RELEASING, +}; + +struct rpmi_tee_signal_reservation { + struct rpmi_tee_device *rdev; + rpmi_tee_notifier_cb cb; + void *cb_data; + enum rpmi_tee_signal_state state; +}; + +struct rpmi_tee_signal_bus { + struct list_head node; /* Link in the notification signal-bus list. */ + struct mutex lock; /* Serializes reservation state and lifetime. */ + struct xarray reservations; + u32 endpoint_id; + u32 width; + u32 tee_to_ree_count; +}; + +/** + * struct rpmi_tee_notif_state - Signal notification state + * @buses: List of signal buses established for TEE endpoints. + * @work: Retrieves and dispatches pending TEE-to-REE signals. + * @wq: Workqueue used for @work. + * @ops_lock: Serializes public notification operations with signal-bus + * teardown. It prevents new operations after @shutting_down is set and + * serializes rpmi_tee_op_notify_relinquish() with the empty + * SIGNAL_RETRIEVE release barrier. + * @feature: SIGNAL feature value reported by the transport. + * @irq: Linux IRQ assigned to the signal notification interrupt. + * @irq_requested: Whether @irq has been requested. + * @shutting_down: Prevents public notification operations during teardown. + */ struct rpmi_tee_notif_state { + struct list_head buses; + struct work_struct work; + struct workqueue_struct *wq; + struct mutex ops_lock; u32 feature; + int irq; + bool irq_requested; + bool shutting_down; };
struct rpmi_tee_mbox { @@ -245,6 +368,15 @@ struct rpmi_tee_child { struct rpmi_tee_device *rdev; };
+/** + * struct rpmi_tee_transport - State for one RPMI TEE transport instance + * @dev: Parent platform device. + * @mbox: RPMI mailbox transport state. + * @self_id: Local REE physical endpoint identifier. + * @devices: List of registered TEE service devices. + * @mem: Memory parcel operation state. + * @notif: Signal notification state. + */ struct rpmi_tee_transport { struct device *dev; struct rpmi_tee_mbox mbox; @@ -978,6 +1110,275 @@ static int rpmi_tee_op_memory_share(struct rpmi_tee_device *rdev, return rpmi_tee_op_parcel_create(rdev, args, false); }
+static struct rpmi_tee_signal_bus * +__rpmi_tee_find_signal_bus(struct rpmi_tee_transport *priv, u32 endpoint_id) +{ + struct rpmi_tee_signal_bus *bus; + + list_for_each_entry(bus, &priv->notif.buses, node) { + if (bus->endpoint_id == endpoint_id) + return bus; + } + + return NULL; +} + +static struct rpmi_tee_signal_bus * +rpmi_tee_find_signal_bus(struct rpmi_tee_transport *priv, u32 endpoint_id) +{ + lockdep_assert_held(&priv->notif.ops_lock); + /* Do not access signal buses after notification shutdown starts. */ + if (priv->notif.shutting_down) + return NULL; + + return __rpmi_tee_find_signal_bus(priv, endpoint_id); +} + +/* Invoke an active signal callback without holding the bus lock. */ +static int rpmi_tee_dispatch_signal(struct rpmi_tee_signal_bus *bus, + u32 signal) +{ + struct rpmi_tee_signal_reservation *resv; + rpmi_tee_notifier_cb cb = NULL; + struct rpmi_tee_device *rdev = NULL; + void *cb_data = NULL; + + if (signal >= bus->tee_to_ree_count) + return -EPROTO; + + scoped_guard(mutex, &bus->lock) { + resv = xa_load(&bus->reservations, signal); + if (resv && resv->state == RPMI_TEE_SIGNAL_ACTIVE) { + cb = resv->cb; + cb_data = resv->cb_data; + rdev = resv->rdev; + } + } + + if (cb) + cb(rdev, signal, cb_data); + + return 0; +} + +/* Release signal IDs that have passed the empty-retrieval barrier. */ +static void rpmi_tee_signal_bus_drop_releasing(struct rpmi_tee_signal_bus *bus) +{ + struct rpmi_tee_signal_reservation *resv; + unsigned long index; + + guard(mutex)(&bus->lock); + xa_for_each(&bus->reservations, index, resv) { + if (resv->state != RPMI_TEE_SIGNAL_RELEASING) + continue; + + xa_erase(&bus->reservations, index); + kfree(resv); + } +} + +/** + * rpmi_tee_retrieve_signals() - Drain pending TEE-to-REE signals + * @priv: RPMI TEE transport + * + * Retrieve and dispatch signals until the firmware reports no pending data. + * Return relinquished signal IDs to their buses only after that empty + * retrieval. + * + * Return: 0 on success, or a negative error code. + */ +static int rpmi_tee_retrieve_signals(struct rpmi_tee_transport *priv) +{ + size_t resp_len = priv->mbox.max_msg_data_size; + u32 flags, endpoint_id, signal_count, i; + struct rpmi_tee_signal_bus *bus; + s32 status; + int ret; + + struct rpmi_tee_signal_retrieve_resp *resp __free(kfree) = + kzalloc(resp_len, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + for (;;) { + scoped_guard(mutex, &priv->notif.ops_lock) { + resp_len = priv->mbox.max_msg_data_size; + ret = __rpmi_tee_send(priv, RPMI_TEE_SRV_SIGNAL_RETRIEVE, + NULL, 0, resp, &resp_len, &status); + /* + * A signal may be raised after a clear MORE_AVAILABLE + * response and before relinquish. Reusing the ID could + * deliver it to the wrong client. + * Reuse relinquished IDs only after an empty retrieve. + */ + if (!ret && status == RPMI_ERR_NO_DATA) { + struct rpmi_tee_signal_bus *bus; + + list_for_each_entry(bus, &priv->notif.buses, node) + rpmi_tee_signal_bus_drop_releasing(bus); + } + } + + if (ret) + return ret; + /* The firmware has no more pending signals. */ + if (status == RPMI_ERR_NO_DATA) + return 0; + if (status) + return rpmi_to_linux_error(status); + if (resp_len < sizeof(*resp)) + return -EPROTO; + + flags = get_unaligned_le32(&resp->flags); + endpoint_id = get_unaligned_le32(&resp->target_id); + signal_count = get_unaligned_le32(&resp->signal_count); + + /* Validate the response flags and its variable-length signal array. */ + if ((flags & ~RPMI_TEE_SIGNAL_MORE_AVAILABLE) || !signal_count || + signal_count != (resp_len - sizeof(*resp)) / sizeof(__le32)) + return -EPROTO; + + bus = __rpmi_tee_find_signal_bus(priv, endpoint_id); + if (!bus || signal_count > bus->tee_to_ree_count) + return -EPROTO; + + for (i = 0; i < signal_count; i++) { + u32 signal = get_unaligned_le32(&resp->signals[i]); + + ret = rpmi_tee_dispatch_signal(bus, signal); + if (ret) + return ret; + } + } +} + +static void rpmi_tee_notif_work(struct work_struct *work) +{ + struct rpmi_tee_notif_state *notif = + container_of(work, struct rpmi_tee_notif_state, work); + struct rpmi_tee_transport *priv = + container_of(notif, struct rpmi_tee_transport, notif); + int ret; + + ret = rpmi_tee_retrieve_signals(priv); + if (ret) + dev_warn(priv->dev, "failed to retrieve signals: %d\n", ret); +} + +static irqreturn_t rpmi_tee_notif_irq_handler(int irq, void *data) +{ + struct rpmi_tee_transport *priv = data; + + queue_work(priv->notif.wq, &priv->notif.work); + return IRQ_HANDLED; +} + +/* Reserve a TEE-to-REE signal for a notification consumer. */ +static int rpmi_tee_op_notify_request(struct rpmi_tee_device *rdev, + rpmi_tee_notifier_cb cb, void *cb_data, + u32 *signal) +{ + struct rpmi_tee_transport *priv = rpmi_tee_device_to_transport(rdev); + struct rpmi_tee_signal_bus *bus; + u32 id; + + if (!cb || !signal) + return -EINVAL; + + guard(mutex)(&priv->notif.ops_lock); + bus = rpmi_tee_find_signal_bus(priv, rdev->endpoint_id); + if (!bus) + return -EOPNOTSUPP; + + struct rpmi_tee_signal_reservation *resv __free(kfree) = + kzalloc_obj(*resv, GFP_KERNEL); + if (!resv) + return -ENOMEM; + + resv->rdev = rdev; + resv->cb = cb; + resv->cb_data = cb_data; + scoped_guard(mutex, &bus->lock) { + int ret; + + ret = xa_alloc(&bus->reservations, &id, resv, + XA_LIMIT(0, bus->tee_to_ree_count - 1), + GFP_KERNEL); + if (ret) + return ret == -EBUSY ? -ENOSPC : ret; + } + + *signal = id; + /* xa_alloc owns resv. */ + retain_and_null_ptr(resv); + + return 0; +} + +/* Relinquish a previously reserved TEE-to-REE signal. */ +static int rpmi_tee_op_notify_relinquish(struct rpmi_tee_device *rdev, + u32 signal) +{ + struct rpmi_tee_transport *priv = rpmi_tee_device_to_transport(rdev); + struct rpmi_tee_signal_bus *bus; + + guard(mutex)(&priv->notif.ops_lock); + bus = rpmi_tee_find_signal_bus(priv, rdev->endpoint_id); + if (!bus) + return -EOPNOTSUPP; + + if (signal >= bus->tee_to_ree_count) + return -EINVAL; + + scoped_guard(mutex, &bus->lock) { + struct rpmi_tee_signal_reservation *resv; + + resv = xa_load(&bus->reservations, signal); + if (!resv) + return -ENOENT; + /* Release only if @signal belongs to @rdev. */ + if (resv->rdev != rdev) + return -EPERM; + if (resv->state == RPMI_TEE_SIGNAL_RELEASING) + return -EALREADY; + + resv->state = RPMI_TEE_SIGNAL_RELEASING; + } + + queue_work(priv->notif.wq, &priv->notif.work); + + return 0; +} + +/* Raise an REE-to-TEE signal. */ +static int rpmi_tee_op_signal_raise(struct rpmi_tee_device *rdev, u32 signal) +{ + struct rpmi_tee_transport *priv = rpmi_tee_device_to_transport(rdev); + struct rpmi_tee_signal_raise_one_req req = { + .target_id = cpu_to_le32(rdev->endpoint_id), + .signal_count = cpu_to_le32(1), + .signal = cpu_to_le32(signal), + }; + struct rpmi_tee_signal_bus *bus; + + guard(mutex)(&priv->notif.ops_lock); + bus = rpmi_tee_find_signal_bus(priv, rdev->endpoint_id); + if (!bus) + return -EOPNOTSUPP; + + if (signal < bus->tee_to_ree_count || signal >= bus->width) + return -EINVAL; + + return rpmi_tee_send(priv, RPMI_TEE_SRV_SIGNAL_RAISE, &req, + sizeof(req), NULL, NULL); +} + +static const struct rpmi_tee_notifier_ops rpmi_tee_notifier_ops = { + .notify_request = rpmi_tee_op_notify_request, + .notify_relinquish = rpmi_tee_op_notify_relinquish, + .signal_raise = rpmi_tee_op_signal_raise, +}; + static const struct rpmi_tee_msg_ops rpmi_tee_msg_ops = { .call = rpmi_tee_op_call, }; @@ -991,6 +1392,7 @@ static const struct rpmi_tee_mem_ops rpmi_tee_mem_ops = { static const struct rpmi_tee_ops rpmi_tee_ops = { .msg_ops = &rpmi_tee_msg_ops, .mem_ops = &rpmi_tee_mem_ops, + .notifier_ops = &rpmi_tee_notifier_ops, };
static void rpmi_tee_unregister_devices(struct rpmi_tee_transport *priv) @@ -1004,6 +1406,212 @@ static void rpmi_tee_unregister_devices(struct rpmi_tee_transport *priv) } }
+static void rpmi_tee_signal_bus_destroy_resvs(struct rpmi_tee_signal_bus *bus) +{ + struct rpmi_tee_signal_reservation *resv; + unsigned long index; + + xa_for_each(&bus->reservations, index, resv) { + xa_erase(&bus->reservations, index); + kfree(resv); + } + + xa_destroy(&bus->reservations); +} + +static void rpmi_tee_teardown_signal_buses(struct rpmi_tee_transport *priv) +{ + struct rpmi_tee_signal_bus *bus, *tmp; + + list_for_each_entry_safe(bus, tmp, &priv->notif.buses, node) { + struct rpmi_tee_signal_bus_teardown_req req = { + .target_id = cpu_to_le32(bus->endpoint_id), + }; + int ret; + + ret = rpmi_tee_send(priv, RPMI_TEE_SRV_SIGNAL_BUS_TEARDOWN, &req, + sizeof(req), NULL, NULL); + if (ret) + dev_warn(priv->dev, "failed to tear down signal bus for %#x: %d\n", + bus->endpoint_id, ret); + + rpmi_tee_signal_bus_destroy_resvs(bus); + list_del(&bus->node); + kfree(bus); + } +} + +static void rpmi_tee_quiesce_notifications(struct rpmi_tee_transport *priv) +{ + /* Initiate a notification shutdown. */ + scoped_guard(mutex, &priv->notif.ops_lock) + priv->notif.shutting_down = true; + + if (priv->notif.irq_requested) { + free_irq(priv->notif.irq, priv); + priv->notif.irq_requested = false; + } + + if (priv->notif.wq) { + destroy_workqueue(priv->notif.wq); + priv->notif.wq = NULL; + } + + /* shutting_down stops public access and the workqueue has drained. */ + rpmi_tee_teardown_signal_buses(priv); + + if (priv->notif.irq) { + irq_dispose_mapping(priv->notif.irq); + priv->notif.irq = 0; + } +} + +/* Set up an RPMI signal bus for one @endpoint_id TEE endpoint. */ +static int __rpmi_tee_setup_signal_bus(struct rpmi_tee_transport *priv, + u32 endpoint_id) +{ + struct rpmi_tee_signal_bus_setup_req req; + u32 max_width, width, tee_to_ree_count; + struct rpmi_tee_signal_bus *bus; + + /* Avoid duplicate signal bus for endpoint. */ + if (__rpmi_tee_find_signal_bus(priv, endpoint_id)) + return 0; + + if (priv->mbox.max_msg_data_size < + sizeof(struct rpmi_tee_signal_retrieve_resp)) + return -EMSGSIZE; + + max_width = FIELD_GET(RPMI_TEE_SIGNAL_WIDTH_MASK, + priv->notif.feature); + width = min(max_width, + 2 * ((priv->mbox.max_msg_data_size - + sizeof(struct rpmi_tee_signal_retrieve_resp)) / + sizeof(__le32)) + 1); + if (width < 2) + return -EMSGSIZE; + + /* Use half available signals for TEE-to-REE range. */ + tee_to_ree_count = width / 2; + + req.target_id = cpu_to_le32(endpoint_id); + req.bus_width = cpu_to_le32(width); + req.sender_signals = cpu_to_le32(tee_to_ree_count); + if (rpmi_tee_send(priv, RPMI_TEE_SRV_SIGNAL_BUS_SETUP, &req, + sizeof(req), NULL, NULL)) + return -EOPNOTSUPP; + + bus = kzalloc_obj(*bus, GFP_KERNEL); + if (!bus) { + struct rpmi_tee_signal_bus_teardown_req treq = { + .target_id = cpu_to_le32(endpoint_id), + }; + + rpmi_tee_send(priv, RPMI_TEE_SRV_SIGNAL_BUS_TEARDOWN, &treq, + sizeof(treq), NULL, NULL); + + return -ENOMEM; + } + + mutex_init(&bus->lock); + xa_init_flags(&bus->reservations, XA_FLAGS_ALLOC); + bus->endpoint_id = endpoint_id; + bus->width = width; + bus->tee_to_ree_count = tee_to_ree_count; + list_add_tail(&bus->node, &priv->notif.buses); + + return 0; +} + +static int rpmi_tee_map_signal_irq(struct rpmi_tee_transport *priv) +{ + struct device_node *np; + struct of_phandle_args oirq = {}; + u32 mode, index; + int irq; + + mode = FIELD_GET(RPMI_TEE_SIGNAL_MODE_MASK, priv->notif.feature); + if (mode != RPMI_TEE_SIGNAL_MODE_SYSTEM_MSI) + return 0; + + np = of_find_compatible_node(NULL, NULL, "riscv,rpmi-system-msi"); + if (!np) + return 0; + if (!irq_find_host(np)) { + of_node_put(np); + return -EPROBE_DEFER; + } + + index = FIELD_GET(RPMI_TEE_SIGNAL_INDEX_MASK, priv->notif.feature); + oirq.np = np; + oirq.args_count = 1; + oirq.args[0] = index; + irq = irq_create_of_mapping(&oirq); + + of_node_put(np); + + return irq; +} + +/** + * rpmi_tee_setup_signal_bus() - Set up signal notification delivery + * @priv: RPMI TEE transport + * @endpoints: TEE endpoints that may own signal buses + * @endpoint_count: Number of entries in @endpoints + * + * Map the signal interrupt, then set up a bus for each endpoint. Endpoint + * setup failures are nonfatal, allowing notifications for the remaining + * endpoints. Register the interrupt handler only when at least one bus is + * available. + * + * Return: 0 on completion, or -EPROBE_DEFER when the System MSI IRQ domain + * is not ready. + */ +static int +rpmi_tee_setup_signal_bus(struct rpmi_tee_transport *priv, + const struct rpmi_tee_sysinfo_endpoint_info *endpoints, + size_t endpoint_count) +{ + size_t i; + int ret; + + priv->notif.irq = rpmi_tee_map_signal_irq(priv); + if (priv->notif.irq < 0) + return priv->notif.irq; + if (!priv->notif.irq) + return 0; + + for (i = 0; i < endpoint_count; i++) { + ret = __rpmi_tee_setup_signal_bus(priv, + endpoints[i].endpoint_id); + if (ret) + dev_warn(priv->dev, "failed to set up signal bus for %#x: %d\n", + endpoints[i].endpoint_id, ret); + } + + if (list_empty(&priv->notif.buses)) + goto out_failed; + + ret = request_irq(priv->notif.irq, rpmi_tee_notif_irq_handler, 0, + dev_name(priv->dev), priv); + if (ret) { + dev_warn(priv->dev, "failed to request signal IRQ: %d\n", ret); + rpmi_tee_teardown_signal_buses(priv); + goto out_failed; + } + + priv->notif.irq_requested = true; + + return 0; + +out_failed: + /* Failures are nonfatal; only disable notification. */ + irq_dispose_mapping(priv->notif.irq); + priv->notif.irq = 0; + + return 0; +} + static struct rpmi_tee_device * rpmi_tee_find_device(struct rpmi_tee_transport *priv, const uuid_t *uuid, u32 endpoint_id) @@ -1041,6 +1649,7 @@ rpmi_tee_register_devices(struct rpmi_tee_transport *priv, ret = -ENOMEM; goto err_unregister; } + child->rdev = rpmi_tee_device_register(&service->uuid, service->endpoint_id, &rpmi_tee_ops, @@ -1074,8 +1683,16 @@ static int rpmi_tee_setup_endpoints(struct rpmi_tee_transport *priv) if (ret) return ret;
+ ret = rpmi_tee_setup_signal_bus(priv, system.eps, system.ep_count); + if (ret) + goto out_failed; + ret = rpmi_tee_register_devices(priv, system.services, system.service_count); + if (ret) + rpmi_tee_quiesce_notifications(priv); + +out_failed: kfree(system.services); kfree(system.eps);
@@ -1095,7 +1712,11 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev) priv->dev = &pdev->dev; platform_set_drvdata(pdev, priv); INIT_LIST_HEAD(&priv->devices); + INIT_LIST_HEAD(&priv->notif.buses); mutex_init(&priv->mem.lock); + mutex_init(&priv->notif.ops_lock); + INIT_WORK(&priv->notif.work, rpmi_tee_notif_work); + priv->mbox.client.dev = &pdev->dev; priv->mbox.client.tx_sync = true; priv->mbox.chan = mbox_request_channel(&priv->mbox.client, 0); @@ -1103,6 +1724,7 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(priv->mbox.chan), "failed to request mailbox channel\n");
+ /* Validate the RPMI mailbox transport. */ ret = rpmi_tee_check_transport(priv); if (ret) { dev_err_probe(&pdev->dev, ret, @@ -1141,6 +1763,12 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev) if (ret) goto out_failed;
+ priv->notif.wq = alloc_workqueue("rpmi_tee_notif", WQ_UNBOUND, 0); + if (!priv->notif.wq) { + ret = -ENOMEM; + goto out_failed; + } + ret = rpmi_tee_setup_endpoints(priv); if (ret) { dev_err_probe(&pdev->dev, ret, @@ -1151,6 +1779,8 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev) return 0;
out_failed: + if (priv->notif.wq) + destroy_workqueue(priv->notif.wq); mbox_free_channel(priv->mbox.chan);
return ret; @@ -1160,6 +1790,7 @@ static void rpmi_tee_transport_remove(struct platform_device *pdev) { struct rpmi_tee_transport *priv = platform_get_drvdata(pdev);
+ rpmi_tee_quiesce_notifications(priv); rpmi_tee_unregister_devices(priv); mbox_free_channel(priv->mbox.chan); } diff --git a/include/linux/rpmi_tee.h b/include/linux/rpmi_tee.h index c2dd99293481..5f4a0b19b0e1 100644 --- a/include/linux/rpmi_tee.h +++ b/include/linux/rpmi_tee.h @@ -103,10 +103,34 @@ struct rpmi_tee_mem_ops { int (*memory_reclaim)(struct rpmi_tee_device *rdev, u32 parcel_id); };
+typedef void (*rpmi_tee_notifier_cb)(struct rpmi_tee_device *rdev, + u32 signal, void *cb_data); + +/* RPMI TEE signal notification operations. */ +struct rpmi_tee_notifier_ops { + /** + * @notify_request: Allocate a TEE-to-REE signal and associate it with + * @cb and @cb_data. The allocated signal is returned through @signal. + */ + int (*notify_request)(struct rpmi_tee_device *rdev, + rpmi_tee_notifier_cb cb, void *cb_data, u32 *signal); + /** + * @notify_relinquish: Stop dispatching a TEE-to-REE @signal previously + * allocated for @rdev. The release is asynchronous and a callback may + * relinquish its own signal. + */ + int (*notify_relinquish)(struct rpmi_tee_device *rdev, u32 signal); + /** + * @signal_raise: Raise an REE-to-TEE @signal selected by the TEE service. + */ + int (*signal_raise)(struct rpmi_tee_device *rdev, u32 signal); +}; + /* RPMI TEE transport operation groups. */ struct rpmi_tee_ops { const struct rpmi_tee_msg_ops *msg_ops; const struct rpmi_tee_mem_ops *mem_ops; + const struct rpmi_tee_notifier_ops *notifier_ops; };
extern const struct bus_type rpmi_tee_bus_type;
op-tee@lists.trustedfirmware.org