* [PATCH v3 1/5] ufs: decode string descriptors as UTF-16 big-endian
2026-07-23 14:38 [PATCH v3 0/5] ufs: rpmb: route OP-TEE RPMB secure storage over UFS Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-23 14:38 ` Jorge Ramirez-Ortiz via U-Boot
2026-07-29 21:51 ` David Lechner
2026-07-23 14:38 ` [PATCH v3 2/5] ufs: add RPMB transport over SCSI SECURITY PROTOCOL Jorge Ramirez-Ortiz via U-Boot
` (3 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Jorge Ramirez-Ortiz via U-Boot @ 2026-07-23 14:38 UTC (permalink / raw)
To: jorge.ramirez, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, dlechner,
u-boot
UFS string descriptors are UTF-16 big-endian (JESD220), but
ufshcd_read_string_desc() fed the raw bytes to utf16_to_utf8(), which reads
host-endian code units, leaving dev_desc->model blank. Byte-swap to host
order before decoding, matching the kernel.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/ufs/ufs-uclass.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/ufs/ufs-uclass.c b/drivers/ufs/ufs-uclass.c
index 6a51f337e47..8f120fbbee8 100644
--- a/drivers/ufs/ufs-uclass.c
+++ b/drivers/ufs/ufs-uclass.c
@@ -1533,6 +1533,19 @@ static inline void ufshcd_remove_non_printable(uint8_t *val)
*val = ' ';
}
+static inline void ufshcd_str_desc_to_cpu(u8 *desc, u32 size)
+{
+ u16 *p = (u16 *)&desc[QUERY_DESC_HDR_SIZE];
+ u32 len = desc[QUERY_DESC_LENGTH_OFFSET];
+ u32 i;
+
+ if (len > size)
+ len = size;
+
+ for (i = QUERY_DESC_HDR_SIZE; i + 1 < len; i += 2, p++)
+ *p = be16_to_cpu(*p);
+}
+
/**
* ufshcd_uic_pwr_ctrl - executes UIC commands (which affects the link power
* state) and waits for it to take effect.
@@ -1765,6 +1778,8 @@ static int ufshcd_read_string_desc(struct ufs_hba *hba, int desc_index,
goto out;
}
+ ufshcd_str_desc_to_cpu(buf, size);
+
/*
* the descriptor contains string in UTF16 format
* we need to convert to utf-8 so it can be displayed
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v3 1/5] ufs: decode string descriptors as UTF-16 big-endian
2026-07-23 14:38 ` [PATCH v3 1/5] ufs: decode string descriptors as UTF-16 big-endian Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-29 21:51 ` David Lechner
0 siblings, 0 replies; 9+ messages in thread
From: David Lechner @ 2026-07-29 21:51 UTC (permalink / raw)
To: Jorge Ramirez-Ortiz, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, u-boot
On 7/23/26 9:38 AM, Jorge Ramirez-Ortiz wrote:
> UFS string descriptors are UTF-16 big-endian (JESD220), but
> ufshcd_read_string_desc() fed the raw bytes to utf16_to_utf8(), which reads
> host-endian code units, leaving dev_desc->model blank. Byte-swap to host
> order before decoding, matching the kernel.
>
> Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
> ---
> drivers/ufs/ufs-uclass.c | 15 +++++++++++++++
> 1 file changed, 15 insertions(+)
>
> diff --git a/drivers/ufs/ufs-uclass.c b/drivers/ufs/ufs-uclass.c
> index 6a51f337e47..8f120fbbee8 100644
> --- a/drivers/ufs/ufs-uclass.c
> +++ b/drivers/ufs/ufs-uclass.c
> @@ -1533,6 +1533,19 @@ static inline void ufshcd_remove_non_printable(uint8_t *val)
> *val = ' ';
> }
>
> +static inline void ufshcd_str_desc_to_cpu(u8 *desc, u32 size)
> +{
> + u16 *p = (u16 *)&desc[QUERY_DESC_HDR_SIZE];
> + u32 len = desc[QUERY_DESC_LENGTH_OFFSET];
> + u32 i;
> +
> + if (len > size)
> + len = size;
> +
> + for (i = QUERY_DESC_HDR_SIZE; i + 1 < len; i += 2, p++)
> + *p = be16_to_cpu(*p);
I'm not a huge fan of modifying data in place like this. What do people
think about adding an endian parameter to utf16_to_utf8() instead?
> +}
> +
> /**
> * ufshcd_uic_pwr_ctrl - executes UIC commands (which affects the link power
> * state) and waits for it to take effect.
> @@ -1765,6 +1778,8 @@ static int ufshcd_read_string_desc(struct ufs_hba *hba, int desc_index,
> goto out;
> }
>
> + ufshcd_str_desc_to_cpu(buf, size);
> +
> /*
> * the descriptor contains string in UTF16 format
> * we need to convert to utf-8 so it can be displayed
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 2/5] ufs: add RPMB transport over SCSI SECURITY PROTOCOL
2026-07-23 14:38 [PATCH v3 0/5] ufs: rpmb: route OP-TEE RPMB secure storage over UFS Jorge Ramirez-Ortiz via U-Boot
2026-07-23 14:38 ` [PATCH v3 1/5] ufs: decode string descriptors as UTF-16 big-endian Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-23 14:38 ` Jorge Ramirez-Ortiz via U-Boot
2026-07-29 22:27 ` David Lechner
2026-07-23 14:38 ` [PATCH v3 3/5] ufs: derive the per-region RPMB CID and size for OP-TEE Jorge Ramirez-Ortiz via U-Boot
` (2 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Jorge Ramirez-Ortiz via U-Boot @ 2026-07-23 14:38 UTC (permalink / raw)
To: jorge.ramirez, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, dlechner,
u-boot
OP-TEE secure storage (CFG_RPMB_FS) requires RPMB, but on UFS-only
platforms with no eMMC the OP-TEE RPMB supplicant has no way to reach the
device's RPMB Well-Known LUN, which UFS exposes through SCSI SECURITY
PROTOCOL IN/OUT commands rather than an eMMC-style RPMB partition; this
transport provides that missing path so RPMB-backed secure storage works
on UFS-based platforms.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/ufs/Kconfig | 10 +++
drivers/ufs/Makefile | 1 +
drivers/ufs/ufs-rpmb.c | 173 +++++++++++++++++++++++++++++++++++++++
drivers/ufs/ufs-uclass.c | 6 +-
drivers/ufs/ufs.h | 7 ++
include/scsi.h | 2 +
include/ufs.h | 4 +
7 files changed, 200 insertions(+), 3 deletions(-)
create mode 100644 drivers/ufs/ufs-rpmb.c
diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
index 49472933de3..99160a0819b 100644
--- a/drivers/ufs/Kconfig
+++ b/drivers/ufs/Kconfig
@@ -92,4 +92,14 @@ config UFS_TI_J721E
This selects the glue layer driver for Cadence controller
present on TI's J721E devices.
+config SUPPORT_UFS_RPMB
+ bool "Enable UFS RPMB (Replay Protected Memory Block) support"
+ depends on UFS && OPTEE && !SUPPORT_EMMC_RPMB
+ help
+ Route OP-TEE RPMB requests to the UFS RPMB Well-Known LUN using
+ SCSI SECURITY PROTOCOL IN/OUT commands. Required for OP-TEE secure
+ storage (CFG_RPMB_FS) on UFS-based platforms that have no eMMC.
+ The OP-TEE supplicant handles a single RPMB transport, so this is
+ mutually exclusive with the eMMC RPMB supplicant (SUPPORT_EMMC_RPMB).
+
endmenu
diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile
index e7f3c1d30c4..d60440ca119 100644
--- a/drivers/ufs/Makefile
+++ b/drivers/ufs/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_UFS_RENESAS) += ufs-renesas.o
obj-$(CONFIG_UFS_RENESAS_GEN5) += ufs-renesas-rcar-gen5.o
obj-$(CONFIG_UFS_ROCKCHIP) += ufs-rockchip.o
obj-$(CONFIG_UFS_TI_J721E) += ti-j721e-ufs.o
+obj-$(CONFIG_SUPPORT_UFS_RPMB) += ufs-rpmb.o
diff --git a/drivers/ufs/ufs-rpmb.c b/drivers/ufs/ufs-rpmb.c
new file mode 100644
index 00000000000..fb4cd42ad75
--- /dev/null
+++ b/drivers/ufs/ufs-rpmb.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <dm.h>
+#include <log.h>
+#include <malloc.h>
+#include <scsi.h>
+#include <ufs.h>
+#include <asm/cache.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include "ufs.h"
+
+#define RPMB_REQ_KEY 1
+#define RPMB_REQ_WCOUNTER 2
+#define RPMB_REQ_WRITE_DATA 3
+#define RPMB_REQ_READ_DATA 4
+#define RPMB_REQ_STATUS 5
+
+#define RPMB_FRAME_SIZE 512
+#define RPMB_FRAME_REQ_OFFSET 510
+
+#define UFS_RPMB_UA_RETRIES 3
+
+#define SEC_PROTOCOL_UFS 0xEC
+#define UFS_RPMB_SEC_PROTOCOL_ID 0x01
+
+#define GEOMETRY_DESC_RPMB_RW_SIZE 0x17
+
+static u16 rpmb_frame_request(const void *frame)
+{
+ const u8 *p = frame;
+
+ return ((u16)p[RPMB_FRAME_REQ_OFFSET] << 8) |
+ p[RPMB_FRAME_REQ_OFFSET + 1];
+}
+
+static int ufs_rpmb_secprot(struct udevice *scsi_dev, unsigned int region,
+ u8 opcode, void *buf, unsigned int nframes,
+ enum dma_data_direction dir)
+{
+ u16 spsp = (region << 8) | UFS_RPMB_SEC_PROTOCOL_ID;
+ u32 len = nframes * RPMB_FRAME_SIZE;
+ struct scsi_cmd pccb;
+ void *dma_buf = buf;
+ void *bounce = NULL;
+ int retries;
+ int ret = 0;
+
+ if (!IS_ALIGNED((uintptr_t)buf, ARCH_DMA_MINALIGN)) {
+ bounce = memalign(ARCH_DMA_MINALIGN,
+ ALIGN(len, ARCH_DMA_MINALIGN));
+ if (!bounce)
+ return -ENOMEM;
+ dma_buf = bounce;
+ if (dir == DMA_TO_DEVICE)
+ memcpy(bounce, buf, len);
+ }
+
+ memset(&pccb, 0, sizeof(pccb));
+ pccb.lun = UFS_UPIU_RPMB_WLUN;
+ pccb.cmd[0] = opcode;
+ pccb.cmd[1] = SEC_PROTOCOL_UFS;
+ pccb.cmd[2] = (spsp >> 8) & 0xff;
+ pccb.cmd[3] = spsp & 0xff;
+ pccb.cmd[4] = 0;
+ pccb.cmd[5] = 0;
+ pccb.cmd[6] = (len >> 24) & 0xff;
+ pccb.cmd[7] = (len >> 16) & 0xff;
+ pccb.cmd[8] = (len >> 8) & 0xff;
+ pccb.cmd[9] = len & 0xff;
+ pccb.cmd[10] = 0;
+ pccb.cmd[11] = 0;
+ pccb.cmdlen = 12;
+ pccb.pdata = dma_buf;
+ pccb.datalen = len;
+ pccb.dma_dir = dir;
+
+ for (retries = UFS_RPMB_UA_RETRIES; retries > 0; retries--) {
+ ret = scsi_exec(scsi_dev, &pccb);
+ if (!ret)
+ break;
+ }
+
+ if (bounce) {
+ if (!ret && dir == DMA_FROM_DEVICE)
+ memcpy(buf, bounce, len);
+
+ free(bounce);
+ }
+
+ return ret;
+}
+
+static int ufs_rpmb_send(struct udevice *scsi_dev, unsigned int region,
+ void *frames, unsigned int nframes)
+{
+ return ufs_rpmb_secprot(scsi_dev, region, SCSI_SECURITY_PROTOCOL_OUT,
+ frames, nframes, DMA_TO_DEVICE);
+}
+
+static int ufs_rpmb_recv(struct udevice *scsi_dev, unsigned int region,
+ void *frames, unsigned int nframes)
+{
+ return ufs_rpmb_secprot(scsi_dev, region, SCSI_SECURITY_PROTOCOL_IN,
+ frames, nframes, DMA_FROM_DEVICE);
+}
+
+int ufs_rpmb_route_frames(struct udevice *scsi_dev, unsigned int region,
+ void *req, unsigned long reqlen, void *rsp,
+ unsigned long rsplen)
+{
+ unsigned int n_req = reqlen / RPMB_FRAME_SIZE;
+ unsigned int n_rsp = rsplen / RPMB_FRAME_SIZE;
+ u16 request;
+ int ret;
+
+ if (!scsi_dev || reqlen % RPMB_FRAME_SIZE ||
+ rsplen % RPMB_FRAME_SIZE || !n_req)
+ return -EINVAL;
+
+ request = rpmb_frame_request(req);
+
+ switch (request) {
+ case RPMB_REQ_KEY:
+ case RPMB_REQ_WRITE_DATA: {
+ u8 status_frame[RPMB_FRAME_SIZE];
+
+ ret = ufs_rpmb_send(scsi_dev, region, req, n_req);
+ if (ret)
+ return ret;
+
+ memset(status_frame, 0, sizeof(status_frame));
+ status_frame[RPMB_FRAME_REQ_OFFSET] = RPMB_REQ_STATUS >> 8;
+ status_frame[RPMB_FRAME_REQ_OFFSET + 1] = RPMB_REQ_STATUS & 0xff;
+ ret = ufs_rpmb_send(scsi_dev, region, status_frame, 1);
+ if (ret)
+ return ret;
+
+ if (n_rsp < 1)
+ return -EINVAL;
+
+ return ufs_rpmb_recv(scsi_dev, region, rsp, 1);
+ }
+ case RPMB_REQ_WCOUNTER:
+ case RPMB_REQ_READ_DATA:
+ ret = ufs_rpmb_send(scsi_dev, region, req, 1);
+ if (ret)
+ return ret;
+
+ if (n_rsp < 1)
+ return -EINVAL;
+
+ return ufs_rpmb_recv(scsi_dev, region, rsp, n_rsp);
+ default:
+ debug("ufs-rpmb: unsupported request 0x%x\n", request);
+ return -EINVAL;
+ }
+}
+
+static int ufs_rpmb_read_geometry(struct udevice *scsi_dev, u8 *rpmb_rw_size)
+{
+ struct ufs_hba *hba = dev_get_uclass_priv(scsi_dev->parent);
+ u8 desc[QUERY_DESC_GEOMETRY_DEF_SIZE];
+ int ret;
+
+ ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_GEOMETRY, 0, 0,
+ desc, sizeof(desc));
+ if (ret)
+ return ret;
+
+ *rpmb_rw_size = desc[GEOMETRY_DESC_RPMB_RW_SIZE];
+
+ return 0;
+}
diff --git a/drivers/ufs/ufs-uclass.c b/drivers/ufs/ufs-uclass.c
index 8f120fbbee8..e320a6dcfbc 100644
--- a/drivers/ufs/ufs-uclass.c
+++ b/drivers/ufs/ufs-uclass.c
@@ -1457,9 +1457,9 @@ static int ufshcd_map_desc_id_to_length(struct ufs_hba *hba, enum desc_idn desc_
* ufshcd_read_desc_param - read the specified descriptor parameter
*
*/
-static int ufshcd_read_desc_param(struct ufs_hba *hba, enum desc_idn desc_id,
- int desc_index, u8 param_offset,
- u8 *param_read_buf, u8 param_size)
+int ufshcd_read_desc_param(struct ufs_hba *hba, enum desc_idn desc_id,
+ int desc_index, u8 param_offset,
+ u8 *param_read_buf, u8 param_size)
{
int ret;
u8 *desc_buf;
diff --git a/drivers/ufs/ufs.h b/drivers/ufs/ufs.h
index 0f6c93fbce7..9f55735602b 100644
--- a/drivers/ufs/ufs.h
+++ b/drivers/ufs/ufs.h
@@ -10,6 +10,9 @@
struct udevice;
#define UFS_CDB_SIZE 16
+
+#define UFS_UPIU_RPMB_WLUN 0xC4
+
#define UPIU_TRANSACTION_UIC_CMD 0x1F
#define UIC_CMD_SIZE (sizeof(u32) * 4)
#define RESPONSE_UPIU_SENSE_DATA_LENGTH 18
@@ -809,4 +812,8 @@ static inline void ufshcd_rmwl(struct ufs_hba *hba, u32 mask, u32 val, u32 reg)
int ufshcd_probe(struct udevice *dev, struct ufs_hba_ops *hba_ops);
+int ufshcd_read_desc_param(struct ufs_hba *hba, enum desc_idn desc_id,
+ int desc_index, u8 param_offset, u8 *param_read_buf,
+ u8 param_size);
+
#endif
diff --git a/include/scsi.h b/include/scsi.h
index 2520a8b8fe6..cc79b6dd4c4 100644
--- a/include/scsi.h
+++ b/include/scsi.h
@@ -183,6 +183,8 @@ struct scsi_cmd {
#define SCSI_WRITE_LONG 0x3F /* Write Long (O) */
#define SCSI_WRITE_SAME 0x41 /* Write Same (O) */
#define SCSI_UNMAP 0x42 /* Write 10-Byte (MANDATORY) */
+#define SCSI_SECURITY_PROTOCOL_IN 0xA2 /* Security Protocol In (O) */
+#define SCSI_SECURITY_PROTOCOL_OUT 0xB5 /* Security Protocol Out (O) */
/**
* enum scsi_cmd_phase - current phase of the SCSI protocol
diff --git a/include/ufs.h b/include/ufs.h
index f6e27d90e43..1bec3ce73a4 100644
--- a/include/ufs.h
+++ b/include/ufs.h
@@ -20,4 +20,8 @@ int ufs_probe(void);
*/
int ufs_probe_dev(int index);
+int ufs_rpmb_route_frames(struct udevice *scsi_dev, unsigned int region,
+ void *req, unsigned long reqlen, void *rsp,
+ unsigned long rsplen);
+
#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v3 2/5] ufs: add RPMB transport over SCSI SECURITY PROTOCOL
2026-07-23 14:38 ` [PATCH v3 2/5] ufs: add RPMB transport over SCSI SECURITY PROTOCOL Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-29 22:27 ` David Lechner
0 siblings, 0 replies; 9+ messages in thread
From: David Lechner @ 2026-07-29 22:27 UTC (permalink / raw)
To: Jorge Ramirez-Ortiz, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, u-boot
On 7/23/26 9:38 AM, Jorge Ramirez-Ortiz wrote:
> OP-TEE secure storage (CFG_RPMB_FS) requires RPMB, but on UFS-only
> platforms with no eMMC the OP-TEE RPMB supplicant has no way to reach the
> device's RPMB Well-Known LUN, which UFS exposes through SCSI SECURITY
> PROTOCOL IN/OUT commands rather than an eMMC-style RPMB partition; this
> transport provides that missing path so RPMB-backed secure storage works
> on UFS-based platforms.
>
> Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
> ---
> drivers/ufs/Kconfig | 10 +++
> drivers/ufs/Makefile | 1 +
> drivers/ufs/ufs-rpmb.c | 173 +++++++++++++++++++++++++++++++++++++++
> drivers/ufs/ufs-uclass.c | 6 +-
> drivers/ufs/ufs.h | 7 ++
> include/scsi.h | 2 +
> include/ufs.h | 4 +
> 7 files changed, 200 insertions(+), 3 deletions(-)
> create mode 100644 drivers/ufs/ufs-rpmb.c
>
> diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
> index 49472933de3..99160a0819b 100644
> --- a/drivers/ufs/Kconfig
> +++ b/drivers/ufs/Kconfig
> @@ -92,4 +92,14 @@ config UFS_TI_J721E
> This selects the glue layer driver for Cadence controller
> present on TI's J721E devices.
>
> +config SUPPORT_UFS_RPMB
> + bool "Enable UFS RPMB (Replay Protected Memory Block) support"
> + depends on UFS && OPTEE && !SUPPORT_EMMC_RPMB
> + help
> + Route OP-TEE RPMB requests to the UFS RPMB Well-Known LUN using
> + SCSI SECURITY PROTOCOL IN/OUT commands. Required for OP-TEE secure
> + storage (CFG_RPMB_FS) on UFS-based platforms that have no eMMC.
> + The OP-TEE supplicant handles a single RPMB transport, so this is
> + mutually exclusive with the eMMC RPMB supplicant (SUPPORT_EMMC_RPMB).
> +
> endmenu
> diff --git a/drivers/ufs/Makefile b/drivers/ufs/Makefile
> index e7f3c1d30c4..d60440ca119 100644
> --- a/drivers/ufs/Makefile
> +++ b/drivers/ufs/Makefile
> @@ -13,3 +13,4 @@ obj-$(CONFIG_UFS_RENESAS) += ufs-renesas.o
> obj-$(CONFIG_UFS_RENESAS_GEN5) += ufs-renesas-rcar-gen5.o
> obj-$(CONFIG_UFS_ROCKCHIP) += ufs-rockchip.o
> obj-$(CONFIG_UFS_TI_J721E) += ti-j721e-ufs.o
> +obj-$(CONFIG_SUPPORT_UFS_RPMB) += ufs-rpmb.o
> diff --git a/drivers/ufs/ufs-rpmb.c b/drivers/ufs/ufs-rpmb.c
> new file mode 100644
> index 00000000000..fb4cd42ad75
> --- /dev/null
> +++ b/drivers/ufs/ufs-rpmb.c
> @@ -0,0 +1,173 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +#include <dm.h>
> +#include <log.h>
> +#include <malloc.h>
> +#include <scsi.h>
> +#include <ufs.h>
> +#include <asm/cache.h>
> +#include <linux/errno.h>
> +#include <linux/string.h>
> +#include "ufs.h"
> +
> +#define RPMB_REQ_KEY 1
> +#define RPMB_REQ_WCOUNTER 2
> +#define RPMB_REQ_WRITE_DATA 3
> +#define RPMB_REQ_READ_DATA 4
> +#define RPMB_REQ_STATUS 5
> +
> +#define RPMB_FRAME_SIZE 512
> +#define RPMB_FRAME_REQ_OFFSET 510
> +
> +#define UFS_RPMB_UA_RETRIES 3
> +
> +#define SEC_PROTOCOL_UFS 0xEC
> +#define UFS_RPMB_SEC_PROTOCOL_ID 0x01
> +
> +#define GEOMETRY_DESC_RPMB_RW_SIZE 0x17
> +
> +static u16 rpmb_frame_request(const void *frame)
> +{
> + const u8 *p = frame;
> +
> + return ((u16)p[RPMB_FRAME_REQ_OFFSET] << 8) |
> + p[RPMB_FRAME_REQ_OFFSET + 1];
> +}
I would drop the function and just use get_unaligned_be16()
directly.
> +
> +static int ufs_rpmb_secprot(struct udevice *scsi_dev, unsigned int region,
> + u8 opcode, void *buf, unsigned int nframes,
> + enum dma_data_direction dir)
> +{
> + u16 spsp = (region << 8) | UFS_RPMB_SEC_PROTOCOL_ID;
> + u32 len = nframes * RPMB_FRAME_SIZE;
> + struct scsi_cmd pccb;
> + void *dma_buf = buf;
> + void *bounce = NULL;
> + int retries;
> + int ret = 0;
> +
> + if (!IS_ALIGNED((uintptr_t)buf, ARCH_DMA_MINALIGN)) {
> + bounce = memalign(ARCH_DMA_MINALIGN,
> + ALIGN(len, ARCH_DMA_MINALIGN));
> + if (!bounce)
> + return -ENOMEM;
> + dma_buf = bounce;
> + if (dir == DMA_TO_DEVICE)
> + memcpy(bounce, buf, len);
> + }
> +
> + memset(&pccb, 0, sizeof(pccb));
> + pccb.lun = UFS_UPIU_RPMB_WLUN;
> + pccb.cmd[0] = opcode;
> + pccb.cmd[1] = SEC_PROTOCOL_UFS;
> + pccb.cmd[2] = (spsp >> 8) & 0xff;
> + pccb.cmd[3] = spsp & 0xff;
How about using put_unaligned_be16()?
> + pccb.cmd[4] = 0;
> + pccb.cmd[5] = 0;
> + pccb.cmd[6] = (len >> 24) & 0xff;
> + pccb.cmd[7] = (len >> 16) & 0xff;
> + pccb.cmd[8] = (len >> 8) & 0xff;
> + pccb.cmd[9] = len & 0xff;
And put_unaligned_be32().
> + pccb.cmd[10] = 0;
> + pccb.cmd[11] = 0;
> + pccb.cmdlen = 12;
> + pccb.pdata = dma_buf;
> + pccb.datalen = len;
> + pccb.dma_dir = dir;
> +
> + for (retries = UFS_RPMB_UA_RETRIES; retries > 0; retries--) {
> + ret = scsi_exec(scsi_dev, &pccb);
> + if (!ret)
> + break;
> + }
> +
> + if (bounce) {
> + if (!ret && dir == DMA_FROM_DEVICE)
> + memcpy(buf, bounce, len);
> +
> + free(bounce);
> + }
> +
> + return ret;
> +}
> +
> +static int ufs_rpmb_send(struct udevice *scsi_dev, unsigned int region,
> + void *frames, unsigned int nframes)
> +{
> + return ufs_rpmb_secprot(scsi_dev, region, SCSI_SECURITY_PROTOCOL_OUT,
> + frames, nframes, DMA_TO_DEVICE);
> +}
> +
> +static int ufs_rpmb_recv(struct udevice *scsi_dev, unsigned int region,
> + void *frames, unsigned int nframes)
> +{
> + return ufs_rpmb_secprot(scsi_dev, region, SCSI_SECURITY_PROTOCOL_IN,
> + frames, nframes, DMA_FROM_DEVICE);
> +}
> +
> +int ufs_rpmb_route_frames(struct udevice *scsi_dev, unsigned int region,
> + void *req, unsigned long reqlen, void *rsp,
> + unsigned long rsplen)
> +{
> + unsigned int n_req = reqlen / RPMB_FRAME_SIZE;
> + unsigned int n_rsp = rsplen / RPMB_FRAME_SIZE;
> + u16 request;
> + int ret;
> +
> + if (!scsi_dev || reqlen % RPMB_FRAME_SIZE ||
> + rsplen % RPMB_FRAME_SIZE || !n_req)
> + return -EINVAL;
> +
> + request = rpmb_frame_request(req);
> +
> + switch (request) {
> + case RPMB_REQ_KEY:
> + case RPMB_REQ_WRITE_DATA: {
> + u8 status_frame[RPMB_FRAME_SIZE];
> +
> + ret = ufs_rpmb_send(scsi_dev, region, req, n_req);
> + if (ret)
> + return ret;
> +
> + memset(status_frame, 0, sizeof(status_frame));
> + status_frame[RPMB_FRAME_REQ_OFFSET] = RPMB_REQ_STATUS >> 8;
> + status_frame[RPMB_FRAME_REQ_OFFSET + 1] = RPMB_REQ_STATUS & 0xff;
put_unaligned_be16()
> + ret = ufs_rpmb_send(scsi_dev, region, status_frame, 1);
> + if (ret)
> + return ret;
> +
> + if (n_rsp < 1)
> + return -EINVAL;
> +
> + return ufs_rpmb_recv(scsi_dev, region, rsp, 1);
> + }
> + case RPMB_REQ_WCOUNTER:
> + case RPMB_REQ_READ_DATA:
> + ret = ufs_rpmb_send(scsi_dev, region, req, 1);
> + if (ret)
> + return ret;
> +
> + if (n_rsp < 1)
> + return -EINVAL;
> +
> + return ufs_rpmb_recv(scsi_dev, region, rsp, n_rsp);
> + default:
> + debug("ufs-rpmb: unsupported request 0x%x\n", request);
> + return -EINVAL;
> + }
> +}
> +
> +static int ufs_rpmb_read_geometry(struct udevice *scsi_dev, u8 *rpmb_rw_size)
> +{
> + struct ufs_hba *hba = dev_get_uclass_priv(scsi_dev->parent);
> + u8 desc[QUERY_DESC_GEOMETRY_DEF_SIZE];
> + int ret;
> +
> + ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_GEOMETRY, 0, 0,
> + desc, sizeof(desc));
> + if (ret)
> + return ret;
> +
> + *rpmb_rw_size = desc[GEOMETRY_DESC_RPMB_RW_SIZE];
> +
> + return 0;
> +}
This function should be defered to the next patch were it is used.
Otherwise we could get compilers complaining during git bisect about
unused static function.
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 3/5] ufs: derive the per-region RPMB CID and size for OP-TEE
2026-07-23 14:38 [PATCH v3 0/5] ufs: rpmb: route OP-TEE RPMB secure storage over UFS Jorge Ramirez-Ortiz via U-Boot
2026-07-23 14:38 ` [PATCH v3 1/5] ufs: decode string descriptors as UTF-16 big-endian Jorge Ramirez-Ortiz via U-Boot
2026-07-23 14:38 ` [PATCH v3 2/5] ufs: add RPMB transport over SCSI SECURITY PROTOCOL Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-23 14:38 ` Jorge Ramirez-Ortiz via U-Boot
2026-07-29 22:40 ` David Lechner
2026-07-23 14:38 ` [PATCH v3 4/5] optee: rename rpmb.c to rpmb_emmc.c Jorge Ramirez-Ortiz via U-Boot
2026-07-23 14:38 ` [PATCH v3 5/5] optee: implement the RPMB subsystem interface for UFS Jorge Ramirez-Ortiz via U-Boot
4 siblings, 1 reply; 9+ messages in thread
From: Jorge Ramirez-Ortiz via U-Boot @ 2026-07-23 14:38 UTC (permalink / raw)
To: jorge.ramirez, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, dlechner,
u-boot
OP-TEE computes its RPMB authentication key from a device identifier, so
U-Boot must hand it the exact same identifier the Linux kernel uses;
otherwise the derived key differs and OP-TEE cannot access RPMB secure
storage provisioned under Linux, and vice versa. The OP-TEE RPMB probe
also needs each region's size and reliable-write count to discover which
regions exist and how they may be written. Provide both so OP-TEE secure
storage stays interoperable across U-Boot and Linux on the same device.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/ufs/Kconfig | 7 +-
drivers/ufs/ufs-rpmb.c | 148 +++++++++++++++++++++++++++++++++++++++++
include/ufs.h | 6 ++
3 files changed, 159 insertions(+), 2 deletions(-)
diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
index 99160a0819b..d39fcda42dc 100644
--- a/drivers/ufs/Kconfig
+++ b/drivers/ufs/Kconfig
@@ -95,11 +95,14 @@ config UFS_TI_J721E
config SUPPORT_UFS_RPMB
bool "Enable UFS RPMB (Replay Protected Memory Block) support"
depends on UFS && OPTEE && !SUPPORT_EMMC_RPMB
+ select BLAKE2
help
Route OP-TEE RPMB requests to the UFS RPMB Well-Known LUN using
SCSI SECURITY PROTOCOL IN/OUT commands. Required for OP-TEE secure
storage (CFG_RPMB_FS) on UFS-based platforms that have no eMMC.
- The OP-TEE supplicant handles a single RPMB transport, so this is
- mutually exclusive with the eMMC RPMB supplicant (SUPPORT_EMMC_RPMB).
+ BLAKE2 is used to derive the fixed-length RPMB CID that matches the
+ Linux kernel UFS device_id ABI. The OP-TEE supplicant handles a
+ single RPMB transport, so this is mutually exclusive with the eMMC
+ RPMB supplicant (SUPPORT_EMMC_RPMB).
endmenu
diff --git a/drivers/ufs/ufs-rpmb.c b/drivers/ufs/ufs-rpmb.c
index fb4cd42ad75..ed5ecc0a5e6 100644
--- a/drivers/ufs/ufs-rpmb.c
+++ b/drivers/ufs/ufs-rpmb.c
@@ -1,10 +1,14 @@
// SPDX-License-Identifier: GPL-2.0+
#include <dm.h>
+#include <hexdump.h>
#include <log.h>
#include <malloc.h>
#include <scsi.h>
#include <ufs.h>
+#include <vsprintf.h>
+#include <u-boot/blake2.h>
#include <asm/cache.h>
+#include <asm/unaligned.h>
#include <linux/errno.h>
#include <linux/string.h>
#include "ufs.h"
@@ -25,6 +29,15 @@
#define GEOMETRY_DESC_RPMB_RW_SIZE 0x17
+#define RPMB_UNIT_DESC_LOGICAL_BLK_SIZE 0x0A
+#define RPMB_UNIT_DESC_LOGICAL_BLK_COUNT 0x0B
+#define RPMB_UNIT_DESC_REGION0_SIZE 0x13
+#define RPMB_UNIT_DESC_REGION1_SIZE 0x14
+#define RPMB_UNIT_DESC_REGION2_SIZE 0x15
+#define RPMB_UNIT_DESC_REGION3_SIZE 0x16
+#define UFS_RPMB_LEGACY_SPEC_VER 0x0220
+#define UFS_RPMB_REGION_UNIT_SHIFT 17
+
static u16 rpmb_frame_request(const void *frame)
{
const u8 *p = frame;
@@ -171,3 +184,138 @@ static int ufs_rpmb_read_geometry(struct udevice *scsi_dev, u8 *rpmb_rw_size)
return 0;
}
+
+static int ufs_rpmb_read_region_sizes(struct ufs_hba *hba, u16 spec_ver,
+ u8 sizes[UFS_RPMB_NUM_REGIONS])
+{
+ u8 unit[QUERY_DESC_UNIT_DEF_SIZE] = { };
+ int ret;
+
+ ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_UNIT,
+ UFS_UPIU_RPMB_WLUN, 0, unit, sizeof(unit));
+ if (ret)
+ return ret;
+
+ memset(sizes, 0, UFS_RPMB_NUM_REGIONS);
+
+ if (spec_ver > UFS_RPMB_LEGACY_SPEC_VER) {
+ sizes[0] = unit[RPMB_UNIT_DESC_REGION0_SIZE];
+ sizes[1] = unit[RPMB_UNIT_DESC_REGION1_SIZE];
+ sizes[2] = unit[RPMB_UNIT_DESC_REGION2_SIZE];
+ sizes[3] = unit[RPMB_UNIT_DESC_REGION3_SIZE];
+ } else {
+ u64 region = (get_unaligned_be64(unit +
+ RPMB_UNIT_DESC_LOGICAL_BLK_COUNT)
+ << unit[RPMB_UNIT_DESC_LOGICAL_BLK_SIZE])
+ >> UFS_RPMB_REGION_UNIT_SHIFT;
+
+ sizes[0] = region > 0xff ? 0xff : region;
+ }
+
+ return 0;
+}
+
+static void ufs_rpmb_string_to_ascii(const u8 *raw, char *out, size_t outsz)
+{
+ int nchars = ((int)raw[QUERY_DESC_LENGTH_OFFSET] - QUERY_DESC_HDR_SIZE);
+ int i, n = 0;
+
+ nchars = nchars > 0 ? nchars / 2 : 0;
+ for (i = 0; i < nchars && n < (int)outsz - 1; i++) {
+ u16 c = get_unaligned_be16(raw + QUERY_DESC_HDR_SIZE + i * 2);
+
+ out[n++] = (c >= 0x20 && c <= 0x7e) ? (char)c : ' ';
+ }
+ out[n] = '\0';
+}
+
+static int ufs_rpmb_build_cid(struct ufs_hba *hba, const u8 *dev_desc,
+ unsigned int region, u8 *cid)
+{
+ char serial_hex[QUERY_DESC_MAX_SIZE * 2 + 1];
+ u16 manf_id, spec_ver, dev_ver, manf_date;
+ u8 serial[QUERY_DESC_MAX_SIZE] = { };
+ char idstr[QUERY_DESC_MAX_SIZE * 3];
+ char model[MAX_MODEL_LEN * 8];
+ u8 raw[QUERY_DESC_MAX_SIZE];
+ u8 blen;
+ int ret;
+
+ manf_date = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_MANF_DATE);
+ spec_ver = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_SPEC_VER);
+ manf_id = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_MANF_ID);
+ dev_ver = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_DEV_VER);
+
+ ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_STRING,
+ dev_desc[DEVICE_DESC_PARAM_PRDCT_NAME], 0,
+ raw, sizeof(raw));
+ if (ret)
+ return ret;
+
+ ufs_rpmb_string_to_ascii(raw, model, sizeof(model));
+
+ ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_STRING,
+ dev_desc[DEVICE_DESC_PARAM_SN], 0,
+ raw, sizeof(raw));
+ if (ret)
+ return ret;
+
+ blen = raw[QUERY_DESC_LENGTH_OFFSET];
+ if (blen < QUERY_DESC_HDR_SIZE)
+ return -EINVAL;
+
+ memcpy(serial, raw + QUERY_DESC_HDR_SIZE, blen - QUERY_DESC_HDR_SIZE);
+ bin2hex(serial_hex, serial, blen);
+ serial_hex[blen * 2] = '\0';
+
+ snprintf(idstr, sizeof(idstr), "%04X-%04X-%s-%s-%04X-%04X-R%u",
+ manf_id, spec_ver, model, serial_hex, dev_ver, manf_date,
+ region);
+
+ if (blake2b(cid, UFS_RPMB_CID_SIZE, idstr, strlen(idstr), NULL, 0))
+ return -EIO;
+
+ return 0;
+}
+
+int ufs_rpmb_get_region_info(struct udevice *scsi_dev, unsigned int region,
+ u8 *size_mult, u8 *rel_wr, u8 *cid)
+{
+ struct ufs_hba *hba = dev_get_uclass_priv(scsi_dev->parent);
+ u8 dev_desc[QUERY_DESC_DEVICE_DEF_SIZE] = { };
+ u8 sizes[UFS_RPMB_NUM_REGIONS];
+ u16 spec_ver;
+ int ret;
+
+ if (region >= UFS_RPMB_NUM_REGIONS)
+ return 0;
+
+ ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_DEVICE, 0, 0,
+ dev_desc, sizeof(dev_desc));
+ if (ret)
+ return ret;
+
+ spec_ver = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_SPEC_VER);
+
+ ret = ufs_rpmb_read_region_sizes(hba, spec_ver, sizes);
+ if (ret)
+ return ret;
+
+ if (!sizes[region])
+ return 0;
+
+ ret = ufs_rpmb_read_geometry(scsi_dev, rel_wr);
+ if (ret)
+ return ret;
+
+ if (!*rel_wr)
+ *rel_wr = 1;
+
+ ret = ufs_rpmb_build_cid(hba, dev_desc, region, cid);
+ if (ret)
+ return ret;
+
+ *size_mult = sizes[region];
+
+ return 1;
+}
diff --git a/include/ufs.h b/include/ufs.h
index 1bec3ce73a4..a8b7d370f78 100644
--- a/include/ufs.h
+++ b/include/ufs.h
@@ -20,8 +20,14 @@ int ufs_probe(void);
*/
int ufs_probe_dev(int index);
+#define UFS_RPMB_CID_SIZE 16
+#define UFS_RPMB_NUM_REGIONS 4
+
int ufs_rpmb_route_frames(struct udevice *scsi_dev, unsigned int region,
void *req, unsigned long reqlen, void *rsp,
unsigned long rsplen);
+int ufs_rpmb_get_region_info(struct udevice *scsi_dev, unsigned int region,
+ u8 *size_mult, u8 *rel_wr, u8 *cid);
+
#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH v3 3/5] ufs: derive the per-region RPMB CID and size for OP-TEE
2026-07-23 14:38 ` [PATCH v3 3/5] ufs: derive the per-region RPMB CID and size for OP-TEE Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-29 22:40 ` David Lechner
0 siblings, 0 replies; 9+ messages in thread
From: David Lechner @ 2026-07-29 22:40 UTC (permalink / raw)
To: Jorge Ramirez-Ortiz, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, u-boot
On 7/23/26 9:38 AM, Jorge Ramirez-Ortiz wrote:
> OP-TEE computes its RPMB authentication key from a device identifier, so
> U-Boot must hand it the exact same identifier the Linux kernel uses;
> otherwise the derived key differs and OP-TEE cannot access RPMB secure
> storage provisioned under Linux, and vice versa. The OP-TEE RPMB probe
> also needs each region's size and reliable-write count to discover which
> regions exist and how they may be written. Provide both so OP-TEE secure
> storage stays interoperable across U-Boot and Linux on the same device.
>
> Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
> ---
> drivers/ufs/Kconfig | 7 +-
> drivers/ufs/ufs-rpmb.c | 148 +++++++++++++++++++++++++++++++++++++++++
> include/ufs.h | 6 ++
> 3 files changed, 159 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
> index 99160a0819b..d39fcda42dc 100644
> --- a/drivers/ufs/Kconfig
> +++ b/drivers/ufs/Kconfig
> @@ -95,11 +95,14 @@ config UFS_TI_J721E
> config SUPPORT_UFS_RPMB
> bool "Enable UFS RPMB (Replay Protected Memory Block) support"
> depends on UFS && OPTEE && !SUPPORT_EMMC_RPMB
> + select BLAKE2
> help
> Route OP-TEE RPMB requests to the UFS RPMB Well-Known LUN using
> SCSI SECURITY PROTOCOL IN/OUT commands. Required for OP-TEE secure
> storage (CFG_RPMB_FS) on UFS-based platforms that have no eMMC.
> - The OP-TEE supplicant handles a single RPMB transport, so this is
> - mutually exclusive with the eMMC RPMB supplicant (SUPPORT_EMMC_RPMB).
> + BLAKE2 is used to derive the fixed-length RPMB CID that matches the
> + Linux kernel UFS device_id ABI. The OP-TEE supplicant handles a
> + single RPMB transport, so this is mutually exclusive with the eMMC
> + RPMB supplicant (SUPPORT_EMMC_RPMB).
>
> endmenu
> diff --git a/drivers/ufs/ufs-rpmb.c b/drivers/ufs/ufs-rpmb.c
> index fb4cd42ad75..ed5ecc0a5e6 100644
> --- a/drivers/ufs/ufs-rpmb.c
> +++ b/drivers/ufs/ufs-rpmb.c
> @@ -1,10 +1,14 @@
> // SPDX-License-Identifier: GPL-2.0+
> #include <dm.h>
> +#include <hexdump.h>
> #include <log.h>
> #include <malloc.h>
> #include <scsi.h>
> #include <ufs.h>
> +#include <vsprintf.h>
> +#include <u-boot/blake2.h>
These are mostly in alphabetical order, so would make sense to put
<u-boot/... after <linux/...
> #include <asm/cache.h>
> +#include <asm/unaligned.h>
> #include <linux/errno.h>
> #include <linux/string.h>
> #include "ufs.h"
> @@ -25,6 +29,15 @@
>
> #define GEOMETRY_DESC_RPMB_RW_SIZE 0x17
>
> +#define RPMB_UNIT_DESC_LOGICAL_BLK_SIZE 0x0A
> +#define RPMB_UNIT_DESC_LOGICAL_BLK_COUNT 0x0B
> +#define RPMB_UNIT_DESC_REGION0_SIZE 0x13
> +#define RPMB_UNIT_DESC_REGION1_SIZE 0x14
> +#define RPMB_UNIT_DESC_REGION2_SIZE 0x15
> +#define RPMB_UNIT_DESC_REGION3_SIZE 0x16
> +#define UFS_RPMB_LEGACY_SPEC_VER 0x0220
> +#define UFS_RPMB_REGION_UNIT_SHIFT 17
> +
> static u16 rpmb_frame_request(const void *frame)
> {
> const u8 *p = frame;
> @@ -171,3 +184,138 @@ static int ufs_rpmb_read_geometry(struct udevice *scsi_dev, u8 *rpmb_rw_size)
>
> return 0;
> }
> +
> +static int ufs_rpmb_read_region_sizes(struct ufs_hba *hba, u16 spec_ver,
> + u8 sizes[UFS_RPMB_NUM_REGIONS])
> +{
> + u8 unit[QUERY_DESC_UNIT_DEF_SIZE] = { };
> + int ret;
> +
> + ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_UNIT,
> + UFS_UPIU_RPMB_WLUN, 0, unit, sizeof(unit));
> + if (ret)
> + return ret;
> +
> + memset(sizes, 0, UFS_RPMB_NUM_REGIONS);
> +
> + if (spec_ver > UFS_RPMB_LEGACY_SPEC_VER) {
> + sizes[0] = unit[RPMB_UNIT_DESC_REGION0_SIZE];
> + sizes[1] = unit[RPMB_UNIT_DESC_REGION1_SIZE];
> + sizes[2] = unit[RPMB_UNIT_DESC_REGION2_SIZE];
> + sizes[3] = unit[RPMB_UNIT_DESC_REGION3_SIZE];
> + } else {
> + u64 region = (get_unaligned_be64(unit +
> + RPMB_UNIT_DESC_LOGICAL_BLK_COUNT)
> + << unit[RPMB_UNIT_DESC_LOGICAL_BLK_SIZE])
> + >> UFS_RPMB_REGION_UNIT_SHIFT;
A bit hard to read. Maybe like this?
u64 region;
region = get_unaligned_be64(unit + RPMB_UNIT_DESC_LOGICAL_BLK_COUNT);
region <<= unit[RPMB_UNIT_DESC_LOGICAL_BLK_SIZE];
region >>= UFS_RPMB_REGION_UNIT_SHIFT;
Also probably worth checking value of unit[RPMB_UNIT_DESC_LOGICAL_BLK_SIZE] before
using it if comes from a potentially untrusted source.
> +
> + sizes[0] = region > 0xff ? 0xff : region;
> + }
> +
> + return 0;
> +}
> +
> +static void ufs_rpmb_string_to_ascii(const u8 *raw, char *out, size_t outsz)
> +{
> + int nchars = ((int)raw[QUERY_DESC_LENGTH_OFFSET] - QUERY_DESC_HDR_SIZE);
> + int i, n = 0;
> +
> + nchars = nchars > 0 ? nchars / 2 : 0;
> + for (i = 0; i < nchars && n < (int)outsz - 1; i++) {
> + u16 c = get_unaligned_be16(raw + QUERY_DESC_HDR_SIZE + i * 2);
> +
> + out[n++] = (c >= 0x20 && c <= 0x7e) ? (char)c : ' ';
> + }
> + out[n] = '\0';
> +}
This looks similar to ufshcd_remove_non_printable(). Should we try
to share code more?
> +
> +static int ufs_rpmb_build_cid(struct ufs_hba *hba, const u8 *dev_desc,
> + unsigned int region, u8 *cid)
> +{
> + char serial_hex[QUERY_DESC_MAX_SIZE * 2 + 1];
> + u16 manf_id, spec_ver, dev_ver, manf_date;
> + u8 serial[QUERY_DESC_MAX_SIZE] = { };
> + char idstr[QUERY_DESC_MAX_SIZE * 3];
> + char model[MAX_MODEL_LEN * 8];
> + u8 raw[QUERY_DESC_MAX_SIZE];
> + u8 blen;
> + int ret;
> +
> + manf_date = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_MANF_DATE);
> + spec_ver = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_SPEC_VER);
> + manf_id = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_MANF_ID);
> + dev_ver = get_unaligned_be16(dev_desc + DEVICE_DESC_PARAM_DEV_VER);
> +
> + ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_STRING,
> + dev_desc[DEVICE_DESC_PARAM_PRDCT_NAME], 0,
> + raw, sizeof(raw));
> + if (ret)
> + return ret;
> +
> + ufs_rpmb_string_to_ascii(raw, model, sizeof(model));
> +
> + ret = ufshcd_read_desc_param(hba, QUERY_DESC_IDN_STRING,
> + dev_desc[DEVICE_DESC_PARAM_SN], 0,
> + raw, sizeof(raw));
> + if (ret)
> + return ret;
> +
> + blen = raw[QUERY_DESC_LENGTH_OFFSET];
> + if (blen < QUERY_DESC_HDR_SIZE)
> + return -EINVAL;
> +
> + memcpy(serial, raw + QUERY_DESC_HDR_SIZE, blen - QUERY_DESC_HDR_SIZE);
> + bin2hex(serial_hex, serial, blen);
Why isn't this also blen - QUERY_DESC_HDR_SIZE? Is the 00 padding intentional?
> + serial_hex[blen * 2] = '\0';
> +
> + snprintf(idstr, sizeof(idstr), "%04X-%04X-%s-%s-%04X-%04X-R%u",
> + manf_id, spec_ver, model, serial_hex, dev_ver, manf_date,
> + region);
> +
> + if (blake2b(cid, UFS_RPMB_CID_SIZE, idstr, strlen(idstr), NULL, 0))
> + return -EIO;
> +
> + return 0;
> +}
> +
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 4/5] optee: rename rpmb.c to rpmb_emmc.c
2026-07-23 14:38 [PATCH v3 0/5] ufs: rpmb: route OP-TEE RPMB secure storage over UFS Jorge Ramirez-Ortiz via U-Boot
` (2 preceding siblings ...)
2026-07-23 14:38 ` [PATCH v3 3/5] ufs: derive the per-region RPMB CID and size for OP-TEE Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-23 14:38 ` Jorge Ramirez-Ortiz via U-Boot
2026-07-23 14:38 ` [PATCH v3 5/5] optee: implement the RPMB subsystem interface for UFS Jorge Ramirez-Ortiz via U-Boot
4 siblings, 0 replies; 9+ messages in thread
From: Jorge Ramirez-Ortiz via U-Boot @ 2026-07-23 14:38 UTC (permalink / raw)
To: jorge.ramirez, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, dlechner,
u-boot
This file implements the legacy single-command OPTEE_MSG_RPC_CMD_RPMB
supplicant (eMMC only). Rename it to rpmb_emmc.c.
No functional change.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/tee/optee/Makefile | 2 +-
drivers/tee/optee/{rpmb.c => rpmb_emmc.c} | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename drivers/tee/optee/{rpmb.c => rpmb_emmc.c} (100%)
diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile
index 36ac085ef42..d0c7f8dfb12 100644
--- a/drivers/tee/optee/Makefile
+++ b/drivers/tee/optee/Makefile
@@ -3,4 +3,4 @@
obj-$(CONFIG_OPTEE) += core.o
obj-y += supplicant.o
obj-$(CONFIG_DM_I2C) += i2c.o
-obj-$(CONFIG_SUPPORT_EMMC_RPMB) += rpmb.o
+obj-$(CONFIG_SUPPORT_EMMC_RPMB) += rpmb_emmc.o
diff --git a/drivers/tee/optee/rpmb.c b/drivers/tee/optee/rpmb_emmc.c
similarity index 100%
rename from drivers/tee/optee/rpmb.c
rename to drivers/tee/optee/rpmb_emmc.c
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v3 5/5] optee: implement the RPMB subsystem interface for UFS
2026-07-23 14:38 [PATCH v3 0/5] ufs: rpmb: route OP-TEE RPMB secure storage over UFS Jorge Ramirez-Ortiz via U-Boot
` (3 preceding siblings ...)
2026-07-23 14:38 ` [PATCH v3 4/5] optee: rename rpmb.c to rpmb_emmc.c Jorge Ramirez-Ortiz via U-Boot
@ 2026-07-23 14:38 ` Jorge Ramirez-Ortiz via U-Boot
4 siblings, 0 replies; 9+ messages in thread
From: Jorge Ramirez-Ortiz via U-Boot @ 2026-07-23 14:38 UTC (permalink / raw)
To: jorge.ramirez, neil.armstrong, trini, jens.wiklander,
ilias.apalodimas, peng.fan, jh80.chung, bhupesh.linux, n-francis,
marek.vasut+renesas, igor.belwon, shawn.lin, alchark,
tuyen.dang.xa, yoshihiro.shimoda.uh, padmarao.begari, jstephan,
hayashi.kunihiko, macpaul.lin, venkyada, j-mcarthur, dlechner,
u-boot
OP-TEE's legacy RPMB supplicant interface assumes the RPMB device is eMMC
and derives the key from an eMMC-shaped device ID, so it can never
reproduce the key Linux derives for a UFS device; secure storage on
UFS-only platforms instead needs the transport-agnostic RPMB subsystem
interface, through which the normal world describes the real RPMB device
to OP-TEE. Add a UFS backend for it so OP-TEE derives a key matching Linux
and can use RPMB secure storage on UFS, leaving the legacy eMMC path
untouched.
This subsystem backend currently supports UFS drives only; eMMC still uses
the legacy interface.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/tee/optee/Makefile | 1 +
drivers/tee/optee/optee_msg_supplicant.h | 8 ++
drivers/tee/optee/optee_private.h | 41 +++++++
drivers/tee/optee/rpmb_ufs.c | 141 +++++++++++++++++++++++
drivers/tee/optee/supplicant.c | 9 ++
drivers/ufs/Kconfig | 8 ++
6 files changed, 208 insertions(+)
create mode 100644 drivers/tee/optee/rpmb_ufs.c
diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile
index d0c7f8dfb12..f2af10999a9 100644
--- a/drivers/tee/optee/Makefile
+++ b/drivers/tee/optee/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_OPTEE) += core.o
obj-y += supplicant.o
obj-$(CONFIG_DM_I2C) += i2c.o
obj-$(CONFIG_SUPPORT_EMMC_RPMB) += rpmb_emmc.o
+obj-$(CONFIG_SUPPORT_UFS_RPMB) += rpmb_ufs.o
diff --git a/drivers/tee/optee/optee_msg_supplicant.h b/drivers/tee/optee/optee_msg_supplicant.h
index 963cfd47824..b720d5d7b3f 100644
--- a/drivers/tee/optee/optee_msg_supplicant.h
+++ b/drivers/tee/optee/optee_msg_supplicant.h
@@ -152,6 +152,14 @@
*/
#define OPTEE_MSG_RPC_CMD_I2C_TRANSFER 21
+#define OPTEE_MSG_RPC_CMD_RPMB_PROBE_RESET 22
+#define OPTEE_MSG_RPC_CMD_RPMB_PROBE_NEXT 23
+#define OPTEE_MSG_RPC_CMD_RPMB_FRAMES 24
+
+#define OPTEE_RPC_SHM_TYPE_APPL 0
+
+#define OPTEE_RPC_RPMB_UFS 1
+
/*
* Was OPTEE_MSG_RPC_CMD_SQL_FS, which isn't supported any longer
*/
diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h
index 1f07a27ee4b..2888257aefe 100644
--- a/drivers/tee/optee/optee_private.h
+++ b/drivers/tee/optee/optee_private.h
@@ -9,6 +9,17 @@
#include <tee.h>
#include <log.h>
+#ifdef CONFIG_SUPPORT_UFS_RPMB
+/**
+ * struct optee_private - OP-TEE driver private data
+ * @rpmb_next_region: next UFS RPMB region to report on PROBE_NEXT
+ * @rpmb_cur_region: UFS RPMB region selected by the last PROBE_NEXT
+ */
+struct optee_private {
+ unsigned int rpmb_next_region;
+ unsigned int rpmb_cur_region;
+};
+#else
/**
* struct optee_private - OP-TEE driver private data
* @rpmb_mmc: mmc device for the RPMB partition
@@ -22,6 +33,7 @@ struct optee_private {
int rpmb_dev_id;
int rpmb_original_part;
};
+#endif
struct optee_msg_arg;
@@ -60,6 +72,35 @@ static inline void optee_suppl_rpmb_release(struct udevice *dev)
}
#endif
+#ifdef CONFIG_SUPPORT_UFS_RPMB
+void optee_suppl_cmd_rpmb_probe_reset(struct udevice *dev,
+ struct optee_msg_arg *arg);
+
+void optee_suppl_cmd_rpmb_probe_next(struct udevice *dev,
+ struct optee_msg_arg *arg);
+
+void optee_suppl_cmd_rpmb_frames(struct udevice *dev,
+ struct optee_msg_arg *arg);
+#else
+static inline void optee_suppl_cmd_rpmb_probe_reset(struct udevice *dev,
+ struct optee_msg_arg *arg)
+{
+ arg->ret = TEE_ERROR_NOT_IMPLEMENTED;
+}
+
+static inline void optee_suppl_cmd_rpmb_probe_next(struct udevice *dev,
+ struct optee_msg_arg *arg)
+{
+ arg->ret = TEE_ERROR_NOT_IMPLEMENTED;
+}
+
+static inline void optee_suppl_cmd_rpmb_frames(struct udevice *dev,
+ struct optee_msg_arg *arg)
+{
+ arg->ret = TEE_ERROR_NOT_IMPLEMENTED;
+}
+#endif
+
#ifdef CONFIG_DM_I2C
/**
* optee_suppl_cmd_i2c_transfer() - route I2C requests to an I2C chip
diff --git a/drivers/tee/optee/rpmb_ufs.c b/drivers/tee/optee/rpmb_ufs.c
new file mode 100644
index 00000000000..3e54a1a37d7
--- /dev/null
+++ b/drivers/tee/optee/rpmb_ufs.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <dm.h>
+#include <tee.h>
+#include <ufs.h>
+
+#include "optee_msg.h"
+#include "optee_msg_supplicant.h"
+#include "optee_private.h"
+
+static int optee_rpmb_get_dev(struct udevice **scsi_devp)
+{
+ struct udevice *ufs_dev, *scsi_dev;
+ int ret;
+
+ ret = uclass_get_device(UCLASS_UFS, CONFIG_UFS_RPMB_CONTROLLER,
+ &ufs_dev);
+ if (ret)
+ return ret;
+
+ ret = device_get_child(ufs_dev, 0, &scsi_dev);
+ if (ret)
+ return ret;
+
+ *scsi_devp = scsi_dev;
+
+ return 0;
+}
+
+void optee_suppl_cmd_rpmb_probe_reset(struct udevice *dev,
+ struct optee_msg_arg *arg)
+{
+ struct optee_private *priv = dev_get_priv(dev);
+
+ if (arg->num_params != 1 ||
+ arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT) {
+ arg->ret = TEE_ERROR_BAD_PARAMETERS;
+ return;
+ }
+
+ priv->rpmb_next_region = 0;
+ priv->rpmb_cur_region = 0;
+
+ arg->params[0].u.value.a = OPTEE_RPC_SHM_TYPE_APPL;
+ arg->ret = TEE_SUCCESS;
+}
+
+void optee_suppl_cmd_rpmb_probe_next(struct udevice *dev,
+ struct optee_msg_arg *arg)
+{
+ struct optee_private *priv = dev_get_priv(dev);
+ struct udevice *scsi_dev;
+ struct tee_shm *cid_shm;
+ u8 size_mult = 0;
+ u8 rel_wr = 0;
+ void *cid_buf;
+ ulong cid_size;
+ int ret;
+
+ if (arg->num_params != 2 ||
+ arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT ||
+ arg->params[1].attr != OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT) {
+ arg->ret = TEE_ERROR_BAD_PARAMETERS;
+ return;
+ }
+
+ cid_shm = (struct tee_shm *)(ulong)arg->params[1].u.rmem.shm_ref;
+ cid_buf = (u8 *)cid_shm->addr + arg->params[1].u.rmem.offs;
+ cid_size = arg->params[1].u.rmem.size;
+ if (cid_size < UFS_RPMB_CID_SIZE) {
+ arg->ret = TEE_ERROR_SHORT_BUFFER;
+ return;
+ }
+
+ if (optee_rpmb_get_dev(&scsi_dev)) {
+ arg->ret = TEE_ERROR_ITEM_NOT_FOUND;
+ return;
+ }
+
+ while (priv->rpmb_next_region < UFS_RPMB_NUM_REGIONS) {
+ unsigned int region = priv->rpmb_next_region++;
+
+ ret = ufs_rpmb_get_region_info(scsi_dev, region, &size_mult,
+ &rel_wr, cid_buf);
+ if (ret < 0) {
+ arg->ret = TEE_ERROR_GENERIC;
+ return;
+ }
+ if (!ret)
+ continue;
+
+ priv->rpmb_cur_region = region;
+ arg->params[0].u.value.a = OPTEE_RPC_RPMB_UFS;
+ arg->params[0].u.value.b = size_mult;
+ arg->params[0].u.value.c = rel_wr;
+ arg->params[1].u.rmem.size = UFS_RPMB_CID_SIZE;
+ arg->ret = TEE_SUCCESS;
+ return;
+ }
+
+ arg->ret = TEE_ERROR_ITEM_NOT_FOUND;
+}
+
+void optee_suppl_cmd_rpmb_frames(struct udevice *dev,
+ struct optee_msg_arg *arg)
+{
+ struct optee_private *priv = dev_get_priv(dev);
+ struct tee_shm *req_shm;
+ struct tee_shm *rsp_shm;
+ struct udevice *scsi_dev;
+ void *req_buf;
+ void *rsp_buf;
+ ulong req_size;
+ ulong rsp_size;
+
+ if (arg->num_params != 2 ||
+ arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_RMEM_INPUT ||
+ arg->params[1].attr != OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT) {
+ arg->ret = TEE_ERROR_BAD_PARAMETERS;
+ return;
+ }
+
+ if (optee_rpmb_get_dev(&scsi_dev)) {
+ arg->ret = TEE_ERROR_ITEM_NOT_FOUND;
+ return;
+ }
+
+ req_shm = (struct tee_shm *)(ulong)arg->params[0].u.rmem.shm_ref;
+ req_buf = (u8 *)req_shm->addr + arg->params[0].u.rmem.offs;
+ req_size = arg->params[0].u.rmem.size;
+
+ rsp_shm = (struct tee_shm *)(ulong)arg->params[1].u.rmem.shm_ref;
+ rsp_buf = (u8 *)rsp_shm->addr + arg->params[1].u.rmem.offs;
+ rsp_size = arg->params[1].u.rmem.size;
+
+ if (ufs_rpmb_route_frames(scsi_dev, priv->rpmb_cur_region, req_buf,
+ req_size, rsp_buf, rsp_size))
+ arg->ret = TEE_ERROR_BAD_PARAMETERS;
+ else
+ arg->ret = TEE_SUCCESS;
+}
diff --git a/drivers/tee/optee/supplicant.c b/drivers/tee/optee/supplicant.c
index 8a426f53ba8..50b780037fb 100644
--- a/drivers/tee/optee/supplicant.c
+++ b/drivers/tee/optee/supplicant.c
@@ -89,6 +89,15 @@ void optee_suppl_cmd(struct udevice *dev, struct tee_shm *shm_arg,
case OPTEE_MSG_RPC_CMD_RPMB:
optee_suppl_cmd_rpmb(dev, arg);
break;
+ case OPTEE_MSG_RPC_CMD_RPMB_PROBE_RESET:
+ optee_suppl_cmd_rpmb_probe_reset(dev, arg);
+ break;
+ case OPTEE_MSG_RPC_CMD_RPMB_PROBE_NEXT:
+ optee_suppl_cmd_rpmb_probe_next(dev, arg);
+ break;
+ case OPTEE_MSG_RPC_CMD_RPMB_FRAMES:
+ optee_suppl_cmd_rpmb_frames(dev, arg);
+ break;
case OPTEE_MSG_RPC_CMD_I2C_TRANSFER:
optee_suppl_cmd_i2c_transfer(arg);
break;
diff --git a/drivers/ufs/Kconfig b/drivers/ufs/Kconfig
index d39fcda42dc..d7fe2486ee2 100644
--- a/drivers/ufs/Kconfig
+++ b/drivers/ufs/Kconfig
@@ -105,4 +105,12 @@ config SUPPORT_UFS_RPMB
single RPMB transport, so this is mutually exclusive with the eMMC
RPMB supplicant (SUPPORT_EMMC_RPMB).
+config UFS_RPMB_CONTROLLER
+ int "UFS controller index used for RPMB"
+ depends on SUPPORT_UFS_RPMB
+ default 0
+ help
+ Index of the RPMB-owning UFS controller in the UCLASS_UFS device
+ list. Leave at 0 unless the board has more than one UFS controller.
+
endmenu
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread