* [PATCH 0/3] tee: add MbedTEE driver
@ 2026-07-01 13:25 Xing Loong
2026-07-01 13:25 ` [PATCH 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong
` (3 more replies)
0 siblings, 4 replies; 15+ messages in thread
From: Xing Loong @ 2026-07-01 13:25 UTC (permalink / raw)
To: Jens Wiklander
Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg,
op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong
This series adds a Linux TEE driver for MbedTEE, a Trusted
Execution Environment for embedded systems
(https://github.com/mbedtee).
Two RPC transports are provided for systems where platform
firmware or board configuration has already established the
REE/TEE separation before Linux boots:
- ARM/ARM64: SMC calls and GIC SPI notifications (TrustZone)
- RISC-V: shared-memory ring buffers and IMSIC MSI notifications
The driver implements the TEE subsystem interface (tee_driver_ops)
and provides GlobalPlatform TEE Client API support, dynamic shared
memory registration, and tee-supplicant support for REE filesystem
and RPMB operations.
The series is structured as follows:
[1/3] dt-bindings: vendor-prefixes: add mbedtee
[2/3] dt-bindings: firmware: add mbedtee,rpc binding
[3/3] tee: add MbedTEE driver
Xing Loong (3):
dt-bindings: vendor-prefixes: add mbedtee
dt-bindings: firmware: add mbedtee,rpc binding
tee: add MbedTEE driver
.../bindings/firmware/mbedtee,rpc.yaml | 221 +++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
Documentation/tee/index.rst | 1 +
Documentation/tee/mbedtee.rst | 155 +++++
MAINTAINERS | 9 +
drivers/tee/Kconfig | 3 +-
drivers/tee/Makefile | 1 +
drivers/tee/mbedtee/Kconfig | 20 +
drivers/tee/mbedtee/Makefile | 11 +
drivers/tee/mbedtee/core.c | 236 +++++++
drivers/tee/mbedtee/mbedtee_drv.h | 269 ++++++++
drivers/tee/mbedtee/mbedtee_msg.h | 219 +++++++
drivers/tee/mbedtee/rpc_callee.c | 609 ++++++++++++++++++
drivers/tee/mbedtee/rpc_callee_arm.c | 91 +++
drivers/tee/mbedtee/rpc_callee_riscv.c | 203 ++++++
drivers/tee/mbedtee/rpc_caller.c | 539 ++++++++++++++++
drivers/tee/mbedtee/rpc_caller_arm.c | 66 ++
drivers/tee/mbedtee/rpc_caller_riscv.c | 192 ++++++
drivers/tee/mbedtee/shm_pool.c | 105 +++
drivers/tee/mbedtee/shm_pool.h | 15 +
drivers/tee/mbedtee/supp.c | 310 +++++++++
include/uapi/linux/tee.h | 1 +
22 files changed, 3277 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml
create mode 100644 Documentation/tee/mbedtee.rst
create mode 100644 drivers/tee/mbedtee/Kconfig
create mode 100644 drivers/tee/mbedtee/Makefile
create mode 100644 drivers/tee/mbedtee/core.c
create mode 100644 drivers/tee/mbedtee/mbedtee_drv.h
create mode 100644 drivers/tee/mbedtee/mbedtee_msg.h
create mode 100644 drivers/tee/mbedtee/rpc_callee.c
create mode 100644 drivers/tee/mbedtee/rpc_callee_arm.c
create mode 100644 drivers/tee/mbedtee/rpc_callee_riscv.c
create mode 100644 drivers/tee/mbedtee/rpc_caller.c
create mode 100644 drivers/tee/mbedtee/rpc_caller_arm.c
create mode 100644 drivers/tee/mbedtee/rpc_caller_riscv.c
create mode 100644 drivers/tee/mbedtee/shm_pool.c
create mode 100644 drivers/tee/mbedtee/shm_pool.h
create mode 100644 drivers/tee/mbedtee/supp.c
base-commit: 03e2778d1f11de9260543f969e9e888a1c2bf830
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread* [PATCH 1/3] dt-bindings: vendor-prefixes: add mbedtee 2026-07-01 13:25 [PATCH 0/3] tee: add MbedTEE driver Xing Loong @ 2026-07-01 13:25 ` Xing Loong 2026-07-01 13:25 ` [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding Xing Loong ` (2 subsequent siblings) 3 siblings, 0 replies; 15+ messages in thread From: Xing Loong @ 2026-07-01 13:25 UTC (permalink / raw) To: Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong Register the "mbedtee" vendor prefix for the MbedTEE Trusted Execution Environment project (https://github.com/mbedtee). Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 28784d6..fd032a3 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1011,6 +1011,8 @@ patternProperties: description: Maxtor Corporation "^mayqueen,.*": description: Mayqueen Technologies Ltd. + "^mbedtee,.*": + description: MbedTEE Trusted Execution Environment project "^mbvl,.*": description: Mobiveil Inc. "^mcube,.*": -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding 2026-07-01 13:25 [PATCH 0/3] tee: add MbedTEE driver Xing Loong 2026-07-01 13:25 ` [PATCH 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong @ 2026-07-01 13:25 ` Xing Loong 2026-07-01 13:32 ` sashiko-bot ` (2 more replies) 2026-07-01 13:25 ` [PATCH 3/3] tee: add MbedTEE driver Xing Loong 2026-07-02 15:11 ` [PATCH v2 0/3] " Xing Loong 3 siblings, 3 replies; 15+ messages in thread From: Xing Loong @ 2026-07-01 13:25 UTC (permalink / raw) To: Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong Add YAML devicetree binding for the MbedTEE Trusted Execution Environment driver. The binding covers two platform configurations: - ARM/AArch64 (TrustZone, SMC): two reserved-memory regions (rpc-t2r-ring and rpc-t2r-shm) plus a GIC SPI edge interrupt for TEE-to-REE notifications. - RISC-V (IMSIC): three reserved-memory regions, adding rpc-r2t-ring for REE-to-TEE command submissions; no interrupts property (T2R notifications use IMSIC MSI allocated at runtime). Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> --- .../bindings/firmware/mbedtee,rpc.yaml | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml diff --git a/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml b/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml new file mode 100644 index 0000000..08ae255 --- /dev/null +++ b/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml @@ -0,0 +1,221 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/firmware/mbedtee,rpc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MbedTEE Trusted Execution Environment + +maintainers: + - Xing Loong <xing.xl.loong@gmail.com> + +description: | + MbedTEE is a Trusted Execution Environment for embedded systems. + This binding describes the shared-memory regions used for RPC + communication between the Linux REE driver and MbedTEE OS. + + The REE and TEE CPUs sharing the RPC memory must be in a + hardware-coherent domain (same CPU cluster, coherent caches). + + Two or three reserved-memory regions are required: + + rpc-t2r-ring ring buffer for TEE-to-REE notifications (all platforms) + rpc-t2r-shm shared memory for TEE-to-REE RPC payloads (all platforms) + rpc-r2t-ring ring buffer for REE-to-TEE command submissions (RISC-V only) + + On ARM/AArch64 the transport uses SMC calls; TEE-to-REE + notifications use a GIC SPI edge interrupt. + + On RISC-V the TEE notifies the REE via IMSIC MSI; the REE submits + commands via shared-memory rpc-r2t-ring that the TEE polls. No + REE-to-TEE interrupt is used. No SBI ecall is involved. + +properties: + $nodename: + const: mbedtee + + compatible: + const: mbedtee,rpc + + interrupts: + description: + GIC interrupt used by the TEE to notify the REE of pending RPC + responses (ARM/AArch64 only). Not present on RISC-V platforms which + use IMSIC platform MSI interrupts allocated dynamically at runtime. + + msi-parent: + maxItems: 1 + description: + IMSIC MSI controller used by the Linux driver to allocate the + TEE-to-REE notification interrupt on RISC-V platforms. Not present on + ARM/AArch64 platforms, which use the interrupts property. + + memory-region: + minItems: 2 + maxItems: 3 + description: + References to reserved-memory regions for REE<->TEE communication. + Entries must match memory-region-names order. + + memory-region-names: + minItems: 2 + maxItems: 3 + items: + enum: + - rpc-t2r-ring + - rpc-t2r-shm + - rpc-r2t-ring + +required: + - compatible + +allOf: + - if: + required: + - interrupts + then: + required: + - interrupts + - memory-region + - memory-region-names + properties: + msi-parent: false + memory-region: + minItems: 2 + maxItems: 2 + memory-region-names: + items: + - const: rpc-t2r-ring + - const: rpc-t2r-shm + else: + required: + - msi-parent + - memory-region + - memory-region-names + properties: + memory-region: + minItems: 3 + maxItems: 3 + memory-region-names: + items: + - const: rpc-t2r-ring + - const: rpc-t2r-shm + - const: rpc-r2t-ring + +additionalProperties: false + +examples: + - | + /* ARM TrustZone (SMC) */ + #include <dt-bindings/interrupt-controller/arm-gic.h> + / { + #address-cells = <2>; + #size-cells = <2>; + + gic: interrupt-controller@2f000000 { + compatible = "arm,gic-v3"; + reg = <0 0x2f000000 0 0x10000>, + <0 0x2f100000 0 0x200000>; + interrupt-controller; + #interrupt-cells = <3>; + }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + mbedtee_t2r_ring: rpc-t2r-ring@85f10000 { + reg = <0 0x85f10000 0 0x20000>; + no-map; + }; + + mbedtee_t2r_shm: rpc-t2r-shm@85f30000 { + reg = <0 0x85f30000 0 0x40000>; + no-map; + }; + }; + + firmware { + mbedtee { + compatible = "mbedtee,rpc"; + interrupt-parent = <&gic>; + interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>; + memory-region = <&mbedtee_t2r_ring>, <&mbedtee_t2r_shm>; + memory-region-names = "rpc-t2r-ring", "rpc-t2r-shm"; + }; + }; + }; + + - | + /* RISC-V IMSIC (ring-buffer polling REE->TEE, MSI TEE->REE) */ + / { + #address-cells = <2>; + #size-cells = <2>; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + device_type = "cpu"; + reg = <0>; + + cpu0_intc: interrupt-controller { + compatible = "riscv,cpu-intc"; + interrupt-controller; + #interrupt-cells = <1>; + }; + }; + }; + + imsic: interrupt-controller@28000000 { + compatible = "qemu,imsics", "riscv,imsics"; + reg = <0 0x28000000 0 0x1000>; + interrupts-extended = <&cpu0_intc 9>; + interrupt-controller; + #interrupt-cells = <0>; + msi-controller; + #msi-cells = <0>; + riscv,num-ids = <255>; + }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + rv_t2r_ring: rpc-t2r-ring@5f10000 { + reg = <0 0x5f10000 0 0x20000>; + no-map; + }; + + rv_t2r_shm: rpc-t2r-shm@5f30000 { + reg = <0 0x5f30000 0 0x40000>; + no-map; + }; + + rv_r2t_ring: rpc-r2t-ring@5f70000 { + reg = <0 0x5f70000 0 0x20000>; + no-map; + }; + }; + + /* + * The riscv container avoids a duplicate /firmware/mbedtee path + * with the ARM example above when the DT checker concatenates + * examples into a single .dts. + */ + riscv { + firmware { + mbedtee { + compatible = "mbedtee,rpc"; + msi-parent = <&imsic>; + memory-region = <&rv_t2r_ring>, <&rv_t2r_shm>, + <&rv_r2t_ring>; + memory-region-names = "rpc-t2r-ring", "rpc-t2r-shm", + "rpc-r2t-ring"; + }; + }; + }; + }; -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding 2026-07-01 13:25 ` [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding Xing Loong @ 2026-07-01 13:32 ` sashiko-bot 2026-07-01 14:05 ` Krzysztof Kozlowski 2026-07-01 16:39 ` Rob Herring (Arm) 2 siblings, 0 replies; 15+ messages in thread From: sashiko-bot @ 2026-07-01 13:32 UTC (permalink / raw) To: Xing Loong; +Cc: devicetree, conor+dt, robh Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Missing `maxItems` constraint for the `interrupts` property. -- commit ad51309fadfb753784caac099277a6b19357c757 Author: Xing Loong <xing.xl.loong@gmail.com> dt-bindings: firmware: add mbedtee,rpc binding This commit adds the YAML devicetree binding for the MbedTEE Trusted Execution Environment driver. It describes the shared-memory regions used for RPC communication between the Linux REE driver and MbedTEE OS on ARM/AArch64 and RISC-V platforms. > diff --git a/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml b/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml > new file mode 100644 > index 0000000000000..08ae255d32c2a > --- /dev/null > +++ b/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml [ ... ] > +properties: > + $nodename: > + const: mbedtee > + > + compatible: > + const: mbedtee,rpc > + > + interrupts: > + description: > + GIC interrupt used by the TEE to notify the REE of pending RPC > + responses (ARM/AArch64 only). Not present on RISC-V platforms which > + use IMSIC platform MSI interrupts allocated dynamically at runtime. [Severity: Low] Does this property need a maxItems constraint? Without maxItems: 1, device tree validation will not catch configurations that accidentally supply multiple interrupts. > + > + msi-parent: > + maxItems: 1 > + description: > + IMSIC MSI controller used by the Linux driver to allocate the [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260701132514.186953-1-xing.xl.loong@gmail.com?part=2 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding 2026-07-01 13:25 ` [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding Xing Loong 2026-07-01 13:32 ` sashiko-bot @ 2026-07-01 14:05 ` Krzysztof Kozlowski 2026-07-01 16:39 ` Rob Herring (Arm) 2 siblings, 0 replies; 15+ messages in thread From: Krzysztof Kozlowski @ 2026-07-01 14:05 UTC (permalink / raw) To: Xing Loong, Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel On 01/07/2026 15:25, Xing Loong wrote: > Add YAML devicetree binding for the MbedTEE Trusted Execution Drop YAML, there is no such thing as YAML binding. > Environment driver. We don't take bindings for drivers but for hardware or firmware. Please describe these instead. > > The binding covers two platform configurations: > - ARM/AArch64 (TrustZone, SMC): two reserved-memory regions > (rpc-t2r-ring and rpc-t2r-shm) plus a GIC SPI edge interrupt > for TEE-to-REE notifications. > - RISC-V (IMSIC): three reserved-memory regions, adding > rpc-r2t-ring for REE-to-TEE command submissions; no interrupts > property (T2R notifications use IMSIC MSI allocated at runtime). > > Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> > --- > .../bindings/firmware/mbedtee,rpc.yaml | 221 ++++++++++++++++++ > 1 file changed, 221 insertions(+) > create mode 100644 Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml > > diff --git a/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml b/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml > new file mode 100644 > index 0000000..08ae255 > --- /dev/null > +++ b/Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml > @@ -0,0 +1,221 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/firmware/mbedtee,rpc.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: MbedTEE Trusted Execution Environment > + > +maintainers: > + - Xing Loong <xing.xl.loong@gmail.com> > + > +description: | > + MbedTEE is a Trusted Execution Environment for embedded systems. > + This binding describes the shared-memory regions used for RPC Describe firmware, not the binding. It's redundant to say what the binding is about, just say what is the hardware. > + communication between the Linux REE driver and MbedTEE OS. > + > + The REE and TEE CPUs sharing the RPC memory must be in a > + hardware-coherent domain (same CPU cluster, coherent caches). > + > + Two or three reserved-memory regions are required: > + > + rpc-t2r-ring ring buffer for TEE-to-REE notifications (all platforms) > + rpc-t2r-shm shared memory for TEE-to-REE RPC payloads (all platforms) > + rpc-r2t-ring ring buffer for REE-to-TEE command submissions (RISC-V only) > + > + On ARM/AArch64 the transport uses SMC calls; TEE-to-REE > + notifications use a GIC SPI edge interrupt. > + > + On RISC-V the TEE notifies the REE via IMSIC MSI; the REE submits > + commands via shared-memory rpc-r2t-ring that the TEE polls. No > + REE-to-TEE interrupt is used. No SBI ecall is involved. > + > +properties: > + $nodename: > + const: mbedtee Drop, why would it be relevant? > + > + compatible: > + const: mbedtee,rpc Feels way too generic. First, Google results on mbedtee are basically non-existing, so what sort of company is that? Second, rpc is just not specific enough. Please carefully read writing bindings doc. > + > + interrupts: > + description: > + GIC interrupt used by the TEE to notify the REE of pending RPC > + responses (ARM/AArch64 only). Not present on RISC-V platforms which > + use IMSIC platform MSI interrupts allocated dynamically at runtime. Please read writing bindings doc. > + > + msi-parent: > + maxItems: 1 > + description: > + IMSIC MSI controller used by the Linux driver to allocate the Again drivers... > + TEE-to-REE notification interrupt on RISC-V platforms. Not present on > + ARM/AArch64 platforms, which use the interrupts property. > + > + memory-region: > + minItems: 2 > + maxItems: 3 > + description: > + References to reserved-memory regions for REE<->TEE communication. > + Entries must match memory-region-names order. Obvious. Please do not come with your own style of bindings. > + > + memory-region-names: > + minItems: 2 > + maxItems: 3 Why is this flexible? > + items: > + enum: > + - rpc-t2r-ring rpc is redundant, drop > + - rpc-t2r-shm > + - rpc-r2t-ring > + > +required: > + - compatible > + > +allOf: > + - if: > + required: > + - interrupts > + then: > + required: > + - interrupts > + - memory-region > + - memory-region-names > + properties: > + msi-parent: false > + memory-region: > + minItems: 2 > + maxItems: 2 > + memory-region-names: > + items: > + - const: rpc-t2r-ring > + - const: rpc-t2r-shm > + else: > + required: > + - msi-parent > + - memory-region > + - memory-region-names So memory-region is always required? > + properties: > + memory-region: > + minItems: 3 > + maxItems: 3 > + memory-region-names: > + items: > + - const: rpc-t2r-ring > + - const: rpc-t2r-shm > + - const: rpc-r2t-ring Your top level schema said that. You only need minItems. > + > +additionalProperties: false > + > +examples: > + - | > + /* ARM TrustZone (SMC) */ > + #include <dt-bindings/interrupt-controller/arm-gic.h> > + / { > + #address-cells = <2>; > + #size-cells = <2>; > + > + gic: interrupt-controller@2f000000 { > + compatible = "arm,gic-v3"; > + reg = <0 0x2f000000 0 0x10000>, > + <0 0x2f100000 0 0x200000>; > + interrupt-controller; > + #interrupt-cells = <3>; > + }; > + > + reserved-memory { > + #address-cells = <2>; > + #size-cells = <2>; > + ranges; > + > + mbedtee_t2r_ring: rpc-t2r-ring@85f10000 { > + reg = <0 0x85f10000 0 0x20000>; > + no-map; > + }; > + > + mbedtee_t2r_shm: rpc-t2r-shm@85f30000 { > + reg = <0 0x85f30000 0 0x40000>; > + no-map; > + }; > + }; None of the above is relevant, drop. > + > + firmware { > + mbedtee { > + compatible = "mbedtee,rpc"; > + interrupt-parent = <&gic>; > + interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>; > + memory-region = <&mbedtee_t2r_ring>, <&mbedtee_t2r_shm>; > + memory-region-names = "rpc-t2r-ring", "rpc-t2r-shm"; > + }; > + }; > + }; > + Best regards, Krzysztof ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding 2026-07-01 13:25 ` [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding Xing Loong 2026-07-01 13:32 ` sashiko-bot 2026-07-01 14:05 ` Krzysztof Kozlowski @ 2026-07-01 16:39 ` Rob Herring (Arm) 2 siblings, 0 replies; 15+ messages in thread From: Rob Herring (Arm) @ 2026-07-01 16:39 UTC (permalink / raw) To: Xing Loong Cc: Conor Dooley, linux-kernel, devicetree, op-tee, Krzysztof Kozlowski, linux-riscv, Sumit Garg, Jens Wiklander On Wed, 01 Jul 2026 21:25:13 +0800, Xing Loong wrote: > Add YAML devicetree binding for the MbedTEE Trusted Execution > Environment driver. > > The binding covers two platform configurations: > - ARM/AArch64 (TrustZone, SMC): two reserved-memory regions > (rpc-t2r-ring and rpc-t2r-shm) plus a GIC SPI edge interrupt > for TEE-to-REE notifications. > - RISC-V (IMSIC): three reserved-memory regions, adding > rpc-r2t-ring for REE-to-TEE command submissions; no interrupts > property (T2R notifications use IMSIC MSI allocated at runtime). > > Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> > --- > .../bindings/firmware/mbedtee,rpc.yaml | 221 ++++++++++++++++++ > 1 file changed, 221 insertions(+) > create mode 100644 Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml > My bot found errors running 'make dt_binding_check' on your patch: yamllint warnings/errors: dtschema/dtc warnings/errors: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/firmware/mbedtee,rpc.example.dtb: /: 'compatible' is a required property from schema $id: http://devicetree.org/schemas/root-node.yaml /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/firmware/mbedtee,rpc.example.dtb: /: 'model' is a required property from schema $id: http://devicetree.org/schemas/root-node.yaml doc reference errors (make refcheckdocs): See https://patchwork.kernel.org/project/devicetree/patch/20260701132514.186953-3-xing.xl.loong@gmail.com The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema. ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 3/3] tee: add MbedTEE driver 2026-07-01 13:25 [PATCH 0/3] tee: add MbedTEE driver Xing Loong 2026-07-01 13:25 ` [PATCH 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong 2026-07-01 13:25 ` [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding Xing Loong @ 2026-07-01 13:25 ` Xing Loong 2026-07-01 13:42 ` sashiko-bot 2026-07-02 15:11 ` [PATCH v2 0/3] " Xing Loong 3 siblings, 1 reply; 15+ messages in thread From: Xing Loong @ 2026-07-01 13:25 UTC (permalink / raw) To: Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong This patch adds a Linux TEE driver that communicates with MbedTEE OS, a Trusted Execution Environment for embedded systems (https://github.com/mbedtee) via a fixed shared-memory RPC protocol over architecture-specific transports: - ARM/ARM64: SMCCC fast/yield calls for REE-to-TEE requests; TEE-to-REE notifications via a GIC SPI edge interrupt. - RISC-V: REE submits commands by writing to a shared-memory rpc-r2t-ring that the TEE polls; the TEE notifies the REE by writing to rpc-t2r-ring and raising a platform IMSIC MSI. No REE-to-TEE interrupt is used. The driver implements the TEE subsystem interface (tee_driver_ops) and provides GlobalPlatform TEE Client API support, dynamic shared memory registration, and tee-supplicant support. Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> --- Documentation/tee/index.rst | 1 + Documentation/tee/mbedtee.rst | 155 +++++++ MAINTAINERS | 9 + drivers/tee/Kconfig | 3 +- drivers/tee/Makefile | 1 + drivers/tee/mbedtee/Kconfig | 20 + drivers/tee/mbedtee/Makefile | 11 + drivers/tee/mbedtee/core.c | 236 ++++++++++ drivers/tee/mbedtee/mbedtee_drv.h | 269 +++++++++++ drivers/tee/mbedtee/mbedtee_msg.h | 219 +++++++++ drivers/tee/mbedtee/rpc_callee.c | 609 +++++++++++++++++++++++++ drivers/tee/mbedtee/rpc_callee_arm.c | 91 ++++ drivers/tee/mbedtee/rpc_callee_riscv.c | 203 +++++++++ drivers/tee/mbedtee/rpc_caller.c | 539 ++++++++++++++++++++++ drivers/tee/mbedtee/rpc_caller_arm.c | 66 +++ drivers/tee/mbedtee/rpc_caller_riscv.c | 192 ++++++++ drivers/tee/mbedtee/shm_pool.c | 105 +++++ drivers/tee/mbedtee/shm_pool.h | 15 + drivers/tee/mbedtee/supp.c | 310 +++++++++++++ include/uapi/linux/tee.h | 1 + 20 files changed, 3054 insertions(+), 1 deletion(-) create mode 100644 Documentation/tee/mbedtee.rst create mode 100644 drivers/tee/mbedtee/Kconfig create mode 100644 drivers/tee/mbedtee/Makefile create mode 100644 drivers/tee/mbedtee/core.c create mode 100644 drivers/tee/mbedtee/mbedtee_drv.h create mode 100644 drivers/tee/mbedtee/mbedtee_msg.h create mode 100644 drivers/tee/mbedtee/rpc_callee.c create mode 100644 drivers/tee/mbedtee/rpc_callee_arm.c create mode 100644 drivers/tee/mbedtee/rpc_callee_riscv.c create mode 100644 drivers/tee/mbedtee/rpc_caller.c create mode 100644 drivers/tee/mbedtee/rpc_caller_arm.c create mode 100644 drivers/tee/mbedtee/rpc_caller_riscv.c create mode 100644 drivers/tee/mbedtee/shm_pool.c create mode 100644 drivers/tee/mbedtee/shm_pool.h create mode 100644 drivers/tee/mbedtee/supp.c diff --git a/Documentation/tee/index.rst b/Documentation/tee/index.rst index 10c3cec..6415855 100644 --- a/Documentation/tee/index.rst +++ b/Documentation/tee/index.rst @@ -12,3 +12,4 @@ TEE Subsystem amd-tee ts-tee qtee + mbedtee diff --git a/Documentation/tee/mbedtee.rst b/Documentation/tee/mbedtee.rst new file mode 100644 index 0000000..c6fe28b --- /dev/null +++ b/Documentation/tee/mbedtee.rst @@ -0,0 +1,155 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================== +MbedTEE (Trusted Execution Environment) +============================================== + +The MbedTEE driver supports MbedTEE-based TEEs on ARM TrustZone (SMC calls, +GIC SPI notifications) and RISC-V IMSIC (shared-memory polling, MSI +notifications) platforms. + +The driver requires the REE and TEE CPUs that share the RPC shared memory to +be hardware coherent. In practice, the REE/TEE CPUs are expected to be in the +same CPU cluster, with coherent caches and shared visibility for the ring and +payload buffers. + +Communication overview +====================== + +The driver communicates with the TEE using a fixed shared-memory RPC protocol +layered above the architecture-specific call mechanism. + +ARM platforms (SMC) +------------------- + +On ARM and AArch64 platforms, the driver uses ARM SMC Calling Convention +(SMCCC) to issue fast calls and yield calls to the TEE: + +- **Fast calls** (MBEDTEE_RPC_OS_VERSION, MBEDTEE_RPC_SUPPORT_YIELD, + MBEDTEE_RPC_COMPLETE_TEE) are self-contained SMC calls that do + not rely on shared-memory RPC structures. + +- **Yield calls** post the physical address of an ``rpc_cmd`` structure + in shared memory to the TEE. The TEE may issue RPC requests back to the + driver before the call completes; kernel-directed RPCs are handled + directly, while supplicant RPCs (filesystem and RPMB) are forwarded to + tee-supplicant. + +TEE-to-REE notifications on ARM are delivered via a GIC SPI edge interrupt +specified in the device tree (``interrupts`` property). + +RISC-V platforms +---------------- + +On RISC-V platforms, SMCCC is not available. Communication is split into two +directions: + +- **REE to TEE (R2T)**: The driver submits commands by writing the physical + address of an ``rpc_cmd`` structure to a REE-to-TEE ring buffer + (``rpc-r2t-ring``). The TEE polls that ring for new commands. No interrupt + notification is sent; the protocol relies on TEE-side polling. + +- **TEE to REE (T2R)**: The TEE writes an ``rpc_cmd`` into the TEE-to-REE + ring and raises an IMSIC MSI to notify the REE driver. The MSI is + allocated at runtime via the ``msi-parent`` DT property and its identity + is published in ``callee_imsic_id``; ``callee_hartid`` tracks the + target hart for migration during CPU hotplug. No SBI ecall is involved. + +RISC-V REE-to-TEE polling rationale +----------------------------------- + +The REE-to-TEE direction uses polling-only on RISC-V to avoid direct +notification interrupt writes from Linux to TEE-owned interrupt files. +This design is platform-independent and does not require ownership of +TEE-only hart interrupt files. The TEE-to-REE direction remains fully +functional via standard Linux MSI notifications and does not depend on +REE-to-TEE notification latency. + +Shared memory regions +===================== + +Two or three fixed shared memory regions are described in the device tree: + +``rpc-t2r-ring`` + Ring buffer used by the TEE to post RPC request notifications to the REE + driver. Present on all platforms. + +``rpc-t2r-shm`` + Shared memory region carrying the actual ``rpc_cmd`` payloads for + TEE-to-REE RPCs. Present on all platforms. + +``rpc-r2t-ring`` + Ring buffer used by the REE driver to submit commands to the TEE on + RISC-V IMSIC platforms. + +RPC protocol +============ + +The TEE and REE communicate through the ``rpc_cmd`` structure in shared memory +and ring buffers:: + + struct rpc_cmd { + u32 id; /* RPC function ID */ + u16 size; /* payload size in bytes */ + u8 interrupted; /* set if wait was interrupted */ + u8 reserved; /* explicit alignment padding */ + s32 ret; /* return value */ + u32 pad; /* explicit alignment padding */ + u64 waiter_id; /* sync RPC request ID echoed on completion */ + u64 shm; /* physical address of payload (sync RPC) */ + u64 data[]; /* inline payload (async RPC) */ + }; + + struct rpc_ringbuf { + u32 wr; /* producer write pointer */ + u32 rd; /* consumer read pointer */ + u32 callee_ready; /* callee ready flag */ + u32 callee_imsic_id; /* RISC-V only: IMSIC local interrupt id */ + u32 callee_hartid; /* RISC-V only: target hart-id for T2R notification */ + u32 reserved; /* padding, must be zero */ + u8 mem[]; + }; + +For RISC-V T2R MSI, one MSI message targets one hart IMSIC file at a time. +The wire-visible state is split between ``callee_imsic_id`` and +``callee_hartid`` in the ring header; Linux may retarget the MSI across +online CPUs via ``irq_set_affinity()``, and the driver updates those fields +to match the selected hart. + + + +Architecture diagram:: + + User space Kernel TEE side + ~~~~~~~~~~ ~~~~~~ ~~~~~~~~~~~~ + +--------+ +--------------+ + | Client | | Trusted App | + +--------+ +--------------+ + /\ /\ + || +------------+ || + || | MbedTEE- | \/ + || | supplicant | +--------------+ + || +------------+ | TEE Internal | + \/ /\ | API | + +-------+ || +--------------+ + | TEE | || +---------+---------------+ | MbedTEE | + | Client| || | TEE | MbedTEE | | Trusted OS | + | API | \/ | subsys | client driver | +--------------+ + +-------+-------------+-----+--------+----------+----+ | + | Generic TEE API | | RPC (cmd/ring)| | + | IOCTL (TEE_IOC_*) | | SMC / IMSIC | | + +---------------------------+ +---------------+--------------+ + +Device tree binding +=================== + +See Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml for the +complete device tree binding specification, including the RISC-V +``msi-parent`` requirement. + +References +========== + +- [1] MbedTEE project: https://github.com/mbedtee +- [2] ARM SMC Calling Convention: https://developer.arm.com/architectures/system-architectures/software-standards/smccc +- [3] RISC-V IMSIC specification: https://github.com/riscv/riscv-aia diff --git a/MAINTAINERS b/MAINTAINERS index c8d4b91..9393dfb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15920,6 +15920,15 @@ F: Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml F: drivers/net/dsa/mxl862xx/ F: net/dsa/tag_mxl862xx.c +MBEDTEE TEE DRIVER +M: Xing Loong <xing.xl.loong@gmail.com> +L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers) +S: Maintained +W: https://github.com/mbedtee +F: Documentation/devicetree/bindings/firmware/mbedtee,rpc.yaml +F: Documentation/tee/mbedtee.rst +F: drivers/tee/mbedtee/ + MCAN DEVICE DRIVER M: Markus Schneider-Pargmann <msp@baylibre.com> L: linux-can@vger.kernel.org diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index 98c3ad0..3dc3439 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 || RISCV || COMPILE_TEST || CPU_SUP_AMD select CRYPTO_LIB_SHA1 select DMA_SHARED_BUFFER select GENERIC_ALLOCATOR @@ -21,5 +21,6 @@ source "drivers/tee/optee/Kconfig" source "drivers/tee/amdtee/Kconfig" source "drivers/tee/tstee/Kconfig" source "drivers/tee/qcomtee/Kconfig" +source "drivers/tee/mbedtee/Kconfig" endif diff --git a/drivers/tee/Makefile b/drivers/tee/Makefile index 3239b91..33117ea 100644 --- a/drivers/tee/Makefile +++ b/drivers/tee/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_OPTEE) += optee/ obj-$(CONFIG_AMDTEE) += amdtee/ obj-$(CONFIG_ARM_TSTEE) += tstee/ obj-$(CONFIG_QCOMTEE) += qcomtee/ +obj-$(CONFIG_MBEDTEE) += mbedtee/ diff --git a/drivers/tee/mbedtee/Kconfig b/drivers/tee/mbedtee/Kconfig new file mode 100644 index 0000000..a28be27 --- /dev/null +++ b/drivers/tee/mbedtee/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +# MbedTEE Trusted Execution Environment Configuration +config MBEDTEE + tristate "MbedTEE" + depends on MMU && OF + depends on ((ARM || ARM64) && HAVE_ARM_SMCCC) || (RISCV && GENERIC_MSI_IRQ) + help + This enables the MbedTEE Trusted Execution Environment (TEE) + driver. + + The driver supports ARM TrustZone (SMC calls, GIC SPI notifications) + and RISC-V IMSIC (shared-memory polling, MSI notifications), with + GlobalPlatform client API via /dev/tee*. + + The DT node should be under a firmware node and have compatible = + "mbedtee,rpc" with the RPC ring buffer addresses and, + for ARM/ARM64, an interrupt property for TEE-to-REE notifications. + See Documentation/tee/mbedtee.rst for protocol details. + + If unsure, say N. diff --git a/drivers/tee/mbedtee/Makefile b/drivers/tee/mbedtee/Makefile new file mode 100644 index 0000000..309eb6d --- /dev/null +++ b/drivers/tee/mbedtee/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_MBEDTEE) += mbedtee.o + +mbedtee-y += core.o +mbedtee-y += supp.o +mbedtee-y += shm_pool.o +mbedtee-y += rpc_caller.o rpc_callee.o +mbedtee-$(CONFIG_ARM) += rpc_caller_arm.o rpc_callee_arm.o +mbedtee-$(CONFIG_ARM64) += rpc_caller_arm.o rpc_callee_arm.o +mbedtee-$(CONFIG_RISCV) += rpc_caller_riscv.o rpc_callee_riscv.o diff --git a/drivers/tee/mbedtee/core.c b/drivers/tee/mbedtee/core.c new file mode 100644 index 0000000..06ed21b --- /dev/null +++ b/drivers/tee/mbedtee/core.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/tee_drv.h> +#include <linux/types.h> + +#include "mbedtee_drv.h" +#include "shm_pool.h" + +int mbedtee_get_resource(struct device_node *node, + const char *name, struct resource *res) +{ + struct device_node *rmem; + int idx; + int ret; + + idx = of_property_match_string(node, "memory-region-names", name); + if (idx < 0) + return idx; + + rmem = of_parse_phandle(node, "memory-region", idx); + if (!rmem) + return -ENODEV; + + ret = of_address_to_resource(rmem, 0, res); + of_node_put(rmem); + + if (ret) + return ret; + + res->name = name; + return 0; +} + +static void mbedtee_get_version(struct tee_device *teedev, + struct tee_ioctl_version_data *vers) +{ + struct tee_ioctl_version_data v = { + .impl_id = TEE_IMPL_ID_MBEDTEE, + .impl_caps = 0, + .gen_caps = TEE_GEN_CAP_GP | TEE_GEN_CAP_REG_MEM | + TEE_GEN_CAP_MEMREF_NULL, + }; + + *vers = v; +} + +static int mbedtee_open(struct tee_context *ctx) +{ + struct mbedtee_context_data *ctxdata; + + ctxdata = kzalloc_obj(*ctxdata, GFP_KERNEL); + if (!ctxdata) + return -ENOMEM; + + mutex_init(&ctxdata->mutex); + INIT_LIST_HEAD(&ctxdata->sess_list); + + ctx->data = ctxdata; + ctx->cap_memref_null = true; + + return 0; +} + +static void mbedtee_release(struct tee_context *ctx) +{ + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_context_data *d = ctx->data; + struct mbedtee_session *s, *n; + + if (!d) + return; + + if (d->is_supp_ctx) { + mbedtee_supp_release(&mbedtee->supp, ctx); + } else { + list_for_each_entry_safe(s, n, &d->sess_list, list_node) + mbedtee_close_session(ctx, s->session_id); + } + + kfree(d); + ctx->data = NULL; +} + +static const struct tee_driver_ops mbedtee_ops = { + .get_version = mbedtee_get_version, + .open = mbedtee_open, + .release = mbedtee_release, + .open_session = mbedtee_open_session, + .close_session = mbedtee_close_session, + .invoke_func = mbedtee_invoke_func, + .cancel_req = mbedtee_cancel_req, + .supp_recv = mbedtee_supp_recv, + .supp_send = mbedtee_supp_send, + .shm_register = mbedtee_shm_register, + .shm_unregister = mbedtee_shm_unregister, +}; + +static const struct tee_desc mbedtee_desc = { + .name = "mbedtee", + .ops = &mbedtee_ops, + .owner = THIS_MODULE, +}; + +static int mbedtee_probe(struct platform_device *pdev) +{ + int ret; + long version; + long yield; + struct tee_shm_pool *pool; + struct tee_device *teedev; + struct mbedtee_device *mbedtee; + + mbedtee = devm_kzalloc(&pdev->dev, sizeof(*mbedtee), GFP_KERNEL); + if (!mbedtee) + return -ENOMEM; + + mbedtee->dev = &pdev->dev; + xa_init_flags(&mbedtee->rpc_calls, XA_FLAGS_ALLOC1); + mbedtee_supp_init(&mbedtee->supp); + + /* + * R2T must be ready before T2R is advertised: synchronous T2R RPC + * work replies with COMPLETE_TEE over the R2T path. + */ + ret = mbedtee_r2t_init(mbedtee); + if (ret != 0) + goto err_supp; + + ret = mbedtee_rpc_init(mbedtee); + if (ret != 0) + goto err_r2t; + + version = mbedtee_rpc_fastcall(mbedtee, MBEDTEE_RPC_OS_VERSION, 0, 0, 0); + if (version < 0) { + dev_err(&pdev->dev, "MBEDTEE_RPC_OS_VERSION failed: %ld\n", version); + ret = version; + goto err_rpc; + } + + if (!MBEDTEE_VALID_VERSION(version)) { + dev_err(&pdev->dev, "mbedtee not present (version=0x%lx)\n", + version); + ret = -ENODEV; + goto err_rpc; + } + + yield = mbedtee_rpc_fastcall(mbedtee, MBEDTEE_RPC_SUPPORT_YIELD, 0, 0, 0); + dev_info(&pdev->dev, "version: 0x%06lx yield: %ld\n", version, yield); + + pool = mbedtee_shm_pool_alloc_pages(); + if (IS_ERR(pool)) { + ret = PTR_ERR(pool); + goto err_rpc; + } + + teedev = tee_device_alloc(&mbedtee_desc, &pdev->dev, pool, mbedtee); + if (IS_ERR(teedev)) { + ret = PTR_ERR(teedev); + goto err_pool; + } + + ret = tee_device_register(teedev); + if (ret) + goto err_teedev; + + mbedtee->teedev = teedev; + mbedtee->version = version; + mbedtee->yield = !!yield; + mbedtee->pool = pool; + platform_set_drvdata(pdev, mbedtee); + + dev_dbg(&pdev->dev, "client initialized\n"); + return 0; + +err_teedev: + tee_device_unregister(teedev); +err_pool: + tee_shm_pool_free(pool); +err_rpc: + mbedtee_rpc_uninit(mbedtee); +err_r2t: + mbedtee_r2t_uninit(mbedtee); +err_supp: + mbedtee_supp_uninit(&mbedtee->supp); + xa_destroy(&mbedtee->rpc_calls); + return ret; +} + +static void mbedtee_remove(struct platform_device *pdev) +{ + struct mbedtee_device *mbedtee = platform_get_drvdata(pdev); + + tee_device_unregister(mbedtee->teedev); + /* + * Drain T2R before tearing down R2T: queued RPC work may still need + * to send COMPLETE_TEE over R2T. Destroy the supplicant mutex only + * after RPC work can no longer enter mbedtee_supp_handler(). + */ + mbedtee_rpc_uninit(mbedtee); + mbedtee_r2t_uninit(mbedtee); + mbedtee_supp_uninit(&mbedtee->supp); + tee_shm_pool_free(mbedtee->pool); + xa_destroy(&mbedtee->rpc_calls); +} + +static const struct of_device_id mbedtee_dt_match[] = { + { .compatible = "mbedtee,rpc" }, + { }, +}; +MODULE_DEVICE_TABLE(of, mbedtee_dt_match); + +static struct platform_driver mbedtee_driver = { + .probe = mbedtee_probe, + .remove = mbedtee_remove, + .driver = { + .name = "mbedtee", + .of_match_table = mbedtee_dt_match, + }, +}; +module_platform_driver(mbedtee_driver); + +MODULE_AUTHOR("Xing Loong <xing.xl.loong@gmail.com>"); +MODULE_DESCRIPTION("MbedTEE Trusted Execution Environment driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tee/mbedtee/mbedtee_drv.h b/drivers/tee/mbedtee/mbedtee_drv.h new file mode 100644 index 0000000..59b5b27 --- /dev/null +++ b/drivers/tee/mbedtee/mbedtee_drv.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#ifndef MBEDTEE_DRV_H +#define MBEDTEE_DRV_H + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/atomic.h> +#include <linux/of.h> +#include <linux/ioport.h> +#include <linux/tee_drv.h> +#include <linux/tee_core.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/msi.h> +#include <linux/workqueue.h> +#include <linux/cpuhotplug.h> +#include <linux/cpumask.h> +#include <linux/xarray.h> +#include "mbedtee_msg.h" +#include "shm_pool.h" + +/* + * GlobalPlatform TEE Client API return codes/origins + * used by this driver. Only the codes actually + * referenced in the driver are defined here. + */ +#define TEEC_SUCCESS 0x00000000 +#define TEEC_ERROR_OUT_OF_MEMORY 0xFFFF000C +#define TEEC_ERROR_COMMUNICATION 0xFFFF000E + +#define TEEC_ORIGIN_COMMS 0x00000002 + +#define MBEDTEE_VERSION_MAJOR 1 +#define MBEDTEE_VALID_VERSION(x) (((x) >> 16) == MBEDTEE_VERSION_MAJOR) + +/* Maximum payload size for inline (async) RPC data. */ +#define MBEDTEE_ASYNC_RPC_DATA_MAX 256 + +struct rpc_transport_ops; +struct mbedtee_rpc_call; +struct mbedtee_supp_req; +struct mbedtee_device; + +struct rpc_work { + struct work_struct work; + struct mbedtee_device *mbedtee; + u64 waiter_id; + size_t size; + bool complete_only; + /* Points to ctx->rpc_data (async) or into t2r_shm (sync) */ + void *data; + void (*func)(struct mbedtee_device *mbedtee, void *data, size_t size); +}; + +struct mbedtee_t2r_ctx { + spinlock_t ring_lock; /* protects T2R ring producer/consumer state */ + void *t2r_shm; + phys_addr_t t2r_shm_phys; + resource_size_t t2r_shm_sz; + struct rpc_ringbuf *t2r_ring; + u32 t2r_ring_sz; + u32 t2r_ring_rd; + + void (*rpc_handlers[MBEDTEE_RPC_MAX])(struct mbedtee_device *mbedtee, + void *data, size_t size); + + /* + * Inline buffer for async RPC payloads (waiter_id == 0). + * Sync RPC payloads (waiter_id != 0) live in shared memory (t2r_shm). + */ + u8 rpc_data[MBEDTEE_ASYNC_RPC_DATA_MAX]; + + /* + * CPU-hotplug tracking for T2R interrupt delivery. + * callee_cpus: shadow of cpu_online_mask maintained by the core + * callee layer (rpc_callee.c) for both ARM and RISC-V; used by + * ARM to feed irq_set_affinity() and by RISC-V as the gate for + * MSI migration decisions. + * callee_virq: single MSI virq currently pinned to one CPU + * (RISC-V IMSIC only; 0 for ARM). + * hp_node: per-instance node for cpuhp_setup_state_multi(). + */ + struct cpumask callee_cpus; + int callee_virq; + struct hlist_node hp_node; + bool cpuhp_added; + bool complete_work_pending; + bool pending_async; + u32 pending_size; + struct workqueue_struct *rpc_wq; + struct rpc_work complete_work; + void (*pending_func)(struct mbedtee_device *mbedtee, + void *data, size_t size); +}; + +struct mbedtee_r2t_ctx { + spinlock_t lock; /* protects R2T ring write-side state */ + struct rpc_ringbuf *ring; + u32 ring_sz; + u32 ring_wr; +}; + +struct mbedtee_rpc_transport_ctx { + int rpc_notify_virq; + struct msi_msg rpc_msi_msg; +}; + +struct mbedtee_supp { + struct mutex mutex; /* serializes supp request queue and active requests */ + struct tee_context *ctx; + bool shutting_down; + struct list_head reqs; + struct list_head active_reqs; + struct completion reqs_c; +}; + +struct mbedtee_device { + u32 version; + bool yield; + struct device *dev; + struct tee_device *teedev; + struct tee_shm_pool *pool; + struct mbedtee_supp supp; + struct xarray rpc_calls; + atomic_long_t rpc_call_seq; + const struct rpc_transport_ops *rpc_ops; + struct mbedtee_t2r_ctx t2r; + struct mbedtee_r2t_ctx r2t; + struct mbedtee_rpc_transport_ctx transport; +}; + +struct mbedtee_rpc_call { + struct completion c; + phys_addr_t rpc_phys; + u32 state; + /* + * Wire-format rpc_cmd with inline data[] payload. + * MUST be the last field: rpc.data[] is a flexible array. + */ + struct rpc_cmd rpc; +}; + +struct mbedtee_session { + struct list_head list_node; + u32 session_id; +}; + +struct mbedtee_context_data { + bool is_supp_ctx; + struct mutex mutex; /* serializes session list updates */ + struct list_head sess_list; +}; + +static inline phys_addr_t mbedtee_virt_to_phys(void *va) +{ + if (is_vmalloc_or_module_addr(va)) + return page_to_phys(vmalloc_to_page(va)) + + offset_in_page(va); + + return virt_to_phys(va); +} + +int mbedtee_get_resource(struct device_node *node, + const char *name, struct resource *res); + +/* + * RPC transport operations -- implemented per-architecture. + * + * Caller (rpc_caller_{arm,riscv}.c) platform differences: + * fastcall: ARM/ARM64 - direct SMC (arm_smccc_smc), synchronous, + * never returns -ENOSPC. + * RISC-V - writes rpc_cmd phys addr to the r2t ring + * buffer, then waits for TEE completion via the t2r + * ring. Despite the "fastcall" name this is a + * synchronous ring-buffer round-trip; it may return + * -ENOSPC when the r2t ring is full, and the caller + * must retry (see rpc_routine() for the + * MBEDTEE_RPC_COMPLETE_TEE retry loop). + * yieldcall: ARM/ARM64 - one SMC posting rpc_cmd phys addr, waits + * for completion. + * RISC-V - writes rpc_cmd phys addr to r2t ring, waits + * for completion. + * Callee (rpc_callee_{arm,riscv}.c) platform differences: + * notify: ARM/ARM64 - GIC SPI; the TEE broadcasts via GIC + * hardware (GICv2 ITARGETS=0xFF, GICv3 IROUTER.IRM=1). + * Ring metadata unchanged on CPU affinity events. + * RISC-V - IMSIC MSI; the driver writes callee_hartid + * and callee_imsic_id into the ring so the TEE knows + * where to send the MSI. The affinity handler updates + * both ring fields when the callee CPU goes offline. + * + * init: Set up the interrupt delivery mechanism (MSI on RISC-V, + * GIC/OF-IRQ on ARM/ARM64) and, on RISC-V, write + * ring->callee_hartid and ring->callee_imsic_id. @handler + * is the hard-IRQ handler that drains the ring buffer and + * dispatches RPC commands. + * uninit: Tear down and free all interrupt resources. + * update_affinity: Migrate T2R interrupt delivery to @new_cpu. Called by the + * common cpuhp handler when the current callee CPU goes + * offline. On RISC-V IMSIC, must update ring->callee_hartid + * and ring->callee_imsic_id. ARM uses GIC hardware routing + * and requires no ring update. + * + * The getter is implemented per-architecture in rpc_callee_{arm,riscv}.c; + * only one is linked per build. + */ +struct rpc_transport_ops { + int (*init)(struct mbedtee_device *mbedtee, struct rpc_ringbuf *ring, + irq_handler_t handler); + void (*uninit)(struct mbedtee_device *mbedtee); + int (*update_affinity)(struct mbedtee_device *mbedtee, + unsigned int new_cpu); +}; + +const struct rpc_transport_ops *mbedtee_get_rpc_transport_ops(void); + +/* rpc-callee.c */ +int mbedtee_rpc_init(struct mbedtee_device *mbedtee); +void mbedtee_rpc_uninit(struct mbedtee_device *mbedtee); +irqreturn_t mbedtee_rpc_irq_handler(int irq, void *dev_id); + +/* rpc_caller.c */ +int mbedtee_rpc_call_alloc(struct mbedtee_device *mbedtee, + size_t payload_size, struct mbedtee_rpc_call **call); +void mbedtee_rpc_call_free(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call); +void mbedtee_rpc_complete_call(struct mbedtee_device *mbedtee, + u64 waiter_id); +long mbedtee_rpc_wait_for_completion(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call, bool killable); +int mbedtee_open_session(struct tee_context *ctx, + struct tee_ioctl_open_session_arg *arg, + struct tee_param *param); +int mbedtee_close_session(struct tee_context *ctx, u32 session); +int mbedtee_invoke_func(struct tee_context *ctx, + struct tee_ioctl_invoke_arg *arg, + struct tee_param *param); +int mbedtee_cancel_req(struct tee_context *ctx, u32 cancel_id, + u32 session); +int mbedtee_shm_register(struct tee_context *ctx, struct tee_shm *shm, + struct page **pages, size_t num_pages, unsigned long start); +int mbedtee_shm_unregister(struct tee_context *ctx, struct tee_shm *shm); + +/* rpc-caller-{arm,riscv}.c */ +long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee, + unsigned long fn, struct mbedtee_rpc_call *call, + bool interruptible); +long mbedtee_rpc_fastcall(struct mbedtee_device *mbedtee, + unsigned long fn, unsigned long a0, + unsigned long a1, unsigned long a2); +int mbedtee_r2t_init(struct mbedtee_device *mbedtee); +void mbedtee_r2t_uninit(struct mbedtee_device *mbedtee); + +/* supp.c */ +void mbedtee_supp_init(struct mbedtee_supp *supp); +void mbedtee_supp_uninit(struct mbedtee_supp *supp); +void mbedtee_supp_release(struct mbedtee_supp *supp, struct tee_context *ctx); +void mbedtee_supp_abort_all(struct mbedtee_supp *supp); +void mbedtee_supp_handler(struct mbedtee_device *mbedtee, + u32 func, void *data, size_t size); +int mbedtee_supp_recv(struct tee_context *ctx, u32 *func, + u32 *num_params, struct tee_param *param); +int mbedtee_supp_send(struct tee_context *ctx, u32 ret, + u32 num_params, struct tee_param *param); + +#endif /* MBEDTEE_DRV_H */ diff --git a/drivers/tee/mbedtee/mbedtee_msg.h b/drivers/tee/mbedtee/mbedtee_msg.h new file mode 100644 index 0000000..deb2d8c --- /dev/null +++ b/drivers/tee/mbedtee/mbedtee_msg.h @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * + * MbedTEE RPC wire protocol between REE (Linux) and TEE (MbedTEE OS). + * + * This header is the Linux-kernel mirror of + * mbedtee-os/drivers/rpc/include/rpc/rpc.h. Both copies MUST define + * identical struct layouts. The static_assert() checks at the bottom + * of this file (and the _Static_assert() checks in rpc.h) enforce this + * contract at compile time on every architecture. + * + * Type mapping (wire -> Linux kernel alias): + * uint8_t -> u8 -- boolean flag / padding byte + * uint16_t -> u16 -- byte-count field (< 64 KiB payload limit) + * uint32_t -> u32 -- 32-bit ID, counter, flag, or enum + * int32_t -> s32 -- function-defined signed result code + * uint64_t -> u64 -- physical address, kernel pointer token, or size + * + * Using u64 for all pointer-sized fields eliminates ABI width mismatches + * in mixed-width configurations (e.g. RV64 REE + RV32 TEE, or ARM32-LPAE + * REE + ARM32 TEE). It also prevents phys_addr_t/unsigned-long confusion + * when CONFIG_PHYS_ADDR_T_64BIT=y on a 32-bit REE kernel. + */ + +#ifndef MBEDTEE_MSG_H +#define MBEDTEE_MSG_H + +#include <linux/bits.h> +#include <linux/build_bug.h> +#include <linux/types.h> + +/* + * Fastcall / yield-call classification + */ +#define MBEDTEE_RPC_FASTCALL BIT(31) +#define MBEDTEE_RPC_IS_FASTCALL(fn) ((fn) & MBEDTEE_RPC_FASTCALL) + +/* + * REE -> TEE fast calls + */ +#define MBEDTEE_RPC_OS_VERSION (0x0100 | MBEDTEE_RPC_FASTCALL) +#define MBEDTEE_RPC_SUPPORT_YIELD (0x0101 | MBEDTEE_RPC_FASTCALL) +#define MBEDTEE_RPC_COMPLETE_TEE (0x0102 | MBEDTEE_RPC_FASTCALL) + +/* + * REE -> TEE yield calls + */ +#define MBEDTEE_RPC_OPEN_SESSION 1 +#define MBEDTEE_RPC_INVOKE_SESSION 2 +#define MBEDTEE_RPC_CLOSE_SESSION 3 +#define MBEDTEE_RPC_REGISTER_SHM 5 +#define MBEDTEE_RPC_UNREGISTER_SHM 6 +#define MBEDTEE_RPC_CANCEL 7 + +/* + * TEE -> REE RPC calls + */ +#define MBEDTEE_RPC_COMPLETE_REE 0 +#define MBEDTEE_RPC_REETIME 1 +#define MBEDTEE_RPC_REEFS 2 +#define MBEDTEE_RPC_RPMB 3 +#define MBEDTEE_RPC_MAX 4 + +/* + * Supplicant types (TEE -> REE -> tee-supplicant) + */ +#define MBEDTEE_SUPP_REEFS 1 +#define MBEDTEE_SUPP_RPMB 2 + +/* MbedTEE RPC protocol uses 4 KiB page units regardless of host PAGE_SIZE. */ +#define MBEDTEE_PAGE_SIZE 4096UL + +/* + * REE <-> TEE RPC call command (wire format, 32 bytes fixed header). + * + * Fixed layout: + * +0 id u32 - RPC function identifier + * +4 size u16 - inline payload byte count (0..65535) + * +6 interrupted u8 - set by REE when caller is interrupted + * +7 reserved u8 - must be zero (explicit alignment pad) + * +8 ret s32 - return value written by callee + * +12 pad u32 - must be zero (explicit alignment pad) + * +16 waiter_id u64 - sync RPC request ID echoed on completion + * +24 shm u64 - physical address of sync-RPC shared memory + * +32 data[] u64 - inline payload (waiter_id==0) or empty + * + * Yield-call contract: + * - Session/control RPCs return GlobalPlatform result codes here. + * - Host-local errno values must be translated before being put on the wire. + * - Fast calls may use function-specific return values. + */ +struct rpc_cmd { + u32 id; + u16 size; + u8 interrupted; + u8 reserved; + s32 ret; + u32 pad; + u64 waiter_id; + u64 shm; + u64 data[]; +}; + +/* + * REE -> TEE pages for GP shared memory. + * + * All fields are u64 so the layout is identical when REE is 64-bit + * but TEE is 32-bit (physical addresses, sizes and counts use 64 bits). + */ +struct rpc_memref { + u64 id; + u64 pages; + u64 offset; + u64 size; + u64 cnt; +}; + +/* + * REE -> TEE parameter entry: value or shared memory reference. + */ +union rpc_tee_param { + struct rpc_memref memref; + + struct { + u32 a; + u32 b; + } value; +}; + +/* + * REE -> TEE parameters for RPC session operations + * (MBEDTEE_RPC_OPEN_SESSION / MBEDTEE_RPC_INVOKE_SESSION / + * MBEDTEE_RPC_CLOSE_SESSION). + */ +struct rpc_param { + s32 session_id; + u32 cmd_id; + u32 ret_origin; + u32 params_type; + union rpc_tee_param params[4]; + u8 uuid[16]; + u8 clnt_uuid[16]; +}; + +/* + * REE -> TEE cancellation request (MBEDTEE_RPC_CANCEL). + */ +struct rpc_cancel_req { + u32 session_id; + u32 cancel_id; +}; + +/* + * TEE <-> REE supplicant payloads for REEFS and RPMB RPCs. + * Only the fixed headers are interpreted in the Linux driver. + */ +/* + * Common supplicant command header. Both reefs_cmd and rpmb_cmd embed this + * as their first member so the kernel can write a status code through a + * single cast to (struct supp_cmd_hdr *) without caring about the full + * payload type. The wire layout is identical on every architecture. + */ +struct supp_cmd_hdr { + s32 ret; + s32 op; +}; + +struct reefs_cmd { + struct supp_cmd_hdr hdr; + s32 flags; + s32 fd; + u64 len; + u8 data[]; +}; + +struct rpmb_cmd { + struct supp_cmd_hdr hdr; + u32 nframes; + u8 data[]; +}; + +/* + * TEE <-> REE RPC ring buffer header (24 bytes fixed). + */ +struct rpc_ringbuf { + u32 wr; /* producer write pointer */ + u32 rd; /* consumer read pointer */ + u32 callee_ready; /* callee ready flag */ + u32 callee_imsic_id; /* RISC-V only: IMSIC local interrupt id */ + u32 callee_hartid; /* RISC-V only: target hart-id for T2R notification */ + u32 reserved; /* padding, must be zero */ + u8 mem[]; +}; + +/* + * Compile-time ABI layout assertions. + * These must match the _Static_assert() checks in mbedtee-os rpc/rpc.h. + */ +static_assert(sizeof(struct rpc_cmd) == 32, + "rpc_cmd wire size mismatch"); +static_assert(offsetof(struct rpc_cmd, ret) == 8, + "rpc_cmd.ret offset mismatch"); +static_assert(offsetof(struct rpc_cmd, waiter_id) == 16, + "rpc_cmd.waiter_id offset mismatch"); +static_assert(offsetof(struct rpc_cmd, shm) == 24, + "rpc_cmd.shm offset mismatch"); +static_assert(offsetof(struct rpc_cmd, data) == 32, + "rpc_cmd.data offset mismatch"); +static_assert(sizeof(struct rpc_memref) == 40, + "rpc_memref wire size mismatch"); +static_assert(sizeof(struct rpc_ringbuf) == 24, + "rpc_ringbuf header size mismatch"); +static_assert(sizeof(struct rpc_param) == 208, + "rpc_param wire size mismatch"); +static_assert(sizeof(struct rpc_cancel_req) == 8, + "rpc_cancel_req wire size mismatch"); + +#endif /* MBEDTEE_MSG_H */ diff --git a/drivers/tee/mbedtee/rpc_callee.c b/drivers/tee/mbedtee/rpc_callee.c new file mode 100644 index 0000000..f0b4a5e --- /dev/null +++ b/drivers/tee/mbedtee/rpc_callee.c @@ -0,0 +1,609 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * TEE->REE callee-side: handles interrupts from TEE, processes RPC requests. + */ +#include <linux/cpu.h> +#include <linux/cpuhotplug.h> +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/tee_drv.h> +#include <linux/workqueue.h> + +#include "mbedtee_drv.h" + +/* + * CPU hotplug state shared across all mbedtee instances (registered once). + */ +static int mbedtee_cpuhp_state = -EINVAL; +static unsigned int mbedtee_cpuhp_instances; +static DEFINE_MUTEX(mbedtee_cpuhp_mutex); +/* rpc_routine() runs on workqueue, re-drains ring on completion */ +static void rpc_routine(struct work_struct *work); + +/* + * Common CPU-online handler: add the new CPU into the eligible T2R + * delivery set and let the transport re-apply affinity. + */ +static int mbedtee_cpu_online(unsigned int cpu, struct hlist_node *node) +{ + struct mbedtee_t2r_ctx *ctx = + hlist_entry(node, struct mbedtee_t2r_ctx, hp_node); + struct mbedtee_device *mbedtee = + container_of(ctx, struct mbedtee_device, t2r); + unsigned int target_cpu; + + if (!ctx->t2r_ring || cpumask_test_cpu(cpu, &ctx->callee_cpus)) + return 0; + + cpumask_set_cpu(cpu, &ctx->callee_cpus); + target_cpu = cpumask_first(&ctx->callee_cpus); + + if (mbedtee->rpc_ops && mbedtee->rpc_ops->update_affinity) + mbedtee->rpc_ops->update_affinity(mbedtee, target_cpu); + + return 0; +} + +/* + * Common CPU-offline handler: migrates T2R interrupt delivery to another + * online CPU when the current callee CPU is being taken offline. + * + * The transport-specific update_affinity() callback handles the actual + * interrupt migration (e.g. irq_set_affinity + callee_hartid update for + * RISC-V IMSIC; irq_set_affinity only for ARM - no ring update needed). + */ +static int mbedtee_cpu_offline(unsigned int cpu, struct hlist_node *node) +{ + struct mbedtee_t2r_ctx *ctx = + hlist_entry(node, struct mbedtee_t2r_ctx, hp_node); + struct mbedtee_device *mbedtee = + container_of(ctx, struct mbedtee_device, t2r); + unsigned int new_cpu; + + if (!ctx->t2r_ring || !cpumask_test_cpu(cpu, &ctx->callee_cpus)) + return 0; + + /* + * Remove the dying CPU from the eligible set. If the set is now + * empty (all CPUs going away at once -- highly unlikely but safe), + * fall back to CPU 0 which Linux guarantees to be the last offline. + */ + cpumask_clear_cpu(cpu, &ctx->callee_cpus); + if (cpumask_empty(&ctx->callee_cpus)) + cpumask_set_cpu(0, &ctx->callee_cpus); + + new_cpu = cpumask_first(&ctx->callee_cpus); + + if (mbedtee->rpc_ops && mbedtee->rpc_ops->update_affinity) + mbedtee->rpc_ops->update_affinity(mbedtee, new_cpu); + return 0; +} + +static inline size_t t2r_available_size(struct mbedtee_t2r_ctx *ctx) +{ + u32 wr; + u32 rd; + u32 shm_size = ctx->t2r_ring_sz; + + /* Pair with producer release store after writing ring payload. */ + wr = smp_load_acquire(&ctx->t2r_ring->wr); + rd = READ_ONCE(ctx->t2r_ring_rd); + + if (wr > shm_size) + return 0; + + if (wr >= rd) + return wr - rd; + return shm_size + wr - rd; +} + +static void t2r_ring_copy(struct mbedtee_t2r_ctx *ctx, + void *data, size_t size, u32 rd) +{ + u32 remain; + u32 shm_size = ctx->t2r_ring_sz; + struct rpc_ringbuf *shm = ctx->t2r_ring; + + if (rd + size <= shm_size) { + memcpy(data, &shm->mem[rd], size); + } else { + remain = rd + size - shm_size; + memcpy(data, &shm->mem[rd], size - remain); + memcpy((u8 *)data + size - remain, + &shm->mem[0], remain); + } +} + +static void t2r_ring_advance(struct mbedtee_t2r_ctx *ctx, size_t size) +{ + u32 rd = READ_ONCE(ctx->t2r_ring_rd); + u32 shm_size = ctx->t2r_ring_sz; + struct rpc_ringbuf *shm = ctx->t2r_ring; + + if (rd + size > shm_size) + rd = rd + size - shm_size; + else + rd += size; + + WRITE_ONCE(ctx->t2r_ring_rd, rd); + /* Publish updated consumer index after command parsing is complete. */ + smp_store_release(&shm->rd, rd); +} + +/* + * Read and consume @size bytes from the TEE-to-REE ring buffer. + * Must be called with ctx->ring_lock held. + */ +static void t2r_ring_read(struct mbedtee_t2r_ctx *ctx, + void *data, size_t size) +{ + t2r_ring_copy(ctx, data, size, READ_ONCE(ctx->t2r_ring_rd)); + t2r_ring_advance(ctx, size); +} + +static bool rpc_queue_complete_only(struct mbedtee_t2r_ctx *ctx, + u64 waiter_id) +{ + if (ctx->complete_work_pending) + return false; + + ctx->complete_work_pending = true; + ctx->complete_work.waiter_id = waiter_id; + queue_work(ctx->rpc_wq, &ctx->complete_work.work); + + return true; +} + +/* + * Try to pick the next RPC command from the ring buffer. + * + * For asynchronous RPCs the data payload follows the cmd header in the + * ring. If the payload has not fully arrived yet the cmd header is still + * consumed (it was already read) but the entry is marked incomplete so + * that the next IRQ resumes reading the payload instead of re-reading + * a header. + * + * For synchronous RPCs (waiter_id != 0) the work descriptor is allocated + * BEFORE the ring header is consumed. This guarantees that if the + * allocator returns NULL the ring entry is left intact so the IRQ + * handler will retry on the next wake-up rather than leaving a TEE + * thread blocked with no response. + * + * Returns a work descriptor on success, or NULL when no complete entry + * is available. + */ +static struct rpc_work *rpc_pick_next(struct mbedtee_device *mbedtee, + struct mbedtee_t2r_ctx *ctx, struct rpc_work *c) +{ + struct rpc_cmd cmd; + void (*func)(struct mbedtee_device *mbedtee, void *data, size_t size); + struct rpc_work *new_work = NULL; + resource_size_t off; + phys_addr_t shm_phys; + u64 shm_wire; + + /* Resume reading payload of a previously incomplete async RPC */ + if (ctx->pending_async) { + if (t2r_available_size(ctx) < ctx->pending_size) + return NULL; + ctx->pending_async = false; + t2r_ring_read(ctx, c->data, ctx->pending_size); + c->func = ctx->pending_func; + c->size = ctx->pending_size; + c->waiter_id = 0; + return c; + } + + if (t2r_available_size(ctx) < sizeof(cmd)) + return NULL; + + /* + * Peek at the header without advancing the ring pointer. + * For sync RPCs we must pre-allocate before consuming the entry. + */ + memset(&cmd, 0, sizeof(cmd)); + t2r_ring_copy(ctx, &cmd, sizeof(cmd), READ_ONCE(ctx->t2r_ring_rd)); + + if (cmd.id >= MBEDTEE_RPC_MAX) + goto skip; + + func = ctx->rpc_handlers[cmd.id]; + if (!func) + goto skip; + + if (cmd.waiter_id == 0) { + /* Async RPC: payload follows header in the ring */ + if (cmd.size > sizeof(ctx->rpc_data)) + goto skip; + + /* Consume the header now that we know it is valid */ + t2r_ring_advance(ctx, sizeof(cmd)); + + if (t2r_available_size(ctx) < cmd.size) { + /* Mark as incomplete, resume on next iteration */ + ctx->pending_async = true; + ctx->pending_size = cmd.size; + ctx->pending_func = func; + return NULL; + } + + t2r_ring_read(ctx, c->data, cmd.size); + } else { + /* Sync RPC: data lives in the shared memory region */ + shm_wire = cmd.shm; + shm_phys = (phys_addr_t)shm_wire; + if ((u64)shm_phys != shm_wire) + goto skip; + + if (shm_phys < ctx->t2r_shm_phys) + goto skip; + + off = shm_phys - ctx->t2r_shm_phys; + if (cmd.size == 0 || cmd.size > ctx->t2r_shm_sz) + goto skip; + + if (off > ctx->t2r_shm_sz - cmd.size) + goto skip; + + new_work = kzalloc_obj(*new_work, GFP_ATOMIC); + if (!new_work) + return NULL; + + t2r_ring_advance(ctx, sizeof(cmd)); + + c = new_work; + c->mbedtee = mbedtee; + c->data = ctx->t2r_shm + off; + } + + c->func = func; + c->waiter_id = cmd.waiter_id; + c->size = cmd.size; + + return c; + +skip: + /* + * If this was a sync RPC (waiter_id != 0) we must notify the TEE + * thread so it does not wait forever. Defer the completion fastcall + * to process context because transport implementations may sleep. + * If the single deferred-completion slot is already in use, leave the + * header in place and retry after the pending completion has drained. + */ + if (cmd.waiter_id != 0 && !rpc_queue_complete_only(ctx, cmd.waiter_id)) + return NULL; + + /* Bad or unhandled header: consume it to keep the ring moving */ + t2r_ring_advance(ctx, sizeof(cmd)); + + return NULL; +} + +static void rpc_drain_ring(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct rpc_work rw = {}, *c = NULL; + unsigned long flags; + + if (!ctx->t2r_ring) + return; + + spin_lock_irqsave(&ctx->ring_lock, flags); + while (READ_ONCE(ctx->t2r_ring_rd) != + /* Pair with producer store-release after ring write. */ + smp_load_acquire(&ctx->t2r_ring->wr)) { + rw.data = ctx->rpc_data; + c = rpc_pick_next(mbedtee, ctx, &rw); + if (!c) + break; + + if (c->waiter_id == 0) { + c->func(mbedtee, c->data, c->size); + } else { + INIT_WORK(&c->work, rpc_routine); + queue_work(ctx->rpc_wq, &c->work); + } + } + spin_unlock_irqrestore(&ctx->ring_lock, flags); +} + +static void rpc_routine(struct work_struct *work) +{ + struct rpc_work *c = container_of(work, struct rpc_work, work); + struct mbedtee_t2r_ctx *ctx = &c->mbedtee->t2r; + + if (!c->complete_only) + c->func(c->mbedtee, c->data, c->size); + + /* + * COMPLETE_TEE must reach the TEE thread that issued the sync RPC. + * On RISC-V the notification uses a shared ring buffer that may be + * transiently full when many RPCs are in flight concurrently. + * Keep retrying rather than silently dropping the completion, which + * would leave the TEE thread blocked in rpc_call_sync forever. + * On ARM the fastcall is a direct SMC so -ENOSPC never occurs. + */ + while (mbedtee_rpc_fastcall(c->mbedtee, MBEDTEE_RPC_COMPLETE_TEE, + (unsigned long)c->waiter_id, 0, 0) == -ENOSPC) + cond_resched(); + + if (c != &ctx->complete_work) { + kfree(c); + return; + } + + WRITE_ONCE(ctx->complete_work_pending, false); + rpc_drain_ring(c->mbedtee); +} + +/* + * Hard IRQ handler -- drains the TEE-to-REE ring buffer. + * + * Asynchronous RPCs (waiter_id == 0) are handled inline since all async + * handlers (complete(), ktime_get_real_ts64()) are non-blocking. + * + * Synchronous RPCs (waiter_id != 0) may block waiting for the + * tee-supplicant, so they are dispatched to a workqueue. + */ +irqreturn_t mbedtee_rpc_irq_handler(int irq, void *dev_id) +{ + struct mbedtee_device *mbedtee = dev_id; + + if (!mbedtee) + return IRQ_NONE; + + rpc_drain_ring(mbedtee); + + return IRQ_HANDLED; +} + +static void mbedtee_rpc_complete(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + u64 waiter_id; + + if (size < sizeof(waiter_id)) + return; + + memcpy(&waiter_id, data, sizeof(waiter_id)); + if (!waiter_id) + return; + + mbedtee_rpc_complete_call(mbedtee, waiter_id); +} + +static void mbedtee_ree_time(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + struct timespec64 *ts = data; + + if (size < sizeof(*ts)) + return; + + ktime_get_real_ts64(ts); +} + +static void mbedtee_reefs_supp(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + mbedtee_supp_handler(mbedtee, MBEDTEE_SUPP_REEFS, data, size); +} + +static void mbedtee_rpmb_supp(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + mbedtee_supp_handler(mbedtee, MBEDTEE_SUPP_RPMB, data, size); +} + +static void mbedtee_register_rpc(struct mbedtee_device *mbedtee, + u32 id, void (*func)(struct mbedtee_device *mbedtee, + void *data, size_t size)) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + + if (WARN_ON(id >= MBEDTEE_RPC_MAX || !func)) + return; + + ctx->rpc_handlers[id] = func; +} + +int mbedtee_rpc_init(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct device_node *node = mbedtee->dev->of_node; + struct resource res; + bool cpuhp_state_created = false; + int ret; + + memset(ctx, 0, sizeof(*ctx)); + spin_lock_init(&ctx->ring_lock); + mbedtee->rpc_ops = mbedtee_get_rpc_transport_ops(); + if (!mbedtee->rpc_ops) + return -EIO; + + ret = mbedtee_get_resource(node, "rpc-t2r-ring", &res); + if (ret) { + dev_err(mbedtee->dev, "rpc-t2r-ring not found in DT\n"); + return ret; + } + + ctx->t2r_ring = memremap(res.start, resource_size(&res), + MEMREMAP_WB); + if (!ctx->t2r_ring) { + dev_err(mbedtee->dev, "failed to map t2r ring\n"); + return -ENOMEM; + } + if (resource_size(&res) <= sizeof(struct rpc_ringbuf)) { + dev_err(mbedtee->dev, "rpc-t2r-ring too small\n"); + memunmap(ctx->t2r_ring); + ctx->t2r_ring = NULL; + return -EINVAL; + } + ctx->t2r_ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); + /* Observe latest producer index only after ring metadata is visible. */ + WRITE_ONCE(ctx->t2r_ring_rd, smp_load_acquire(&ctx->t2r_ring->rd)); + + dev_dbg(mbedtee->dev, "rpc-t2r-ring %pa\n", &res.start); + + ret = mbedtee_get_resource(node, "rpc-t2r-shm", &res); + if (ret) { + dev_err(mbedtee->dev, "rpc-t2r-shm not found in DT\n"); + goto err_ring; + } + + ctx->t2r_shm_phys = res.start; + ctx->t2r_shm_sz = resource_size(&res); + ctx->t2r_shm = memremap(res.start, ctx->t2r_shm_sz, + MEMREMAP_WB); + if (!ctx->t2r_shm) { + dev_err(mbedtee->dev, "failed to map t2r shm\n"); + ret = -ENOMEM; + goto err_ring; + } + + dev_dbg(mbedtee->dev, "rpc-t2r-shm %pa\n", &res.start); + + ctx->rpc_wq = alloc_workqueue("mbedtee-rpc", WQ_UNBOUND, 0); + if (!ctx->rpc_wq) { + dev_err(mbedtee->dev, "failed to create rpc workqueue\n"); + ret = -ENOMEM; + goto err_shm; + } + + ctx->complete_work.mbedtee = mbedtee; + ctx->complete_work.complete_only = true; + INIT_WORK(&ctx->complete_work.work, rpc_routine); + + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_COMPLETE_REE, mbedtee_rpc_complete); + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_REETIME, mbedtee_ree_time); + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_REEFS, mbedtee_reefs_supp); + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_RPMB, mbedtee_rpmb_supp); + + /* + * Register cpuhp instance BEFORE enabling interrupts so that + * T2R delivery migration is in place before the first IRQ fires. + */ + mutex_lock(&mbedtee_cpuhp_mutex); + if (mbedtee_cpuhp_state < 0) { + mbedtee_cpuhp_state = + cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "tee/mbedtee:rpc-callee", + mbedtee_cpu_online, + mbedtee_cpu_offline); + if (mbedtee_cpuhp_state < 0) { + ret = mbedtee_cpuhp_state; + mutex_unlock(&mbedtee_cpuhp_mutex); + goto err_wq; + } + cpuhp_state_created = true; + } + + ret = cpuhp_state_add_instance(mbedtee_cpuhp_state, &ctx->hp_node); + if (!ret) { + mbedtee_cpuhp_instances++; + ctx->cpuhp_added = true; + } else if (cpuhp_state_created) { + cpuhp_remove_multi_state(mbedtee_cpuhp_state); + mbedtee_cpuhp_state = -EINVAL; + } + mutex_unlock(&mbedtee_cpuhp_mutex); + if (ret != 0) + goto err_wq; + + ret = mbedtee->rpc_ops->init(mbedtee, ctx->t2r_ring, + mbedtee_rpc_irq_handler); + if (ret != 0) + goto err_cpuhp; + + /* Signal the TEE that the REE callee is ready to receive. */ + smp_store_release(&ctx->t2r_ring->callee_ready, true); + + return 0; + +err_cpuhp: + mutex_lock(&mbedtee_cpuhp_mutex); + if (mbedtee_cpuhp_state >= 0 && ctx->cpuhp_added) { + cpuhp_state_remove_instance_nocalls(mbedtee_cpuhp_state, + &ctx->hp_node); + ctx->cpuhp_added = false; + if (mbedtee_cpuhp_instances) + mbedtee_cpuhp_instances--; + if (!mbedtee_cpuhp_instances) { + cpuhp_remove_multi_state(mbedtee_cpuhp_state); + mbedtee_cpuhp_state = -EINVAL; + } + } + mutex_unlock(&mbedtee_cpuhp_mutex); +err_wq: + destroy_workqueue(ctx->rpc_wq); + ctx->rpc_wq = NULL; +err_shm: + memunmap(ctx->t2r_shm); + ctx->t2r_shm = NULL; +err_ring: + memunmap(ctx->t2r_ring); + ctx->t2r_ring = NULL; + return ret; +} + +void mbedtee_rpc_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx; + + if (!mbedtee) + return; + + ctx = &mbedtee->t2r; + /* Stop advertising the REE callee before interrupt teardown. */ + if (ctx->t2r_ring) + WRITE_ONCE(ctx->t2r_ring->callee_ready, false); + + mutex_lock(&mbedtee_cpuhp_mutex); + if (mbedtee_cpuhp_state >= 0 && ctx->cpuhp_added) { + cpuhp_state_remove_instance_nocalls(mbedtee_cpuhp_state, + &ctx->hp_node); + ctx->cpuhp_added = false; + if (mbedtee_cpuhp_instances) + mbedtee_cpuhp_instances--; + if (!mbedtee_cpuhp_instances) { + cpuhp_remove_multi_state(mbedtee_cpuhp_state); + mbedtee_cpuhp_state = -EINVAL; + } + } + mutex_unlock(&mbedtee_cpuhp_mutex); + + if (mbedtee->rpc_ops) + mbedtee->rpc_ops->uninit(mbedtee); + + /* + * Work queued before IRQ teardown may be blocked in the supplicant + * path. Abort those requests and reject later ones before draining the + * workqueue. R2T must stay alive until destroy_workqueue() returns + * because rpc_routine() sends COMPLETE_TEE replies over R2T. + */ + mbedtee_supp_abort_all(&mbedtee->supp); + + if (ctx->rpc_wq) { + destroy_workqueue(ctx->rpc_wq); + ctx->rpc_wq = NULL; + } + + if (ctx->t2r_shm) { + memunmap(ctx->t2r_shm); + ctx->t2r_shm = NULL; + } + + if (ctx->t2r_ring) { + memunmap(ctx->t2r_ring); + ctx->t2r_ring = NULL; + } +} diff --git a/drivers/tee/mbedtee/rpc_callee_arm.c b/drivers/tee/mbedtee/rpc_callee_arm.c new file mode 100644 index 0000000..0163331 --- /dev/null +++ b/drivers/tee/mbedtee/rpc_callee_arm.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * ARM/ARM64 GIC transport for the TEE->REE RPC callee. + * + * On ARM/ARM64 the TEE raises a rising-edge software-generated interrupt + * described by the "interrupts" property of the mbedtee DT node. This file + * obtains that virq via of_irq_get() and registers the hard IRQ handler + * supplied by the core callee layer. + */ +#include <linux/interrupt.h> +#include <linux/cpu.h> +#include <linux/of.h> +#include <linux/of_irq.h> + +#include "mbedtee_drv.h" + +static int arm_transport_init(struct mbedtee_device *mbedtee, + struct rpc_ringbuf *ring, irq_handler_t handler) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + int ret; + + tctx->rpc_notify_virq = of_irq_get(mbedtee->dev->of_node, 0); + if (tctx->rpc_notify_virq <= 0) + return tctx->rpc_notify_virq ? tctx->rpc_notify_virq : -ENODEV; + + ret = request_irq(tctx->rpc_notify_virq, handler, + 0, "mbedtee-rpc", mbedtee); + if (ret) { + dev_warn(mbedtee->dev, "request_irq %d failed: %d\n", + tctx->rpc_notify_virq, ret); + tctx->rpc_notify_virq = 0; + return ret; + } + + /* + * Spread the T2R SPI across all online CPUs in Linux. + * The TEE needs no CPU information from Linux: it broadcasts + * via GIC hardware (GICv2 ITARGETS=0xFF / GICv3 IROUTER.IRM=1). + */ + cpumask_copy(&ctx->callee_cpus, cpu_online_mask); + if (irq_set_affinity(tctx->rpc_notify_virq, &ctx->callee_cpus)) + dev_warn(mbedtee->dev, "irq_set_affinity failed\n"); + + dev_dbg(mbedtee->dev, "rpc-t2r-irq %d\n", tctx->rpc_notify_virq); + return 0; +} + +static void arm_transport_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + + if (tctx->rpc_notify_virq > 0) { + free_irq(tctx->rpc_notify_virq, mbedtee); + tctx->rpc_notify_virq = 0; + } +} + +/* + * ARM T2R uses a GIC SPI: the TEE broadcasts it via GIC hardware + * (GICv2 ITARGETS=0xFF or GICv3 IROUTER.IRM=1) to any alive CPU. + * TrustZone shares physical CPUs -- no ring update is ever needed. + * + * @new_cpu is ignored: the IRQ is re-affined to ctx->callee_cpus which + * the core callee layer keeps in sync (dying CPU already removed before + * this callback is invoked, so the mask is always accurate). + */ +static int arm_update_affinity(struct mbedtee_device *mbedtee, + unsigned int new_cpu) +{ + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + + if (tctx->rpc_notify_virq > 0 && + irq_set_affinity(tctx->rpc_notify_virq, &mbedtee->t2r.callee_cpus)) + dev_warn(mbedtee->dev, "irq_set_affinity failed\n"); + + return 0; +} + +static const struct rpc_transport_ops arm_transport_ops = { + .init = arm_transport_init, + .uninit = arm_transport_uninit, + .update_affinity = arm_update_affinity, +}; + +const struct rpc_transport_ops *mbedtee_get_rpc_transport_ops(void) +{ + return &arm_transport_ops; +} diff --git a/drivers/tee/mbedtee/rpc_callee_riscv.c b/drivers/tee/mbedtee/rpc_callee_riscv.c new file mode 100644 index 0000000..7ac0f8e --- /dev/null +++ b/drivers/tee/mbedtee/rpc_callee_riscv.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Xing Loong <xing.xl.loong@gmail.com> + * RISC-V MSI transport for the TEE->REE RPC callee. + * + * On RISC-V the TEE wakes the REE by writing a Message Signalled Interrupt + * into the target hart's IMSIC (Incoming MSI Controller). This file allocates + * one platform MSI from the DT-provided MSI parent and registers the hard IRQ + * handler supplied by the core callee layer. + * + * CPU hotplug: the common mbedtee_cpu_offline() callback in rpc_callee.c + * detects when the callee CPU goes offline and calls riscv_update_affinity() + * to migrate the MSI to another online CPU. + */ +#include <linux/interrupt.h> +#include <linux/msi.h> +#include <linux/of_irq.h> +#include <linux/smp.h> + +#include "mbedtee_drv.h" + +static int mbedtee_ring_set_hartid(struct mbedtee_device *mbedtee, + struct rpc_ringbuf *ring, + unsigned int cpu) +{ + long hartid = cpuid_to_hartid_map(cpu); + + if (hartid < 0) { + dev_err(mbedtee->dev, "CPU%u has no valid hart-id\n", cpu); + return -ERANGE; + } + + WRITE_ONCE(ring->callee_hartid, (u32)hartid); + return 0; +} + +static void mbedtee_rpc_write_msi_msg(struct msi_desc *desc_msi, + struct msi_msg *msg) +{ + struct mbedtee_device *mbedtee = dev_get_drvdata(desc_msi->dev); + + if (mbedtee) + mbedtee->transport.rpc_msi_msg = *msg; +} + +static int riscv_transport_init(struct mbedtee_device *mbedtee, + struct rpc_ringbuf *ring, irq_handler_t handler) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + const struct cpumask *eff; + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + unsigned int cpu; + struct device *dev = mbedtee->dev; + int virq; + int ret; + + /* + * Set a bootstrap callee_hartid (hart 0) so the TEE can poll + * even if MSI setup fails below. + */ + ret = mbedtee_ring_set_hartid(mbedtee, ring, 0); + if (ret) + return ret; + + dev_set_drvdata(dev, mbedtee); + + /* + * The MSI domain of an OF device is resolved only once, when the + * platform device is created (of_msi_configure() in of/platform.c). + * mbedtee's MSI parent is the IMSIC, whose MSI domain is created + * later, when the IMSIC builtin_platform_driver probes -- typically + * after this device has already been created with a NULL msi.domain. + * + * Deferred probing alone cannot recover from this: the driver core + * re-runs probe() but never re-runs of_msi_configure(), so the stale + * NULL domain would persist across every retry. Re-resolve it here + * and defer until the IMSIC driver is up. This mirrors the RISC-V + * APLIC/IOMMU platform-MSI drivers. + */ + if (!dev_get_msi_domain(dev)) { + of_msi_configure(dev, dev->of_node); + + if (!dev_get_msi_domain(dev)) + return -EPROBE_DEFER; + } + + ret = platform_device_msi_init_and_alloc_irqs(dev, 1, + mbedtee_rpc_write_msi_msg); + if (ret) { + dev_err(mbedtee->dev, "MSI alloc failed: %d\n", ret); + return ret; + } + + virq = msi_get_virq(dev, 0); + if (virq <= 0) { + dev_err(mbedtee->dev, "no MSI virq\n"); + ret = -ENOENT; + goto err_msi; + } + + ret = request_irq(virq, handler, 0, "mbedtee-rpc", mbedtee); + if (ret) { + dev_err(mbedtee->dev, "request_irq %d failed: %d\n", virq, ret); + goto err_msi; + } + + eff = irq_get_effective_affinity_mask(virq); + cpu = eff ? cpumask_first(eff) : 0; + + dev_dbg(mbedtee->dev, "MSI addr 0x%x%08x data %d virq %d hart %ld\n", + tctx->rpc_msi_msg.address_hi, tctx->rpc_msi_msg.address_lo, + tctx->rpc_msi_msg.data, virq, cpuid_to_hartid_map(cpu)); + + /* Inform TEE of the IMSIC identity used for T2R notifications. */ + ret = mbedtee_ring_set_hartid(mbedtee, ring, cpu); + if (ret) + goto err_irq; + /* Ensure callee_hartid is visible before advertising MSI interrupt ID. */ + smp_store_release(&ring->callee_imsic_id, tctx->rpc_msi_msg.data); + + /* Track the active callee CPU for hotplug migration. */ + ctx->callee_virq = virq; + cpumask_set_cpu(cpu, &ctx->callee_cpus); + + return 0; + +err_irq: + free_irq(virq, mbedtee); + +err_msi: + platform_device_msi_free_irqs_all(dev); + return ret; +} + +static void riscv_transport_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + int virq; + + virq = ctx->callee_virq; + ctx->callee_virq = 0; + if (virq > 0) + free_irq(virq, mbedtee); + + platform_device_msi_free_irqs_all(mbedtee->dev); +} + +/* + * Migrate the IMSIC MSI delivery to @new_cpu. + * + * Ordering guarantee for the TEE reader: + * 1. Clear callee_imsic_id to 0 -- TEE skips IMSIC path while we migrate. + * 2. Call irq_set_affinity() -- triggers mbedtee_rpc_write_msi_msg() which + * updates tctx->rpc_msi_msg with the new IMSIC identity. + * 3. Update callee_hartid (WRITE_ONCE inside mbedtee_ring_set_hartid()); + * no explicit barrier needed here since the release store on + * callee_imsic_id in step 4 provides the ordering. + * 4. Publish new callee_imsic_id with smp_store_release -- TEE's + * smp_load_acquire on callee_imsic_id pairs with this store. + */ +static int riscv_update_affinity(struct mbedtee_device *mbedtee, + unsigned int new_cpu) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + struct rpc_ringbuf *ring = ctx->t2r_ring; + int virq = ctx->callee_virq; + + if (!virq) + return -ENODEV; + + /* Step 1: prevent TEE from using the stale IMSIC identity. */ + smp_store_release(&ring->callee_imsic_id, 0); + + /* Step 2: retarget the MSI; write_msi_msg callback updates rpc_msi_msg. */ + if (irq_set_affinity(virq, cpumask_of(new_cpu))) { + dev_warn(mbedtee->dev, "irq_set_affinity to CPU%u failed\n", + new_cpu); + return -EIO; + } + + /* Step 3: update callee_hartid. */ + if (mbedtee_ring_set_hartid(mbedtee, ring, new_cpu)) + return -ERANGE; + + /* Step 4: publish new IMSIC id with release barrier. */ + smp_store_release(&ring->callee_imsic_id, tctx->rpc_msi_msg.data); + + dev_dbg(mbedtee->dev, "T2R callee migrated to CPU%u (hart %ld)\n", + new_cpu, cpuid_to_hartid_map(new_cpu)); + return 0; +} + +static const struct rpc_transport_ops riscv_transport_ops = { + .init = riscv_transport_init, + .uninit = riscv_transport_uninit, + .update_affinity = riscv_update_affinity, +}; + +const struct rpc_transport_ops *mbedtee_get_rpc_transport_ops(void) +{ + return &riscv_transport_ops; +} diff --git a/drivers/tee/mbedtee/rpc_caller.c b/drivers/tee/mbedtee/rpc_caller.c new file mode 100644 index 0000000..cc40f72 --- /dev/null +++ b/drivers/tee/mbedtee/rpc_caller.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * REE->TEE session management and yield-call interface. + */ +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include "mbedtee_drv.h" + +#define MBEDTEE_RPC_CALL_ACTIVE 0 +#define MBEDTEE_RPC_CALL_DONE 1 +#define MBEDTEE_RPC_CALL_INTERRUPTED 2 + +int mbedtee_rpc_call_alloc(struct mbedtee_device *mbedtee, + size_t payload_size, struct mbedtee_rpc_call **call) +{ + struct mbedtee_rpc_call *rcall; + unsigned long rpc_index; + int ret; + + if (sizeof(*rcall) + payload_size > PAGE_SIZE) + return -E2BIG; + + rcall = kzalloc(sizeof(*rcall) + payload_size, GFP_KERNEL); + if (!rcall) + return -ENOMEM; + + init_completion(&rcall->c); + rcall->state = MBEDTEE_RPC_CALL_ACTIVE; + rcall->rpc.size = payload_size; + rcall->rpc.ret = -EOPNOTSUPP; + rcall->rpc_phys = mbedtee_virt_to_phys(&rcall->rpc); + + do { + rpc_index = atomic_long_inc_return(&mbedtee->rpc_call_seq); + } while (rpc_index == 0); + + ret = xa_insert_irq(&mbedtee->rpc_calls, rpc_index, rcall, GFP_KERNEL); + if (ret != 0) { + kfree(rcall); + return ret; + } + + rcall->rpc.waiter_id = rpc_index; + *call = rcall; + + return 0; +} + +void mbedtee_rpc_call_free(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call) +{ + unsigned long rpc_index; + + if (!call) + return; + + rpc_index = (unsigned long)call->rpc.waiter_id; + + /* + * If interrupted/completed, complete_call owns this allocation + * and will xa_erase + kfree when the TEE eventually finishes. + */ + if (READ_ONCE(call->state) == MBEDTEE_RPC_CALL_INTERRUPTED) + return; + + xa_erase_irq(&mbedtee->rpc_calls, rpc_index); + kfree(call); +} + +void mbedtee_rpc_complete_call(struct mbedtee_device *mbedtee, + u64 waiter_id) +{ + struct mbedtee_rpc_call *call; + unsigned long rpc_index; + unsigned long flags; + unsigned int state; + + rpc_index = (unsigned long)waiter_id; + if ((u64)rpc_index != waiter_id) + return; + + xa_lock_irqsave(&mbedtee->rpc_calls, flags); + call = xa_load(&mbedtee->rpc_calls, rpc_index); + if (!call) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return; + } + + state = cmpxchg(&call->state, MBEDTEE_RPC_CALL_ACTIVE, + MBEDTEE_RPC_CALL_DONE); + __xa_erase(&mbedtee->rpc_calls, rpc_index); + + if (state == MBEDTEE_RPC_CALL_INTERRUPTED) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + kfree(call); + return; + } + + complete(&call->c); + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); +} + +long mbedtee_rpc_wait_for_completion(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call, bool killable) +{ + long ret; + unsigned int state; + unsigned long flags; + unsigned long rpc_index; + + if (!killable) { + wait_for_completion(&call->c); + return 0; + } + + rpc_index = (unsigned long)call->rpc.waiter_id; + + ret = wait_for_completion_killable(&call->c); + if (ret == 0) + return 0; + + /* + * Synchronize with mbedtee_rpc_complete_call() via the + * xa_lock. If complete_call already consumed this entry + * before we acquired the lock, the entry is already gone + * from the xarray and the TEE already completed (or freed) + * the call -- treat as success since the RPC is finished. + * If the entry is still present, we hold the lock so + * complete_call cannot race with our state transition. + */ + xa_lock_irqsave(&mbedtee->rpc_calls, flags); + if (xa_load(&mbedtee->rpc_calls, rpc_index) != call) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return 0; + } + + state = cmpxchg(&call->state, MBEDTEE_RPC_CALL_ACTIVE, + MBEDTEE_RPC_CALL_INTERRUPTED); + if (state == MBEDTEE_RPC_CALL_DONE) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return 0; + } + + if (state == MBEDTEE_RPC_CALL_ACTIVE) + /* Publish interruption state before the TEE waiter reads it. */ + smp_store_release(&call->rpc.interrupted, true); + + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return ret; +} + +/* Local helpers for encoding/decoding the 4-bit-per-parameter type field. */ +static inline u32 mbedtee_param_type_get(u32 types, unsigned int idx) +{ + return (types >> (idx * 4)) & 0xF; +} + +static inline u32 mbedtee_param_type_set(u32 type, unsigned int idx) +{ + return (type & 0xF) << (idx * 4); +} + +static int mbedtee_param_decode(struct tee_param *params, + size_t num_params, const struct rpc_param *rp) +{ + size_t n; + + if (num_params > ARRAY_SIZE(rp->params)) + return -EINVAL; + + for (n = 0; n < num_params; n++) { + struct tee_param *p = params + n; + const union rpc_tee_param *rtp = rp->params + n; + u32 attr = mbedtee_param_type_get(rp->params_type, n); + + switch (attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_NONE: + memset(&p->u, 0, sizeof(p->u)); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: + p->u.value.a = rtp->value.a; + p->u.value.b = rtp->value.b; + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: + p->u.memref.size = rtp->memref.size; + break; + default: + return -EINVAL; + } + p->attr = attr; + } + return 0; +} + +static int mbedtee_param_encode(struct rpc_param *rp, + size_t num_params, const struct tee_param *params) +{ + size_t n; + + if (num_params > ARRAY_SIZE(rp->params)) + return -EINVAL; + + rp->params_type = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + + for (n = 0; n < num_params; n++) { + const struct tee_param *p = params + n; + union rpc_tee_param *rtp = rp->params + n; + + rp->params_type |= mbedtee_param_type_set(p->attr, n); + + switch (p->attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_NONE: + memset(rtp, 0, sizeof(*rtp)); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: + rtp->value.a = p->u.value.a; + rtp->value.b = p->u.value.b; + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: + if (p->u.memref.shm) + rtp->memref.id = p->u.memref.shm->sec_world_id; + else + rtp->memref.id = 0; /* invalid-id @ mbedtee */ + rtp->memref.size = p->u.memref.size; + rtp->memref.offset = p->u.memref.shm_offs; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +/* + * cancel_req() and shm_{register,unregister} still return Linux errno to the + * kernel TEE core. Preserve any local transport failure in @ret; otherwise + * fold a non-success TEE-side GP status into the generic -EIO expected by + * these internal-only hooks. + */ +static int mbedtee_rpc_gp_ret_to_errno(int ret, s32 gp_ret) +{ + if (ret) + return ret; + + return gp_ret == TEEC_SUCCESS ? 0 : -EIO; +} + +static struct mbedtee_session * +mbedtee_find_session_locked(struct mbedtee_context_data *ctxdata, + u32 session_id) +{ + struct mbedtee_session *sess; + + list_for_each_entry(sess, &ctxdata->sess_list, list_node) + if (sess->session_id == session_id) + return sess; + + return NULL; +} + +int mbedtee_open_session(struct tee_context *ctx, + struct tee_ioctl_open_session_arg *arg, + struct tee_param *param) +{ + int ret; + u32 session_id = 0; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_param *rp; + struct mbedtee_session *sess; + + sess = kzalloc_obj(*sess, GFP_KERNEL); + if (!sess) + return -ENOMEM; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*rp), &call); + if (ret != 0) { + kfree(sess); + return ret; + } + + rp = (struct rpc_param *)call->rpc.data; + + ret = mbedtee_param_encode(rp, arg->num_params, param); + if (ret) { + kfree(sess); + mbedtee_rpc_call_free(mbedtee, call); + return ret; + } + + memcpy(rp->uuid, arg->uuid, sizeof(arg->uuid)); + memcpy(rp->clnt_uuid, arg->clnt_uuid, sizeof(arg->clnt_uuid)); + rp->ret_origin = TEEC_ORIGIN_COMMS; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_OPEN_SESSION, call, false); + + dev_dbg(mbedtee->dev, "open session ret %d gp_ret %d\n", ret, + call->rpc.ret); + + if (ret == 0 && call->rpc.ret == TEEC_SUCCESS) { + session_id = rp->session_id; + sess->session_id = session_id; + mutex_lock(&ctxdata->mutex); + list_add(&sess->list_node, &ctxdata->sess_list); + mutex_unlock(&ctxdata->mutex); + } else { + kfree(sess); + } + + if (ret != 0) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + } else if (mbedtee_param_decode(param, arg->num_params, rp)) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + if (call->rpc.ret == TEEC_SUCCESS) + mbedtee_close_session(ctx, session_id); + } else { + arg->session = rp->session_id; + arg->ret = (u32)call->rpc.ret; + arg->ret_origin = rp->ret_origin; + } + + mbedtee_rpc_call_free(mbedtee, call); + return 0; +} + +int mbedtee_invoke_func(struct tee_context *ctx, + struct tee_ioctl_invoke_arg *arg, + struct tee_param *param) +{ + int ret; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_param *rp; + struct mbedtee_session *sess; + + mutex_lock(&ctxdata->mutex); + sess = mbedtee_find_session_locked(ctxdata, arg->session); + mutex_unlock(&ctxdata->mutex); + if (!sess) + return -EINVAL; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*rp), &call); + if (ret != 0) + return ret; + + rp = (struct rpc_param *)call->rpc.data; + + rp->session_id = arg->session; + rp->cmd_id = arg->func; + rp->ret_origin = TEEC_ORIGIN_COMMS; + + ret = mbedtee_param_encode(rp, arg->num_params, param); + if (ret) { + mbedtee_rpc_call_free(mbedtee, call); + return ret; + } + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_INVOKE_SESSION, call, true); + + dev_dbg(mbedtee->dev, "invoke session ret %d gp_ret %d\n", ret, + call->rpc.ret); + + if (ret != 0) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + } else if (mbedtee_param_decode(param, arg->num_params, rp)) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + } else { + arg->ret = (u32)call->rpc.ret; + arg->ret_origin = rp->ret_origin; + } + + mbedtee_rpc_call_free(mbedtee, call); + return 0; +} + +int mbedtee_close_session(struct tee_context *ctx, u32 session) +{ + int ret; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_param *rp; + struct mbedtee_session *sess; + + mutex_lock(&ctxdata->mutex); + sess = mbedtee_find_session_locked(ctxdata, session); + if (!sess) { + mutex_unlock(&ctxdata->mutex); + return -EINVAL; + } + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*rp), &call); + if (ret != 0) { + /* + * RPC allocation failed but the session is still alive on + * the TEE side. Leave it in the session list so a future + * close attempt can retry; warn so the condition is visible. + */ + mutex_unlock(&ctxdata->mutex); + return ret; + } + + list_del(&sess->list_node); + mutex_unlock(&ctxdata->mutex); + + rp = (struct rpc_param *)call->rpc.data; + rp->session_id = session; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_CLOSE_SESSION, call, false); + + mbedtee_rpc_call_free(mbedtee, call); + kfree(sess); + + return ret; +} + +int mbedtee_cancel_req(struct tee_context *ctx, + u32 cancel_id, u32 session) +{ + int ret; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_cancel_req *cancel; + struct mbedtee_session *sess; + + mutex_lock(&ctxdata->mutex); + sess = mbedtee_find_session_locked(ctxdata, session); + mutex_unlock(&ctxdata->mutex); + if (!sess) + return -EINVAL; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*cancel), &call); + if (ret != 0) + return ret; + + cancel = (struct rpc_cancel_req *)call->rpc.data; + cancel->session_id = session; + cancel->cancel_id = cancel_id; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_CANCEL, call, false); + ret = mbedtee_rpc_gp_ret_to_errno(ret, call->rpc.ret); + + mbedtee_rpc_call_free(mbedtee, call); + + return ret; +} + +int mbedtee_shm_register(struct tee_context *ctx, struct tee_shm *shm, + struct page **pages, size_t nr_pages, unsigned long start) +{ + int ret; + size_t i, j; + u64 *pagearray; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_memref *memref; + /* Multiple: number of MBEDTEE_PAGE_SIZE pages per Linux page. */ + const size_t multiple = PAGE_SIZE / MBEDTEE_PAGE_SIZE; + + BUILD_BUG_ON(PAGE_SIZE < MBEDTEE_PAGE_SIZE); + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*memref), &call); + if (ret != 0) + return ret; + + memref = (struct rpc_memref *)call->rpc.data; + + pagearray = kcalloc(nr_pages * multiple, sizeof(*pagearray), GFP_KERNEL); + if (!pagearray) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < nr_pages; i++) { + for (j = 0; j < multiple; j++) + pagearray[i * multiple + j] = MBEDTEE_PAGE_SIZE * j + + page_to_phys(pages[i]); + } + + memref->size = tee_shm_get_size(shm); + memref->offset = tee_shm_get_page_offset(shm); + memref->pages = mbedtee_virt_to_phys(pagearray); + memref->cnt = nr_pages * multiple; + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_REGISTER_SHM, call, false); + ret = mbedtee_rpc_gp_ret_to_errno(ret, call->rpc.ret); + + if (ret == 0) + shm->sec_world_id = memref->id; + + kfree(pagearray); + +out: + mbedtee_rpc_call_free(mbedtee, call); + return ret; +} + +int mbedtee_shm_unregister(struct tee_context *ctx, + struct tee_shm *shm) +{ + int ret; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_memref *memref; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*memref), &call); + if (ret != 0) + return ret; + + memref = (struct rpc_memref *)call->rpc.data; + memref->id = shm->sec_world_id; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_UNREGISTER_SHM, call, false); + ret = mbedtee_rpc_gp_ret_to_errno(ret, call->rpc.ret); + + mbedtee_rpc_call_free(mbedtee, call); + + return ret; +} diff --git a/drivers/tee/mbedtee/rpc_caller_arm.c b/drivers/tee/mbedtee/rpc_caller_arm.c new file mode 100644 index 0000000..1dd665a --- /dev/null +++ b/drivers/tee/mbedtee/rpc_caller_arm.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * ARM RPC calls to TEE - relies on SMC + * + * Uses arm_smccc_smc() with SMCCC-compatible calling convention: + * Fast call : r0/x0 = fn (bit 31 set), r1-r3/x1-x3 = args. + * Yield call : r0/x0 = fn (bit 31 clear), r1/x1 = phys(rpc_cmd). + * + * ARM fast calls are synchronous SMC instructions. + */ +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/arm-smccc.h> +#include "mbedtee_drv.h" + +long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee, + unsigned long fn, struct mbedtee_rpc_call *call, + bool interruptible) +{ + long ret; + struct arm_smccc_res res; + + if (MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + call->rpc.id = fn; + + arm_smccc_smc(fn, call->rpc_phys, + 0, 0, 0, 0, 0, 0, &res); + ret = res.a0; + if (ret != 0) + return ret; + + return mbedtee_rpc_wait_for_completion(mbedtee, call, interruptible); +} + +long mbedtee_rpc_fastcall(struct mbedtee_device *mbedtee, + unsigned long fn, unsigned long a0, + unsigned long a1, unsigned long a2) +{ + struct arm_smccc_res res; + + if (!MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + arm_smccc_smc(fn, a0, a1, a2, 0, 0, 0, 0, &res); + + return (long)res.a0; +} + +/* + * ARM uses direct SMC; no caller-side ring buffer initialisation needed. + */ +int mbedtee_r2t_init(struct mbedtee_device *mbedtee) +{ + return 0; +} + +void mbedtee_r2t_uninit(struct mbedtee_device *mbedtee) +{ +} diff --git a/drivers/tee/mbedtee/rpc_caller_riscv.c b/drivers/tee/mbedtee/rpc_caller_riscv.c new file mode 100644 index 0000000..3769463 --- /dev/null +++ b/drivers/tee/mbedtee/rpc_caller_riscv.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Xing Loong <xing.xl.loong@gmail.com> + * REE->TEE RPC calls for RISC-V (IMSIC) + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/sizes.h> + +#include "mbedtee_drv.h" + +static bool rpc_ring_enough(struct mbedtee_r2t_ctx *ctx, size_t size) +{ + u32 wr; + u32 rd; + u32 remain; + + wr = READ_ONCE(ctx->ring_wr); + /* Pair with callee release store when publishing ring->rd */ + rd = smp_load_acquire(&ctx->ring->rd); + + if (rd > ctx->ring_sz || wr > ctx->ring_sz) + return false; + + if (rd <= wr) + remain = ctx->ring_sz + rd - wr; + else + remain = rd - wr; + + return remain > size; +} + +static void rpc_ring_write(struct mbedtee_r2t_ctx *ctx, + void *data, size_t size) +{ + struct rpc_ringbuf *shm = ctx->ring; + u32 remain; + u32 wr = READ_ONCE(ctx->ring_wr); + + if (wr + size > ctx->ring_sz) { + remain = wr + size - ctx->ring_sz; + memcpy(&shm->mem[wr], data, size - remain); + memcpy(&shm->mem[0], (char *)data + size - remain, remain); + wr = remain; + } else { + memcpy(&shm->mem[wr], data, size); + wr += size; + } + + WRITE_ONCE(ctx->ring_wr, wr); + /* Publish writer index after payload bytes become visible. */ + smp_store_release(&shm->wr, wr); +} + +long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee, + unsigned long fn, struct mbedtee_rpc_call *call, + bool interruptible) +{ + struct mbedtee_r2t_ctx *ctx = &mbedtee->r2t; + unsigned long flags; + u64 phys; + + if (!ctx->ring) + return -ENXIO; + + if (MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + call->rpc.id = fn; + + spin_lock_irqsave(&ctx->lock, flags); + if (!rpc_ring_enough(ctx, sizeof(u64))) { + dev_err_ratelimited(mbedtee->dev, "rpc ring full\n"); + spin_unlock_irqrestore(&ctx->lock, flags); + return -ENOSPC; + } + + phys = call->rpc_phys; + rpc_ring_write(ctx, &phys, sizeof(phys)); + spin_unlock_irqrestore(&ctx->lock, flags); + + return mbedtee_rpc_wait_for_completion(mbedtee, call, interruptible); +} + +long mbedtee_rpc_fastcall(struct mbedtee_device *mbedtee, + unsigned long fn, unsigned long a0, + unsigned long a1, unsigned long a2) +{ + struct mbedtee_r2t_ctx *ctx = &mbedtee->r2t; + struct mbedtee_rpc_call *call; + unsigned long flags; + u64 phys; + int ret; + + if (!ctx->ring) + return -ENXIO; + + if (!MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + ret = mbedtee_rpc_call_alloc(mbedtee, 3 * sizeof(u64), &call); + if (ret != 0) + return ret; + + call->rpc.id = fn; + call->rpc.size = 3 * sizeof(u64); + call->rpc.data[0] = a0; + call->rpc.data[1] = a1; + call->rpc.data[2] = a2; + + spin_lock_irqsave(&ctx->lock, flags); + if (!rpc_ring_enough(ctx, sizeof(u64))) { + dev_err_ratelimited(mbedtee->dev, "rpc ring full\n"); + spin_unlock_irqrestore(&ctx->lock, flags); + mbedtee_rpc_call_free(mbedtee, call); + return -ENOSPC; + } + + phys = call->rpc_phys; + rpc_ring_write(ctx, &phys, sizeof(phys)); + spin_unlock_irqrestore(&ctx->lock, flags); + + ret = mbedtee_rpc_wait_for_completion(mbedtee, call, true); + if (ret != 0) { + mbedtee_rpc_call_free(mbedtee, call); + return ret; + } + + ret = call->rpc.ret; + mbedtee_rpc_call_free(mbedtee, call); + + return ret; +} + +int mbedtee_r2t_init(struct mbedtee_device *mbedtee) +{ + struct mbedtee_r2t_ctx *ctx = &mbedtee->r2t; + struct device_node *node = mbedtee->dev->of_node; + struct resource res; + int ret; + + memset(ctx, 0, sizeof(*ctx)); + spin_lock_init(&ctx->lock); + + ret = mbedtee_get_resource(node, "rpc-r2t-ring", &res); + if (ret) + return ret; + + if (resource_size(&res) <= sizeof(struct rpc_ringbuf)) { + dev_err(mbedtee->dev, "rpc-r2t-ring too small\n"); + return -EINVAL; + } + + ctx->ring = memremap(res.start, resource_size(&res), + MEMREMAP_WB); + if (!ctx->ring) { + dev_err(mbedtee->dev, "failed to map r2t ring at %pa\n", + &res.start); + return -ENOMEM; + } + ctx->ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); + /* Read initial writer index with acquire for coherent ring bootstrap. */ + WRITE_ONCE(ctx->ring_wr, smp_load_acquire(&ctx->ring->wr)); + + dev_dbg(mbedtee->dev, "rpc-r2t-ring %pa\n", &res.start); + + return 0; +} + +void mbedtee_r2t_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_r2t_ctx *ctx; + + if (!mbedtee) + return; + + ctx = &mbedtee->r2t; + if (ctx->ring) { + memunmap(ctx->ring); + ctx->ring = NULL; + ctx->ring_sz = 0; + ctx->ring_wr = 0; + } +} diff --git a/drivers/tee/mbedtee/shm_pool.c b/drivers/tee/mbedtee/shm_pool.c new file mode 100644 index 0000000..708ab24 --- /dev/null +++ b/drivers/tee/mbedtee/shm_pool.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2017, EPAM Systems + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#include <linux/device.h> +#include <linux/genalloc.h> +#include <linux/slab.h> + +#include "mbedtee_drv.h" + +static int pool_op_alloc(struct tee_shm_pool *pool, + struct tee_shm *shm, size_t size, size_t align) +{ + unsigned int order = get_order(size); + struct page *page; + int rc = 0; + + page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); + if (!page) + return -ENOMEM; + + shm->kaddr = page_address(page); + shm->paddr = page_to_phys(page); + shm->size = PAGE_SIZE << order; + + if (!(shm->flags & TEE_SHM_PRIV)) { + struct mbedtee_device *mbedtee = tee_get_drvdata(shm->ctx->teedev); + + if (mbedtee->yield) { + unsigned int nr_pages = 1 << order; + unsigned int i; + struct page **pages; + + pages = kmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) { + rc = -ENOMEM; + goto err_pages; + } + + for (i = 0; i < nr_pages; i++) + pages[i] = page + i; + + rc = mbedtee_shm_register(shm->ctx, shm, pages, + nr_pages, + (unsigned long)shm->kaddr); + kfree(pages); + if (rc) + goto err_pages; + } + } + + return 0; + +err_pages: + free_pages((unsigned long)shm->kaddr, get_order(shm->size)); + shm->kaddr = NULL; + return rc; +} + +static void pool_op_free(struct tee_shm_pool *pool, + struct tee_shm *shm) +{ + if (!(shm->flags & TEE_SHM_PRIV)) { + struct mbedtee_device *mbedtee = tee_get_drvdata(shm->ctx->teedev); + + if (mbedtee->yield && shm->sec_world_id) + mbedtee_shm_unregister(shm->ctx, shm); + } + + free_pages((unsigned long)shm->kaddr, get_order(shm->size)); + shm->kaddr = NULL; +} + +static void pool_op_destroy_pool(struct tee_shm_pool *pool) +{ + kfree(pool); +} + +static const struct tee_shm_pool_ops pool_ops = { + .alloc = pool_op_alloc, + .free = pool_op_free, + .destroy_pool = pool_op_destroy_pool, +}; + +/** + * mbedtee_shm_pool_alloc_pages() - create page-based allocator pool + * + * This pool is used when MbedTEE supports dynamic shared memory. Command + * buffers and similar structures are allocated from kernel's own memory. + * + * Return: pointer to a tee_shm_pool or ERR_PTR on failure + */ +struct tee_shm_pool *mbedtee_shm_pool_alloc_pages(void) +{ + struct tee_shm_pool *pool = kzalloc_obj(*pool, GFP_KERNEL); + + if (!pool) + return ERR_PTR(-ENOMEM); + + pool->ops = &pool_ops; + + return pool; +} diff --git a/drivers/tee/mbedtee/shm_pool.h b/drivers/tee/mbedtee/shm_pool.h new file mode 100644 index 0000000..4510676 --- /dev/null +++ b/drivers/tee/mbedtee/shm_pool.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2016, EPAM Systems + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ + +#ifndef SHM_POOL_H +#define SHM_POOL_H + +#include <linux/tee_drv.h> + +struct tee_shm_pool *mbedtee_shm_pool_alloc_pages(void); + +#endif diff --git a/drivers/tee/mbedtee/supp.c b/drivers/tee/mbedtee/supp.c new file mode 100644 index 0000000..853f3ad --- /dev/null +++ b/drivers/tee/mbedtee/supp.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "mbedtee_drv.h" + +struct mbedtee_supp_req { + int ret; + u32 func; + + struct list_head node; + struct task_struct *worker; + struct tee_context *ctx; /* owning supplicant context */ + + struct tee_shm *shm; + void *data; + size_t size; + + struct completion c; +}; + +static int mbedtee_supp_check_recv_params(size_t num_params, + struct tee_param *params) +{ + if (num_params != 1) + return -EINVAL; + + if (!tee_param_is_memref(params) || !params->u.memref.shm) + return -EINVAL; + + return 0; +} + +void mbedtee_supp_init(struct mbedtee_supp *supp) +{ + memset(supp, 0, sizeof(*supp)); + mutex_init(&supp->mutex); + init_completion(&supp->reqs_c); + INIT_LIST_HEAD(&supp->reqs); + INIT_LIST_HEAD(&supp->active_reqs); +} + +void mbedtee_supp_uninit(struct mbedtee_supp *supp) +{ + mutex_destroy(&supp->mutex); +} + +static void mbedtee_supp_complete_req(struct mbedtee_supp_req *r) +{ + list_del_init(&r->node); + if (r->shm) + tee_shm_put(r->shm); + r->shm = NULL; + r->ret = TEEC_ERROR_COMMUNICATION; + complete(&r->c); +} + +void mbedtee_supp_release(struct mbedtee_supp *supp, struct tee_context *ctx) +{ + struct mbedtee_supp_req *r, *n; + + mutex_lock(&supp->mutex); + + /* + * Cancel only requests owned by @ctx (or unclaimed ones). + * Requests owned by other supplicant contexts are left alone + * so that independent worker threads on separate fds do not + * interfere with each other. + */ + list_for_each_entry_safe(r, n, &supp->reqs, node) { + if (r->ctx && r->ctx != ctx) + continue; + mbedtee_supp_complete_req(r); + } + + list_for_each_entry_safe(r, n, &supp->active_reqs, node) { + if (r->ctx && r->ctx != ctx) + continue; + mbedtee_supp_complete_req(r); + } + + if (supp->ctx == ctx) + supp->ctx = NULL; + + mutex_unlock(&supp->mutex); +} + +void mbedtee_supp_abort_all(struct mbedtee_supp *supp) +{ + struct mbedtee_supp_req *r, *n; + + mutex_lock(&supp->mutex); + supp->shutting_down = true; + + list_for_each_entry_safe(r, n, &supp->reqs, node) + mbedtee_supp_complete_req(r); + + list_for_each_entry_safe(r, n, &supp->active_reqs, node) + mbedtee_supp_complete_req(r); + + supp->ctx = NULL; + + mutex_unlock(&supp->mutex); + complete_all(&supp->reqs_c); +} + +static int supp_enqueue_req(struct mbedtee_supp *supp, + struct mbedtee_supp_req *req) +{ + int ret = 0; + + mutex_lock(&supp->mutex); + if (supp->shutting_down) + ret = -ESHUTDOWN; + else + list_add_tail(&req->node, &supp->reqs); + mutex_unlock(&supp->mutex); + + return ret; +} + +/* + * Receive an RPC request from TEE and dispatch it to supplicant. + * Blocks until the supplicant sends back the result. + */ +void mbedtee_supp_handler(struct mbedtee_device *mbedtee, + u32 func, void *data, size_t size) +{ + struct mbedtee_supp *supp = &mbedtee->supp; + struct mbedtee_supp_req *req = kzalloc_obj(*req, GFP_KERNEL); + struct supp_cmd_hdr *cmd = data; + int ret; + + if (!req) { + if (cmd && size >= sizeof(*cmd)) + cmd->ret = TEEC_ERROR_OUT_OF_MEMORY; + return; + } + + init_completion(&req->c); + req->func = func; + req->data = data; + req->size = size; + + ret = supp_enqueue_req(supp, req); + if (ret != 0) { + if (cmd && size >= sizeof(*cmd)) + cmd->ret = TEEC_ERROR_COMMUNICATION; + kfree(req); + return; + } + + /* Wake up the supplicant daemon to handle this request. */ + complete(&supp->reqs_c); + + wait_for_completion(&req->c); + + kfree(req); +} + +static int supp_pop_req(struct mbedtee_supp *supp, + struct mbedtee_supp_req **req) +{ + struct mbedtee_supp_req *r = NULL; + int ret = 0; + + mutex_lock(&supp->mutex); + if (supp->shutting_down) { + ret = -ESHUTDOWN; + goto out; + } + + r = list_first_entry_or_null(&supp->reqs, struct mbedtee_supp_req, node); + + if (r) { + list_del_init(&r->node); + r->worker = current; + list_add_tail(&r->node, &supp->active_reqs); + } + +out: + mutex_unlock(&supp->mutex); + *req = r; + return ret; +} + +/* + * Called by supplicant to receive the next pending request. + */ +int mbedtee_supp_recv(struct tee_context *ctx, + u32 *func, u32 *num_params, struct tee_param *param) +{ + struct tee_device *teedev = ctx->teedev; + struct mbedtee_device *mbedtee = tee_get_drvdata(teedev); + struct mbedtee_supp *supp = &mbedtee->supp; + struct mbedtee_supp_req *req; + struct mbedtee_context_data *d = ctx->data; + struct tee_shm *shm = param->u.memref.shm; + int ret; + + ret = mbedtee_supp_check_recv_params(*num_params, param); + if (ret != 0) + return ret; + + shm = tee_shm_get_from_id(ctx, shm->id); + tee_shm_put(param->u.memref.shm); + if (IS_ERR(shm)) + return PTR_ERR(shm); + param->u.memref.shm = shm; + + d->is_supp_ctx = true; + + while (true) { + ret = supp_pop_req(supp, &req); + if (ret != 0) + goto err; + if (req) + break; + + if (wait_for_completion_interruptible(&supp->reqs_c)) { + ret = -ERESTARTSYS; + goto err; + } + } + + /* Record which supplicant context owns this request. */ + req->ctx = ctx; + + if (req->size > param->u.memref.size) { + /* Return the request to the queue so it isn't lost */ + mutex_lock(&supp->mutex); + list_del(&req->node); + list_add(&req->node, &supp->reqs); + mutex_unlock(&supp->mutex); + ret = -EOVERFLOW; + goto err; + } + + *func = req->func; + req->shm = shm; + + memcpy(shm->kaddr, req->data, req->size); + param->u.memref.size = req->size; + + return 0; + +err: + tee_shm_put(shm); + param->u.memref.shm = NULL; + return ret; +} + +/* + * Called by supplicant to send back the result of a request. + */ +int mbedtee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params, + struct tee_param *param) +{ + struct tee_device *teedev = ctx->teedev; + struct mbedtee_device *mbedtee = tee_get_drvdata(teedev); + struct mbedtee_supp *supp = &mbedtee->supp; + struct mbedtee_supp_req *req; + struct mbedtee_supp_req *_req; + + if (num_params != 1) + return -EINVAL; + + if (!tee_param_is_memref(param)) + return -EINVAL; + + /* + * Find the active request belonging to this worker thread. + * Each worker is identified by its task_struct pointer, which + * was recorded when supp_pop_req() dispatched the request. + */ + mutex_lock(&supp->mutex); + req = NULL; + list_for_each_entry(_req, &supp->active_reqs, node) { + if (_req->worker == current) { + req = _req; + list_del_init(&req->node); + break; + } + } + mutex_unlock(&supp->mutex); + + if (!req) + return -ENOENT; + + if (param->u.memref.size > req->size) { + req->ret = TEEC_ERROR_COMMUNICATION; + } else { + req->ret = ret; + memcpy(req->data, req->shm->kaddr, param->u.memref.size); + } + if (req->shm) + tee_shm_put(req->shm); + req->shm = NULL; + + /* Wake up mbedtee_supp_handler(). */ + complete(&req->c); + + return 0; +} diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index 5203977..2d30296 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -60,6 +60,7 @@ #define TEE_IMPL_ID_AMDTEE 2 #define TEE_IMPL_ID_TSTEE 3 #define TEE_IMPL_ID_QTEE 4 +#define TEE_IMPL_ID_MBEDTEE 5 /* * OP-TEE specific capabilities -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 3/3] tee: add MbedTEE driver 2026-07-01 13:25 ` [PATCH 3/3] tee: add MbedTEE driver Xing Loong @ 2026-07-01 13:42 ` sashiko-bot 0 siblings, 0 replies; 15+ messages in thread From: sashiko-bot @ 2026-07-01 13:42 UTC (permalink / raw) To: Xing Loong; +Cc: conor+dt, robh, devicetree Thank you for your contribution! Sashiko AI review found 16 potential issue(s) to consider: - [High] Calling xa_insert_irq() with GFP_KERNEL without XA_FLAGS_LOCK_IRQ causes a sleep-in-atomic kernel panic. - [High] mbedtee_release() leaks sessions if a TEE context acts as a supplicant. - [High] Transient GFP_ATOMIC allocation failures cause a permanent hang of the TEE-to-REE channel. - [High] rpc_routine() silently drops the COMPLETE_TEE notification if mbedtee_rpc_fastcall() fails with -ENOMEM. - [High] Closing any single supplicant context incorrectly aborts all unassigned global requests. - [High] Dereferencing and freeing `call` after transferring ownership to the background completion handler on interrupted wait. - [High] Double free of `tee_shm` object on error path in `mbedtee_supp_recv` due to missing `NULL` assignment. - [High] Missing reference acquisition for `req->shm` leads to UAF and double free in `mbedtee_supp_send`. - [High] Uninitialized or stale error codes are sent to the TEE when supplicant requests complete or abort. - [High] The TEE-to-REE RPC hardirq handler sleeps while holding a spinlock if the TEE sends an async supplicant request, leading to an immediate kernel panic. - [High] The RPC ring buffer parser skips only the command header when encountering invalid or oversized async RPCs, abandoning payload bytes in the ring. - [High] Physical pages backing dynamic shared memory are unconditionally freed to the Linux allocator even if the driver fails to revoke the TEE's access. - [High] The driver initializes its local ring buffer indices directly from unvalidated shared memory, enabling arbitrary out-of-bounds kernel memory accesses. - [Medium] mbedtee_release() leaks mbedtee_session objects if mbedtee_close_session() fails. - [Medium] Bypassing wait_for_completion_interruptible() creates an unbounded phantom wakeup accumulation causing CPU spikes. - [Medium] Permanent memory leak of `mbedtee_rpc_call` structures on module removal for interrupted calls. -- commit 37902c22c67b16bd6c582807221293c21dc8b899 Author: Xing Loong <xing.xl.loong@gmail.com> Subject: tee: add MbedTEE driver This commit adds a new Linux TEE driver for communicating with the MbedTEE OS. It supports ARM TrustZone and RISC-V IMSIC platforms, utilizing a shared-memory RPC protocol to interact with the Trusted Execution Environment. > diff --git a/drivers/tee/mbedtee/core.c b/drivers/tee/mbedtee/core.c > --- a/drivers/tee/mbedtee/core.c > +++ b/drivers/tee/mbedtee/core.c > @@ -83,10 +83,11 @@ static void mbedtee_release(struct tee_context *ctx) > > if (d->is_supp_ctx) { > mbedtee_supp_release(&mbedtee->supp, ctx); > } else { > list_for_each_entry_safe(s, n, &d->sess_list, list_node) > mbedtee_close_session(ctx, s->session_id); > } > > kfree(d); [Severity: High] Does this mutually exclusive block skip session cleanup entirely if the context was acting as a supplicant? It appears a userspace application could open a session, act as a supplicant, and close the file descriptor, resulting in leaked mbedtee_session objects since the else branch is completely bypassed. [Severity: Medium] If mbedtee_close_session fails (for example, due to an RPC allocation failure) and leaves a session in the list, does freeing the context data via kfree permanently lose the pointers to those session objects? > [ ... ] > @@ -195,12 +196,10 @@ static void mbedtee_remove(struct platform_device *pdev) > mbedtee_rpc_uninit(mbedtee); > mbedtee_r2t_uninit(mbedtee); > mbedtee_supp_uninit(&mbedtee->supp); > tee_shm_pool_free(mbedtee->pool); > xa_destroy(&mbedtee->rpc_calls); > } [Severity: Medium] Does xa_destroy only free the internal XArray nodes? If there are any mbedtee_rpc_call structures left behind by interrupted calls when the module is removed, it seems this might cause a kernel memory leak. > diff --git a/drivers/tee/mbedtee/rpc_callee.c b/drivers/tee/mbedtee/rpc_callee.c > --- a/drivers/tee/mbedtee/rpc_callee.c > +++ b/drivers/tee/mbedtee/rpc_callee.c > @@ -224,10 +224,9 @@ static struct rpc_work *rpc_pick_next(struct mbedtee_device *mbedtee, > skip: > if (cmd.waiter_id != 0 && !rpc_queue_complete_only(ctx, cmd.waiter_id)) > return NULL; > > /* Bad or unhandled header: consume it to keep the ring moving */ > t2r_ring_advance(ctx, sizeof(cmd)); > > return NULL; > } [Severity: High] If an async RPC with an inline payload reaches this skip path, does advancing only the header size abandon the payload bytes in the ring? It looks like the next iteration might mistakenly parse those remaining payload bytes as a new command header, corrupting the ring buffer permanently. > [ ... ] > @@ -245,10 +244,9 @@ static struct rpc_work *rpc_pick_next(struct mbedtee_device *mbedtee, > if (off > ctx->t2r_shm_sz - cmd.size) > goto skip; > > new_work = kzalloc_obj(*new_work, GFP_ATOMIC); > if (!new_work) > return NULL; > > t2r_ring_advance(ctx, sizeof(cmd)); [Severity: High] If this transient GFP_ATOMIC memory allocation fails, returning NULL without advancing the ring or queuing a retry mechanism seems to leave the ring permanently stalled. Is there a way for the TEE-to-REE channel to recover from this condition? > [ ... ] > @@ -288,14 +286,13 @@ static void rpc_drain_ring(struct mbedtee_device *mbedtee) > while (READ_ONCE(ctx->t2r_ring_rd) != > /* Pair with producer store-release after ring write. */ > smp_load_acquire(&ctx->t2r_ring->wr)) { > rw.data = ctx->rpc_data; > c = rpc_pick_next(mbedtee, ctx, &rw); > if (!c) > break; > > if (c->waiter_id == 0) { > c->func(mbedtee, c->data, c->size); > } else { > INIT_WORK(&c->work, rpc_routine); [Severity: High] Breaking the loop here on a NULL return from rpc_pick_next due to an allocation failure appears to abandon ring processing entirely. Will this leave the TEE waiting for completion indefinitely? [Severity: High] For async requests (waiter_id == 0), the handler is called inline while holding the ctx->ring_lock spinlock with interrupts disabled. If the TEE sends an async supplicant request, does mbedtee_supp_handler end up sleeping on wait_for_completion in atomic context, leading to a kernel panic? > [ ... ] > @@ -328,9 +325,8 @@ static void rpc_routine(struct work_struct *work) > * Keep retrying rather than silently dropping the completion, which > * would leave the TEE thread blocked in rpc_call_sync forever. > * On ARM the fastcall is a direct SMC so -ENOSPC never occurs. > */ > while (mbedtee_rpc_fastcall(c->mbedtee, MBEDTEE_RPC_COMPLETE_TEE, > (unsigned long)c->waiter_id, 0, 0) == -ENOSPC) > cond_resched(); [Severity: High] Could mbedtee_rpc_fastcall return -ENOMEM instead of -ENOSPC on RISC-V? If so, this loop exits immediately and drops the COMPLETE_TEE notification, which might leave the TEE thread permanently blocked. > [ ... ] > @@ -420,10 +416,9 @@ int mbedtee_rpc_init(struct mbedtee_device *mbedtee) > memunmap(ctx->t2r_ring); > ctx->t2r_ring = NULL; > return -EINVAL; > } > ctx->t2r_ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); > /* Observe latest producer index only after ring metadata is visible. */ > WRITE_ONCE(ctx->t2r_ring_rd, smp_load_acquire(&ctx->t2r_ring->rd)); [Severity: High] Does reading the initial consumer index directly from unvalidated shared memory allow a compromised TEE to provide out-of-bounds indices? This might lead to out-of-bounds reads or writes in kernel memory during ring buffer copies. > diff --git a/drivers/tee/mbedtee/rpc_caller.c b/drivers/tee/mbedtee/rpc_caller.c > --- a/drivers/tee/mbedtee/rpc_caller.c > +++ b/drivers/tee/mbedtee/rpc_caller.c > @@ -37,10 +37,9 @@ int mbedtee_rpc_call_alloc(struct mbedtee_device *mbedtee, > > do { > rpc_index = atomic_long_inc_return(&mbedtee->rpc_call_seq); > } while (rpc_index == 0); > > ret = xa_insert_irq(&mbedtee->rpc_calls, rpc_index, rcall, GFP_KERNEL); > if (ret != 0) { > kfree(rcall); [Severity: High] The XArray for rpc_calls is initialized with XA_FLAGS_ALLOC1 but lacks XA_FLAGS_LOCK_IRQ. When xa_insert_irq is called here with GFP_KERNEL, it disables local interrupts and might attempt to sleep during memory allocation if the array needs to expand. Could this trigger a sleep-in-atomic panic? > [ ... ] > @@ -58,10 +57,9 @@ void mbedtee_rpc_call_free(struct mbedtee_device *mbedtee, > > /* > * If interrupted/completed, complete_call owns this allocation > * and will xa_erase + kfree when the TEE eventually finishes. > */ > if (READ_ONCE(call->state) == MBEDTEE_RPC_CALL_INTERRUPTED) > return; > > xa_erase_irq(&mbedtee->rpc_calls, rpc_index); [Severity: High] If ownership is transferred to the background completion handler upon an interrupted wait, could the background handler free the call object concurrently while the caller is still accessing it? > [ ... ] > @@ -382,10 +380,9 @@ int mbedtee_invoke_func(struct tee_context *ctx, > > ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_INVOKE_SESSION, call, true); > > dev_dbg(mbedtee->dev, "invoke session ret %d gp_ret %d\n", ret, > call->rpc.ret); > > if (ret != 0) { > arg->ret = TEEC_ERROR_COMMUNICATION; [Severity: High] If mbedtee_rpc_yieldcall was interrupted and transferred ownership of the call object to the background handler, is reading call->rpc.ret here susceptible to a use-after-free? > [ ... ] > @@ -416,10 +413,9 @@ int mbedtee_close_session(struct tee_context *ctx, u32 session) > ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*rp), &call); > if (ret != 0) { > /* > * RPC allocation failed but the session is still alive on > * the TEE side. Leave it in the session list so a future > * close attempt can retry; warn so the condition is visible. > */ > mutex_unlock(&ctxdata->mutex); > return ret; [Severity: Medium] If this returns early leaving the session in the list, the caller (mbedtee_release) ignores the error and proceeds to kfree the context data. Does this permanently leak the mbedtee_session objects? > diff --git a/drivers/tee/mbedtee/rpc_caller_riscv.c b/drivers/tee/mbedtee/rpc_caller_riscv.c > --- a/drivers/tee/mbedtee/rpc_caller_riscv.c > +++ b/drivers/tee/mbedtee/rpc_caller_riscv.c > @@ -170,9 +170,8 @@ int mbedtee_r2t_init(struct mbedtee_device *mbedtee) > } > ctx->ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); > /* Read initial writer index with acquire for coherent ring bootstrap. */ > WRITE_ONCE(ctx->ring_wr, smp_load_acquire(&ctx->ring->wr)); > > dev_dbg(mbedtee->dev, "rpc-r2t-ring %pa\n", &res.start); [Severity: High] Similar to the T2R ring, does accepting this unvalidated write index from the TEE-controlled shared memory pose an out-of-bounds memory access risk? > diff --git a/drivers/tee/mbedtee/shm_pool.c b/drivers/tee/mbedtee/shm_pool.c > --- a/drivers/tee/mbedtee/shm_pool.c > +++ b/drivers/tee/mbedtee/shm_pool.c > @@ -62,10 +62,9 @@ static void pool_op_free(struct tee_shm_pool *pool, > if (!(shm->flags & TEE_SHM_PRIV)) { > struct mbedtee_device *mbedtee = tee_get_drvdata(shm->ctx->teedev); > > if (mbedtee->yield && shm->sec_world_id) > mbedtee_shm_unregister(shm->ctx, shm); > } > > free_pages((unsigned long)shm->kaddr, get_order(shm->size)); > shm->kaddr = NULL; [Severity: High] If mbedtee_shm_unregister fails to revoke the TEE's access (for instance, if the RPC ring is full), the memory pages are still unconditionally freed to the Linux allocator. Could this allow the TEE to maintain access to memory that gets reused for other kernel processes? > diff --git a/drivers/tee/mbedtee/supp.c b/drivers/tee/mbedtee/supp.c > --- a/drivers/tee/mbedtee/supp.c > +++ b/drivers/tee/mbedtee/supp.c > @@ -73,10 +73,9 @@ void mbedtee_supp_release(struct mbedtee_supp *supp, struct tee_context *ctx) > * interfere with each other. > */ > list_for_each_entry_safe(r, n, &supp->reqs, node) { > if (r->ctx && r->ctx != ctx) > continue; > mbedtee_supp_complete_req(r); > } [Severity: High] Unassigned global requests waiting in supp->reqs will have r->ctx set to NULL. This makes the condition evaluate to false. Does this mean that whenever any single supplicant context is closed, it incorrectly aborts all unassigned global requests across the system? > [ ... ] > @@ -156,10 +155,9 @@ void mbedtee_supp_handler(struct mbedtee_device *mbedtee, > > /* Wake up the supplicant daemon to handle this request. */ > complete(&supp->reqs_c); > > wait_for_completion(&req->c); > > kfree(req); > } [Severity: High] When the request finishes, req->ret contains the completion status. Is this return code ever copied back into the wire-format cmd->ret in shared memory before freeing the request, or is the TEE receiving uninitialized data? > [ ... ] > @@ -206,10 +204,9 @@ int mbedtee_supp_recv(struct tee_context *ctx, > if (ret != 0) > return ret; > > shm = tee_shm_get_from_id(ctx, shm->id); > tee_shm_put(param->u.memref.shm); > if (IS_ERR(shm)) > return PTR_ERR(shm); > param->u.memref.shm = shm; [Severity: High] If tee_shm_get_from_id fails, the function returns the error without clearing param->u.memref.shm. Will the TEE core caller's cleanup path call tee_shm_put again on this same pointer, causing a double free? > [ ... ] > @@ -217,10 +214,9 @@ int mbedtee_supp_recv(struct tee_context *ctx, > d->is_supp_ctx = true; > > while (true) { > ret = supp_pop_req(supp, &req); > if (ret != 0) > goto err; > if (req) > break; [Severity: Medium] If a request is already queued, this bypasses wait_for_completion_interruptible entirely. Could this leave the internal completion counter un-decremented, causing phantom wakeups to accumulate and creating a high CPU spinning loop later when the queue is empty? > [ ... ] > @@ -238,10 +234,9 @@ int mbedtee_supp_recv(struct tee_context *ctx, > goto err; > } > > *func = req->func; > req->shm = shm; > > memcpy(shm->kaddr, req->data, req->size); > param->u.memref.size = req->size; [Severity: High] This assigns the shared memory pointer to req->shm without bumping its reference count. If the TEE core drops its reference when returning to userspace, might this memory be freed prematurely? > [ ... ] > @@ -295,11 +290,10 @@ int mbedtee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params, > > if (param->u.memref.size > req->size) { > req->ret = TEEC_ERROR_COMMUNICATION; > } else { > req->ret = ret; > memcpy(req->data, req->shm->kaddr, param->u.memref.size); > } > if (req->shm) > tee_shm_put(req->shm); [Severity: High] Following up on the previous question, if req->shm was freed because a reference was not acquired in mbedtee_supp_recv, does this memcpy and subsequent tee_shm_put result in a use-after-free and a double free? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260701132514.186953-1-xing.xl.loong@gmail.com?part=3 ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 0/3] tee: add MbedTEE driver 2026-07-01 13:25 [PATCH 0/3] tee: add MbedTEE driver Xing Loong ` (2 preceding siblings ...) 2026-07-01 13:25 ` [PATCH 3/3] tee: add MbedTEE driver Xing Loong @ 2026-07-02 15:11 ` Xing Loong 2026-07-02 15:11 ` [PATCH v2 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong ` (2 more replies) 3 siblings, 3 replies; 15+ messages in thread From: Xing Loong @ 2026-07-02 15:11 UTC (permalink / raw) To: Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong This series adds a Linux TEE driver for MbedTEE, a Trusted Execution Environment for embedded systems (https://github.com/mbedtee). Two RPC transports are provided for systems where platform firmware or board configuration has already established the REE/TEE separation before Linux boots: - ARM/ARM64: SMC calls and GIC SPI notifications (TrustZone) - RISC-V: shared-memory ring buffers and IMSIC MSI notifications The driver implements the TEE subsystem interface (tee_driver_ops) and provides GlobalPlatform TEE Client API support, dynamic shared memory registration, and tee-supplicant support for REE filesystem and RPMB operations. The series is structured as follows: [1/3] dt-bindings: vendor-prefixes: add mbedtee [2/3] dt-bindings: firmware: add mbedtee,tee binding [3/3] tee: add MbedTEE driver --- Changes in v2: - 0002: Fix DT binding review comments from Krzysztof Kozlowski: - Drop $nodename, "YAML devicetree binding" wording, property descriptions - Rename compatible string to mbedtee,tee - Rename memory regions: rpc-t2r-ring -> t2r-ring, rpc-t2r-shm -> t2r-shm, rpc-r2t-ring -> r2t-ring - Add memory-region / memory-region-names to required - Simplify allOf constraints (drop redundant else-branch items) - Rewrite description to describe hardware/firmware, not the binding or driver - Drop all irrelevant platform nodes (gic, cpus, reserved-memory containers, reg addresses, riscv wrapper); the ARM example now uses bare interrupts (matching arm,sbsa-gwdt.yaml precedent) with only phandle-required stubs (imsic, t2r-ring, r2t-ring) - Add maxItems: 1 constraint to interrupts property (Sashiko AI review) - 0003: - Fix supp_release incorrectly aborting unclaimed requests on close - Fix potential tee_shm double-free on supp_recv error path - Fix async RPC ring skip leaving orphaned payload bytes - Fix COMPLETE_TEE retry to also handle transient -ENOMEM on RISC-V - Fix session leak on close_session allocation failure: release kernel resources before sending RPC --- Xing Loong (3): dt-bindings: vendor-prefixes: add mbedtee dt-bindings: firmware: add mbedtee,tee binding tee: add MbedTEE driver .../bindings/firmware/mbedtee,tee.yaml | 132 ++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + Documentation/tee/index.rst | 1 + Documentation/tee/mbedtee.rst | 155 +++++ MAINTAINERS | 9 + drivers/tee/Kconfig | 3 +- drivers/tee/Makefile | 1 + drivers/tee/mbedtee/Kconfig | 20 + drivers/tee/mbedtee/Makefile | 11 + drivers/tee/mbedtee/core.c | 236 +++++++ drivers/tee/mbedtee/mbedtee_drv.h | 269 ++++++++ drivers/tee/mbedtee/mbedtee_msg.h | 219 +++++++ drivers/tee/mbedtee/rpc_callee.c | 618 ++++++++++++++++++ drivers/tee/mbedtee/rpc_callee_arm.c | 91 +++ drivers/tee/mbedtee/rpc_callee_riscv.c | 203 ++++++ drivers/tee/mbedtee/rpc_caller.c | 532 +++++++++++++++ drivers/tee/mbedtee/rpc_caller_arm.c | 66 ++ drivers/tee/mbedtee/rpc_caller_riscv.c | 192 ++++++ drivers/tee/mbedtee/shm_pool.c | 105 +++ drivers/tee/mbedtee/shm_pool.h | 15 + drivers/tee/mbedtee/supp.c | 310 +++++++++ include/uapi/linux/tee.h | 1 + 22 files changed, 3190 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml create mode 100644 Documentation/tee/mbedtee.rst create mode 100644 drivers/tee/mbedtee/Kconfig create mode 100644 drivers/tee/mbedtee/Makefile create mode 100644 drivers/tee/mbedtee/core.c create mode 100644 drivers/tee/mbedtee/mbedtee_drv.h create mode 100644 drivers/tee/mbedtee/mbedtee_msg.h create mode 100644 drivers/tee/mbedtee/rpc_callee.c create mode 100644 drivers/tee/mbedtee/rpc_callee_arm.c create mode 100644 drivers/tee/mbedtee/rpc_callee_riscv.c create mode 100644 drivers/tee/mbedtee/rpc_caller.c create mode 100644 drivers/tee/mbedtee/rpc_caller_arm.c create mode 100644 drivers/tee/mbedtee/rpc_caller_riscv.c create mode 100644 drivers/tee/mbedtee/shm_pool.c create mode 100644 drivers/tee/mbedtee/shm_pool.h create mode 100644 drivers/tee/mbedtee/supp.c base-commit: 03e2778d1f11de9260543f969e9e888a1c2bf830 -- 2.43.0 ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 1/3] dt-bindings: vendor-prefixes: add mbedtee 2026-07-02 15:11 ` [PATCH v2 0/3] " Xing Loong @ 2026-07-02 15:11 ` Xing Loong 2026-07-02 15:11 ` [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong 2026-07-02 15:11 ` [PATCH v2 3/3] tee: add MbedTEE driver Xing Loong 2 siblings, 0 replies; 15+ messages in thread From: Xing Loong @ 2026-07-02 15:11 UTC (permalink / raw) To: Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong Register the "mbedtee" vendor prefix for the MbedTEE Trusted Execution Environment project (https://github.com/mbedtee). Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 28784d6..fd032a3 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1011,6 +1011,8 @@ patternProperties: description: Maxtor Corporation "^mayqueen,.*": description: Mayqueen Technologies Ltd. + "^mbedtee,.*": + description: MbedTEE Trusted Execution Environment project "^mbvl,.*": description: Mobiveil Inc. "^mcube,.*": -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding 2026-07-02 15:11 ` [PATCH v2 0/3] " Xing Loong 2026-07-02 15:11 ` [PATCH v2 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong @ 2026-07-02 15:11 ` Xing Loong 2026-07-02 15:21 ` sashiko-bot 2026-07-02 15:27 ` Krzysztof Kozlowski 2026-07-02 15:11 ` [PATCH v2 3/3] tee: add MbedTEE driver Xing Loong 2 siblings, 2 replies; 15+ messages in thread From: Xing Loong @ 2026-07-02 15:11 UTC (permalink / raw) To: Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong MbedTEE is a Trusted Execution Environment for embedded systems (https://github.com/mbedtee). It communicates with the REE via shared-memory ring buffers using a fixed RPC protocol. Two platform configurations are supported: - ARM/AArch64 (TrustZone, SMC): two reserved-memory regions (t2r-ring and t2r-shm) plus a GIC SPI edge interrupt for TEE-to-REE notifications. - RISC-V (IMSIC): three reserved-memory regions, adding r2t-ring for REE-to-TEE command submissions; no interrupts property (T2R notifications use IMSIC MSI allocated at runtime). Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> --- Changes in v2: - Fix DT binding review comments from Krzysztof Kozlowski: - Drop $nodename, "YAML devicetree binding" wording, property descriptions - Rename compatible string to mbedtee,tee - Rename memory regions: rpc-t2r-ring -> t2r-ring, rpc-t2r-shm -> t2r-shm, rpc-r2t-ring -> r2t-ring - Add memory-region / memory-region-names to required - Simplify allOf constraints (drop redundant else-branch items) - Rewrite description to describe hardware/firmware, not the binding or driver - Drop all irrelevant platform nodes (gic, cpus, reserved-memory containers, reg addresses, riscv wrapper); the ARM example now uses bare interrupts (matching arm,sbsa-gwdt.yaml precedent) with only phandle-required stubs (imsic, t2r-ring, r2t-ring) - Add maxItems: 1 constraint to interrupts property (Sashiko AI review) --- .../bindings/firmware/mbedtee,tee.yaml | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml diff --git a/Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml b/Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml new file mode 100644 index 0000000..62f2522 --- /dev/null +++ b/Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/firmware/mbedtee,tee.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MbedTEE Trusted Execution Environment + +maintainers: + - Xing Loong <xing.xl.loong@gmail.com> + +description: | + MbedTEE is a Trusted Execution Environment for embedded systems. + It communicates with the REE (Linux) via shared-memory ring + buffers using a fixed RPC protocol. + + The REE and TEE CPUs sharing the RPC memory must be in a + hardware-coherent domain (same CPU cluster, coherent caches). + + Two or three reserved-memory regions are required: + + t2r-ring ring buffer for TEE-to-REE notifications (all platforms) + t2r-shm shared memory for TEE-to-REE RPC payloads (all platforms) + r2t-ring ring buffer for REE-to-TEE command submissions (RISC-V only) + + On ARM/AArch64 the TEE is invoked via SMC and signals the REE via + a GIC SPI edge interrupt. + + On RISC-V the TEE signals the REE via IMSIC MSI; the REE submits + commands via r2t-ring that the TEE polls. No REE-to-TEE interrupt + is used. No SBI ecall is involved. + +properties: + compatible: + const: mbedtee,tee + + interrupts: + maxItems: 1 + + msi-parent: + maxItems: 1 + + memory-region: + minItems: 2 + maxItems: 3 + # Minimum across platforms (ARM: 2, RISC-V: 3), constrained by allOf below + + memory-region-names: + minItems: 2 + maxItems: 3 + items: + enum: + - t2r-ring + - t2r-shm + - r2t-ring + +required: + - compatible + - memory-region + - memory-region-names + +allOf: + - if: + required: + - interrupts + then: + required: + - interrupts + properties: + msi-parent: false + memory-region: + maxItems: 2 + memory-region-names: + items: + - const: t2r-ring + - const: t2r-shm + else: + required: + - msi-parent + properties: + memory-region: + minItems: 3 + memory-region-names: + items: + - const: t2r-ring + - const: t2r-shm + - const: r2t-ring + +additionalProperties: false + +examples: + - | + /* ARM TrustZone (SMC) */ + #include <dt-bindings/interrupt-controller/arm-gic.h> + + firmware { + mbedtee { + compatible = "mbedtee,tee"; + interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>; + memory-region = <&mbedtee_t2r_ring>, <&mbedtee_t2r_shm>; + memory-region-names = "t2r-ring", "t2r-shm"; + }; + }; + + /* memory-region phandle targets */ + mbedtee_t2r_ring: t2r-ring {}; + mbedtee_t2r_shm: t2r-shm {}; + + - | + /* RISC-V IMSIC (ring-buffer polling REE->TEE, MSI TEE->REE) */ + + firmware { + mbedtee { + compatible = "mbedtee,tee"; + msi-parent = <&imsic>; + memory-region = <&rv_t2r_ring>, <&rv_t2r_shm>, + <&rv_r2t_ring>; + memory-region-names = "t2r-ring", "t2r-shm", + "r2t-ring"; + }; + }; + + /* msi-parent phandle target */ + imsic: imsic { + msi-controller; + #msi-cells = <0>; + }; + + /* memory-region phandle targets */ + rv_t2r_ring: t2r-ring {}; + rv_t2r_shm: t2r-shm {}; + rv_r2t_ring: r2t-ring {}; -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding 2026-07-02 15:11 ` [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong @ 2026-07-02 15:21 ` sashiko-bot 2026-07-02 15:27 ` Krzysztof Kozlowski 1 sibling, 0 replies; 15+ messages in thread From: sashiko-bot @ 2026-07-02 15:21 UTC (permalink / raw) To: Xing Loong; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] The `memory-region-names` property incorrectly defines `items` as an object instead of an array of schemas. - [Low] The provided DT binding examples contain malformed root-level nodes that will cause `dt_binding_check` to fail. -- commit 5f6618bd551455752fa4251daab79dbc393c89d6 Author: Xing Loong <xing.xl.loong@gmail.com> dt-bindings: firmware: add mbedtee,tee binding This commit introduces a new device tree binding for MbedTEE, a Trusted Execution Environment for embedded systems. It supports both ARM/AArch64 using SMC and RISC-V using IMSIC configurations, communicating via shared memory ring buffers. > diff --git a/Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml b/Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml > new file mode 100644 > --- /dev/null > +++ b/Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml [ ... ] > + memory-region-names: > + minItems: 2 > + maxItems: 3 > + items: > + enum: > + - t2r-ring > + - t2r-shm > + - r2t-ring [Severity: Low] Does this items definition violate the dt-schema constraints for properties ending in -names? dt-schema typically requires an array of schemas for items to enforce strict sequence order, rather than an enum object. This could cause make dt_binding_check to fail against the core meta-schema. [ ... ] > +examples: > + - | > + /* ARM TrustZone (SMC) */ > + #include <dt-bindings/interrupt-controller/arm-gic.h> > + > + firmware { > + mbedtee { > + compatible = "mbedtee,tee"; > + interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>; > + memory-region = <&mbedtee_t2r_ring>, <&mbedtee_t2r_shm>; > + memory-region-names = "t2r-ring", "t2r-shm"; > + }; > + }; > + > + /* memory-region phandle targets */ > + mbedtee_t2r_ring: t2r-ring {}; > + mbedtee_t2r_shm: t2r-shm {}; [Severity: Low] Will placing these memory region nodes at the root level instead of inside a reserved-memory node cause dt_binding_check to reject the example? The root node schema typically rejects arbitrary children lacking compatible strings. [ ... ] > + /* msi-parent phandle target */ > + imsic: imsic { > + msi-controller; > + #msi-cells = <0>; > + }; [Severity: Low] Can this dummy node cause schema validation failures? It lacks a compatible string and is placed directly at the root level, which dt-schema usually flags as malformed. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260702151115.544016-1-xing.xl.loong@gmail.com?part=2 ^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding 2026-07-02 15:11 ` [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong 2026-07-02 15:21 ` sashiko-bot @ 2026-07-02 15:27 ` Krzysztof Kozlowski 1 sibling, 0 replies; 15+ messages in thread From: Krzysztof Kozlowski @ 2026-07-02 15:27 UTC (permalink / raw) To: Xing Loong, Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel On 02/07/2026 17:11, Xing Loong wrote: > + > + On RISC-V the TEE signals the REE via IMSIC MSI; the REE submits > + commands via r2t-ring that the TEE polls. No REE-to-TEE interrupt > + is used. No SBI ecall is involved. > + > +properties: > + compatible: > + const: mbedtee,tee No, you need to respond to feedback instead of sending the same code called v2. You received comments for a reason. Sending v2 now hides all this comments and people might think discussion is resolved. This is very bad practice. Also, Do not attach (thread) your patchsets to some other threads (unrelated or older versions). This buries them deep in the mailbox and might interfere with applying entire sets. See also: https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830 ... > + > +examples: > + - | > + /* ARM TrustZone (SMC) */ > + #include <dt-bindings/interrupt-controller/arm-gic.h> > + > + firmware { > + mbedtee { > + compatible = "mbedtee,tee"; > + interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>; > + memory-region = <&mbedtee_t2r_ring>, <&mbedtee_t2r_shm>; > + memory-region-names = "t2r-ring", "t2r-shm"; > + }; > + }; > + > + /* memory-region phandle targets */ > + mbedtee_t2r_ring: t2r-ring {}; > + mbedtee_t2r_shm: t2r-shm {}; I asked you to drop all these irrelevant entries. > + > + - | > + /* RISC-V IMSIC (ring-buffer polling REE->TEE, MSI TEE->REE) */ > + > + firmware { > + mbedtee { > + compatible = "mbedtee,tee"; > + msi-parent = <&imsic>; > + memory-region = <&rv_t2r_ring>, <&rv_t2r_shm>, > + <&rv_r2t_ring>; > + memory-region-names = "t2r-ring", "t2r-shm", > + "r2t-ring"; > + }; > + }; > + > + /* msi-parent phandle target */ > + imsic: imsic { > + msi-controller; > + #msi-cells = <0>; > + }; Also drop. > + > + /* memory-region phandle targets */ > + rv_t2r_ring: t2r-ring {}; > + rv_t2r_shm: t2r-shm {}; > + rv_r2t_ring: r2t-ring {}; Also not relevant. I will not continue the review. Please go back to v1 and continue the discussion. I consider this version NAKed. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 3/3] tee: add MbedTEE driver 2026-07-02 15:11 ` [PATCH v2 0/3] " Xing Loong 2026-07-02 15:11 ` [PATCH v2 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong 2026-07-02 15:11 ` [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong @ 2026-07-02 15:11 ` Xing Loong 2026-07-02 15:32 ` sashiko-bot 2 siblings, 1 reply; 15+ messages in thread From: Xing Loong @ 2026-07-02 15:11 UTC (permalink / raw) To: Jens Wiklander Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Sumit Garg, op-tee, devicetree, linux-riscv, linux-kernel, Xing Loong This patch adds a Linux TEE driver that communicates with MbedTEE OS, a Trusted Execution Environment for embedded systems (https://github.com/mbedtee) via a fixed shared-memory RPC protocol over architecture-specific transports: - ARM/ARM64: SMCCC fast/yield calls for REE-to-TEE requests; TEE-to-REE notifications via a GIC SPI edge interrupt. - RISC-V: REE submits commands by writing to a shared-memory r2t-ring that the TEE polls; the TEE notifies the REE by writing to t2r-ring and raising a platform IMSIC MSI. No REE-to-TEE interrupt is used. The driver implements the TEE subsystem interface (tee_driver_ops) and provides GlobalPlatform TEE Client API support, dynamic shared memory registration, and tee-supplicant support. Signed-off-by: Xing Loong <xing.xl.loong@gmail.com> --- Changes in v2: - Fix supp_release incorrectly aborting unclaimed requests on close - Fix potential tee_shm double-free on supp_recv error path - Fix async RPC ring skip leaving orphaned payload bytes - Fix COMPLETE_TEE retry to also handle transient -ENOMEM on RISC-V - Fix session leak on close_session allocation failure: release kernel resources before sending RPC --- Documentation/tee/index.rst | 1 + Documentation/tee/mbedtee.rst | 155 +++++++ MAINTAINERS | 9 + drivers/tee/Kconfig | 3 +- drivers/tee/Makefile | 1 + drivers/tee/mbedtee/Kconfig | 20 + drivers/tee/mbedtee/Makefile | 11 + drivers/tee/mbedtee/core.c | 236 ++++++++++ drivers/tee/mbedtee/mbedtee_drv.h | 269 +++++++++++ drivers/tee/mbedtee/mbedtee_msg.h | 219 +++++++++ drivers/tee/mbedtee/rpc_callee.c | 618 +++++++++++++++++++++++++ drivers/tee/mbedtee/rpc_callee_arm.c | 91 ++++ drivers/tee/mbedtee/rpc_callee_riscv.c | 203 ++++++++ drivers/tee/mbedtee/rpc_caller.c | 532 +++++++++++++++++++++ drivers/tee/mbedtee/rpc_caller_arm.c | 66 +++ drivers/tee/mbedtee/rpc_caller_riscv.c | 192 ++++++++ drivers/tee/mbedtee/shm_pool.c | 105 +++++ drivers/tee/mbedtee/shm_pool.h | 15 + drivers/tee/mbedtee/supp.c | 310 +++++++++++++ include/uapi/linux/tee.h | 1 + 20 files changed, 3056 insertions(+), 1 deletion(-) create mode 100644 Documentation/tee/mbedtee.rst create mode 100644 drivers/tee/mbedtee/Kconfig create mode 100644 drivers/tee/mbedtee/Makefile create mode 100644 drivers/tee/mbedtee/core.c create mode 100644 drivers/tee/mbedtee/mbedtee_drv.h create mode 100644 drivers/tee/mbedtee/mbedtee_msg.h create mode 100644 drivers/tee/mbedtee/rpc_callee.c create mode 100644 drivers/tee/mbedtee/rpc_callee_arm.c create mode 100644 drivers/tee/mbedtee/rpc_callee_riscv.c create mode 100644 drivers/tee/mbedtee/rpc_caller.c create mode 100644 drivers/tee/mbedtee/rpc_caller_arm.c create mode 100644 drivers/tee/mbedtee/rpc_caller_riscv.c create mode 100644 drivers/tee/mbedtee/shm_pool.c create mode 100644 drivers/tee/mbedtee/shm_pool.h create mode 100644 drivers/tee/mbedtee/supp.c diff --git a/Documentation/tee/index.rst b/Documentation/tee/index.rst index 10c3cec..6415855 100644 --- a/Documentation/tee/index.rst +++ b/Documentation/tee/index.rst @@ -12,3 +12,4 @@ TEE Subsystem amd-tee ts-tee qtee + mbedtee diff --git a/Documentation/tee/mbedtee.rst b/Documentation/tee/mbedtee.rst new file mode 100644 index 0000000..dfb57b8 --- /dev/null +++ b/Documentation/tee/mbedtee.rst @@ -0,0 +1,155 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================== +MbedTEE (Trusted Execution Environment) +============================================== + +The MbedTEE driver supports MbedTEE-based TEEs on ARM TrustZone (SMC calls, +GIC SPI notifications) and RISC-V IMSIC (shared-memory polling, MSI +notifications) platforms. + +The driver requires the REE and TEE CPUs that share the RPC shared memory to +be hardware coherent. In practice, the REE/TEE CPUs are expected to be in the +same CPU cluster, with coherent caches and shared visibility for the ring and +payload buffers. + +Communication overview +====================== + +The driver communicates with the TEE using a fixed shared-memory RPC protocol +layered above the architecture-specific call mechanism. + +ARM platforms (SMC) +------------------- + +On ARM and AArch64 platforms, the driver uses ARM SMC Calling Convention +(SMCCC) to issue fast calls and yield calls to the TEE: + +- **Fast calls** (MBEDTEE_RPC_OS_VERSION, MBEDTEE_RPC_SUPPORT_YIELD, + MBEDTEE_RPC_COMPLETE_TEE) are self-contained SMC calls that do + not rely on shared-memory RPC structures. + +- **Yield calls** post the physical address of an ``rpc_cmd`` structure + in shared memory to the TEE. The TEE may issue RPC requests back to the + driver before the call completes; kernel-directed RPCs are handled + directly, while supplicant RPCs (filesystem and RPMB) are forwarded to + tee-supplicant. + +TEE-to-REE notifications on ARM are delivered via a GIC SPI edge interrupt +specified in the device tree (``interrupts`` property). + +RISC-V platforms +---------------- + +On RISC-V platforms, SMCCC is not available. Communication is split into two +directions: + +- **REE to TEE (R2T)**: The driver submits commands by writing the physical + address of an ``rpc_cmd`` structure to a REE-to-TEE ring buffer + (``r2t-ring``). The TEE polls that ring for new commands. No interrupt + notification is sent; the protocol relies on TEE-side polling. + +- **TEE to REE (T2R)**: The TEE writes an ``rpc_cmd`` into the TEE-to-REE + ring and raises an IMSIC MSI to notify the REE driver. The MSI is + allocated at runtime via the ``msi-parent`` DT property and its identity + is published in ``callee_imsic_id``; ``callee_hartid`` tracks the + target hart for migration during CPU hotplug. No SBI ecall is involved. + +RISC-V REE-to-TEE polling rationale +----------------------------------- + +The REE-to-TEE direction uses polling-only on RISC-V to avoid direct +notification interrupt writes from Linux to TEE-owned interrupt files. +This design is platform-independent and does not require ownership of +TEE-only hart interrupt files. The TEE-to-REE direction remains fully +functional via standard Linux MSI notifications and does not depend on +REE-to-TEE notification latency. + +Shared memory regions +===================== + +Two or three fixed shared memory regions are described in the device tree: + +``t2r-ring`` + Ring buffer used by the TEE to post RPC request notifications to the REE + driver. Present on all platforms. + +``t2r-shm`` + Shared memory region carrying the actual ``rpc_cmd`` payloads for + TEE-to-REE RPCs. Present on all platforms. + +``r2t-ring`` + Ring buffer used by the REE driver to submit commands to the TEE on + RISC-V IMSIC platforms. + +RPC protocol +============ + +The TEE and REE communicate through the ``rpc_cmd`` structure in shared memory +and ring buffers:: + + struct rpc_cmd { + u32 id; /* RPC function ID */ + u16 size; /* payload size in bytes */ + u8 interrupted; /* set if wait was interrupted */ + u8 reserved; /* explicit alignment padding */ + s32 ret; /* return value */ + u32 pad; /* explicit alignment padding */ + u64 waiter_id; /* sync RPC request ID echoed on completion */ + u64 shm; /* physical address of payload (sync RPC) */ + u64 data[]; /* inline payload (async RPC) */ + }; + + struct rpc_ringbuf { + u32 wr; /* producer write pointer */ + u32 rd; /* consumer read pointer */ + u32 callee_ready; /* callee ready flag */ + u32 callee_imsic_id; /* RISC-V only: IMSIC local interrupt id */ + u32 callee_hartid; /* RISC-V only: target hart-id for T2R notification */ + u32 reserved; /* padding, must be zero */ + u8 mem[]; + }; + +For RISC-V T2R MSI, one MSI message targets one hart IMSIC file at a time. +The wire-visible state is split between ``callee_imsic_id`` and +``callee_hartid`` in the ring header; Linux may retarget the MSI across +online CPUs via ``irq_set_affinity()``, and the driver updates those fields +to match the selected hart. + + + +Architecture diagram:: + + User space Kernel TEE side + ~~~~~~~~~~ ~~~~~~ ~~~~~~~~~~~~ + +--------+ +--------------+ + | Client | | Trusted App | + +--------+ +--------------+ + /\ /\ + || +------------+ || + || | MbedTEE- | \/ + || | supplicant | +--------------+ + || +------------+ | TEE Internal | + \/ /\ | API | + +-------+ || +--------------+ + | TEE | || +---------+---------------+ | MbedTEE | + | Client| || | TEE | MbedTEE | | Trusted OS | + | API | \/ | subsys | client driver | +--------------+ + +-------+-------------+-----+--------+----------+----+ | + | Generic TEE API | | RPC (cmd/ring)| | + | IOCTL (TEE_IOC_*) | | SMC / IMSIC | | + +---------------------------+ +---------------+--------------+ + +Device tree binding +=================== + +See Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml for the +complete device tree binding specification, including the RISC-V +``msi-parent`` requirement. + +References +========== + +- [1] MbedTEE project: https://github.com/mbedtee +- [2] ARM SMC Calling Convention: https://developer.arm.com/architectures/system-architectures/software-standards/smccc +- [3] RISC-V IMSIC specification: https://github.com/riscv/riscv-aia diff --git a/MAINTAINERS b/MAINTAINERS index c8d4b91..344b82b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15920,6 +15920,15 @@ F: Documentation/devicetree/bindings/net/dsa/maxlinear,mxl862xx.yaml F: drivers/net/dsa/mxl862xx/ F: net/dsa/tag_mxl862xx.c +MBEDTEE TEE DRIVER +M: Xing Loong <xing.xl.loong@gmail.com> +L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers) +S: Maintained +W: https://github.com/mbedtee +F: Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml +F: Documentation/tee/mbedtee.rst +F: drivers/tee/mbedtee/ + MCAN DEVICE DRIVER M: Markus Schneider-Pargmann <msp@baylibre.com> L: linux-can@vger.kernel.org diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index 98c3ad0..3dc3439 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 || RISCV || COMPILE_TEST || CPU_SUP_AMD select CRYPTO_LIB_SHA1 select DMA_SHARED_BUFFER select GENERIC_ALLOCATOR @@ -21,5 +21,6 @@ source "drivers/tee/optee/Kconfig" source "drivers/tee/amdtee/Kconfig" source "drivers/tee/tstee/Kconfig" source "drivers/tee/qcomtee/Kconfig" +source "drivers/tee/mbedtee/Kconfig" endif diff --git a/drivers/tee/Makefile b/drivers/tee/Makefile index 3239b91..33117ea 100644 --- a/drivers/tee/Makefile +++ b/drivers/tee/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_OPTEE) += optee/ obj-$(CONFIG_AMDTEE) += amdtee/ obj-$(CONFIG_ARM_TSTEE) += tstee/ obj-$(CONFIG_QCOMTEE) += qcomtee/ +obj-$(CONFIG_MBEDTEE) += mbedtee/ diff --git a/drivers/tee/mbedtee/Kconfig b/drivers/tee/mbedtee/Kconfig new file mode 100644 index 0000000..b1fa38a --- /dev/null +++ b/drivers/tee/mbedtee/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +# MbedTEE Trusted Execution Environment Configuration +config MBEDTEE + tristate "MbedTEE" + depends on MMU && OF + depends on ((ARM || ARM64) && HAVE_ARM_SMCCC) || (RISCV && GENERIC_MSI_IRQ) + help + This enables the MbedTEE Trusted Execution Environment (TEE) + driver. + + The driver supports ARM TrustZone (SMC calls, GIC SPI notifications) + and RISC-V IMSIC (shared-memory polling, MSI notifications), with + GlobalPlatform client API via /dev/tee*. + + The DT node should be under a firmware node and have compatible = + "mbedtee,tee" with the RPC ring buffer addresses and, + for ARM/ARM64, an interrupt property for TEE-to-REE notifications. + See Documentation/tee/mbedtee.rst for protocol details. + + If unsure, say N. diff --git a/drivers/tee/mbedtee/Makefile b/drivers/tee/mbedtee/Makefile new file mode 100644 index 0000000..309eb6d --- /dev/null +++ b/drivers/tee/mbedtee/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_MBEDTEE) += mbedtee.o + +mbedtee-y += core.o +mbedtee-y += supp.o +mbedtee-y += shm_pool.o +mbedtee-y += rpc_caller.o rpc_callee.o +mbedtee-$(CONFIG_ARM) += rpc_caller_arm.o rpc_callee_arm.o +mbedtee-$(CONFIG_ARM64) += rpc_caller_arm.o rpc_callee_arm.o +mbedtee-$(CONFIG_RISCV) += rpc_caller_riscv.o rpc_callee_riscv.o diff --git a/drivers/tee/mbedtee/core.c b/drivers/tee/mbedtee/core.c new file mode 100644 index 0000000..5807a93 --- /dev/null +++ b/drivers/tee/mbedtee/core.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/tee_drv.h> +#include <linux/types.h> + +#include "mbedtee_drv.h" +#include "shm_pool.h" + +int mbedtee_get_resource(struct device_node *node, + const char *name, struct resource *res) +{ + struct device_node *rmem; + int idx; + int ret; + + idx = of_property_match_string(node, "memory-region-names", name); + if (idx < 0) + return idx; + + rmem = of_parse_phandle(node, "memory-region", idx); + if (!rmem) + return -ENODEV; + + ret = of_address_to_resource(rmem, 0, res); + of_node_put(rmem); + + if (ret) + return ret; + + res->name = name; + return 0; +} + +static void mbedtee_get_version(struct tee_device *teedev, + struct tee_ioctl_version_data *vers) +{ + struct tee_ioctl_version_data v = { + .impl_id = TEE_IMPL_ID_MBEDTEE, + .impl_caps = 0, + .gen_caps = TEE_GEN_CAP_GP | TEE_GEN_CAP_REG_MEM | + TEE_GEN_CAP_MEMREF_NULL, + }; + + *vers = v; +} + +static int mbedtee_open(struct tee_context *ctx) +{ + struct mbedtee_context_data *ctxdata; + + ctxdata = kzalloc_obj(*ctxdata, GFP_KERNEL); + if (!ctxdata) + return -ENOMEM; + + mutex_init(&ctxdata->mutex); + INIT_LIST_HEAD(&ctxdata->sess_list); + + ctx->data = ctxdata; + ctx->cap_memref_null = true; + + return 0; +} + +static void mbedtee_release(struct tee_context *ctx) +{ + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_context_data *d = ctx->data; + struct mbedtee_session *s, *n; + + if (!d) + return; + + if (d->is_supp_ctx) { + mbedtee_supp_release(&mbedtee->supp, ctx); + } else { + list_for_each_entry_safe(s, n, &d->sess_list, list_node) + mbedtee_close_session(ctx, s->session_id); + } + + kfree(d); + ctx->data = NULL; +} + +static const struct tee_driver_ops mbedtee_ops = { + .get_version = mbedtee_get_version, + .open = mbedtee_open, + .release = mbedtee_release, + .open_session = mbedtee_open_session, + .close_session = mbedtee_close_session, + .invoke_func = mbedtee_invoke_func, + .cancel_req = mbedtee_cancel_req, + .supp_recv = mbedtee_supp_recv, + .supp_send = mbedtee_supp_send, + .shm_register = mbedtee_shm_register, + .shm_unregister = mbedtee_shm_unregister, +}; + +static const struct tee_desc mbedtee_desc = { + .name = "mbedtee", + .ops = &mbedtee_ops, + .owner = THIS_MODULE, +}; + +static int mbedtee_probe(struct platform_device *pdev) +{ + int ret; + long version; + long yield; + struct tee_shm_pool *pool; + struct tee_device *teedev; + struct mbedtee_device *mbedtee; + + mbedtee = devm_kzalloc(&pdev->dev, sizeof(*mbedtee), GFP_KERNEL); + if (!mbedtee) + return -ENOMEM; + + mbedtee->dev = &pdev->dev; + xa_init_flags(&mbedtee->rpc_calls, XA_FLAGS_ALLOC1); + mbedtee_supp_init(&mbedtee->supp); + + /* + * R2T must be ready before T2R is advertised: synchronous T2R RPC + * work replies with COMPLETE_TEE over the R2T path. + */ + ret = mbedtee_r2t_init(mbedtee); + if (ret != 0) + goto err_supp; + + ret = mbedtee_rpc_init(mbedtee); + if (ret != 0) + goto err_r2t; + + version = mbedtee_rpc_fastcall(mbedtee, MBEDTEE_RPC_OS_VERSION, 0, 0, 0); + if (version < 0) { + dev_err(&pdev->dev, "MBEDTEE_RPC_OS_VERSION failed: %ld\n", version); + ret = version; + goto err_rpc; + } + + if (!MBEDTEE_VALID_VERSION(version)) { + dev_err(&pdev->dev, "mbedtee not present (version=0x%lx)\n", + version); + ret = -ENODEV; + goto err_rpc; + } + + yield = mbedtee_rpc_fastcall(mbedtee, MBEDTEE_RPC_SUPPORT_YIELD, 0, 0, 0); + dev_info(&pdev->dev, "version: 0x%06lx yield: %ld\n", version, yield); + + pool = mbedtee_shm_pool_alloc_pages(); + if (IS_ERR(pool)) { + ret = PTR_ERR(pool); + goto err_rpc; + } + + teedev = tee_device_alloc(&mbedtee_desc, &pdev->dev, pool, mbedtee); + if (IS_ERR(teedev)) { + ret = PTR_ERR(teedev); + goto err_pool; + } + + ret = tee_device_register(teedev); + if (ret) + goto err_teedev; + + mbedtee->teedev = teedev; + mbedtee->version = version; + mbedtee->yield = !!yield; + mbedtee->pool = pool; + platform_set_drvdata(pdev, mbedtee); + + dev_dbg(&pdev->dev, "client initialized\n"); + return 0; + +err_teedev: + tee_device_unregister(teedev); +err_pool: + tee_shm_pool_free(pool); +err_rpc: + mbedtee_rpc_uninit(mbedtee); +err_r2t: + mbedtee_r2t_uninit(mbedtee); +err_supp: + mbedtee_supp_uninit(&mbedtee->supp); + xa_destroy(&mbedtee->rpc_calls); + return ret; +} + +static void mbedtee_remove(struct platform_device *pdev) +{ + struct mbedtee_device *mbedtee = platform_get_drvdata(pdev); + + tee_device_unregister(mbedtee->teedev); + /* + * Drain T2R before tearing down R2T: queued RPC work may still need + * to send COMPLETE_TEE over R2T. Destroy the supplicant mutex only + * after RPC work can no longer enter mbedtee_supp_handler(). + */ + mbedtee_rpc_uninit(mbedtee); + mbedtee_r2t_uninit(mbedtee); + mbedtee_supp_uninit(&mbedtee->supp); + tee_shm_pool_free(mbedtee->pool); + xa_destroy(&mbedtee->rpc_calls); +} + +static const struct of_device_id mbedtee_dt_match[] = { + { .compatible = "mbedtee,tee" }, + { }, +}; +MODULE_DEVICE_TABLE(of, mbedtee_dt_match); + +static struct platform_driver mbedtee_driver = { + .probe = mbedtee_probe, + .remove = mbedtee_remove, + .driver = { + .name = "mbedtee", + .of_match_table = mbedtee_dt_match, + }, +}; +module_platform_driver(mbedtee_driver); + +MODULE_AUTHOR("Xing Loong <xing.xl.loong@gmail.com>"); +MODULE_DESCRIPTION("MbedTEE Trusted Execution Environment driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/tee/mbedtee/mbedtee_drv.h b/drivers/tee/mbedtee/mbedtee_drv.h new file mode 100644 index 0000000..59b5b27 --- /dev/null +++ b/drivers/tee/mbedtee/mbedtee_drv.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#ifndef MBEDTEE_DRV_H +#define MBEDTEE_DRV_H + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/atomic.h> +#include <linux/of.h> +#include <linux/ioport.h> +#include <linux/tee_drv.h> +#include <linux/tee_core.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/msi.h> +#include <linux/workqueue.h> +#include <linux/cpuhotplug.h> +#include <linux/cpumask.h> +#include <linux/xarray.h> +#include "mbedtee_msg.h" +#include "shm_pool.h" + +/* + * GlobalPlatform TEE Client API return codes/origins + * used by this driver. Only the codes actually + * referenced in the driver are defined here. + */ +#define TEEC_SUCCESS 0x00000000 +#define TEEC_ERROR_OUT_OF_MEMORY 0xFFFF000C +#define TEEC_ERROR_COMMUNICATION 0xFFFF000E + +#define TEEC_ORIGIN_COMMS 0x00000002 + +#define MBEDTEE_VERSION_MAJOR 1 +#define MBEDTEE_VALID_VERSION(x) (((x) >> 16) == MBEDTEE_VERSION_MAJOR) + +/* Maximum payload size for inline (async) RPC data. */ +#define MBEDTEE_ASYNC_RPC_DATA_MAX 256 + +struct rpc_transport_ops; +struct mbedtee_rpc_call; +struct mbedtee_supp_req; +struct mbedtee_device; + +struct rpc_work { + struct work_struct work; + struct mbedtee_device *mbedtee; + u64 waiter_id; + size_t size; + bool complete_only; + /* Points to ctx->rpc_data (async) or into t2r_shm (sync) */ + void *data; + void (*func)(struct mbedtee_device *mbedtee, void *data, size_t size); +}; + +struct mbedtee_t2r_ctx { + spinlock_t ring_lock; /* protects T2R ring producer/consumer state */ + void *t2r_shm; + phys_addr_t t2r_shm_phys; + resource_size_t t2r_shm_sz; + struct rpc_ringbuf *t2r_ring; + u32 t2r_ring_sz; + u32 t2r_ring_rd; + + void (*rpc_handlers[MBEDTEE_RPC_MAX])(struct mbedtee_device *mbedtee, + void *data, size_t size); + + /* + * Inline buffer for async RPC payloads (waiter_id == 0). + * Sync RPC payloads (waiter_id != 0) live in shared memory (t2r_shm). + */ + u8 rpc_data[MBEDTEE_ASYNC_RPC_DATA_MAX]; + + /* + * CPU-hotplug tracking for T2R interrupt delivery. + * callee_cpus: shadow of cpu_online_mask maintained by the core + * callee layer (rpc_callee.c) for both ARM and RISC-V; used by + * ARM to feed irq_set_affinity() and by RISC-V as the gate for + * MSI migration decisions. + * callee_virq: single MSI virq currently pinned to one CPU + * (RISC-V IMSIC only; 0 for ARM). + * hp_node: per-instance node for cpuhp_setup_state_multi(). + */ + struct cpumask callee_cpus; + int callee_virq; + struct hlist_node hp_node; + bool cpuhp_added; + bool complete_work_pending; + bool pending_async; + u32 pending_size; + struct workqueue_struct *rpc_wq; + struct rpc_work complete_work; + void (*pending_func)(struct mbedtee_device *mbedtee, + void *data, size_t size); +}; + +struct mbedtee_r2t_ctx { + spinlock_t lock; /* protects R2T ring write-side state */ + struct rpc_ringbuf *ring; + u32 ring_sz; + u32 ring_wr; +}; + +struct mbedtee_rpc_transport_ctx { + int rpc_notify_virq; + struct msi_msg rpc_msi_msg; +}; + +struct mbedtee_supp { + struct mutex mutex; /* serializes supp request queue and active requests */ + struct tee_context *ctx; + bool shutting_down; + struct list_head reqs; + struct list_head active_reqs; + struct completion reqs_c; +}; + +struct mbedtee_device { + u32 version; + bool yield; + struct device *dev; + struct tee_device *teedev; + struct tee_shm_pool *pool; + struct mbedtee_supp supp; + struct xarray rpc_calls; + atomic_long_t rpc_call_seq; + const struct rpc_transport_ops *rpc_ops; + struct mbedtee_t2r_ctx t2r; + struct mbedtee_r2t_ctx r2t; + struct mbedtee_rpc_transport_ctx transport; +}; + +struct mbedtee_rpc_call { + struct completion c; + phys_addr_t rpc_phys; + u32 state; + /* + * Wire-format rpc_cmd with inline data[] payload. + * MUST be the last field: rpc.data[] is a flexible array. + */ + struct rpc_cmd rpc; +}; + +struct mbedtee_session { + struct list_head list_node; + u32 session_id; +}; + +struct mbedtee_context_data { + bool is_supp_ctx; + struct mutex mutex; /* serializes session list updates */ + struct list_head sess_list; +}; + +static inline phys_addr_t mbedtee_virt_to_phys(void *va) +{ + if (is_vmalloc_or_module_addr(va)) + return page_to_phys(vmalloc_to_page(va)) + + offset_in_page(va); + + return virt_to_phys(va); +} + +int mbedtee_get_resource(struct device_node *node, + const char *name, struct resource *res); + +/* + * RPC transport operations -- implemented per-architecture. + * + * Caller (rpc_caller_{arm,riscv}.c) platform differences: + * fastcall: ARM/ARM64 - direct SMC (arm_smccc_smc), synchronous, + * never returns -ENOSPC. + * RISC-V - writes rpc_cmd phys addr to the r2t ring + * buffer, then waits for TEE completion via the t2r + * ring. Despite the "fastcall" name this is a + * synchronous ring-buffer round-trip; it may return + * -ENOSPC when the r2t ring is full, and the caller + * must retry (see rpc_routine() for the + * MBEDTEE_RPC_COMPLETE_TEE retry loop). + * yieldcall: ARM/ARM64 - one SMC posting rpc_cmd phys addr, waits + * for completion. + * RISC-V - writes rpc_cmd phys addr to r2t ring, waits + * for completion. + * Callee (rpc_callee_{arm,riscv}.c) platform differences: + * notify: ARM/ARM64 - GIC SPI; the TEE broadcasts via GIC + * hardware (GICv2 ITARGETS=0xFF, GICv3 IROUTER.IRM=1). + * Ring metadata unchanged on CPU affinity events. + * RISC-V - IMSIC MSI; the driver writes callee_hartid + * and callee_imsic_id into the ring so the TEE knows + * where to send the MSI. The affinity handler updates + * both ring fields when the callee CPU goes offline. + * + * init: Set up the interrupt delivery mechanism (MSI on RISC-V, + * GIC/OF-IRQ on ARM/ARM64) and, on RISC-V, write + * ring->callee_hartid and ring->callee_imsic_id. @handler + * is the hard-IRQ handler that drains the ring buffer and + * dispatches RPC commands. + * uninit: Tear down and free all interrupt resources. + * update_affinity: Migrate T2R interrupt delivery to @new_cpu. Called by the + * common cpuhp handler when the current callee CPU goes + * offline. On RISC-V IMSIC, must update ring->callee_hartid + * and ring->callee_imsic_id. ARM uses GIC hardware routing + * and requires no ring update. + * + * The getter is implemented per-architecture in rpc_callee_{arm,riscv}.c; + * only one is linked per build. + */ +struct rpc_transport_ops { + int (*init)(struct mbedtee_device *mbedtee, struct rpc_ringbuf *ring, + irq_handler_t handler); + void (*uninit)(struct mbedtee_device *mbedtee); + int (*update_affinity)(struct mbedtee_device *mbedtee, + unsigned int new_cpu); +}; + +const struct rpc_transport_ops *mbedtee_get_rpc_transport_ops(void); + +/* rpc-callee.c */ +int mbedtee_rpc_init(struct mbedtee_device *mbedtee); +void mbedtee_rpc_uninit(struct mbedtee_device *mbedtee); +irqreturn_t mbedtee_rpc_irq_handler(int irq, void *dev_id); + +/* rpc_caller.c */ +int mbedtee_rpc_call_alloc(struct mbedtee_device *mbedtee, + size_t payload_size, struct mbedtee_rpc_call **call); +void mbedtee_rpc_call_free(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call); +void mbedtee_rpc_complete_call(struct mbedtee_device *mbedtee, + u64 waiter_id); +long mbedtee_rpc_wait_for_completion(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call, bool killable); +int mbedtee_open_session(struct tee_context *ctx, + struct tee_ioctl_open_session_arg *arg, + struct tee_param *param); +int mbedtee_close_session(struct tee_context *ctx, u32 session); +int mbedtee_invoke_func(struct tee_context *ctx, + struct tee_ioctl_invoke_arg *arg, + struct tee_param *param); +int mbedtee_cancel_req(struct tee_context *ctx, u32 cancel_id, + u32 session); +int mbedtee_shm_register(struct tee_context *ctx, struct tee_shm *shm, + struct page **pages, size_t num_pages, unsigned long start); +int mbedtee_shm_unregister(struct tee_context *ctx, struct tee_shm *shm); + +/* rpc-caller-{arm,riscv}.c */ +long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee, + unsigned long fn, struct mbedtee_rpc_call *call, + bool interruptible); +long mbedtee_rpc_fastcall(struct mbedtee_device *mbedtee, + unsigned long fn, unsigned long a0, + unsigned long a1, unsigned long a2); +int mbedtee_r2t_init(struct mbedtee_device *mbedtee); +void mbedtee_r2t_uninit(struct mbedtee_device *mbedtee); + +/* supp.c */ +void mbedtee_supp_init(struct mbedtee_supp *supp); +void mbedtee_supp_uninit(struct mbedtee_supp *supp); +void mbedtee_supp_release(struct mbedtee_supp *supp, struct tee_context *ctx); +void mbedtee_supp_abort_all(struct mbedtee_supp *supp); +void mbedtee_supp_handler(struct mbedtee_device *mbedtee, + u32 func, void *data, size_t size); +int mbedtee_supp_recv(struct tee_context *ctx, u32 *func, + u32 *num_params, struct tee_param *param); +int mbedtee_supp_send(struct tee_context *ctx, u32 ret, + u32 num_params, struct tee_param *param); + +#endif /* MBEDTEE_DRV_H */ diff --git a/drivers/tee/mbedtee/mbedtee_msg.h b/drivers/tee/mbedtee/mbedtee_msg.h new file mode 100644 index 0000000..deb2d8c --- /dev/null +++ b/drivers/tee/mbedtee/mbedtee_msg.h @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * + * MbedTEE RPC wire protocol between REE (Linux) and TEE (MbedTEE OS). + * + * This header is the Linux-kernel mirror of + * mbedtee-os/drivers/rpc/include/rpc/rpc.h. Both copies MUST define + * identical struct layouts. The static_assert() checks at the bottom + * of this file (and the _Static_assert() checks in rpc.h) enforce this + * contract at compile time on every architecture. + * + * Type mapping (wire -> Linux kernel alias): + * uint8_t -> u8 -- boolean flag / padding byte + * uint16_t -> u16 -- byte-count field (< 64 KiB payload limit) + * uint32_t -> u32 -- 32-bit ID, counter, flag, or enum + * int32_t -> s32 -- function-defined signed result code + * uint64_t -> u64 -- physical address, kernel pointer token, or size + * + * Using u64 for all pointer-sized fields eliminates ABI width mismatches + * in mixed-width configurations (e.g. RV64 REE + RV32 TEE, or ARM32-LPAE + * REE + ARM32 TEE). It also prevents phys_addr_t/unsigned-long confusion + * when CONFIG_PHYS_ADDR_T_64BIT=y on a 32-bit REE kernel. + */ + +#ifndef MBEDTEE_MSG_H +#define MBEDTEE_MSG_H + +#include <linux/bits.h> +#include <linux/build_bug.h> +#include <linux/types.h> + +/* + * Fastcall / yield-call classification + */ +#define MBEDTEE_RPC_FASTCALL BIT(31) +#define MBEDTEE_RPC_IS_FASTCALL(fn) ((fn) & MBEDTEE_RPC_FASTCALL) + +/* + * REE -> TEE fast calls + */ +#define MBEDTEE_RPC_OS_VERSION (0x0100 | MBEDTEE_RPC_FASTCALL) +#define MBEDTEE_RPC_SUPPORT_YIELD (0x0101 | MBEDTEE_RPC_FASTCALL) +#define MBEDTEE_RPC_COMPLETE_TEE (0x0102 | MBEDTEE_RPC_FASTCALL) + +/* + * REE -> TEE yield calls + */ +#define MBEDTEE_RPC_OPEN_SESSION 1 +#define MBEDTEE_RPC_INVOKE_SESSION 2 +#define MBEDTEE_RPC_CLOSE_SESSION 3 +#define MBEDTEE_RPC_REGISTER_SHM 5 +#define MBEDTEE_RPC_UNREGISTER_SHM 6 +#define MBEDTEE_RPC_CANCEL 7 + +/* + * TEE -> REE RPC calls + */ +#define MBEDTEE_RPC_COMPLETE_REE 0 +#define MBEDTEE_RPC_REETIME 1 +#define MBEDTEE_RPC_REEFS 2 +#define MBEDTEE_RPC_RPMB 3 +#define MBEDTEE_RPC_MAX 4 + +/* + * Supplicant types (TEE -> REE -> tee-supplicant) + */ +#define MBEDTEE_SUPP_REEFS 1 +#define MBEDTEE_SUPP_RPMB 2 + +/* MbedTEE RPC protocol uses 4 KiB page units regardless of host PAGE_SIZE. */ +#define MBEDTEE_PAGE_SIZE 4096UL + +/* + * REE <-> TEE RPC call command (wire format, 32 bytes fixed header). + * + * Fixed layout: + * +0 id u32 - RPC function identifier + * +4 size u16 - inline payload byte count (0..65535) + * +6 interrupted u8 - set by REE when caller is interrupted + * +7 reserved u8 - must be zero (explicit alignment pad) + * +8 ret s32 - return value written by callee + * +12 pad u32 - must be zero (explicit alignment pad) + * +16 waiter_id u64 - sync RPC request ID echoed on completion + * +24 shm u64 - physical address of sync-RPC shared memory + * +32 data[] u64 - inline payload (waiter_id==0) or empty + * + * Yield-call contract: + * - Session/control RPCs return GlobalPlatform result codes here. + * - Host-local errno values must be translated before being put on the wire. + * - Fast calls may use function-specific return values. + */ +struct rpc_cmd { + u32 id; + u16 size; + u8 interrupted; + u8 reserved; + s32 ret; + u32 pad; + u64 waiter_id; + u64 shm; + u64 data[]; +}; + +/* + * REE -> TEE pages for GP shared memory. + * + * All fields are u64 so the layout is identical when REE is 64-bit + * but TEE is 32-bit (physical addresses, sizes and counts use 64 bits). + */ +struct rpc_memref { + u64 id; + u64 pages; + u64 offset; + u64 size; + u64 cnt; +}; + +/* + * REE -> TEE parameter entry: value or shared memory reference. + */ +union rpc_tee_param { + struct rpc_memref memref; + + struct { + u32 a; + u32 b; + } value; +}; + +/* + * REE -> TEE parameters for RPC session operations + * (MBEDTEE_RPC_OPEN_SESSION / MBEDTEE_RPC_INVOKE_SESSION / + * MBEDTEE_RPC_CLOSE_SESSION). + */ +struct rpc_param { + s32 session_id; + u32 cmd_id; + u32 ret_origin; + u32 params_type; + union rpc_tee_param params[4]; + u8 uuid[16]; + u8 clnt_uuid[16]; +}; + +/* + * REE -> TEE cancellation request (MBEDTEE_RPC_CANCEL). + */ +struct rpc_cancel_req { + u32 session_id; + u32 cancel_id; +}; + +/* + * TEE <-> REE supplicant payloads for REEFS and RPMB RPCs. + * Only the fixed headers are interpreted in the Linux driver. + */ +/* + * Common supplicant command header. Both reefs_cmd and rpmb_cmd embed this + * as their first member so the kernel can write a status code through a + * single cast to (struct supp_cmd_hdr *) without caring about the full + * payload type. The wire layout is identical on every architecture. + */ +struct supp_cmd_hdr { + s32 ret; + s32 op; +}; + +struct reefs_cmd { + struct supp_cmd_hdr hdr; + s32 flags; + s32 fd; + u64 len; + u8 data[]; +}; + +struct rpmb_cmd { + struct supp_cmd_hdr hdr; + u32 nframes; + u8 data[]; +}; + +/* + * TEE <-> REE RPC ring buffer header (24 bytes fixed). + */ +struct rpc_ringbuf { + u32 wr; /* producer write pointer */ + u32 rd; /* consumer read pointer */ + u32 callee_ready; /* callee ready flag */ + u32 callee_imsic_id; /* RISC-V only: IMSIC local interrupt id */ + u32 callee_hartid; /* RISC-V only: target hart-id for T2R notification */ + u32 reserved; /* padding, must be zero */ + u8 mem[]; +}; + +/* + * Compile-time ABI layout assertions. + * These must match the _Static_assert() checks in mbedtee-os rpc/rpc.h. + */ +static_assert(sizeof(struct rpc_cmd) == 32, + "rpc_cmd wire size mismatch"); +static_assert(offsetof(struct rpc_cmd, ret) == 8, + "rpc_cmd.ret offset mismatch"); +static_assert(offsetof(struct rpc_cmd, waiter_id) == 16, + "rpc_cmd.waiter_id offset mismatch"); +static_assert(offsetof(struct rpc_cmd, shm) == 24, + "rpc_cmd.shm offset mismatch"); +static_assert(offsetof(struct rpc_cmd, data) == 32, + "rpc_cmd.data offset mismatch"); +static_assert(sizeof(struct rpc_memref) == 40, + "rpc_memref wire size mismatch"); +static_assert(sizeof(struct rpc_ringbuf) == 24, + "rpc_ringbuf header size mismatch"); +static_assert(sizeof(struct rpc_param) == 208, + "rpc_param wire size mismatch"); +static_assert(sizeof(struct rpc_cancel_req) == 8, + "rpc_cancel_req wire size mismatch"); + +#endif /* MBEDTEE_MSG_H */ diff --git a/drivers/tee/mbedtee/rpc_callee.c b/drivers/tee/mbedtee/rpc_callee.c new file mode 100644 index 0000000..e3373a7 --- /dev/null +++ b/drivers/tee/mbedtee/rpc_callee.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * TEE->REE callee-side: handles interrupts from TEE, processes RPC requests. + */ +#include <linux/cpu.h> +#include <linux/cpuhotplug.h> +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/tee_drv.h> +#include <linux/workqueue.h> + +#include "mbedtee_drv.h" + +/* + * CPU hotplug state shared across all mbedtee instances (registered once). + */ +static int mbedtee_cpuhp_state = -EINVAL; +static unsigned int mbedtee_cpuhp_instances; +static DEFINE_MUTEX(mbedtee_cpuhp_mutex); +/* rpc_routine() runs on workqueue, re-drains ring on completion */ +static void rpc_routine(struct work_struct *work); + +/* + * Common CPU-online handler: add the new CPU into the eligible T2R + * delivery set and let the transport re-apply affinity. + */ +static int mbedtee_cpu_online(unsigned int cpu, struct hlist_node *node) +{ + struct mbedtee_t2r_ctx *ctx = + hlist_entry(node, struct mbedtee_t2r_ctx, hp_node); + struct mbedtee_device *mbedtee = + container_of(ctx, struct mbedtee_device, t2r); + unsigned int target_cpu; + + if (!ctx->t2r_ring || cpumask_test_cpu(cpu, &ctx->callee_cpus)) + return 0; + + cpumask_set_cpu(cpu, &ctx->callee_cpus); + target_cpu = cpumask_first(&ctx->callee_cpus); + + if (mbedtee->rpc_ops && mbedtee->rpc_ops->update_affinity) + mbedtee->rpc_ops->update_affinity(mbedtee, target_cpu); + + return 0; +} + +/* + * Common CPU-offline handler: migrates T2R interrupt delivery to another + * online CPU when the current callee CPU is being taken offline. + * + * The transport-specific update_affinity() callback handles the actual + * interrupt migration (e.g. irq_set_affinity + callee_hartid update for + * RISC-V IMSIC; irq_set_affinity only for ARM - no ring update needed). + */ +static int mbedtee_cpu_offline(unsigned int cpu, struct hlist_node *node) +{ + struct mbedtee_t2r_ctx *ctx = + hlist_entry(node, struct mbedtee_t2r_ctx, hp_node); + struct mbedtee_device *mbedtee = + container_of(ctx, struct mbedtee_device, t2r); + unsigned int new_cpu; + + if (!ctx->t2r_ring || !cpumask_test_cpu(cpu, &ctx->callee_cpus)) + return 0; + + /* + * Remove the dying CPU from the eligible set. If the set is now + * empty (all CPUs going away at once -- highly unlikely but safe), + * fall back to CPU 0 which Linux guarantees to be the last offline. + */ + cpumask_clear_cpu(cpu, &ctx->callee_cpus); + if (cpumask_empty(&ctx->callee_cpus)) + cpumask_set_cpu(0, &ctx->callee_cpus); + + new_cpu = cpumask_first(&ctx->callee_cpus); + + if (mbedtee->rpc_ops && mbedtee->rpc_ops->update_affinity) + mbedtee->rpc_ops->update_affinity(mbedtee, new_cpu); + return 0; +} + +static inline size_t t2r_available_size(struct mbedtee_t2r_ctx *ctx) +{ + u32 wr; + u32 rd; + u32 shm_size = ctx->t2r_ring_sz; + + /* Pair with producer release store after writing ring payload. */ + wr = smp_load_acquire(&ctx->t2r_ring->wr); + rd = READ_ONCE(ctx->t2r_ring_rd); + + if (wr > shm_size) + return 0; + + if (wr >= rd) + return wr - rd; + return shm_size + wr - rd; +} + +static void t2r_ring_copy(struct mbedtee_t2r_ctx *ctx, + void *data, size_t size, u32 rd) +{ + u32 remain; + u32 shm_size = ctx->t2r_ring_sz; + struct rpc_ringbuf *shm = ctx->t2r_ring; + + if (rd + size <= shm_size) { + memcpy(data, &shm->mem[rd], size); + } else { + remain = rd + size - shm_size; + memcpy(data, &shm->mem[rd], size - remain); + memcpy((u8 *)data + size - remain, + &shm->mem[0], remain); + } +} + +static void t2r_ring_advance(struct mbedtee_t2r_ctx *ctx, size_t size) +{ + u32 rd = READ_ONCE(ctx->t2r_ring_rd); + u32 shm_size = ctx->t2r_ring_sz; + struct rpc_ringbuf *shm = ctx->t2r_ring; + + if (rd + size > shm_size) + rd = rd + size - shm_size; + else + rd += size; + + WRITE_ONCE(ctx->t2r_ring_rd, rd); + /* Publish updated consumer index after command parsing is complete. */ + smp_store_release(&shm->rd, rd); +} + +/* + * Read and consume @size bytes from the TEE-to-REE ring buffer. + * Must be called with ctx->ring_lock held. + */ +static void t2r_ring_read(struct mbedtee_t2r_ctx *ctx, + void *data, size_t size) +{ + t2r_ring_copy(ctx, data, size, READ_ONCE(ctx->t2r_ring_rd)); + t2r_ring_advance(ctx, size); +} + +static bool rpc_queue_complete_only(struct mbedtee_t2r_ctx *ctx, + u64 waiter_id) +{ + if (ctx->complete_work_pending) + return false; + + ctx->complete_work_pending = true; + ctx->complete_work.waiter_id = waiter_id; + queue_work(ctx->rpc_wq, &ctx->complete_work.work); + + return true; +} + +/* + * Try to pick the next RPC command from the ring buffer. + * + * For asynchronous RPCs the data payload follows the cmd header in the + * ring. If the payload has not fully arrived yet the cmd header is still + * consumed (it was already read) but the entry is marked incomplete so + * that the next IRQ resumes reading the payload instead of re-reading + * a header. + * + * For synchronous RPCs (waiter_id != 0) the work descriptor is allocated + * BEFORE the ring header is consumed. This guarantees that if the + * allocator returns NULL the ring entry is left intact so the IRQ + * handler will retry on the next wake-up rather than leaving a TEE + * thread blocked with no response. + * + * Returns a work descriptor on success, or NULL when no complete entry + * is available. + */ +static struct rpc_work *rpc_pick_next(struct mbedtee_device *mbedtee, + struct mbedtee_t2r_ctx *ctx, struct rpc_work *c) +{ + struct rpc_cmd cmd; + void (*func)(struct mbedtee_device *mbedtee, void *data, size_t size); + struct rpc_work *new_work = NULL; + resource_size_t off; + phys_addr_t shm_phys; + u64 shm_wire; + + /* Resume reading payload of a previously incomplete async RPC */ + if (ctx->pending_async) { + if (t2r_available_size(ctx) < ctx->pending_size) + return NULL; + ctx->pending_async = false; + t2r_ring_read(ctx, c->data, ctx->pending_size); + c->func = ctx->pending_func; + c->size = ctx->pending_size; + c->waiter_id = 0; + return c; + } + + if (t2r_available_size(ctx) < sizeof(cmd)) + return NULL; + + /* + * Peek at the header without advancing the ring pointer. + * For sync RPCs we must pre-allocate before consuming the entry. + */ + memset(&cmd, 0, sizeof(cmd)); + t2r_ring_copy(ctx, &cmd, sizeof(cmd), READ_ONCE(ctx->t2r_ring_rd)); + + if (cmd.id >= MBEDTEE_RPC_MAX) + goto skip; + + func = ctx->rpc_handlers[cmd.id]; + if (!func) + goto skip; + + if (cmd.waiter_id == 0) { + /* Async RPC: payload follows header in the ring */ + if (cmd.size > sizeof(ctx->rpc_data)) + goto skip; + + /* Consume the header now that we know it is valid */ + t2r_ring_advance(ctx, sizeof(cmd)); + + if (t2r_available_size(ctx) < cmd.size) { + /* Mark as incomplete, resume on next iteration */ + ctx->pending_async = true; + ctx->pending_size = cmd.size; + ctx->pending_func = func; + return NULL; + } + + t2r_ring_read(ctx, c->data, cmd.size); + } else { + /* Sync RPC: data lives in the shared memory region */ + shm_wire = cmd.shm; + shm_phys = (phys_addr_t)shm_wire; + if ((u64)shm_phys != shm_wire) + goto skip; + + if (shm_phys < ctx->t2r_shm_phys) + goto skip; + + off = shm_phys - ctx->t2r_shm_phys; + if (cmd.size == 0 || cmd.size > ctx->t2r_shm_sz) + goto skip; + + if (off > ctx->t2r_shm_sz - cmd.size) + goto skip; + + new_work = kzalloc_obj(*new_work, GFP_ATOMIC); + if (!new_work) + return NULL; + + t2r_ring_advance(ctx, sizeof(cmd)); + + c = new_work; + c->mbedtee = mbedtee; + c->data = ctx->t2r_shm + off; + } + + c->func = func; + c->waiter_id = cmd.waiter_id; + c->size = cmd.size; + + return c; + +skip: + /* + * If this was a sync RPC (waiter_id != 0) we must notify the TEE + * thread so it does not wait forever. Defer the completion fastcall + * to process context because transport implementations may sleep. + * If the single deferred-completion slot is already in use, leave the + * header in place and retry after the pending completion has drained. + */ + if (cmd.waiter_id != 0 && !rpc_queue_complete_only(ctx, cmd.waiter_id)) + return NULL; + + /* + * Bad or unhandled entry: consume header plus any inline payload + * (async RPC payloads follow the header in the ring) to keep the + * ring moving. For sync RPCs payload is in t2r_shm, not the ring. + */ + t2r_ring_advance(ctx, sizeof(cmd) + (cmd.waiter_id ? 0 : cmd.size)); + + return NULL; +} + +static void rpc_drain_ring(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct rpc_work rw = {}, *c = NULL; + unsigned long flags; + + if (!ctx->t2r_ring) + return; + + spin_lock_irqsave(&ctx->ring_lock, flags); + while (READ_ONCE(ctx->t2r_ring_rd) != + /* Pair with producer store-release after ring write. */ + smp_load_acquire(&ctx->t2r_ring->wr)) { + rw.data = ctx->rpc_data; + c = rpc_pick_next(mbedtee, ctx, &rw); + if (!c) + break; + + if (c->waiter_id == 0) { + c->func(mbedtee, c->data, c->size); + } else { + INIT_WORK(&c->work, rpc_routine); + queue_work(ctx->rpc_wq, &c->work); + } + } + spin_unlock_irqrestore(&ctx->ring_lock, flags); +} + +static void rpc_routine(struct work_struct *work) +{ + struct rpc_work *c = container_of(work, struct rpc_work, work); + struct mbedtee_t2r_ctx *ctx = &c->mbedtee->t2r; + long ret; + + if (!c->complete_only) + c->func(c->mbedtee, c->data, c->size); + + /* + * COMPLETE_TEE must reach the TEE thread that issued the sync RPC. + * On RISC-V the notification uses a shared ring buffer that may be + * transiently full when many RPCs are in flight concurrently. + * Keep retrying rather than silently dropping the completion, which + * would leave the TEE thread blocked in rpc_call_sync forever. + * On ARM the fastcall is a direct SMC so -ENOSPC never occurs. + */ + do { + ret = mbedtee_rpc_fastcall(c->mbedtee, MBEDTEE_RPC_COMPLETE_TEE, + (unsigned long)c->waiter_id, 0, 0); + if (ret != -ENOSPC && ret != -ENOMEM) + break; + cond_resched(); + } while (1); + + if (c != &ctx->complete_work) { + kfree(c); + return; + } + + WRITE_ONCE(ctx->complete_work_pending, false); + rpc_drain_ring(c->mbedtee); +} + +/* + * Hard IRQ handler -- drains the TEE-to-REE ring buffer. + * + * Asynchronous RPCs (waiter_id == 0) are handled inline since all async + * handlers (complete(), ktime_get_real_ts64()) are non-blocking. + * + * Synchronous RPCs (waiter_id != 0) may block waiting for the + * tee-supplicant, so they are dispatched to a workqueue. + */ +irqreturn_t mbedtee_rpc_irq_handler(int irq, void *dev_id) +{ + struct mbedtee_device *mbedtee = dev_id; + + if (!mbedtee) + return IRQ_NONE; + + rpc_drain_ring(mbedtee); + + return IRQ_HANDLED; +} + +static void mbedtee_rpc_complete(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + u64 waiter_id; + + if (size < sizeof(waiter_id)) + return; + + memcpy(&waiter_id, data, sizeof(waiter_id)); + if (!waiter_id) + return; + + mbedtee_rpc_complete_call(mbedtee, waiter_id); +} + +static void mbedtee_ree_time(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + struct timespec64 *ts = data; + + if (size < sizeof(*ts)) + return; + + ktime_get_real_ts64(ts); +} + +static void mbedtee_reefs_supp(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + mbedtee_supp_handler(mbedtee, MBEDTEE_SUPP_REEFS, data, size); +} + +static void mbedtee_rpmb_supp(struct mbedtee_device *mbedtee, + void *data, size_t size) +{ + mbedtee_supp_handler(mbedtee, MBEDTEE_SUPP_RPMB, data, size); +} + +static void mbedtee_register_rpc(struct mbedtee_device *mbedtee, + u32 id, void (*func)(struct mbedtee_device *mbedtee, + void *data, size_t size)) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + + if (WARN_ON(id >= MBEDTEE_RPC_MAX || !func)) + return; + + ctx->rpc_handlers[id] = func; +} + +int mbedtee_rpc_init(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct device_node *node = mbedtee->dev->of_node; + struct resource res; + bool cpuhp_state_created = false; + int ret; + + memset(ctx, 0, sizeof(*ctx)); + spin_lock_init(&ctx->ring_lock); + mbedtee->rpc_ops = mbedtee_get_rpc_transport_ops(); + if (!mbedtee->rpc_ops) + return -EIO; + + ret = mbedtee_get_resource(node, "t2r-ring", &res); + if (ret) { + dev_err(mbedtee->dev, "t2r-ring not found in DT\n"); + return ret; + } + + ctx->t2r_ring = memremap(res.start, resource_size(&res), + MEMREMAP_WB); + if (!ctx->t2r_ring) { + dev_err(mbedtee->dev, "failed to map t2r ring\n"); + return -ENOMEM; + } + if (resource_size(&res) <= sizeof(struct rpc_ringbuf)) { + dev_err(mbedtee->dev, "t2r-ring too small\n"); + memunmap(ctx->t2r_ring); + ctx->t2r_ring = NULL; + return -EINVAL; + } + ctx->t2r_ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); + /* Observe latest producer index only after ring metadata is visible. */ + WRITE_ONCE(ctx->t2r_ring_rd, smp_load_acquire(&ctx->t2r_ring->rd)); + + dev_dbg(mbedtee->dev, "t2r-ring %pa\n", &res.start); + + ret = mbedtee_get_resource(node, "t2r-shm", &res); + if (ret) { + dev_err(mbedtee->dev, "t2r-shm not found in DT\n"); + goto err_ring; + } + + ctx->t2r_shm_phys = res.start; + ctx->t2r_shm_sz = resource_size(&res); + ctx->t2r_shm = memremap(res.start, ctx->t2r_shm_sz, + MEMREMAP_WB); + if (!ctx->t2r_shm) { + dev_err(mbedtee->dev, "failed to map t2r shm\n"); + ret = -ENOMEM; + goto err_ring; + } + + dev_dbg(mbedtee->dev, "t2r-shm %pa\n", &res.start); + + ctx->rpc_wq = alloc_workqueue("mbedtee-rpc", WQ_UNBOUND, 0); + if (!ctx->rpc_wq) { + dev_err(mbedtee->dev, "failed to create rpc workqueue\n"); + ret = -ENOMEM; + goto err_shm; + } + + ctx->complete_work.mbedtee = mbedtee; + ctx->complete_work.complete_only = true; + INIT_WORK(&ctx->complete_work.work, rpc_routine); + + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_COMPLETE_REE, mbedtee_rpc_complete); + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_REETIME, mbedtee_ree_time); + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_REEFS, mbedtee_reefs_supp); + mbedtee_register_rpc(mbedtee, MBEDTEE_RPC_RPMB, mbedtee_rpmb_supp); + + /* + * Register cpuhp instance BEFORE enabling interrupts so that + * T2R delivery migration is in place before the first IRQ fires. + */ + mutex_lock(&mbedtee_cpuhp_mutex); + if (mbedtee_cpuhp_state < 0) { + mbedtee_cpuhp_state = + cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "tee/mbedtee:rpc-callee", + mbedtee_cpu_online, + mbedtee_cpu_offline); + if (mbedtee_cpuhp_state < 0) { + ret = mbedtee_cpuhp_state; + mutex_unlock(&mbedtee_cpuhp_mutex); + goto err_wq; + } + cpuhp_state_created = true; + } + + ret = cpuhp_state_add_instance(mbedtee_cpuhp_state, &ctx->hp_node); + if (!ret) { + mbedtee_cpuhp_instances++; + ctx->cpuhp_added = true; + } else if (cpuhp_state_created) { + cpuhp_remove_multi_state(mbedtee_cpuhp_state); + mbedtee_cpuhp_state = -EINVAL; + } + mutex_unlock(&mbedtee_cpuhp_mutex); + if (ret != 0) + goto err_wq; + + ret = mbedtee->rpc_ops->init(mbedtee, ctx->t2r_ring, + mbedtee_rpc_irq_handler); + if (ret != 0) + goto err_cpuhp; + + /* Signal the TEE that the REE callee is ready to receive. */ + smp_store_release(&ctx->t2r_ring->callee_ready, true); + + return 0; + +err_cpuhp: + mutex_lock(&mbedtee_cpuhp_mutex); + if (mbedtee_cpuhp_state >= 0 && ctx->cpuhp_added) { + cpuhp_state_remove_instance_nocalls(mbedtee_cpuhp_state, + &ctx->hp_node); + ctx->cpuhp_added = false; + if (mbedtee_cpuhp_instances) + mbedtee_cpuhp_instances--; + if (!mbedtee_cpuhp_instances) { + cpuhp_remove_multi_state(mbedtee_cpuhp_state); + mbedtee_cpuhp_state = -EINVAL; + } + } + mutex_unlock(&mbedtee_cpuhp_mutex); +err_wq: + destroy_workqueue(ctx->rpc_wq); + ctx->rpc_wq = NULL; +err_shm: + memunmap(ctx->t2r_shm); + ctx->t2r_shm = NULL; +err_ring: + memunmap(ctx->t2r_ring); + ctx->t2r_ring = NULL; + return ret; +} + +void mbedtee_rpc_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx; + + if (!mbedtee) + return; + + ctx = &mbedtee->t2r; + /* Stop advertising the REE callee before interrupt teardown. */ + if (ctx->t2r_ring) + WRITE_ONCE(ctx->t2r_ring->callee_ready, false); + + mutex_lock(&mbedtee_cpuhp_mutex); + if (mbedtee_cpuhp_state >= 0 && ctx->cpuhp_added) { + cpuhp_state_remove_instance_nocalls(mbedtee_cpuhp_state, + &ctx->hp_node); + ctx->cpuhp_added = false; + if (mbedtee_cpuhp_instances) + mbedtee_cpuhp_instances--; + if (!mbedtee_cpuhp_instances) { + cpuhp_remove_multi_state(mbedtee_cpuhp_state); + mbedtee_cpuhp_state = -EINVAL; + } + } + mutex_unlock(&mbedtee_cpuhp_mutex); + + if (mbedtee->rpc_ops) + mbedtee->rpc_ops->uninit(mbedtee); + + /* + * Work queued before IRQ teardown may be blocked in the supplicant + * path. Abort those requests and reject later ones before draining the + * workqueue. R2T must stay alive until destroy_workqueue() returns + * because rpc_routine() sends COMPLETE_TEE replies over R2T. + */ + mbedtee_supp_abort_all(&mbedtee->supp); + + if (ctx->rpc_wq) { + destroy_workqueue(ctx->rpc_wq); + ctx->rpc_wq = NULL; + } + + if (ctx->t2r_shm) { + memunmap(ctx->t2r_shm); + ctx->t2r_shm = NULL; + } + + if (ctx->t2r_ring) { + memunmap(ctx->t2r_ring); + ctx->t2r_ring = NULL; + } +} diff --git a/drivers/tee/mbedtee/rpc_callee_arm.c b/drivers/tee/mbedtee/rpc_callee_arm.c new file mode 100644 index 0000000..871bba5 --- /dev/null +++ b/drivers/tee/mbedtee/rpc_callee_arm.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * ARM/ARM64 GIC transport for the TEE->REE RPC callee. + * + * On ARM/ARM64 the TEE raises a rising-edge software-generated interrupt + * described by the "interrupts" property of the mbedtee DT node. This file + * obtains that virq via of_irq_get() and registers the hard IRQ handler + * supplied by the core callee layer. + */ +#include <linux/interrupt.h> +#include <linux/cpu.h> +#include <linux/of.h> +#include <linux/of_irq.h> + +#include "mbedtee_drv.h" + +static int arm_transport_init(struct mbedtee_device *mbedtee, + struct rpc_ringbuf *ring, irq_handler_t handler) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + int ret; + + tctx->rpc_notify_virq = of_irq_get(mbedtee->dev->of_node, 0); + if (tctx->rpc_notify_virq <= 0) + return tctx->rpc_notify_virq ? tctx->rpc_notify_virq : -ENODEV; + + ret = request_irq(tctx->rpc_notify_virq, handler, + 0, "mbedtee-rpc", mbedtee); + if (ret) { + dev_warn(mbedtee->dev, "request_irq %d failed: %d\n", + tctx->rpc_notify_virq, ret); + tctx->rpc_notify_virq = 0; + return ret; + } + + /* + * Spread the T2R SPI across all online CPUs in Linux. + * The TEE needs no CPU information from Linux: it broadcasts + * via GIC hardware (GICv2 ITARGETS=0xFF / GICv3 IROUTER.IRM=1). + */ + cpumask_copy(&ctx->callee_cpus, cpu_online_mask); + if (irq_set_affinity(tctx->rpc_notify_virq, &ctx->callee_cpus)) + dev_warn(mbedtee->dev, "irq_set_affinity failed\n"); + + dev_dbg(mbedtee->dev, "t2r-irq %d\n", tctx->rpc_notify_virq); + return 0; +} + +static void arm_transport_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + + if (tctx->rpc_notify_virq > 0) { + free_irq(tctx->rpc_notify_virq, mbedtee); + tctx->rpc_notify_virq = 0; + } +} + +/* + * ARM T2R uses a GIC SPI: the TEE broadcasts it via GIC hardware + * (GICv2 ITARGETS=0xFF or GICv3 IROUTER.IRM=1) to any alive CPU. + * TrustZone shares physical CPUs -- no ring update is ever needed. + * + * @new_cpu is ignored: the IRQ is re-affined to ctx->callee_cpus which + * the core callee layer keeps in sync (dying CPU already removed before + * this callback is invoked, so the mask is always accurate). + */ +static int arm_update_affinity(struct mbedtee_device *mbedtee, + unsigned int new_cpu) +{ + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + + if (tctx->rpc_notify_virq > 0 && + irq_set_affinity(tctx->rpc_notify_virq, &mbedtee->t2r.callee_cpus)) + dev_warn(mbedtee->dev, "irq_set_affinity failed\n"); + + return 0; +} + +static const struct rpc_transport_ops arm_transport_ops = { + .init = arm_transport_init, + .uninit = arm_transport_uninit, + .update_affinity = arm_update_affinity, +}; + +const struct rpc_transport_ops *mbedtee_get_rpc_transport_ops(void) +{ + return &arm_transport_ops; +} diff --git a/drivers/tee/mbedtee/rpc_callee_riscv.c b/drivers/tee/mbedtee/rpc_callee_riscv.c new file mode 100644 index 0000000..7ac0f8e --- /dev/null +++ b/drivers/tee/mbedtee/rpc_callee_riscv.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Xing Loong <xing.xl.loong@gmail.com> + * RISC-V MSI transport for the TEE->REE RPC callee. + * + * On RISC-V the TEE wakes the REE by writing a Message Signalled Interrupt + * into the target hart's IMSIC (Incoming MSI Controller). This file allocates + * one platform MSI from the DT-provided MSI parent and registers the hard IRQ + * handler supplied by the core callee layer. + * + * CPU hotplug: the common mbedtee_cpu_offline() callback in rpc_callee.c + * detects when the callee CPU goes offline and calls riscv_update_affinity() + * to migrate the MSI to another online CPU. + */ +#include <linux/interrupt.h> +#include <linux/msi.h> +#include <linux/of_irq.h> +#include <linux/smp.h> + +#include "mbedtee_drv.h" + +static int mbedtee_ring_set_hartid(struct mbedtee_device *mbedtee, + struct rpc_ringbuf *ring, + unsigned int cpu) +{ + long hartid = cpuid_to_hartid_map(cpu); + + if (hartid < 0) { + dev_err(mbedtee->dev, "CPU%u has no valid hart-id\n", cpu); + return -ERANGE; + } + + WRITE_ONCE(ring->callee_hartid, (u32)hartid); + return 0; +} + +static void mbedtee_rpc_write_msi_msg(struct msi_desc *desc_msi, + struct msi_msg *msg) +{ + struct mbedtee_device *mbedtee = dev_get_drvdata(desc_msi->dev); + + if (mbedtee) + mbedtee->transport.rpc_msi_msg = *msg; +} + +static int riscv_transport_init(struct mbedtee_device *mbedtee, + struct rpc_ringbuf *ring, irq_handler_t handler) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + const struct cpumask *eff; + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + unsigned int cpu; + struct device *dev = mbedtee->dev; + int virq; + int ret; + + /* + * Set a bootstrap callee_hartid (hart 0) so the TEE can poll + * even if MSI setup fails below. + */ + ret = mbedtee_ring_set_hartid(mbedtee, ring, 0); + if (ret) + return ret; + + dev_set_drvdata(dev, mbedtee); + + /* + * The MSI domain of an OF device is resolved only once, when the + * platform device is created (of_msi_configure() in of/platform.c). + * mbedtee's MSI parent is the IMSIC, whose MSI domain is created + * later, when the IMSIC builtin_platform_driver probes -- typically + * after this device has already been created with a NULL msi.domain. + * + * Deferred probing alone cannot recover from this: the driver core + * re-runs probe() but never re-runs of_msi_configure(), so the stale + * NULL domain would persist across every retry. Re-resolve it here + * and defer until the IMSIC driver is up. This mirrors the RISC-V + * APLIC/IOMMU platform-MSI drivers. + */ + if (!dev_get_msi_domain(dev)) { + of_msi_configure(dev, dev->of_node); + + if (!dev_get_msi_domain(dev)) + return -EPROBE_DEFER; + } + + ret = platform_device_msi_init_and_alloc_irqs(dev, 1, + mbedtee_rpc_write_msi_msg); + if (ret) { + dev_err(mbedtee->dev, "MSI alloc failed: %d\n", ret); + return ret; + } + + virq = msi_get_virq(dev, 0); + if (virq <= 0) { + dev_err(mbedtee->dev, "no MSI virq\n"); + ret = -ENOENT; + goto err_msi; + } + + ret = request_irq(virq, handler, 0, "mbedtee-rpc", mbedtee); + if (ret) { + dev_err(mbedtee->dev, "request_irq %d failed: %d\n", virq, ret); + goto err_msi; + } + + eff = irq_get_effective_affinity_mask(virq); + cpu = eff ? cpumask_first(eff) : 0; + + dev_dbg(mbedtee->dev, "MSI addr 0x%x%08x data %d virq %d hart %ld\n", + tctx->rpc_msi_msg.address_hi, tctx->rpc_msi_msg.address_lo, + tctx->rpc_msi_msg.data, virq, cpuid_to_hartid_map(cpu)); + + /* Inform TEE of the IMSIC identity used for T2R notifications. */ + ret = mbedtee_ring_set_hartid(mbedtee, ring, cpu); + if (ret) + goto err_irq; + /* Ensure callee_hartid is visible before advertising MSI interrupt ID. */ + smp_store_release(&ring->callee_imsic_id, tctx->rpc_msi_msg.data); + + /* Track the active callee CPU for hotplug migration. */ + ctx->callee_virq = virq; + cpumask_set_cpu(cpu, &ctx->callee_cpus); + + return 0; + +err_irq: + free_irq(virq, mbedtee); + +err_msi: + platform_device_msi_free_irqs_all(dev); + return ret; +} + +static void riscv_transport_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + int virq; + + virq = ctx->callee_virq; + ctx->callee_virq = 0; + if (virq > 0) + free_irq(virq, mbedtee); + + platform_device_msi_free_irqs_all(mbedtee->dev); +} + +/* + * Migrate the IMSIC MSI delivery to @new_cpu. + * + * Ordering guarantee for the TEE reader: + * 1. Clear callee_imsic_id to 0 -- TEE skips IMSIC path while we migrate. + * 2. Call irq_set_affinity() -- triggers mbedtee_rpc_write_msi_msg() which + * updates tctx->rpc_msi_msg with the new IMSIC identity. + * 3. Update callee_hartid (WRITE_ONCE inside mbedtee_ring_set_hartid()); + * no explicit barrier needed here since the release store on + * callee_imsic_id in step 4 provides the ordering. + * 4. Publish new callee_imsic_id with smp_store_release -- TEE's + * smp_load_acquire on callee_imsic_id pairs with this store. + */ +static int riscv_update_affinity(struct mbedtee_device *mbedtee, + unsigned int new_cpu) +{ + struct mbedtee_t2r_ctx *ctx = &mbedtee->t2r; + struct mbedtee_rpc_transport_ctx *tctx = &mbedtee->transport; + struct rpc_ringbuf *ring = ctx->t2r_ring; + int virq = ctx->callee_virq; + + if (!virq) + return -ENODEV; + + /* Step 1: prevent TEE from using the stale IMSIC identity. */ + smp_store_release(&ring->callee_imsic_id, 0); + + /* Step 2: retarget the MSI; write_msi_msg callback updates rpc_msi_msg. */ + if (irq_set_affinity(virq, cpumask_of(new_cpu))) { + dev_warn(mbedtee->dev, "irq_set_affinity to CPU%u failed\n", + new_cpu); + return -EIO; + } + + /* Step 3: update callee_hartid. */ + if (mbedtee_ring_set_hartid(mbedtee, ring, new_cpu)) + return -ERANGE; + + /* Step 4: publish new IMSIC id with release barrier. */ + smp_store_release(&ring->callee_imsic_id, tctx->rpc_msi_msg.data); + + dev_dbg(mbedtee->dev, "T2R callee migrated to CPU%u (hart %ld)\n", + new_cpu, cpuid_to_hartid_map(new_cpu)); + return 0; +} + +static const struct rpc_transport_ops riscv_transport_ops = { + .init = riscv_transport_init, + .uninit = riscv_transport_uninit, + .update_affinity = riscv_update_affinity, +}; + +const struct rpc_transport_ops *mbedtee_get_rpc_transport_ops(void) +{ + return &riscv_transport_ops; +} diff --git a/drivers/tee/mbedtee/rpc_caller.c b/drivers/tee/mbedtee/rpc_caller.c new file mode 100644 index 0000000..1ae55db --- /dev/null +++ b/drivers/tee/mbedtee/rpc_caller.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * REE->TEE session management and yield-call interface. + */ +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include "mbedtee_drv.h" + +#define MBEDTEE_RPC_CALL_ACTIVE 0 +#define MBEDTEE_RPC_CALL_DONE 1 +#define MBEDTEE_RPC_CALL_INTERRUPTED 2 + +int mbedtee_rpc_call_alloc(struct mbedtee_device *mbedtee, + size_t payload_size, struct mbedtee_rpc_call **call) +{ + struct mbedtee_rpc_call *rcall; + unsigned long rpc_index; + int ret; + + if (sizeof(*rcall) + payload_size > PAGE_SIZE) + return -E2BIG; + + rcall = kzalloc(sizeof(*rcall) + payload_size, GFP_KERNEL); + if (!rcall) + return -ENOMEM; + + init_completion(&rcall->c); + rcall->state = MBEDTEE_RPC_CALL_ACTIVE; + rcall->rpc.size = payload_size; + rcall->rpc.ret = -EOPNOTSUPP; + rcall->rpc_phys = mbedtee_virt_to_phys(&rcall->rpc); + + do { + rpc_index = atomic_long_inc_return(&mbedtee->rpc_call_seq); + } while (rpc_index == 0); + + ret = xa_insert_irq(&mbedtee->rpc_calls, rpc_index, rcall, GFP_KERNEL); + if (ret != 0) { + kfree(rcall); + return ret; + } + + rcall->rpc.waiter_id = rpc_index; + *call = rcall; + + return 0; +} + +void mbedtee_rpc_call_free(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call) +{ + unsigned long rpc_index; + + if (!call) + return; + + rpc_index = (unsigned long)call->rpc.waiter_id; + + /* + * If interrupted/completed, complete_call owns this allocation + * and will xa_erase + kfree when the TEE eventually finishes. + */ + if (READ_ONCE(call->state) == MBEDTEE_RPC_CALL_INTERRUPTED) + return; + + xa_erase_irq(&mbedtee->rpc_calls, rpc_index); + kfree(call); +} + +void mbedtee_rpc_complete_call(struct mbedtee_device *mbedtee, + u64 waiter_id) +{ + struct mbedtee_rpc_call *call; + unsigned long rpc_index; + unsigned long flags; + unsigned int state; + + rpc_index = (unsigned long)waiter_id; + if ((u64)rpc_index != waiter_id) + return; + + xa_lock_irqsave(&mbedtee->rpc_calls, flags); + call = xa_load(&mbedtee->rpc_calls, rpc_index); + if (!call) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return; + } + + state = cmpxchg(&call->state, MBEDTEE_RPC_CALL_ACTIVE, + MBEDTEE_RPC_CALL_DONE); + __xa_erase(&mbedtee->rpc_calls, rpc_index); + + if (state == MBEDTEE_RPC_CALL_INTERRUPTED) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + kfree(call); + return; + } + + complete(&call->c); + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); +} + +long mbedtee_rpc_wait_for_completion(struct mbedtee_device *mbedtee, + struct mbedtee_rpc_call *call, bool killable) +{ + long ret; + unsigned int state; + unsigned long flags; + unsigned long rpc_index; + + if (!killable) { + wait_for_completion(&call->c); + return 0; + } + + rpc_index = (unsigned long)call->rpc.waiter_id; + + ret = wait_for_completion_killable(&call->c); + if (ret == 0) + return 0; + + /* + * Synchronize with mbedtee_rpc_complete_call() via the + * xa_lock. If complete_call already consumed this entry + * before we acquired the lock, the entry is already gone + * from the xarray and the TEE already completed (or freed) + * the call -- treat as success since the RPC is finished. + * If the entry is still present, we hold the lock so + * complete_call cannot race with our state transition. + */ + xa_lock_irqsave(&mbedtee->rpc_calls, flags); + if (xa_load(&mbedtee->rpc_calls, rpc_index) != call) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return 0; + } + + state = cmpxchg(&call->state, MBEDTEE_RPC_CALL_ACTIVE, + MBEDTEE_RPC_CALL_INTERRUPTED); + if (state == MBEDTEE_RPC_CALL_DONE) { + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return 0; + } + + if (state == MBEDTEE_RPC_CALL_ACTIVE) + /* Publish interruption state before the TEE waiter reads it. */ + smp_store_release(&call->rpc.interrupted, true); + + xa_unlock_irqrestore(&mbedtee->rpc_calls, flags); + return ret; +} + +/* Local helpers for encoding/decoding the 4-bit-per-parameter type field. */ +static inline u32 mbedtee_param_type_get(u32 types, unsigned int idx) +{ + return (types >> (idx * 4)) & 0xF; +} + +static inline u32 mbedtee_param_type_set(u32 type, unsigned int idx) +{ + return (type & 0xF) << (idx * 4); +} + +static int mbedtee_param_decode(struct tee_param *params, + size_t num_params, const struct rpc_param *rp) +{ + size_t n; + + if (num_params > ARRAY_SIZE(rp->params)) + return -EINVAL; + + for (n = 0; n < num_params; n++) { + struct tee_param *p = params + n; + const union rpc_tee_param *rtp = rp->params + n; + u32 attr = mbedtee_param_type_get(rp->params_type, n); + + switch (attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_NONE: + memset(&p->u, 0, sizeof(p->u)); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: + p->u.value.a = rtp->value.a; + p->u.value.b = rtp->value.b; + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: + p->u.memref.size = rtp->memref.size; + break; + default: + return -EINVAL; + } + p->attr = attr; + } + return 0; +} + +static int mbedtee_param_encode(struct rpc_param *rp, + size_t num_params, const struct tee_param *params) +{ + size_t n; + + if (num_params > ARRAY_SIZE(rp->params)) + return -EINVAL; + + rp->params_type = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + + for (n = 0; n < num_params; n++) { + const struct tee_param *p = params + n; + union rpc_tee_param *rtp = rp->params + n; + + rp->params_type |= mbedtee_param_type_set(p->attr, n); + + switch (p->attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_NONE: + memset(rtp, 0, sizeof(*rtp)); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: + rtp->value.a = p->u.value.a; + rtp->value.b = p->u.value.b; + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: + if (p->u.memref.shm) + rtp->memref.id = p->u.memref.shm->sec_world_id; + else + rtp->memref.id = 0; /* invalid-id @ mbedtee */ + rtp->memref.size = p->u.memref.size; + rtp->memref.offset = p->u.memref.shm_offs; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +/* + * cancel_req() and shm_{register,unregister} still return Linux errno to the + * kernel TEE core. Preserve any local transport failure in @ret; otherwise + * fold a non-success TEE-side GP status into the generic -EIO expected by + * these internal-only hooks. + */ +static int mbedtee_rpc_gp_ret_to_errno(int ret, s32 gp_ret) +{ + if (ret) + return ret; + + return gp_ret == TEEC_SUCCESS ? 0 : -EIO; +} + +static struct mbedtee_session * +mbedtee_find_session_locked(struct mbedtee_context_data *ctxdata, + u32 session_id) +{ + struct mbedtee_session *sess; + + list_for_each_entry(sess, &ctxdata->sess_list, list_node) + if (sess->session_id == session_id) + return sess; + + return NULL; +} + +int mbedtee_open_session(struct tee_context *ctx, + struct tee_ioctl_open_session_arg *arg, + struct tee_param *param) +{ + int ret; + u32 session_id = 0; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_param *rp; + struct mbedtee_session *sess; + + sess = kzalloc_obj(*sess, GFP_KERNEL); + if (!sess) + return -ENOMEM; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*rp), &call); + if (ret != 0) { + kfree(sess); + return ret; + } + + rp = (struct rpc_param *)call->rpc.data; + + ret = mbedtee_param_encode(rp, arg->num_params, param); + if (ret) { + kfree(sess); + mbedtee_rpc_call_free(mbedtee, call); + return ret; + } + + memcpy(rp->uuid, arg->uuid, sizeof(arg->uuid)); + memcpy(rp->clnt_uuid, arg->clnt_uuid, sizeof(arg->clnt_uuid)); + rp->ret_origin = TEEC_ORIGIN_COMMS; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_OPEN_SESSION, call, false); + + dev_dbg(mbedtee->dev, "open session ret %d gp_ret %d\n", ret, + call->rpc.ret); + + if (ret == 0 && call->rpc.ret == TEEC_SUCCESS) { + session_id = rp->session_id; + sess->session_id = session_id; + mutex_lock(&ctxdata->mutex); + list_add(&sess->list_node, &ctxdata->sess_list); + mutex_unlock(&ctxdata->mutex); + } else { + kfree(sess); + } + + if (ret != 0) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + } else if (mbedtee_param_decode(param, arg->num_params, rp)) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + if (call->rpc.ret == TEEC_SUCCESS) + mbedtee_close_session(ctx, session_id); + } else { + arg->session = rp->session_id; + arg->ret = (u32)call->rpc.ret; + arg->ret_origin = rp->ret_origin; + } + + mbedtee_rpc_call_free(mbedtee, call); + return 0; +} + +int mbedtee_invoke_func(struct tee_context *ctx, + struct tee_ioctl_invoke_arg *arg, + struct tee_param *param) +{ + int ret; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_param *rp; + struct mbedtee_session *sess; + + mutex_lock(&ctxdata->mutex); + sess = mbedtee_find_session_locked(ctxdata, arg->session); + mutex_unlock(&ctxdata->mutex); + if (!sess) + return -EINVAL; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*rp), &call); + if (ret != 0) + return ret; + + rp = (struct rpc_param *)call->rpc.data; + + rp->session_id = arg->session; + rp->cmd_id = arg->func; + rp->ret_origin = TEEC_ORIGIN_COMMS; + + ret = mbedtee_param_encode(rp, arg->num_params, param); + if (ret) { + mbedtee_rpc_call_free(mbedtee, call); + return ret; + } + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_INVOKE_SESSION, call, true); + + dev_dbg(mbedtee->dev, "invoke session ret %d gp_ret %d\n", ret, + call->rpc.ret); + + if (ret != 0) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + } else if (mbedtee_param_decode(param, arg->num_params, rp)) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + } else { + arg->ret = (u32)call->rpc.ret; + arg->ret_origin = rp->ret_origin; + } + + mbedtee_rpc_call_free(mbedtee, call); + return 0; +} + +int mbedtee_close_session(struct tee_context *ctx, u32 session) +{ + int ret; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_param *rp; + struct mbedtee_session *sess; + + mutex_lock(&ctxdata->mutex); + sess = mbedtee_find_session_locked(ctxdata, session); + if (!sess) { + mutex_unlock(&ctxdata->mutex); + return -EINVAL; + } + + list_del(&sess->list_node); + mutex_unlock(&ctxdata->mutex); + kfree(sess); + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*rp), &call); + if (ret != 0) + return ret; + + rp = (struct rpc_param *)call->rpc.data; + rp->session_id = session; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_CLOSE_SESSION, call, false); + + mbedtee_rpc_call_free(mbedtee, call); + + return ret; +} + +int mbedtee_cancel_req(struct tee_context *ctx, + u32 cancel_id, u32 session) +{ + int ret; + struct mbedtee_context_data *ctxdata = ctx->data; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_cancel_req *cancel; + struct mbedtee_session *sess; + + mutex_lock(&ctxdata->mutex); + sess = mbedtee_find_session_locked(ctxdata, session); + mutex_unlock(&ctxdata->mutex); + if (!sess) + return -EINVAL; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*cancel), &call); + if (ret != 0) + return ret; + + cancel = (struct rpc_cancel_req *)call->rpc.data; + cancel->session_id = session; + cancel->cancel_id = cancel_id; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_CANCEL, call, false); + ret = mbedtee_rpc_gp_ret_to_errno(ret, call->rpc.ret); + + mbedtee_rpc_call_free(mbedtee, call); + + return ret; +} + +int mbedtee_shm_register(struct tee_context *ctx, struct tee_shm *shm, + struct page **pages, size_t nr_pages, unsigned long start) +{ + int ret; + size_t i, j; + u64 *pagearray; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_memref *memref; + /* Multiple: number of MBEDTEE_PAGE_SIZE pages per Linux page. */ + const size_t multiple = PAGE_SIZE / MBEDTEE_PAGE_SIZE; + + BUILD_BUG_ON(PAGE_SIZE < MBEDTEE_PAGE_SIZE); + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*memref), &call); + if (ret != 0) + return ret; + + memref = (struct rpc_memref *)call->rpc.data; + + pagearray = kcalloc(nr_pages * multiple, sizeof(*pagearray), GFP_KERNEL); + if (!pagearray) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < nr_pages; i++) { + for (j = 0; j < multiple; j++) + pagearray[i * multiple + j] = MBEDTEE_PAGE_SIZE * j + + page_to_phys(pages[i]); + } + + memref->size = tee_shm_get_size(shm); + memref->offset = tee_shm_get_page_offset(shm); + memref->pages = mbedtee_virt_to_phys(pagearray); + memref->cnt = nr_pages * multiple; + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_REGISTER_SHM, call, false); + ret = mbedtee_rpc_gp_ret_to_errno(ret, call->rpc.ret); + + if (ret == 0) + shm->sec_world_id = memref->id; + + kfree(pagearray); + +out: + mbedtee_rpc_call_free(mbedtee, call); + return ret; +} + +int mbedtee_shm_unregister(struct tee_context *ctx, + struct tee_shm *shm) +{ + int ret; + struct mbedtee_device *mbedtee = tee_get_drvdata(ctx->teedev); + struct mbedtee_rpc_call *call; + struct rpc_memref *memref; + + ret = mbedtee_rpc_call_alloc(mbedtee, sizeof(*memref), &call); + if (ret != 0) + return ret; + + memref = (struct rpc_memref *)call->rpc.data; + memref->id = shm->sec_world_id; + + ret = mbedtee_rpc_yieldcall(mbedtee, MBEDTEE_RPC_UNREGISTER_SHM, call, false); + ret = mbedtee_rpc_gp_ret_to_errno(ret, call->rpc.ret); + + mbedtee_rpc_call_free(mbedtee, call); + + return ret; +} diff --git a/drivers/tee/mbedtee/rpc_caller_arm.c b/drivers/tee/mbedtee/rpc_caller_arm.c new file mode 100644 index 0000000..1dd665a --- /dev/null +++ b/drivers/tee/mbedtee/rpc_caller_arm.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + * ARM RPC calls to TEE - relies on SMC + * + * Uses arm_smccc_smc() with SMCCC-compatible calling convention: + * Fast call : r0/x0 = fn (bit 31 set), r1-r3/x1-x3 = args. + * Yield call : r0/x0 = fn (bit 31 clear), r1/x1 = phys(rpc_cmd). + * + * ARM fast calls are synchronous SMC instructions. + */ +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/arm-smccc.h> +#include "mbedtee_drv.h" + +long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee, + unsigned long fn, struct mbedtee_rpc_call *call, + bool interruptible) +{ + long ret; + struct arm_smccc_res res; + + if (MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + call->rpc.id = fn; + + arm_smccc_smc(fn, call->rpc_phys, + 0, 0, 0, 0, 0, 0, &res); + ret = res.a0; + if (ret != 0) + return ret; + + return mbedtee_rpc_wait_for_completion(mbedtee, call, interruptible); +} + +long mbedtee_rpc_fastcall(struct mbedtee_device *mbedtee, + unsigned long fn, unsigned long a0, + unsigned long a1, unsigned long a2) +{ + struct arm_smccc_res res; + + if (!MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + arm_smccc_smc(fn, a0, a1, a2, 0, 0, 0, 0, &res); + + return (long)res.a0; +} + +/* + * ARM uses direct SMC; no caller-side ring buffer initialisation needed. + */ +int mbedtee_r2t_init(struct mbedtee_device *mbedtee) +{ + return 0; +} + +void mbedtee_r2t_uninit(struct mbedtee_device *mbedtee) +{ +} diff --git a/drivers/tee/mbedtee/rpc_caller_riscv.c b/drivers/tee/mbedtee/rpc_caller_riscv.c new file mode 100644 index 0000000..387d9bb --- /dev/null +++ b/drivers/tee/mbedtee/rpc_caller_riscv.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Xing Loong <xing.xl.loong@gmail.com> + * REE->TEE RPC calls for RISC-V (IMSIC) + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/sizes.h> + +#include "mbedtee_drv.h" + +static bool rpc_ring_enough(struct mbedtee_r2t_ctx *ctx, size_t size) +{ + u32 wr; + u32 rd; + u32 remain; + + wr = READ_ONCE(ctx->ring_wr); + /* Pair with callee release store when publishing ring->rd */ + rd = smp_load_acquire(&ctx->ring->rd); + + if (rd > ctx->ring_sz || wr > ctx->ring_sz) + return false; + + if (rd <= wr) + remain = ctx->ring_sz + rd - wr; + else + remain = rd - wr; + + return remain > size; +} + +static void rpc_ring_write(struct mbedtee_r2t_ctx *ctx, + void *data, size_t size) +{ + struct rpc_ringbuf *shm = ctx->ring; + u32 remain; + u32 wr = READ_ONCE(ctx->ring_wr); + + if (wr + size > ctx->ring_sz) { + remain = wr + size - ctx->ring_sz; + memcpy(&shm->mem[wr], data, size - remain); + memcpy(&shm->mem[0], (char *)data + size - remain, remain); + wr = remain; + } else { + memcpy(&shm->mem[wr], data, size); + wr += size; + } + + WRITE_ONCE(ctx->ring_wr, wr); + /* Publish writer index after payload bytes become visible. */ + smp_store_release(&shm->wr, wr); +} + +long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee, + unsigned long fn, struct mbedtee_rpc_call *call, + bool interruptible) +{ + struct mbedtee_r2t_ctx *ctx = &mbedtee->r2t; + unsigned long flags; + u64 phys; + + if (!ctx->ring) + return -ENXIO; + + if (MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + call->rpc.id = fn; + + spin_lock_irqsave(&ctx->lock, flags); + if (!rpc_ring_enough(ctx, sizeof(u64))) { + dev_err_ratelimited(mbedtee->dev, "rpc ring full\n"); + spin_unlock_irqrestore(&ctx->lock, flags); + return -ENOSPC; + } + + phys = call->rpc_phys; + rpc_ring_write(ctx, &phys, sizeof(phys)); + spin_unlock_irqrestore(&ctx->lock, flags); + + return mbedtee_rpc_wait_for_completion(mbedtee, call, interruptible); +} + +long mbedtee_rpc_fastcall(struct mbedtee_device *mbedtee, + unsigned long fn, unsigned long a0, + unsigned long a1, unsigned long a2) +{ + struct mbedtee_r2t_ctx *ctx = &mbedtee->r2t; + struct mbedtee_rpc_call *call; + unsigned long flags; + u64 phys; + int ret; + + if (!ctx->ring) + return -ENXIO; + + if (!MBEDTEE_RPC_IS_FASTCALL(fn)) + return -EINVAL; + + ret = mbedtee_rpc_call_alloc(mbedtee, 3 * sizeof(u64), &call); + if (ret != 0) + return ret; + + call->rpc.id = fn; + call->rpc.size = 3 * sizeof(u64); + call->rpc.data[0] = a0; + call->rpc.data[1] = a1; + call->rpc.data[2] = a2; + + spin_lock_irqsave(&ctx->lock, flags); + if (!rpc_ring_enough(ctx, sizeof(u64))) { + dev_err_ratelimited(mbedtee->dev, "rpc ring full\n"); + spin_unlock_irqrestore(&ctx->lock, flags); + mbedtee_rpc_call_free(mbedtee, call); + return -ENOSPC; + } + + phys = call->rpc_phys; + rpc_ring_write(ctx, &phys, sizeof(phys)); + spin_unlock_irqrestore(&ctx->lock, flags); + + ret = mbedtee_rpc_wait_for_completion(mbedtee, call, true); + if (ret != 0) { + mbedtee_rpc_call_free(mbedtee, call); + return ret; + } + + ret = call->rpc.ret; + mbedtee_rpc_call_free(mbedtee, call); + + return ret; +} + +int mbedtee_r2t_init(struct mbedtee_device *mbedtee) +{ + struct mbedtee_r2t_ctx *ctx = &mbedtee->r2t; + struct device_node *node = mbedtee->dev->of_node; + struct resource res; + int ret; + + memset(ctx, 0, sizeof(*ctx)); + spin_lock_init(&ctx->lock); + + ret = mbedtee_get_resource(node, "r2t-ring", &res); + if (ret) + return ret; + + if (resource_size(&res) <= sizeof(struct rpc_ringbuf)) { + dev_err(mbedtee->dev, "r2t-ring too small\n"); + return -EINVAL; + } + + ctx->ring = memremap(res.start, resource_size(&res), + MEMREMAP_WB); + if (!ctx->ring) { + dev_err(mbedtee->dev, "failed to map r2t ring at %pa\n", + &res.start); + return -ENOMEM; + } + ctx->ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); + /* Read initial writer index with acquire for coherent ring bootstrap. */ + WRITE_ONCE(ctx->ring_wr, smp_load_acquire(&ctx->ring->wr)); + + dev_dbg(mbedtee->dev, "r2t-ring %pa\n", &res.start); + + return 0; +} + +void mbedtee_r2t_uninit(struct mbedtee_device *mbedtee) +{ + struct mbedtee_r2t_ctx *ctx; + + if (!mbedtee) + return; + + ctx = &mbedtee->r2t; + if (ctx->ring) { + memunmap(ctx->ring); + ctx->ring = NULL; + ctx->ring_sz = 0; + ctx->ring_wr = 0; + } +} diff --git a/drivers/tee/mbedtee/shm_pool.c b/drivers/tee/mbedtee/shm_pool.c new file mode 100644 index 0000000..708ab24 --- /dev/null +++ b/drivers/tee/mbedtee/shm_pool.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2017, EPAM Systems + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#include <linux/device.h> +#include <linux/genalloc.h> +#include <linux/slab.h> + +#include "mbedtee_drv.h" + +static int pool_op_alloc(struct tee_shm_pool *pool, + struct tee_shm *shm, size_t size, size_t align) +{ + unsigned int order = get_order(size); + struct page *page; + int rc = 0; + + page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); + if (!page) + return -ENOMEM; + + shm->kaddr = page_address(page); + shm->paddr = page_to_phys(page); + shm->size = PAGE_SIZE << order; + + if (!(shm->flags & TEE_SHM_PRIV)) { + struct mbedtee_device *mbedtee = tee_get_drvdata(shm->ctx->teedev); + + if (mbedtee->yield) { + unsigned int nr_pages = 1 << order; + unsigned int i; + struct page **pages; + + pages = kmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) { + rc = -ENOMEM; + goto err_pages; + } + + for (i = 0; i < nr_pages; i++) + pages[i] = page + i; + + rc = mbedtee_shm_register(shm->ctx, shm, pages, + nr_pages, + (unsigned long)shm->kaddr); + kfree(pages); + if (rc) + goto err_pages; + } + } + + return 0; + +err_pages: + free_pages((unsigned long)shm->kaddr, get_order(shm->size)); + shm->kaddr = NULL; + return rc; +} + +static void pool_op_free(struct tee_shm_pool *pool, + struct tee_shm *shm) +{ + if (!(shm->flags & TEE_SHM_PRIV)) { + struct mbedtee_device *mbedtee = tee_get_drvdata(shm->ctx->teedev); + + if (mbedtee->yield && shm->sec_world_id) + mbedtee_shm_unregister(shm->ctx, shm); + } + + free_pages((unsigned long)shm->kaddr, get_order(shm->size)); + shm->kaddr = NULL; +} + +static void pool_op_destroy_pool(struct tee_shm_pool *pool) +{ + kfree(pool); +} + +static const struct tee_shm_pool_ops pool_ops = { + .alloc = pool_op_alloc, + .free = pool_op_free, + .destroy_pool = pool_op_destroy_pool, +}; + +/** + * mbedtee_shm_pool_alloc_pages() - create page-based allocator pool + * + * This pool is used when MbedTEE supports dynamic shared memory. Command + * buffers and similar structures are allocated from kernel's own memory. + * + * Return: pointer to a tee_shm_pool or ERR_PTR on failure + */ +struct tee_shm_pool *mbedtee_shm_pool_alloc_pages(void) +{ + struct tee_shm_pool *pool = kzalloc_obj(*pool, GFP_KERNEL); + + if (!pool) + return ERR_PTR(-ENOMEM); + + pool->ops = &pool_ops; + + return pool; +} diff --git a/drivers/tee/mbedtee/shm_pool.h b/drivers/tee/mbedtee/shm_pool.h new file mode 100644 index 0000000..4510676 --- /dev/null +++ b/drivers/tee/mbedtee/shm_pool.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2016, EPAM Systems + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ + +#ifndef SHM_POOL_H +#define SHM_POOL_H + +#include <linux/tee_drv.h> + +struct tee_shm_pool *mbedtee_shm_pool_alloc_pages(void); + +#endif diff --git a/drivers/tee/mbedtee/supp.c b/drivers/tee/mbedtee/supp.c new file mode 100644 index 0000000..bc7de22 --- /dev/null +++ b/drivers/tee/mbedtee/supp.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2020 Xing Loong <xing.xl.loong@gmail.com> + */ +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "mbedtee_drv.h" + +struct mbedtee_supp_req { + int ret; + u32 func; + + struct list_head node; + struct task_struct *worker; + struct tee_context *ctx; /* owning supplicant context */ + + struct tee_shm *shm; + void *data; + size_t size; + + struct completion c; +}; + +static int mbedtee_supp_check_recv_params(size_t num_params, + struct tee_param *params) +{ + if (num_params != 1) + return -EINVAL; + + if (!tee_param_is_memref(params) || !params->u.memref.shm) + return -EINVAL; + + return 0; +} + +void mbedtee_supp_init(struct mbedtee_supp *supp) +{ + memset(supp, 0, sizeof(*supp)); + mutex_init(&supp->mutex); + init_completion(&supp->reqs_c); + INIT_LIST_HEAD(&supp->reqs); + INIT_LIST_HEAD(&supp->active_reqs); +} + +void mbedtee_supp_uninit(struct mbedtee_supp *supp) +{ + mutex_destroy(&supp->mutex); +} + +static void mbedtee_supp_complete_req(struct mbedtee_supp_req *r) +{ + list_del_init(&r->node); + if (r->shm) + tee_shm_put(r->shm); + r->shm = NULL; + r->ret = TEEC_ERROR_COMMUNICATION; + complete(&r->c); +} + +void mbedtee_supp_release(struct mbedtee_supp *supp, struct tee_context *ctx) +{ + struct mbedtee_supp_req *r, *n; + + mutex_lock(&supp->mutex); + + /* + * Cancel only requests owned by @ctx. Unclaimed requests and + * requests owned by other supplicant contexts are left alone. + */ + list_for_each_entry_safe(r, n, &supp->reqs, node) { + if (r->ctx != ctx) + continue; + mbedtee_supp_complete_req(r); + } + + list_for_each_entry_safe(r, n, &supp->active_reqs, node) { + if (r->ctx != ctx) + continue; + mbedtee_supp_complete_req(r); + } + + if (supp->ctx == ctx) + supp->ctx = NULL; + + mutex_unlock(&supp->mutex); +} + +void mbedtee_supp_abort_all(struct mbedtee_supp *supp) +{ + struct mbedtee_supp_req *r, *n; + + mutex_lock(&supp->mutex); + supp->shutting_down = true; + + list_for_each_entry_safe(r, n, &supp->reqs, node) + mbedtee_supp_complete_req(r); + + list_for_each_entry_safe(r, n, &supp->active_reqs, node) + mbedtee_supp_complete_req(r); + + supp->ctx = NULL; + + mutex_unlock(&supp->mutex); + complete_all(&supp->reqs_c); +} + +static int supp_enqueue_req(struct mbedtee_supp *supp, + struct mbedtee_supp_req *req) +{ + int ret = 0; + + mutex_lock(&supp->mutex); + if (supp->shutting_down) + ret = -ESHUTDOWN; + else + list_add_tail(&req->node, &supp->reqs); + mutex_unlock(&supp->mutex); + + return ret; +} + +/* + * Receive an RPC request from TEE and dispatch it to supplicant. + * Blocks until the supplicant sends back the result. + */ +void mbedtee_supp_handler(struct mbedtee_device *mbedtee, + u32 func, void *data, size_t size) +{ + struct mbedtee_supp *supp = &mbedtee->supp; + struct mbedtee_supp_req *req = kzalloc_obj(*req, GFP_KERNEL); + struct supp_cmd_hdr *cmd = data; + int ret; + + if (!req) { + if (cmd && size >= sizeof(*cmd)) + cmd->ret = TEEC_ERROR_OUT_OF_MEMORY; + return; + } + + init_completion(&req->c); + req->func = func; + req->data = data; + req->size = size; + + ret = supp_enqueue_req(supp, req); + if (ret != 0) { + if (cmd && size >= sizeof(*cmd)) + cmd->ret = TEEC_ERROR_COMMUNICATION; + kfree(req); + return; + } + + /* Wake up the supplicant daemon to handle this request. */ + complete(&supp->reqs_c); + + wait_for_completion(&req->c); + + kfree(req); +} + +static int supp_pop_req(struct mbedtee_supp *supp, + struct mbedtee_supp_req **req) +{ + struct mbedtee_supp_req *r = NULL; + int ret = 0; + + mutex_lock(&supp->mutex); + if (supp->shutting_down) { + ret = -ESHUTDOWN; + goto out; + } + + r = list_first_entry_or_null(&supp->reqs, struct mbedtee_supp_req, node); + + if (r) { + list_del_init(&r->node); + r->worker = current; + list_add_tail(&r->node, &supp->active_reqs); + } + +out: + mutex_unlock(&supp->mutex); + *req = r; + return ret; +} + +/* + * Called by supplicant to receive the next pending request. + */ +int mbedtee_supp_recv(struct tee_context *ctx, + u32 *func, u32 *num_params, struct tee_param *param) +{ + struct tee_device *teedev = ctx->teedev; + struct mbedtee_device *mbedtee = tee_get_drvdata(teedev); + struct mbedtee_supp *supp = &mbedtee->supp; + struct mbedtee_supp_req *req; + struct mbedtee_context_data *d = ctx->data; + struct tee_shm *shm = param->u.memref.shm; + int ret; + + ret = mbedtee_supp_check_recv_params(*num_params, param); + if (ret != 0) + return ret; + + shm = tee_shm_get_from_id(ctx, shm->id); + tee_shm_put(param->u.memref.shm); + if (IS_ERR(shm)) { + param->u.memref.shm = NULL; + return PTR_ERR(shm); + } + param->u.memref.shm = shm; + + d->is_supp_ctx = true; + + while (true) { + ret = supp_pop_req(supp, &req); + if (ret != 0) + goto err; + if (req) + break; + + if (wait_for_completion_interruptible(&supp->reqs_c)) { + ret = -ERESTARTSYS; + goto err; + } + } + + /* Record which supplicant context owns this request. */ + req->ctx = ctx; + + if (req->size > param->u.memref.size) { + /* Return the request to the queue so it isn't lost */ + mutex_lock(&supp->mutex); + list_del(&req->node); + list_add(&req->node, &supp->reqs); + mutex_unlock(&supp->mutex); + ret = -EOVERFLOW; + goto err; + } + + *func = req->func; + req->shm = shm; + + memcpy(shm->kaddr, req->data, req->size); + param->u.memref.size = req->size; + + return 0; + +err: + tee_shm_put(shm); + param->u.memref.shm = NULL; + return ret; +} + +/* + * Called by supplicant to send back the result of a request. + */ +int mbedtee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params, + struct tee_param *param) +{ + struct tee_device *teedev = ctx->teedev; + struct mbedtee_device *mbedtee = tee_get_drvdata(teedev); + struct mbedtee_supp *supp = &mbedtee->supp; + struct mbedtee_supp_req *req; + struct mbedtee_supp_req *_req; + + if (num_params != 1) + return -EINVAL; + + if (!tee_param_is_memref(param)) + return -EINVAL; + + /* + * Find the active request belonging to this worker thread. + * Each worker is identified by its task_struct pointer, which + * was recorded when supp_pop_req() dispatched the request. + */ + mutex_lock(&supp->mutex); + req = NULL; + list_for_each_entry(_req, &supp->active_reqs, node) { + if (_req->worker == current) { + req = _req; + list_del_init(&req->node); + break; + } + } + mutex_unlock(&supp->mutex); + + if (!req) + return -ENOENT; + + if (param->u.memref.size > req->size) { + req->ret = TEEC_ERROR_COMMUNICATION; + } else { + req->ret = ret; + memcpy(req->data, req->shm->kaddr, param->u.memref.size); + } + if (req->shm) + tee_shm_put(req->shm); + req->shm = NULL; + + /* Wake up mbedtee_supp_handler(). */ + complete(&req->c); + + return 0; +} diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index 5203977..2d30296 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -60,6 +60,7 @@ #define TEE_IMPL_ID_AMDTEE 2 #define TEE_IMPL_ID_TSTEE 3 #define TEE_IMPL_ID_QTEE 4 +#define TEE_IMPL_ID_MBEDTEE 5 /* * OP-TEE specific capabilities -- 2.43.0 ^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 3/3] tee: add MbedTEE driver 2026-07-02 15:11 ` [PATCH v2 3/3] tee: add MbedTEE driver Xing Loong @ 2026-07-02 15:32 ` sashiko-bot 0 siblings, 0 replies; 15+ messages in thread From: sashiko-bot @ 2026-07-02 15:32 UTC (permalink / raw) To: Xing Loong; +Cc: robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider: - [Critical] Malicious or malformed asynchronous supplicant RPCs will execute sleeping operations in a hard IRQ handler, causing deadlocks. - [Critical] The `req->shm` reference is under-retained in `mbedtee_supp_recv()`, leading to a Use-After-Free and double free. - [Critical] Unvalidated `cmd.size` for rejected async RPCs causes the consumer index to exceed ring buffer bounds, leading to massive memory corruption. - [Critical] Type confusion privilege escalation via TEE-controlled parameter attributes. - [High] Unhandled MSI migration on RISC-V causes lost TEE interrupts and hanging RPCs. - [High] Physical address of the RPC command is silently truncated on 32-bit ARM with LPAE. - [High] The tee-supplicant's return code is lost and never propagated back to the TEE. - [High] Module removal deadlocks because the T2R interrupt is torn down before aborting supplicant requests. - [High] Initial ring buffer consumer and producer pointers are read from untrusted shared memory without bounds checking. - [High] Unaligned memory access in `mbedtee_ree_time` can trigger alignment faults and kernel panics. - [Medium] The required buffer size is not returned to the tee-supplicant when -EOVERFLOW occurs. - [Medium] Leaking TEE sessions if a context acts as both a client and a supplicant. - [Medium] Encountering an invalid or unhandled RPC command in the TEE-to-REE ring causes a state machine stall. -- commit e35eb394dcd64d2ef130e20e3614fccbb5021a07 Author: Xing Loong <xing.xl.loong@gmail.com> tee: add MbedTEE driver This patch adds a Linux TEE driver that communicates with MbedTEE OS via a fixed shared-memory RPC protocol over architecture-specific transports. > diff --git a/drivers/tee/mbedtee/core.c b/drivers/tee/mbedtee/core.c > --- a/drivers/tee/mbedtee/core.c > +++ b/drivers/tee/mbedtee/core.c > @@ -80,5 +80,6 @@ static void mbedtee_release(struct tee_context *ctx) > > if (d->is_supp_ctx) { > mbedtee_supp_release(&mbedtee->supp, ctx); > } else { > list_for_each_entry_safe(s, n, &d->sess_list, list_node) > mbedtee_close_session(ctx, s->session_id); > } [Severity: Medium] Does this skip session cleanup for mixed-use contexts? If a userspace process opens standard TEE sessions and then calls TEE_IOC_SUPPL_RECV, d->is_supp_ctx becomes true. When the file descriptor is closed, the mutually exclusive else branch is skipped, which appears to leak the sessions in d->sess_list and leaves them permanently open in the TEE. > diff --git a/drivers/tee/mbedtee/rpc_callee.c b/drivers/tee/mbedtee/rpc_callee.c > --- a/drivers/tee/mbedtee/rpc_callee.c > +++ b/drivers/tee/mbedtee/rpc_callee.c > @@ -245,6 +245,5 @@ static struct rpc_work *rpc_pick_next(struct mbedtee_device *mbedtee, > skip: > if (cmd.waiter_id != 0 && !rpc_queue_complete_only(ctx, cmd.waiter_id)) > return NULL; > > t2r_ring_advance(ctx, sizeof(cmd) + (cmd.waiter_id ? 0 : cmd.size)); > > return NULL; > } [Severity: Critical] Does this blind advance allow a malicious TEE to corrupt the ring read pointer? Because cmd.size is not validated on this error path, an arbitrarily large value could cause t2r_ring_advance() to jump far outside the actual ring bounds, leading to integer underflows and out-of-bounds memory accesses in subsequent operations. [Severity: Medium] Does returning NULL here cause the rpc_drain_ring() loop to break entirely? If an unrecognized command is skipped, returning NULL will unconditionally stop the processing loop, leaving subsequent valid commands stranded until another interrupt fires, which could stall the protocol. > @@ -267,7 +267,6 @@ static void rpc_drain_ring(struct mbedtee_device *mbedtee) > c = rpc_pick_next(mbedtee, ctx, &rw); > if (!c) > break; > > if (c->waiter_id == 0) { > c->func(mbedtee, c->data, c->size); > } else { [Severity: Critical] Can this sequence cause a deadlock in hard IRQ context? The rpc_drain_ring() function runs in mbedtee_rpc_irq_handler() with the ctx->ring_lock spinlock held. If a TEE sends a synchronous supplicant RPC like MBEDTEE_RPC_REEFS with waiter_id = 0, this inline execution will route to mbedtee_supp_handler(), which performs a GFP_KERNEL allocation and calls wait_for_completion(). Sleeping in a hard IRQ handler while holding a spinlock will crash the system. > @@ -400,6 +400,5 @@ static void mbedtee_ree_time(struct mbedtee_device *mbedtee, > void *data, size_t size) > { > struct timespec64 *ts = data; > > if (size < sizeof(*ts)) > return; > > ktime_get_real_ts64(ts); > } [Severity: High] Does this cause unaligned memory accesses? The data pointer points directly into shared memory at an offset controlled by the TEE. Writing 64-bit fields into an unaligned timespec64 structure can trigger alignment faults and kernel panics on architectures with strict alignment requirements. > @@ -486,4 +486,5 @@ int mbedtee_rpc_init(struct mbedtee_device *mbedtee) > return -EINVAL; > } > ctx->t2r_ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); > /* Observe latest producer index only after ring metadata is visible. */ > WRITE_ONCE(ctx->t2r_ring_rd, smp_load_acquire(&ctx->t2r_ring->rd)); [Severity: High] Are we missing a bounds check on the initial read pointer? Loading this directly from TEE-controlled shared memory without validating that it is within ctx->t2r_ring_sz could allow a compromised TEE to instantly trigger out-of-bounds accesses during driver initialization. > @@ -585,5 +585,6 @@ void mbedtee_rpc_uninit(struct mbedtee_device *mbedtee) > mutex_unlock(&mbedtee_cpuhp_mutex); > > if (mbedtee->rpc_ops) > mbedtee->rpc_ops->uninit(mbedtee); > > /* > * Work queued before IRQ teardown may be blocked in the supplicant > * path. Abort those requests and reject later ones before draining the > * workqueue. R2T must stay alive until destroy_workqueue() returns > * because rpc_routine() sends COMPLETE_TEE replies over R2T. > */ > mbedtee_supp_abort_all(&mbedtee->supp); [Severity: High] Will this sequence deadlock during module removal? Unregistering the interrupt via uninit() happens before mbedtee_supp_abort_all(). If aborting requests triggers shm unregistration, that process makes a synchronous RPC call to the TEE. Since the interrupt is already torn down, the completion response will never arrive, causing the removal thread to block indefinitely. > diff --git a/drivers/tee/mbedtee/rpc_callee_riscv.c b/drivers/tee/mbedtee/rpc_callee_riscv.c > --- a/drivers/tee/mbedtee/rpc_callee_riscv.c > +++ b/drivers/tee/mbedtee/rpc_callee_riscv.c > @@ -34,6 +34,5 @@ static void mbedtee_rpc_write_msi_msg(struct msi_desc *desc_msi, > struct msi_msg *msg) > { > struct mbedtee_device *mbedtee = dev_get_drvdata(desc_msi->dev); > > if (mbedtee) > mbedtee->transport.rpc_msi_msg = *msg; > } [Severity: High] Does this fail to propagate the new IMSIC target to the TEE? When the IRQ subsystem changes affinity, this callback updates the local message cache, but it does not write the new callee_imsic_id and callee_hartid back to the shared ring buffer header. The TEE will continue sending interrupts to the stale target, leading to lost notifications and hanging RPCs. > diff --git a/drivers/tee/mbedtee/rpc_caller.c b/drivers/tee/mbedtee/rpc_caller.c > --- a/drivers/tee/mbedtee/rpc_caller.c > +++ b/drivers/tee/mbedtee/rpc_caller.c > @@ -182,5 +182,6 @@ static int mbedtee_param_decode(struct tee_param *params, > case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: > p->u.memref.size = rtp->memref.size; > break; > default: > return -EINVAL; > } > p->attr = attr; > } > return 0; > } [Severity: Critical] Could this lead to type confusion and privilege escalation? Blindly overwriting p->attr with the TEE-supplied attribute without verifying it matches the original request allows a malicious TEE to change a VALUE parameter to a MEMREF parameter. The generic subsystem will then interpret the user-controlled value fields as a kernel pointer during tee_shm_put(), allowing arbitrary kernel memory dereferences. > diff --git a/drivers/tee/mbedtee/rpc_caller_arm.c b/drivers/tee/mbedtee/rpc_caller_arm.c > --- a/drivers/tee/mbedtee/rpc_caller_arm.c > +++ b/drivers/tee/mbedtee/rpc_caller_arm.c > @@ -30,5 +30,6 @@ long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee, > call->rpc.id = fn; > > arm_smccc_smc(fn, call->rpc_phys, > 0, 0, 0, 0, 0, 0, &res); > ret = res.a0; [Severity: High] Does this silently truncate the physical address on 32-bit ARM with LPAE? The call->rpc_phys field is a 64-bit phys_addr_t, but arm_smccc_smc() casts its arguments to a 32-bit unsigned long. If the memory resides above 4GB, the upper bits will be lost, giving the TEE an invalid address and causing memory corruption or crashes. > diff --git a/drivers/tee/mbedtee/rpc_caller_riscv.c b/drivers/tee/mbedtee/rpc_caller_riscv.c > --- a/drivers/tee/mbedtee/rpc_caller_riscv.c > +++ b/drivers/tee/mbedtee/rpc_caller_riscv.c > @@ -166,4 +166,5 @@ int mbedtee_r2t_init(struct mbedtee_device *mbedtee) > return -ENOMEM; > } > ctx->ring_sz = resource_size(&res) - sizeof(struct rpc_ringbuf); > /* Read initial writer index with acquire for coherent ring bootstrap. */ > WRITE_ONCE(ctx->ring_wr, smp_load_acquire(&ctx->ring->wr)); [Severity: High] Is it safe to load the initial write pointer from shared memory without bounds validation? A compromised TEE could set this to an out-of-bounds value prior to driver initialization, leading to immediate out-of-bounds memory accesses. > diff --git a/drivers/tee/mbedtee/supp.c b/drivers/tee/mbedtee/supp.c > --- a/drivers/tee/mbedtee/supp.c > +++ b/drivers/tee/mbedtee/supp.c > @@ -153,6 +153,5 @@ void mbedtee_supp_handler(struct mbedtee_device *mbedtee, > /* Wake up the supplicant daemon to handle this request. */ > complete(&supp->reqs_c); > > wait_for_completion(&req->c); > > kfree(req); > } [Severity: High] Are we missing a step to propagate the supplicant's return code back to the TEE? After wait_for_completion() returns, req->ret holds the execution status, but this value is never written into the shared memory cmd->ret before req is freed, causing the TEE to lose the actual result of the operation. > @@ -203,6 +203,5 @@ int mbedtee_supp_recv(struct tee_context *ctx, > return ret; > > shm = tee_shm_get_from_id(ctx, shm->id); > tee_shm_put(param->u.memref.shm); > if (IS_ERR(shm)) { [Severity: Critical] Does this pattern lead to a use-after-free and double free? The code acquires a generic SHM reference and immediately drops the previous reference. This results in a net zero reference count change, meaning the driver does not truly own the reference it assigns to req->shm. When the IOCTL finishes, the generic layer will put the reference again, leaving req->shm dangling. > @@ -231,9 +231,8 @@ int mbedtee_supp_recv(struct tee_context *ctx, > req->ctx = ctx; > > if (req->size > param->u.memref.size) { > /* Return the request to the queue so it isn't lost */ > mutex_lock(&supp->mutex); > list_del(&req->node); > list_add(&req->node, &supp->reqs); > mutex_unlock(&supp->mutex); > ret = -EOVERFLOW; > goto err; > } [Severity: Medium] Is the required buffer size missing from the -EOVERFLOW response? If the user-provided buffer is too small, the driver returns -EOVERFLOW but fails to update param->u.memref.size with req->size. The userspace supplicant daemon relies on this updated size to know how much memory to allocate for a retry. > @@ -298,5 +298,6 @@ int mbedtee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params, > req->ret = ret; > memcpy(req->data, req->shm->kaddr, param->u.memref.size); > } > if (req->shm) > tee_shm_put(req->shm); > req->shm = NULL; [Severity: Critical] Because the reference was not correctly incremented in mbedtee_supp_recv(), calling tee_shm_put(req->shm) here drops a reference that belongs to the userspace process, completing the double free. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260702151115.544016-1-xing.xl.loong@gmail.com?part=3 ^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-02 15:32 UTC | newest] Thread overview: 15+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-01 13:25 [PATCH 0/3] tee: add MbedTEE driver Xing Loong 2026-07-01 13:25 ` [PATCH 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong 2026-07-01 13:25 ` [PATCH 2/3] dt-bindings: firmware: add mbedtee,rpc binding Xing Loong 2026-07-01 13:32 ` sashiko-bot 2026-07-01 14:05 ` Krzysztof Kozlowski 2026-07-01 16:39 ` Rob Herring (Arm) 2026-07-01 13:25 ` [PATCH 3/3] tee: add MbedTEE driver Xing Loong 2026-07-01 13:42 ` sashiko-bot 2026-07-02 15:11 ` [PATCH v2 0/3] " Xing Loong 2026-07-02 15:11 ` [PATCH v2 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong 2026-07-02 15:11 ` [PATCH v2 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong 2026-07-02 15:21 ` sashiko-bot 2026-07-02 15:27 ` Krzysztof Kozlowski 2026-07-02 15:11 ` [PATCH v2 3/3] tee: add MbedTEE driver Xing Loong 2026-07-02 15:32 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox