From 669900c9e2eae121b73899e0c9983e440d3583da Mon Sep 17 00:00:00 2001 From: Marouene Boubakri Date: Thu, 10 Sep 2026 03:20:52 +0200 Subject: [PATCH 1/6] mailbox: riscv-sbi-mpxy: add riscv_sbi_mpxy_mbox_call() for hart-local requests An SBI MPXY message send is not queued in a hardware mailbox: it is an ecall executed on the calling hart with the calling hart's shared memory which returns once the SBI implementation has processed the message. For most RPMI service groups that means forwarding the message to a platform microcontroller, which takes a bounded time. Some message protocols are instead processed on the calling hart itself, for example when the SBI implementation forwards the message to another supervisor domain and switches the hart to it until it responds (this is the model of the TEE service group of RPMI v2.0, where TEE_CALL runs the target TEE on the hart of the caller). Such processing is unbounded and depends on interrupts reaching the other domain so that it can yield. Sending such messages through mbox_send_message() is not an option: the mailbox core calls the controller send_data() callback from msg_submit() with the channel spinlock held and interrupts disabled. Every hart would then serialize on a single spinlock, spinning with interrupts disabled for the whole duration of a message processed on another hart, while the calling hart would run the other domain with the channel lock held. Add riscv_sbi_mpxy_mbox_call() which performs the RPMI transfer directly in the calling context, bypassing the mailbox core queue and channel lock. The channel must still be requested through the mailbox core so that its ownership is tracked. Local interrupts are disabled around the ecall since the per-hart shared memory is also used from hard interrupt context by mbox_send_message() users such as the RPMI system MSI irqchip; calls from different harts proceed in parallel because each hart has its own shared memory. Signed-off-by: Marouene Boubakri Signed-off-by: Linux RISC-V bot --- MAINTAINERS | 1 + drivers/mailbox/riscv-sbi-mpxy-mbox.c | 60 +++++++++++++++++++++ include/linux/mailbox/riscv-sbi-mpxy-mbox.h | 23 ++++++++ 3 files changed, 84 insertions(+) create mode 100644 include/linux/mailbox/riscv-sbi-mpxy-mbox.h diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..f42c0f231e6280 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23586,6 +23586,7 @@ F: drivers/clk/clk-rpmi.c F: drivers/irqchip/irq-riscv-rpmi-sysmsi.c F: drivers/mailbox/riscv-sbi-mpxy-mbox.c F: include/linux/mailbox/riscv-rpmi-message.h +F: include/linux/mailbox/riscv-sbi-mpxy-mbox.h RISC-V SPACEMIT SoC Support M: Yixun Lan diff --git a/drivers/mailbox/riscv-sbi-mpxy-mbox.c b/drivers/mailbox/riscv-sbi-mpxy-mbox.c index 714f7fb97a2fd6..83a89e285bb1a6 100644 --- a/drivers/mailbox/riscv-sbi-mpxy-mbox.c +++ b/drivers/mailbox/riscv-sbi-mpxy-mbox.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -719,6 +720,65 @@ static const struct mbox_chan_ops mpxy_mbox_ops = { .shutdown = mpxy_mbox_shutdown, }; +/** + * riscv_sbi_mpxy_mbox_call() - Send an RPMI message directly on an MPXY channel + * @chan: SBI MPXY mailbox channel owned by the caller + * @msg: RPMI message of type RPMI_MBOX_MSG_TYPE_SEND_WITH_RESPONSE or + * RPMI_MBOX_MSG_TYPE_SEND_WITHOUT_RESPONSE + * + * An SBI MPXY message send is not queued anywhere: it is an ecall executed + * on the calling hart, using the calling hart's shared memory, which only + * returns once the SBI implementation has processed the message. For some + * message protocols that processing is unbounded because it runs on the + * calling hart itself, for example when the SBI implementation forwards + * the message to another supervisor domain and switches the hart to it + * until it responds (the RPMI TEE service group's TEE_CALL does this). + * + * Such messages must not go through mbox_send_message(): the mailbox core + * invokes the controller send_data() callback with the channel spinlock + * held and interrupts disabled, which would serialize all harts on a single + * lock and keep interrupts disabled on the calling hart for the whole + * duration of the call. + * + * This helper bypasses the mailbox core queue and channel lock and performs + * the transfer directly in the calling context. Only local interrupts are + * disabled around the ecall, because the per-hart shared memory can be used + * from hard interrupt context through mbox_send_message() by other clients. + * Calls from different harts run concurrently since each hart has its own + * shared memory. + * + * The caller must own @chan through mbox_request_channel() (or a variant of + * it) so that no other client can use the channel, and must not use + * mbox_send_message() on it concurrently. + * + * Return: 0 on success or a negative error code. + */ +int riscv_sbi_mpxy_mbox_call(struct mbox_chan *chan, + struct rpmi_mbox_message *msg) +{ + struct mpxy_mbox_channel *mchan; + unsigned long flags; + + if (!chan || !chan->cl || !chan->mbox || !msg) + return -EINVAL; + if (chan->mbox->ops != &mpxy_mbox_ops) + return -EINVAL; + if (msg->type != RPMI_MBOX_MSG_TYPE_SEND_WITH_RESPONSE && + msg->type != RPMI_MBOX_MSG_TYPE_SEND_WITHOUT_RESPONSE) + return -EINVAL; + + mchan = chan->con_priv; + if (mchan->attrs.msg_proto_id != SBI_MPXY_MSGPROTO_RPMI_ID) + return -EOPNOTSUPP; + + local_irq_save(flags); + mpxy_mbox_send_rpmi_data(mchan, msg); + local_irq_restore(flags); + + return msg->error; +} +EXPORT_SYMBOL_GPL(riscv_sbi_mpxy_mbox_call); + /* ====== MPXY platform driver ===== */ static void mpxy_mbox_msi_write(struct msi_desc *desc, struct msi_msg *msg) diff --git a/include/linux/mailbox/riscv-sbi-mpxy-mbox.h b/include/linux/mailbox/riscv-sbi-mpxy-mbox.h new file mode 100644 index 00000000000000..4fb7b04cddc724 --- /dev/null +++ b/include/linux/mailbox/riscv-sbi-mpxy-mbox.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2026 NXP */ + +#ifndef _LINUX_RISCV_SBI_MPXY_MBOX_H_ +#define _LINUX_RISCV_SBI_MPXY_MBOX_H_ + +#include + +struct mbox_chan; +struct rpmi_mbox_message; + +#if IS_ENABLED(CONFIG_RISCV_SBI_MPXY_MBOX) +int riscv_sbi_mpxy_mbox_call(struct mbox_chan *chan, + struct rpmi_mbox_message *msg); +#else +static inline int riscv_sbi_mpxy_mbox_call(struct mbox_chan *chan, + struct rpmi_mbox_message *msg) +{ + return -ENODEV; +} +#endif + +#endif /* _LINUX_RISCV_SBI_MPXY_MBOX_H_ */ From a7e9854c37e6571ea322b32d679c9678ce2f8133 Mon Sep 17 00:00:00 2001 From: Marouene Boubakri Date: Thu, 10 Sep 2026 03:20:53 +0200 Subject: [PATCH 2/6] tee: optee: select the SMC ABI conduit from the firmware node match data The SMC ABI is defined in terms of register arguments and return values and does not depend on how they reach secure world: smc_abi.c already abstracts the conduit behind optee_invoke_fn, only its selection is hard-wired to the SMCCC "method" property of the "linaro,optee-tz" node. Introduce struct optee_smc_conduit carried by the match data of the firmware node, move the SMCCC specific wrappers and the "method" property parsing into the SMCCC conduit and build that conduit only when the architecture provides SMCCC (__arm_smccc_hvc() has no stub when CONFIG_HAVE_ARM_SMCCC is not set). This prepares for a conduit that carries the SMC ABI over RISC-V RPMI messages. No functional change on Arm. Signed-off-by: Marouene Boubakri Signed-off-by: Linux RISC-V bot --- drivers/tee/optee/smc_abi.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index b8a2bdac3208f9..221939f72c563a 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -1464,6 +1464,20 @@ optee_config_shm_memremap(optee_invoke_fn *invoke_fn, void **memremaped_shm) return rc; } +/* + * struct optee_smc_conduit - conduit used to invoke the SMC ABI + * @init: probes the conduit and returns the function invoking the + * SMC ABI through it, or an ERR_PTR() on failure + * + * The SMC ABI is defined in terms of register arguments and return values + * (see optee_smc.h) but does not depend on how they reach secure world. A + * conduit is selected by the compatible string of the OP-TEE firmware node. + */ +struct optee_smc_conduit { + optee_invoke_fn *(*init)(struct device *dev); +}; + +#ifdef CONFIG_HAVE_ARM_SMCCC /* Simple wrapper functions to be able to use a function pointer */ static void optee_smccc_smc(unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, @@ -1483,7 +1497,7 @@ static void optee_smccc_hvc(unsigned long a0, unsigned long a1, arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); } -static optee_invoke_fn *get_invoke_func(struct device *dev) +static optee_invoke_fn *optee_smccc_conduit_init(struct device *dev) { const char *method; @@ -1503,6 +1517,11 @@ static optee_invoke_fn *get_invoke_func(struct device *dev) return ERR_PTR(-EINVAL); } +static const struct optee_smc_conduit optee_smccc_conduit = { + .init = optee_smccc_conduit_init, +}; +#endif + /* optee_remove - Device Removal Routine * @pdev: platform device information struct * @@ -1728,6 +1747,7 @@ static int optee_protmem_pool_init(struct optee *optee) static int optee_probe(struct platform_device *pdev) { + const struct optee_smc_conduit *conduit; optee_invoke_fn *invoke_fn; struct tee_shm_pool *pool = ERR_PTR(-EINVAL); struct optee *optee = NULL; @@ -1741,7 +1761,11 @@ static int optee_probe(struct platform_device *pdev) u32 sec_caps; int rc; - invoke_fn = get_invoke_func(&pdev->dev); + conduit = device_get_match_data(&pdev->dev); + if (!conduit) + return -ENODEV; + + invoke_fn = conduit->init(&pdev->dev); if (IS_ERR(invoke_fn)) return PTR_ERR(invoke_fn); @@ -1956,7 +1980,9 @@ static int optee_probe(struct platform_device *pdev) } static const struct of_device_id optee_dt_match[] = { - { .compatible = "linaro,optee-tz" }, +#ifdef CONFIG_HAVE_ARM_SMCCC + { .compatible = "linaro,optee-tz", .data = &optee_smccc_conduit }, +#endif {}, }; MODULE_DEVICE_TABLE(of, optee_dt_match); From a52f2b32b79c32e17420700902463a23fc5083a9 Mon Sep 17 00:00:00 2001 From: Marouene Boubakri Date: Thu, 10 Sep 2026 03:20:54 +0200 Subject: [PATCH 3/6] tee: optee: teach the memory type check about RISC-V page attributes optee_check_mem_type() only lets normal cacheable memory be registered with secure world since OP-TEE maps registered pages as such and must not observe mismatched memory attributes. On RISC-V the memory type of a mapping is encoded in the Svpbmt bits of the PTE (or their T-Head equivalent selected at runtime by the _PAGE_MTMASK alternative): normal cacheable memory (PMA) has them cleared, pgprot_writecombine() and pgprot_noncached() set them. Without Svpbmt the memory type is defined by the PMAs alone, _PAGE_MTMASK is empty and every mapping passes the check, as there is nothing else to inspect. Signed-off-by: Marouene Boubakri Signed-off-by: Linux RISC-V bot --- drivers/tee/optee/call.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/tee/optee/call.c b/drivers/tee/optee/call.c index e046aff6182866..c29bff2248485b 100644 --- a/drivers/tee/optee/call.c +++ b/drivers/tee/optee/call.c @@ -604,6 +604,14 @@ static bool is_normal_memory(pgprot_t p) #elif defined(CONFIG_ARM64) return ((pgprot_val(p) & PTE_ATTRINDX_MASK) == PTE_ATTRINDX(MT_NORMAL)) || ((pgprot_val(p) & PTE_ATTRINDX_MASK) == PTE_ATTRINDX(MT_NORMAL_TAGGED)); +#elif defined(CONFIG_RISCV) + /* + * Svpbmt, or the T-Head equivalent, encodes non-cacheable and I/O + * memory in the memory type bits of the PTE, normal cacheable memory + * (PMA) has them cleared. Without Svpbmt the memory type only comes + * from the PMAs, the mask is empty and all mappings pass the check. + */ + return !(pgprot_val(p) & _PAGE_MTMASK); #else #error "Unsupported architecture" #endif From 1b565822d198d0755d6cbd35bff06c0500411284 Mon Sep 17 00:00:00 2001 From: Marouene Boubakri Date: Thu, 10 Sep 2026 03:20:55 +0200 Subject: [PATCH 4/6] mailbox: riscv-rpmi-message: add TEE service group definitions The RPMI specification v2.0, currently in development on the main branch of the specification repository, adds a TEE service group (SERVICEGROUP_ID 0x0010) which lets a client in a Rich Execution Environment invoke services in a Trusted Execution Environment through the M-mode firmware or hypervisor implementing the group. Its TEE_CALL service is a synchronous request whose target TEE executes on the hart of the caller until a response is returned. Add the service group ID and the service IDs, as defined in the specification draft, so that TEE drivers can use them together with the SBI MPXY mailbox driver. Signed-off-by: Marouene Boubakri Signed-off-by: Linux RISC-V bot --- include/linux/mailbox/riscv-rpmi-message.h | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/linux/mailbox/riscv-rpmi-message.h b/include/linux/mailbox/riscv-rpmi-message.h index e135c6564d0c80..0530e1dddf3c5d 100644 --- a/include/linux/mailbox/riscv-rpmi-message.h +++ b/include/linux/mailbox/riscv-rpmi-message.h @@ -93,6 +93,7 @@ static inline int rpmi_to_linux_error(int rpmi_error) /* RPMI service group IDs */ #define RPMI_SRVGRP_SYSTEM_MSI 0x00002 #define RPMI_SRVGRP_CLOCK 0x00008 +#define RPMI_SRVGRP_TEE 0x00010 /* RPMI clock service IDs */ enum rpmi_clock_service_id { @@ -119,6 +120,30 @@ enum rpmi_sysmsi_service_id { RPMI_SYSMSI_SRV_ID_MAX_COUNT }; +/* RPMI TEE service IDs */ +enum rpmi_tee_service_id { + RPMI_TEE_SRV_ENABLE_NOTIFICATION = 0x01, + RPMI_TEE_SRV_PROBE_FEATURES = 0x02, + RPMI_TEE_SRV_PROBE_SYSTEM = 0x03, + RPMI_TEE_SRV_EXIT = 0x04, + RPMI_TEE_SRV_SIGNAL_BUS_SETUP = 0x05, + RPMI_TEE_SRV_SIGNAL_BUS_TEARDOWN = 0x06, + RPMI_TEE_SRV_SIGNAL_RAISE = 0x07, + RPMI_TEE_SRV_SIGNAL_RETRIEVE = 0x08, + RPMI_TEE_SRV_MEMORY_PARCEL_CREATE = 0x09, + RPMI_TEE_SRV_MEMORY_PARCEL_ACCEPT = 0x0a, + RPMI_TEE_SRV_MEMORY_PARCEL_RELEASE = 0x0b, + RPMI_TEE_SRV_MEMORY_PARCEL_RECLAIM = 0x0c, + RPMI_TEE_SRV_MEMORY_SEGMENT_SEND = 0x0d, + RPMI_TEE_SRV_MEMORY_SEGMENT_RECEIVE = 0x0e, + RPMI_TEE_SRV_REGISTER_FOR_LIFECYCLE_INFO = 0x0f, + RPMI_TEE_SRV_ON_START_INFO = 0x10, + RPMI_TEE_SRV_ON_SHUTDOWN_INFO = 0x11, + RPMI_TEE_SRV_ON_CHANGED_INFO = 0x12, + RPMI_TEE_SRV_CALL = 0x13, + RPMI_TEE_SRV_ID_MAX_COUNT +}; + /* RPMI Linux mailbox attribute IDs */ enum rpmi_mbox_attribute_id { RPMI_MBOX_ATTR_SPEC_VERSION, From f53435d895a28bf8e501ea91e6045d80e230698c Mon Sep 17 00:00:00 2001 From: Marouene Boubakri Date: Thu, 10 Sep 2026 03:20:56 +0200 Subject: [PATCH 5/6] dt-bindings: firmware: add OP-TEE over the RISC-V RPMI TEE service group On RISC-V, OP-TEE runs as a supervisor domain isolated from the Rich Execution Environment by the M-mode firmware. There is no SMC or HVC instruction to reach it: the kernel invokes OP-TEE through the TEE service group of the RISC-V Platform Management Interface (RPMI) v2.0, carried on an SBI Message Proxy (MPXY) channel. Each call is a TEE_CALL service request whose service data carries the register arguments of the OP-TEE SMC ABI, so the ABI itself is unchanged from Arm. Add a binding for such an OP-TEE instance. The compatible string keeps the "linaro" vendor prefix of "linaro,optee-tz" which identifies the OP-TEE reference implementation. The node references the MPXY mailbox channel implementing the TEE service group, and carries the endpoint identifiers assigned by the RPMI TEE framework to the REE and to OP-TEE since the framework does not define any way for an endpoint to learn them other than the optional TEE_PROBE_SYSTEM service, whose CBOR encoded response cannot be parsed in the kernel. Signed-off-by: Marouene Boubakri Signed-off-by: Linux RISC-V bot --- .../bindings/firmware/linaro,optee-rpmi.yaml | 79 +++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 80 insertions(+) create mode 100644 Documentation/devicetree/bindings/firmware/linaro,optee-rpmi.yaml diff --git a/Documentation/devicetree/bindings/firmware/linaro,optee-rpmi.yaml b/Documentation/devicetree/bindings/firmware/linaro,optee-rpmi.yaml new file mode 100644 index 00000000000000..1c3dffc331ff9f --- /dev/null +++ b/Documentation/devicetree/bindings/firmware/linaro,optee-rpmi.yaml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/firmware/linaro,optee-rpmi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OP-TEE accessed through the RISC-V RPMI TEE service group + +maintainers: + - Marouene Boubakri + +description: | + OP-TEE is a piece of software using hardware features to provide a Trusted + Execution Environment. On RISC-V, OP-TEE runs as a supervisor domain + isolated from the Rich Execution Environment (REE) by the M-mode firmware. + + The REE reaches OP-TEE through the TEE service group of the RISC-V Platform + Management Interface (RPMI) [1], carried on a channel of the SBI Message + Proxy (MPXY) extension [2]. Each call into OP-TEE is a TEE_CALL service + request whose service data carries the register arguments of the OP-TEE + SMC ABI (drivers/tee/optee/optee_smc.h), as described in + drivers/tee/optee/optee_rpmi.h. + + The RPMI TEE framework (the M-mode firmware) assigns an identifier to each + endpoint. The identifiers of the REE and of OP-TEE are required to + address TEE_CALL requests and are described here. + + The compatible string uses the same "linaro" vendor prefix as + "linaro,optee-tz", which identifies the OP-TEE reference implementation. + + [1] RISC-V Platform Management Interface (RPMI) v2.0 (or higher) + https://github.com/riscv-non-isa/riscv-rpmi/releases + + [2] RISC-V Supervisor Binary Interface (SBI) v3.0 (or higher) + https://github.com/riscv-non-isa/riscv-sbi-doc/releases + +properties: + $nodename: + const: optee + + compatible: + const: linaro,optee-rpmi + + mboxes: + maxItems: 1 + description: + SBI MPXY channel implementing the RPMI TEE service group. + + riscv,rpmi-tee-sender-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Endpoint identifier of the REE, used as SENDER_ID of the TEE_CALL + requests. + + riscv,rpmi-tee-target-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Endpoint identifier of OP-TEE, used as TARGET_ID of the TEE_CALL + requests. + +required: + - compatible + - mboxes + - riscv,rpmi-tee-sender-id + - riscv,rpmi-tee-target-id + +additionalProperties: false + +examples: + - | + firmware { + optee { + compatible = "linaro,optee-rpmi"; + mboxes = <&mpxy_mbox 0x10 0x0>; + riscv,rpmi-tee-sender-id = <0>; + riscv,rpmi-tee-target-id = <1>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index f42c0f231e6280..fa6ec35555a94b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20420,6 +20420,7 @@ M: Jens Wiklander L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers) S: Maintained F: Documentation/ABI/testing/sysfs-bus-optee-devices +F: Documentation/devicetree/bindings/firmware/linaro,optee-rpmi.yaml F: drivers/tee/optee/ OP-TEE RANDOM NUMBER GENERATOR (RNG) DRIVER From 17c8ceb1f5476ebc1a3e78e5e63ce7b065a7f0a2 Mon Sep 17 00:00:00 2001 From: Marouene Boubakri Date: Thu, 10 Sep 2026 03:20:57 +0200 Subject: [PATCH 6/6] tee: optee: add a RISC-V conduit over the RPMI TEE service group On RISC-V, OP-TEE runs as a supervisor domain isolated from the Rich Execution Environment by the M-mode firmware. There is no SMC or HVC instruction to reach it: the kernel invokes OP-TEE through the TEE service group of the RISC-V Platform Management Interface (RPMI) v2.0, whose TEE_CALL service is carried by the SBI Message Proxy (MPXY) extension of SBI v3.0. The M-mode firmware implementing the service group (the RPMI TEE framework) switches the calling hart to the OP-TEE domain until OP-TEE responds, so a TEE_CALL behaves exactly like an SMC: it runs on the calling hart and returns when OP-TEE completes the call, requests an RPC or yields on a foreign interrupt. Add a conduit for the SMC ABI which sends the register arguments a0-a7 of each invocation as the service data of a TEE_CALL request and takes the return values a0-a3 from its service response, using the OP-TEE API UID as the service UUID. The TEE_CALL request and response follow the specification, only the content of the service data and of the service response, which the specification leaves to the service, is defined here. The message layout is in optee_rpmi.h which is kept in sync with OP-TEE OS like optee_smc.h and optee_msg.h. The SMC ABI itself, the message protocol, the RPC handling, dynamic and static shared memory and notifications are unchanged. The conduit binds to a "linaro,optee-rpmi" firmware node referencing the MPXY mailbox channel and carrying the REE and OP-TEE endpoint identifiers assigned by the framework. The channel is requested through the mailbox core to claim it, and its RPMI attributes are checked at probe, but TEE_CALL requests are sent with riscv_sbi_mpxy_mbox_call() rather than mbox_send_message(): OP-TEE executes on the calling hart for an unbounded time, which cannot happen under the mailbox channel spinlock with interrupts disabled. Errors reported by the SBI implementation or the framework, which mean that OP-TEE was not reached, are converted into OPTEE_SMC_RETURN_EBUSY or OPTEE_SMC_RETURN_ENOTAVAIL so that the callers see a failed call. Signed-off-by: Marouene Boubakri Signed-off-by: Linux RISC-V bot --- Documentation/tee/op-tee.rst | 18 ++- drivers/tee/Kconfig | 2 +- drivers/tee/optee/Kconfig | 11 +- drivers/tee/optee/Makefile | 1 + drivers/tee/optee/optee_private.h | 3 + drivers/tee/optee/optee_rpmi.h | 69 ++++++++++ drivers/tee/optee/rpmi_conduit.c | 203 ++++++++++++++++++++++++++++++ drivers/tee/optee/smc_abi.c | 9 ++ 8 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 drivers/tee/optee/optee_rpmi.h create mode 100644 drivers/tee/optee/rpmi_conduit.c diff --git a/Documentation/tee/op-tee.rst b/Documentation/tee/op-tee.rst index b0ac097d55476a..b56c32bf255bfe 100644 --- a/Documentation/tee/op-tee.rst +++ b/Documentation/tee/op-tee.rst @@ -4,14 +4,24 @@ OP-TEE (Open Portable Trusted Execution Environment) ==================================================== -The OP-TEE driver handles OP-TEE [1] based TEEs. Currently it is only the ARM -TrustZone based OP-TEE solution that is supported. +The OP-TEE driver handles OP-TEE [1] based TEEs. The ARM TrustZone based +OP-TEE solution and OP-TEE running as an isolated supervisor domain on +RISC-V are supported. Lowest level of communication with OP-TEE builds on ARM SMC Calling Convention (SMCCC) [2], which is the foundation for OP-TEE's SMC interface [3] used internally by the driver. Stacked on top of that is OP-TEE Message Protocol [4]. +On RISC-V the SMC interface is unchanged but its register arguments and +return values are carried by the TEE_CALL service of the TEE service group +of the RISC-V Platform Management Interface (RPMI) [7], sent to the M-mode +firmware on an SBI Message Proxy (MPXY) channel [8]. The M-mode firmware +switches the calling hart to the OP-TEE domain until OP-TEE responds, so a +call behaves like an SMC: it runs on the calling hart and returns when +OP-TEE completes, requests an RPC or yields on a foreign interrupt. The +message layout is described in drivers/tee/optee/optee_rpmi.h. + OP-TEE SMC interface provides the basic functions required by SMCCC and some additional functions specific for OP-TEE. The most interesting functions are: @@ -164,3 +174,7 @@ References "TEE Client API Specification v1.0" and click download. [6] https://trustedfirmware-a.readthedocs.io/en/latest/threat_model/threat_model.html + +[7] https://github.com/riscv-non-isa/riscv-rpmi/releases + +[8] https://github.com/riscv-non-isa/riscv-sbi-doc/releases diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index 98c3ad0839409b..6434204d899e38 100644 --- a/drivers/tee/Kconfig +++ b/drivers/tee/Kconfig @@ -2,7 +2,7 @@ # Generic Trusted Execution Environment Configuration menuconfig TEE tristate "Trusted Execution Environment support" - depends on HAVE_ARM_SMCCC || COMPILE_TEST || CPU_SUP_AMD + depends on HAVE_ARM_SMCCC || COMPILE_TEST || CPU_SUP_AMD || RISCV_SBI_MPXY_MBOX select CRYPTO_LIB_SHA1 select DMA_SHARED_BUFFER select GENERIC_ALLOCATOR diff --git a/drivers/tee/optee/Kconfig b/drivers/tee/optee/Kconfig index 50d2051f7f20b7..0b6cd91a8ee885 100644 --- a/drivers/tee/optee/Kconfig +++ b/drivers/tee/optee/Kconfig @@ -2,13 +2,22 @@ # OP-TEE Trusted Execution Environment Configuration config OPTEE tristate "OP-TEE" - depends on HAVE_ARM_SMCCC + depends on HAVE_ARM_SMCCC || RISCV_SBI_MPXY_MBOX + depends on RISCV_SBI_MPXY_MBOX || !RISCV_SBI_MPXY_MBOX depends on MMU depends on RPMB || !RPMB help This implements the OP-TEE Trusted Execution Environment (TEE) driver. +config OPTEE_RPMI_CONDUIT + bool + depends on OPTEE && RISCV_SBI_MPXY_MBOX + default y + help + Reach OP-TEE through the TEE service group of the RISC-V Platform + Management Interface (RPMI) on an SBI Message Proxy (MPXY) channel. + config OPTEE_INSECURE_LOAD_IMAGE bool "Load OP-TEE image as firmware" default n diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile index ad7049c1c10721..4b5a868a939846 100644 --- a/drivers/tee/optee/Makefile +++ b/drivers/tee/optee/Makefile @@ -9,6 +9,7 @@ optee-objs += supp.o optee-objs += device.o optee-objs += smc_abi.o optee-objs += ffa_abi.o +optee-$(CONFIG_OPTEE_RPMI_CONDUIT) += rpmi_conduit.o # for tracing framework to find optee_trace.h CFLAGS_smc_abi.o := -I$(src) diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h index aefe1e6f568915..b90359988a3c1e 100644 --- a/drivers/tee/optee/optee_private.h +++ b/drivers/tee/optee/optee_private.h @@ -424,6 +424,9 @@ static inline void reg_pair_from_64(u32 *reg0, u32 *reg1, u64 val) /* Registration of the ABIs */ int optee_smc_abi_register(void); void optee_smc_abi_unregister(void); +#if IS_ENABLED(CONFIG_OPTEE_RPMI_CONDUIT) +optee_invoke_fn *optee_rpmi_conduit_init(struct device *dev); +#endif int optee_ffa_abi_register(void); void optee_ffa_abi_unregister(void); diff --git a/drivers/tee/optee/optee_rpmi.h b/drivers/tee/optee/optee_rpmi.h new file mode 100644 index 00000000000000..89821255bcc039 --- /dev/null +++ b/drivers/tee/optee/optee_rpmi.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */ +/* + * Copyright 2026 NXP + */ +#ifndef OPTEE_RPMI_H +#define OPTEE_RPMI_H + +#include +#include + +/* + * This file defines how the OP-TEE SMC ABI (optee_smc.h) is carried by the + * TEE service group of the RISC-V Platform Management Interface (RPMI). It + * is kept in sync between secure world and the normal world driver. + * + * An invocation of the SMC ABI is a TEE_CALL service request sent to the + * RPMI TEE framework on an SBI MPXY channel where: + * - SENDER_ID identifies the REE endpoint and TARGET_ID the OP-TEE + * endpoint, both assigned by the framework, + * - SERVICE is OPTEE_RPMI_SERVICE_UUID in RFC 4122 byte order, + * - SERVICE_DATA carries the register arguments a0-a7 of the SMC ABI as + * little-endian 64-bit words. + * + * The SERVICE_RSP of the TEE_CALL response carries the return values a0-a3 + * of the SMC ABI as little-endian 64-bit words. The STATUS of the response + * is set by the framework and is RPMI_SUCCESS whenever OP-TEE was reached, + * errors reported by OP-TEE itself are returned in a0 as usual. + */ + +/* + * UUID identifying the OP-TEE API as a TEE_CALL service, the same value + * as returned by OPTEE_SMC_CALLS_UID (OPTEE_MSG_UID_0..3). + */ +#define OPTEE_RPMI_SERVICE_UUID \ + UUID_INIT(0x384fb3e0, 0xe7f8, 0x11e3, \ + 0xaf, 0x63, 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b) + +#define OPTEE_RPMI_CALL_NUM_ARGS 8 +#define OPTEE_RPMI_CALL_NUM_RETS 4 + +/** + * struct optee_rpmi_call_req - TEE_CALL request data invoking the SMC ABI + * @sender_id: SENDER_ID, endpoint identifier of the REE + * @target_id: TARGET_ID, endpoint identifier of OP-TEE + * @service: SERVICE, bytes of OPTEE_RPMI_SERVICE_UUID + * @data_len: SERVICE_DATA_LEN, sizeof(@args) + * @args: SERVICE_DATA, register arguments a0-a7 of the SMC ABI + */ +struct optee_rpmi_call_req { + __le32 sender_id; + __le32 target_id; + u8 service[UUID_SIZE]; + __le32 data_len; + __le64 args[OPTEE_RPMI_CALL_NUM_ARGS]; +} __packed; + +/** + * struct optee_rpmi_call_rsp - TEE_CALL response data of the SMC ABI + * @status: STATUS, RPMI error code set by the framework + * @rsp_len: SERVICE_RSP_LEN, sizeof(@rets) + * @rets: SERVICE_RSP, return values a0-a3 of the SMC ABI + */ +struct optee_rpmi_call_rsp { + __le32 status; + __le32 rsp_len; + __le64 rets[OPTEE_RPMI_CALL_NUM_RETS]; +} __packed; + +#endif /*OPTEE_RPMI_H*/ diff --git a/drivers/tee/optee/rpmi_conduit.c b/drivers/tee/optee/rpmi_conduit.c new file mode 100644 index 00000000000000..bc4ff975caaf53 --- /dev/null +++ b/drivers/tee/optee/rpmi_conduit.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2026 NXP + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "optee_private.h" +#include "optee_rpmi.h" +#include "optee_smc.h" + +/* + * This file implements the conduit carrying the SMC ABI over the TEE + * service group of the RISC-V Platform Management Interface (RPMI). Each + * invocation of the ABI is a TEE_CALL request sent on an SBI MPXY channel + * with the message layout described in optee_rpmi.h. + * + * The TEE_CALL request is not sent with mbox_send_message(): the target + * TEE executes on the calling hart until it responds, so the transfer is + * done with riscv_sbi_mpxy_mbox_call() which bypasses the mailbox core + * queue and channel lock. Calls from different harts run concurrently, + * exactly like SMCs do. + */ + +static_assert(sizeof(struct optee_rpmi_call_req) == 92); +static_assert(sizeof(struct optee_rpmi_call_rsp) == 40); + +/** + * struct optee_rpmi_conduit - RPMI TEE service group conduit + * @cl: mailbox client owning @chan + * @chan: SBI MPXY channel implementing the RPMI TEE service group + * @sender_id: endpoint identifier of the REE + * @target_id: endpoint identifier of OP-TEE + */ +struct optee_rpmi_conduit { + struct mbox_client cl; + struct mbox_chan *chan; + u32 sender_id; + u32 target_id; +}; + +/* Like the rest of the SMC ABI, at most a single OP-TEE instance is supported */ +static struct optee_rpmi_conduit optee_rpmi; + +static const uuid_t optee_rpmi_service_uuid = OPTEE_RPMI_SERVICE_UUID; + +static void optee_rpmi_invoke_fn(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + struct optee_rpmi_call_req req = { + .sender_id = cpu_to_le32(optee_rpmi.sender_id), + .target_id = cpu_to_le32(optee_rpmi.target_id), + .data_len = cpu_to_le32(sizeof(req.args)), + .args = { + cpu_to_le64(a0), cpu_to_le64(a1), cpu_to_le64(a2), + cpu_to_le64(a3), cpu_to_le64(a4), cpu_to_le64(a5), + cpu_to_le64(a6), cpu_to_le64(a7), + }, + }; + struct optee_rpmi_call_rsp rsp = {}; + struct rpmi_mbox_message msg; + int rc; + + export_uuid(req.service, &optee_rpmi_service_uuid); + + rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_CALL, + &req, sizeof(req), &rsp, sizeof(rsp)); + rc = riscv_sbi_mpxy_mbox_call(optee_rpmi.chan, &msg); + if (!rc) { + if (msg.data.out_response_len < sizeof(rsp.status)) + rc = -EIO; + else + rc = rpmi_to_linux_error((s32)le32_to_cpu(rsp.status)); + } + if (!rc && (msg.data.out_response_len < sizeof(rsp) || + le32_to_cpu(rsp.rsp_len) != sizeof(rsp.rets))) + rc = -EIO; + + if (rc) { + /* + * The SBI implementation or the RPMI TEE framework failed to + * deliver the call, OP-TEE was not reached. Report it in a0 + * as the SMC ABI does for a call that could not be handled. + */ + if (rc != -EBUSY) + pr_warn_ratelimited("TEE_CALL of 0x%lx failed: %d\n", + a0, rc); + res->a0 = rc == -EBUSY ? OPTEE_SMC_RETURN_EBUSY : + OPTEE_SMC_RETURN_ENOTAVAIL; + res->a1 = 0; + res->a2 = 0; + res->a3 = 0; + return; + } + + res->a0 = le64_to_cpu(rsp.rets[0]); + res->a1 = le64_to_cpu(rsp.rets[1]); + res->a2 = le64_to_cpu(rsp.rets[2]); + res->a3 = le64_to_cpu(rsp.rets[3]); +} + +static void optee_rpmi_conduit_release(void *data) +{ + struct optee_rpmi_conduit *conduit = data; + + mbox_free_channel(conduit->chan); + conduit->chan = NULL; +} + +static int optee_rpmi_get_attr(struct optee_rpmi_conduit *conduit, + enum rpmi_mbox_attribute_id id, u32 *value) +{ + struct rpmi_mbox_message msg; + int rc; + + rpmi_mbox_init_get_attribute(&msg, id); + rc = rpmi_mbox_send_message(conduit->chan, &msg); + if (rc) + return rc; + + *value = msg.attr.value; + return 0; +} + +optee_invoke_fn *optee_rpmi_conduit_init(struct device *dev) +{ + struct optee_rpmi_conduit *conduit = &optee_rpmi; + u32 value; + int rc; + + if (conduit->chan) + return ERR_PTR(-EBUSY); + + rc = device_property_read_u32(dev, "riscv,rpmi-tee-sender-id", + &conduit->sender_id); + if (rc) + return ERR_PTR(dev_err_probe(dev, rc, + "missing \"riscv,rpmi-tee-sender-id\" property\n")); + + rc = device_property_read_u32(dev, "riscv,rpmi-tee-target-id", + &conduit->target_id); + if (rc) + return ERR_PTR(dev_err_probe(dev, rc, + "missing \"riscv,rpmi-tee-target-id\" property\n")); + + conduit->cl.dev = dev; + conduit->cl.tx_block = false; + conduit->cl.knows_txdone = true; + conduit->chan = mbox_request_channel(&conduit->cl, 0); + if (IS_ERR(conduit->chan)) { + rc = PTR_ERR(conduit->chan); + conduit->chan = NULL; + return ERR_PTR(dev_err_probe(dev, rc, + "failed to request MPXY channel\n")); + } + + rc = devm_add_action_or_reset(dev, optee_rpmi_conduit_release, conduit); + if (rc) + return ERR_PTR(rc); + + rc = optee_rpmi_get_attr(conduit, RPMI_MBOX_ATTR_SERVICEGROUP_ID, + &value); + if (rc) + return ERR_PTR(dev_err_probe(dev, rc, + "failed to read RPMI service group ID\n")); + if (value != RPMI_SRVGRP_TEE) { + dev_err(dev, "MPXY channel implements RPMI service group 0x%x, not TEE\n", + value); + return ERR_PTR(-ENODEV); + } + + rc = optee_rpmi_get_attr(conduit, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE, + &value); + if (rc) + return ERR_PTR(dev_err_probe(dev, rc, + "failed to read RPMI max message size\n")); + if (value < sizeof(struct optee_rpmi_call_req) || + value < sizeof(struct optee_rpmi_call_rsp)) { + dev_err(dev, "MPXY channel message size %u is too small\n", + value); + return ERR_PTR(-EINVAL); + } + + pr_info("using RPMI TEE service group, endpoints %u -> %u\n", + conduit->sender_id, conduit->target_id); + + return optee_rpmi_invoke_fn; +} diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index 221939f72c563a..3d5d545269b538 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -1522,6 +1522,12 @@ static const struct optee_smc_conduit optee_smccc_conduit = { }; #endif +#if IS_ENABLED(CONFIG_OPTEE_RPMI_CONDUIT) +static const struct optee_smc_conduit optee_rpmi_conduit = { + .init = optee_rpmi_conduit_init, +}; +#endif + /* optee_remove - Device Removal Routine * @pdev: platform device information struct * @@ -1982,6 +1988,9 @@ static int optee_probe(struct platform_device *pdev) static const struct of_device_id optee_dt_match[] = { #ifdef CONFIG_HAVE_ARM_SMCCC { .compatible = "linaro,optee-tz", .data = &optee_smccc_conduit }, +#endif +#if IS_ENABLED(CONFIG_OPTEE_RPMI_CONDUIT) + { .compatible = "linaro,optee-rpmi", .data = &optee_rpmi_conduit }, #endif {}, };