Devicetree
 help / color / mirror / Atom feed
* [PATCH v3 0/3] tee: add MbedTEE driver
@ 2026-07-20  7:35 Xing Loong
  2026-07-20  7:35 ` [PATCH v3 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Xing Loong @ 2026-07-20  7:35 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 v3:
- 0002: Drop all phandle stub nodes from examples.
- 0002: Remove redundant required: - interrupts from then branch.
- 0002: Simplify title and description.
- Link to v2: https://lore.kernel.org/r/20260702151115.544016-1-xing.xl.loong@gmail.com/
- Link to v1: https://lore.kernel.org/r/20260701132514.186953-1-xing.xl.loong@gmail.com/

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)
  - 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        | 109 +++
 .../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, 3167 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] 6+ messages in thread

* [PATCH v3 1/3] dt-bindings: vendor-prefixes: add mbedtee
  2026-07-20  7:35 [PATCH v3 0/3] tee: add MbedTEE driver Xing Loong
@ 2026-07-20  7:35 ` Xing Loong
  2026-07-20  7:35 ` [PATCH v3 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong
  2026-07-20  7:35 ` [PATCH v3 3/3] tee: add MbedTEE driver Xing Loong
  2 siblings, 0 replies; 6+ messages in thread
From: Xing Loong @ 2026-07-20  7:35 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] 6+ messages in thread

* [PATCH v3 2/3] dt-bindings: firmware: add mbedtee,tee binding
  2026-07-20  7:35 [PATCH v3 0/3] tee: add MbedTEE driver Xing Loong
  2026-07-20  7:35 ` [PATCH v3 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong
@ 2026-07-20  7:35 ` Xing Loong
  2026-07-20  7:48   ` sashiko-bot
  2026-07-20  7:35 ` [PATCH v3 3/3] tee: add MbedTEE driver Xing Loong
  2 siblings, 1 reply; 6+ messages in thread
From: Xing Loong @ 2026-07-20  7:35 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 v3:
- Drop all phandle stub nodes from examples.
- Remove redundant required: - interrupts from then branch.
- Simplify title and description.

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)
  - Add maxItems: 1 constraint to interrupts property (Sashiko AI review)

---

 .../bindings/firmware/mbedtee,tee.yaml        | 109 ++++++++++++++++++
 1 file changed, 109 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..a8f1201
--- /dev/null
+++ b/Documentation/devicetree/bindings/firmware/mbedtee,tee.yaml
@@ -0,0 +1,109 @@
+# 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
+
+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.
+
+  We're using "mbedtee" as the vendor prefix for the open-source TEE
+  project at https://github.com/mbedtee.
+
+  The REE and TEE CPUs sharing the RPC memory must be in a
+  hardware-coherent domain.
+
+  Two or three reserved-memory regions are required:
+    t2r-ring: ring buffer for TEE-to-REE notifications
+    t2r-shm:  shared memory for TEE-to-REE RPC payloads
+    r2t-ring: ring buffer for REE-to-TEE command submissions (RISC-V)
+
+  On ARM the transport uses SMC and a GIC SPI interrupt. On RISC-V
+  the transport uses shared-memory rings and IMSIC MSI; the TEE
+  polls r2t-ring for commands from the REE.
+
+properties:
+  compatible:
+    const: mbedtee,tee
+
+  interrupts:
+    maxItems: 1
+
+  msi-parent:
+    maxItems: 1
+
+  memory-region:
+    minItems: 2
+    maxItems: 3
+
+  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:
+      properties:
+        msi-parent: false
+        memory-region:
+          maxItems: 2
+        memory-region-names:
+          items:
+            - const: t2r-ring
+            - const: t2r-shm
+    else:
+      required:
+        - msi-parent
+      properties:
+        interrupts: false
+        memory-region:
+          minItems: 3
+        memory-region-names:
+          items:
+            - const: t2r-ring
+            - const: t2r-shm
+            - const: r2t-ring
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    firmware {
+        mbedtee {
+            compatible = "mbedtee,tee";
+            interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>;
+            memory-region = <&t2r_ring>, <&t2r_shm>;
+            memory-region-names = "t2r-ring", "t2r-shm";
+        };
+    };
+
+  - |
+    firmware {
+        mbedtee {
+            compatible = "mbedtee,tee";
+            msi-parent = <&imsic>;
+            memory-region = <&t2r_ring>, <&t2r_shm>, <&r2t_ring>;
+            memory-region-names = "t2r-ring", "t2r-shm", "r2t-ring";
+        };
+    };
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v3 3/3] tee: add MbedTEE driver
  2026-07-20  7:35 [PATCH v3 0/3] tee: add MbedTEE driver Xing Loong
  2026-07-20  7:35 ` [PATCH v3 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong
  2026-07-20  7:35 ` [PATCH v3 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong
@ 2026-07-20  7:35 ` Xing Loong
  2026-07-20  7:55   ` sashiko-bot
  2 siblings, 1 reply; 6+ messages in thread
From: Xing Loong @ 2026-07-20  7:35 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 v3:
- None (driver changes only in v2)

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] 6+ messages in thread

* Re: [PATCH v3 2/3] dt-bindings: firmware: add mbedtee,tee binding
  2026-07-20  7:35 ` [PATCH v3 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong
@ 2026-07-20  7:48   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-07-20  7:48 UTC (permalink / raw)
  To: Xing Loong; +Cc: robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] The YAML examples will fail `make dt_binding_check` with `dtc` compilation errors due to unresolved phandle references.
- [Low] Top-level `items` for `memory-region-names` is defined as an object `enum` rather than a list, leading to unnecessary schema complexity and bypassing strict sequence expectations.
--

--- Patch [2]: [PATCH v3 2/3] dt-bindings: firmware: add mbedtee,tee binding ---
commit 2b2356c421476302d4bae45809eeb06223db44ab
Author: Xing Loong <xing.xl.loong@gmail.com>

dt-bindings: firmware: add mbedtee,tee binding
    
This commit adds a new device tree binding for MbedTEE, a Trusted
Execution Environment. It supports communication via shared memory ring
buffers on ARM and RISC-V platforms.

> 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 top-level enum definition bypass the strict sequence expectations
for *-names properties?

Typically, items should be an ordered list of elements. Defining it as an enum
here allows the names to appear in any order, which is later overridden in the
allOf branches. Could this be simplified by defining the exact list of const
elements at the top level?

[ ... ]

> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    firmware {
> +        mbedtee {
> +            compatible = "mbedtee,tee";
> +            interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>;
> +            memory-region = <&t2r_ring>, <&t2r_shm>;
> +            memory-region-names = "t2r-ring", "t2r-shm";
> +        };
> +    };
> +
> +  - |
> +    firmware {
> +        mbedtee {
> +            compatible = "mbedtee,tee";
> +            msi-parent = <&imsic>;
> +            memory-region = <&t2r_ring>, <&t2r_shm>, <&r2t_ring>;
> +            memory-region-names = "t2r-ring", "t2r-shm", "r2t-ring";
> +        };
> +    };

[Severity: Low]
Will these examples fail make dt_binding_check with dtc compilation errors?

The phandles like <&t2r_ring>, <&t2r_shm>, <&r2t_ring>, and <&imsic> are used
here but are not defined as stub nodes anywhere in the example blocks. The
device tree compiler generally cannot resolve these phandles without the stub
definitions.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260720073558.799755-1-xing.xl.loong@gmail.com?part=2

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v3 3/3] tee: add MbedTEE driver
  2026-07-20  7:35 ` [PATCH v3 3/3] tee: add MbedTEE driver Xing Loong
@ 2026-07-20  7:55   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-07-20  7:55 UTC (permalink / raw)
  To: Xing Loong; +Cc: conor+dt, devicetree, robh

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] Unvalidated cmd.size in the rpc_pick_next error path causes kernel stack buffer overflow.
- [High] xa_insert_irq() called with GFP_KERNEL causes sleeping in atomic context.
- [High] Early parameter validation failure in mbedtee_supp_send() hangs kernel worker threads.
- [High] Malicious TEE can deadlock the kernel by sending supplicant requests as async RPCs.
- [High] RPC call object is accessed after being freed when wait_for_completion is interrupted.
- [High] UAF and list corruption in mbedtee_supp_recv due to unprotected access.
- [High] Unvalidated parameter attributes from the TEE allow an attacker to permanently leak shared memory object references.
- [Medium] RISC-V yield calls fail spuriously with -ENOSPC when the REE-to-TEE ring is transiently full.
- [Medium] The required buffer size is not returned to userspace when a supplicant buffer is too small.
- [Medium] Infinite retry loop in workqueue on TEE failure blocks teardown.
- [Medium] Leaked RPC calls on module removal due to xa_destroy without element freeing.
- [Medium] Data race on complete_work_pending between IRQ and workqueue contexts.
- [Medium] Transient memory allocation failure in rpc_pick_next stalls the ring buffer permanently.
--

--- Patch [3]: [PATCH v3 3/3] tee: add MbedTEE driver ---
commit a7be6b832a5cd2c0d320336aea6907717da8717c
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. It supports ARM TrustZone and RISC-V IMSIC
platforms.

> 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

[ ... ]

> @@ -198,16 +198,15 @@ 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);

[Severity: Medium]
Are the elements in the xarray leaked on module removal? Calling xa_destroy
frees the internal nodes of the xarray, but it does not iterate over and free
the elements themselves. Any RPC calls left in the MBEDTEE_RPC_CALL_INTERRUPTED
state would be permanently leaked.

>  }

> 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

[ ... ]

> @@ -148,15 +148,15 @@ static void t2r_ring_read(struct mbedtee_t2r_ctx *ctx,
>  }
>  
>  static bool rpc_queue_complete_only(struct mbedtee_t2r_ctx *ctx,
>  				    u64 waiter_id)
>  {
>  	if (ctx->complete_work_pending)

[Severity: Medium]
Is there a data race on complete_work_pending here? This variable is read as a
plain C variable in this IRQ context, but it is modified using WRITE_ONCE in
rpc_routine from a workqueue context. This could allow the compiler to cache
or reorder the read, potentially leaving the driver perpetually thinking a
completion work is 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;
>  }

[ ... ]

> @@ -215,9 +215,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;

[Severity: Medium]
Does a transient memory allocation failure here permanently stall the ring
buffer? If kzalloc_obj fails with GFP_ATOMIC, the function returns NULL and
rpc_drain_ring breaks out of its loop without advancing the read pointer.
The TEE thread would block forever since the entry remains unprocessed and no
retry is scheduled.

>  
>  		t2r_ring_advance(ctx, sizeof(cmd));

[ ... ]

> @@ -279,11 +279,11 @@ skip:
>  	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));

[Severity: Critical]
Can this advance the read pointer by an unvalidated size and cause a stack
buffer overflow? In rpc_pick_next, if an async RPC jumps to the skip label,
t2r_ring_advance is called with 32 + cmd.size without checking if cmd.size
exceeds the ring buffer capacity. 

If a malicious TEE sets a very large cmd.size, the read pointer wraps. On the
next iteration, t2r_available_size underflows and t2r_ring_copy executes
a memcpy of up to 4GB directly into the cmd struct on the kernel stack.

>  
>  	return NULL;
>  }

[ ... ]

> @@ -296,14 +296,14 @@ static void rpc_drain_ring(struct mbedtee_device *mbedtee)
>  	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);

[Severity: High]
Is it possible for a malicious TEE to deadlock the kernel by sending
supplicant requests as async RPCs? The TEE-to-REE interrupt handler runs in
hard IRQ context, and rpc_drain_ring relies entirely on cmd.waiter_id == 0
to process a command inline. 

If the TEE sends MBEDTEE_RPC_REEFS with waiter_id = 0, the driver invokes
mbedtee_supp_handler inline while holding the ring_lock. That function
allocates memory with GFP_KERNEL and calls wait_for_completion, both of
which sleep in atomic context.

>  		} else {
>  			INIT_WORK(&c->work, rpc_routine);
>  			queue_work(ctx->rpc_wq, &c->work);
>  		}
>  	}
>  	spin_unlock_irqrestore(&ctx->ring_lock, flags);
>  }

[ ... ]

> @@ -339,12 +339,12 @@ 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.
>  	 */
>  	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);

[Severity: Medium]
Could this loop infinitely if the ring buffer remains full? If
mbedtee_rpc_fastcall repeatedly returns -ENOSPC (which occurs on RISC-V when
the R2T ring is full), it retries indefinitely. If the TEE crashes and stops
draining the ring, this loop never terminates, and destroy_workqueue during
module removal would block forever waiting for the hung work item.

>  
>  	if (c != &ctx->complete_work) {

> 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

[ ... ]

> @@ -35,12 +35,12 @@ int mbedtee_rpc_call_alloc(struct mbedtee_device *mbedtee,
>  	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);

[Severity: High]
Does this code sleep in atomic context? The driver initializes
mbedtee->rpc_calls with XA_FLAGS_ALLOC1. Calling xa_insert_irq with
GFP_KERNEL drops the spinlock but leaves hardware interrupts disabled, then
calls kmem_cache_alloc with GFP_KERNEL which can sleep, violating atomic
context rules.

>  	if (ret != 0) {
>  		kfree(rcall);
>  		return ret;
>  	}

[ ... ]

> @@ -170,14 +170,14 @@ static int mbedtee_param_decode(struct tee_param *params,
>  		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;

[Severity: High]
Can a malicious TEE permanently leak shared memory object references by
returning unvalidated parameter attributes? The kernel decodes the parameter
types returned by the TEE and unconditionally overwrites the local p->attr
with the TEE's value. 

If userspace passes a MEMREF, a compromised TEE could return a VALUE type for
that slot, causing the TEE subsystem to skip dropping the reference during
cleanup because tee_param_is_memref will see a VALUE type instead.

>  	}
>  	return 0;
>  }

[ ... ]

> @@ -415,12 +415,12 @@ int mbedtee_invoke_func(struct tee_context *ctx,
>  	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);

[Severity: High]
Does this introduce a use-after-free if wait_for_completion is interrupted? If
mbedtee_rpc_wait_for_completion is interrupted by a signal, it sets the
state to MBEDTEE_RPC_CALL_INTERRUPTED and returns -ERESTARTSYS. The caller
here then reads call->rpc.ret and calls mbedtee_rpc_call_free.

Concurrently, mbedtee_rpc_complete_call can see the INTERRUPTED state, erase
the XArray entry, and free the call structure, causing the original caller to
perform UAF reads.

>  
>  	if (ret != 0) {
>  		arg->ret = TEEC_ERROR_COMMUNICATION;

> 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

[ ... ]

> @@ -75,10 +75,10 @@ long mbedtee_rpc_yieldcall(struct mbedtee_device *mbedtee,
>  	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;

[Severity: Medium]
Should this implement a wait queue or backoff instead of failing immediately?
On RISC-V, mbedtee_rpc_yieldcall returns -ENOSPC if the r2t-ring buffer is
full, which mbedtee_invoke_func translates to TEEC_ERROR_COMMUNICATION.
Without a polling backoff or wait mechanism, high concurrency from userspace
will easily fill the ring and cause spurious, unrecoverable failures.

>  	}
>  
>  	phys = call->rpc_phys;

> 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

[ ... ]

> @@ -232,18 +232,18 @@ int mbedtee_supp_recv(struct tee_context *ctx,
>  		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) {

[Severity: High]
Can this cause a use-after-free and linked list corruption? After
supp_pop_req removes a request and drops the mutex, the function accesses
req->ctx and req->size locklessly. 

If mbedtee_supp_abort_all runs concurrently (e.g. on module removal), it
could remove the request from active_reqs, complete it, and free it, causing
UAF reads here. If the size check fails, it then takes the mutex and
manipulates the list links of the freed node, corrupting the supp->reqs list.

[Severity: Medium]
Does this code fail to return the required buffer size to userspace? When
req->size is larger than param->u.memref.size, it correctly returns -EOVERFLOW
but fails to update param->u.memref.size with the required req->size. The
generic TEE framework relies on this updated value to inform userspace how
much memory to allocate, breaking buffer renegotiation.

>  		/* 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;
>  	}

[ ... ]

> @@ -262,11 +262,11 @@ 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;

[Severity: High]
Could this lead to a permanently hung kernel worker thread? If userspace calls
TEE_IOC_SUPPL_SEND with invalid parameters (like num_params != 1),
mbedtee_supp_send returns -EINVAL early. Because it does not pop the active
request or complete it, the worker thread executing mbedtee_supp_handler
would remain permanently blocked in wait_for_completion.

>  
>  	if (!tee_param_is_memref(param))
>  		return -EINVAL;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260720073558.799755-1-xing.xl.loong@gmail.com?part=3

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-07-20  7:55 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20  7:35 [PATCH v3 0/3] tee: add MbedTEE driver Xing Loong
2026-07-20  7:35 ` [PATCH v3 1/3] dt-bindings: vendor-prefixes: add mbedtee Xing Loong
2026-07-20  7:35 ` [PATCH v3 2/3] dt-bindings: firmware: add mbedtee,tee binding Xing Loong
2026-07-20  7:48   ` sashiko-bot
2026-07-20  7:35 ` [PATCH v3 3/3] tee: add MbedTEE driver Xing Loong
2026-07-20  7:55   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox