Complete RPMB well-known logical unit emulation in hw/ufs/rpmb.c and
update docs/system/devices/ufs.rst:

- Support single- and multi-frame authenticated data writes (0x0003),
  checking write counter expiration, address bounds, block count and
  multi-frame header consistency, MAC, and write counter in the order
  specified by JESD220H Section 12.4.7.3.3, and incrementing the write
  counter once per command.
- Support single- and multi-frame authenticated data reads (0x0004),
  validating PRDT buffer capacity before clearing read_pending, checking
  address bounds before block count, and building the response via
  ufs_rpmb_do_read() with Stuff bytes [0:195] and Write counter
  [500:503] zeroed per JESD220H Section 12.4.7.3.4 and Figure 12.8.
- Materialize the signed 1-frame 0x0400 response into the Result
  Register when Result Read (0x0005) arrives while a 0x0004 read is
  pending without a prior SECURITY PROTOCOL IN.
- Free allocated region storage buffers in ufs_unrealize().

Signed-off-by: Stanley Jhu <[email protected]>
---
 docs/system/devices/ufs.rst |   6 +-
 hw/ufs/rpmb.c               | 214 ++++++++++++++++++++++++++++++++++++
 hw/ufs/ufs.c                |   5 +
 3 files changed, 223 insertions(+), 2 deletions(-)

diff --git a/docs/system/devices/ufs.rst b/docs/system/devices/ufs.rst
index 36f5b958cf..7bf7352c4c 100644
--- a/docs/system/devices/ufs.rst
+++ b/docs/system/devices/ufs.rst
@@ -139,6 +139,8 @@ Limitations
   requests are supported; other task management functions such as
   ``LOGICAL UNIT RESET`` return ``TASK MANAGEMENT FUNCTION NOT
   SUPPORTED``.
-* The Boot and RPMB well-known logical units only answer basic SCSI
-  commands such as INQUIRY; boot and RPMB data access are not emulated.
+* The Boot well-known logical unit only answers basic SCSI commands
+  such as INQUIRY; boot data access is not emulated. The RPMB
+  well-known logical unit emulates four 4 MiB regions in host memory
+  (volatile across QEMU restarts).
 * The device cannot be migrated.
diff --git a/hw/ufs/rpmb.c b/hw/ufs/rpmb.c
index edd4b91fd8..344f0708ad 100644
--- a/hw/ufs/rpmb.c
+++ b/hw/ufs/rpmb.c
@@ -10,6 +10,13 @@
 #include "trace.h"
 #include "ufs.h"
 
+static void ufs_rpmb_ensure_storage(UfsRpmbRegion *r)
+{
+    if (!r->storage) {
+        r->storage = g_malloc0(UFS_RPMB_REGION_BYTES);
+    }
+}
+
 /*
  * JESD220H Section 12.4.4.2: the HMAC-SHA256 input message is the
  * concatenation of bytes [228:511] (UFS_RPMB_HASH_OFFSET..511, 284 bytes)
@@ -34,6 +41,142 @@ static void ufs_rpmb_calc_hmac(const UfsRpmbRegion *r,
     qcrypto_hmac_bytesv(hmac, iov, count, &mac, &mac_len, &error_abort);
 }
 
+static uint16_t ufs_rpmb_do_write(UfsHc *u, UfsRpmbRegion *r,
+                                  const UfsRpmbFrame *frames,
+                                  uint32_t nframes)
+{
+    uint8_t mac[UFS_RPMB_KEY_MAC_LEN];
+    uint16_t addr = lduw_be_p(&frames[0].addr);
+    uint16_t blk_cnt = lduw_be_p(&frames[0].block_count);
+    uint32_t req_wc = ldl_be_p(&frames[0].write_counter);
+    uint8_t max_rw = u->geometry_desc.rpmb_read_write_size;
+    uint32_t i;
+
+    /*
+     * JESD220H Section 12.4.7.3.3: check write counter expiration (0x0085),
+     * target address and address + block count bounds (0x0004), block count
+     * and frame fields (0x0001), MAC (0x0002), and write counter (0x0003) in
+     * order.
+     */
+    if (!r->key_set) {
+        return UFS_RPMB_RES_NO_AUTH_KEY;
+    }
+    if (r->write_counter == 0xffffffff) {
+        return UFS_RPMB_RES_WRITE_FAILURE;
+    }
+    if (addr >= UFS_RPMB_REGION_BLOCKS ||
+        (uint32_t)addr + blk_cnt > UFS_RPMB_REGION_BLOCKS) {
+        return UFS_RPMB_RES_ADDR_FAILURE;
+    }
+    if (blk_cnt == 0 || blk_cnt > max_rw || blk_cnt != nframes) {
+        return UFS_RPMB_RES_GENERAL_FAILURE;
+    }
+    /*
+     * JESD220H Section 12.4.7.3.3: every frame of a multi-block Authenticated
+     * Data Write carries identical req_resp, block_count, addr, and
+     * write_counter values, or fails with General failure (0x0001).
+     */
+    for (i = 0; i < nframes; i++) {
+        if (lduw_be_p(&frames[i].req_resp) != UFS_RPMB_REQ_AUTH_DATA_WRITE ||
+            lduw_be_p(&frames[i].block_count) != blk_cnt ||
+            lduw_be_p(&frames[i].addr) != addr ||
+            ldl_be_p(&frames[i].write_counter) != req_wc) {
+            return UFS_RPMB_RES_GENERAL_FAILURE;
+        }
+    }
+    ufs_rpmb_calc_hmac(r, frames, nframes, mac);
+    if (memcmp(mac, frames[nframes - 1].key_mac, UFS_RPMB_KEY_MAC_LEN) != 0) {
+        return UFS_RPMB_RES_AUTH_FAILURE;
+    }
+    if (req_wc != r->write_counter) {
+        return UFS_RPMB_RES_COUNT_FAILURE;
+    }
+
+    ufs_rpmb_ensure_storage(r);
+    for (i = 0; i < nframes; i++) {
+        memcpy(r->storage + ((uint32_t)addr + i) * UFS_RPMB_DATA_LEN,
+               frames[i].data, UFS_RPMB_DATA_LEN);
+    }
+    /*
+     * JESD220H Section 12.4.3.1 & Section 12.4.7.3.3: the write counter is
+     * incremented by one per successful authenticated data write command.
+     */
+    r->write_counter++;
+    return UFS_RPMB_RES_OK;
+}
+
+/*
+ * JESD220H Section 12.4.7.3.4 (Figure 12.8): build the Authenticated Data
+ * Read response (0x0400) for in_frames blocks (with bytes [0:195] Stuff bytes
+ * and [500:503] Write counter set to 0...00h) and update the region's latched
+ * Result Register (r->resp). On error, sign a single error frame (when
+ * key_set) and replicate it across all returned frames, matching the eMMC
+ * RPMB read model in emmc_rpmb_blk_read() (hw/sd/sd.c).
+ */
+static void ufs_rpmb_do_read(UfsHc *u, UfsRpmbRegion *r, uint32_t in_frames,
+                             UfsRpmbFrame *out_frames)
+{
+    uint16_t addr = lduw_be_p(&r->resp.addr);
+    uint16_t req_blk_cnt = lduw_be_p(&r->resp.block_count);
+    uint8_t max_rw = u->geometry_desc.rpmb_read_write_size;
+    uint16_t result;
+    bool ok = false;
+    uint32_t i;
+
+    /*
+     * JESD220H Section 12.4.7.3.4: check address and address + block count
+     * bounds (0x0004) before block count > bRPMB_ReadWriteSize (0x0001).
+     */
+    if (!r->key_set) {
+        result = UFS_RPMB_RES_NO_AUTH_KEY;
+    } else if (addr >= UFS_RPMB_REGION_BLOCKS ||
+               (uint32_t)addr + in_frames > UFS_RPMB_REGION_BLOCKS ||
+               (uint32_t)addr + req_blk_cnt > UFS_RPMB_REGION_BLOCKS) {
+        result = UFS_RPMB_RES_ADDR_FAILURE;
+    } else if (req_blk_cnt > max_rw) {
+        result = UFS_RPMB_RES_GENERAL_FAILURE;
+    } else {
+        result = UFS_RPMB_RES_OK;
+        ok = true;
+    }
+    if (r->write_counter == 0xffffffff) {
+        result |= UFS_RPMB_RES_COUNT_EXPIRED;
+    }
+
+    if (!ok) {
+        memset(&out_frames[0], 0, sizeof(out_frames[0]));
+        memcpy(out_frames[0].nonce, r->resp.nonce, UFS_RPMB_NONCE_LEN);
+        out_frames[0].addr = r->resp.addr;
+        out_frames[0].block_count = cpu_to_be16(in_frames);
+        out_frames[0].result = cpu_to_be16(result);
+        out_frames[0].req_resp = cpu_to_be16(UFS_RPMB_RESP_AUTH_DATA_READ);
+        if (r->key_set) {
+            ufs_rpmb_calc_hmac(r, &out_frames[0], 1, out_frames[0].key_mac);
+        }
+        for (i = 1; i < in_frames; i++) {
+            out_frames[i] = out_frames[0];
+        }
+        r->resp = out_frames[0];
+        return;
+    }
+
+    ufs_rpmb_ensure_storage(r);
+    for (i = 0; i < in_frames; i++) {
+        memset(&out_frames[i], 0, sizeof(out_frames[i]));
+        memcpy(out_frames[i].nonce, r->resp.nonce, UFS_RPMB_NONCE_LEN);
+        out_frames[i].addr = r->resp.addr;
+        out_frames[i].block_count = cpu_to_be16(in_frames);
+        out_frames[i].result = cpu_to_be16(result);
+        out_frames[i].req_resp = cpu_to_be16(UFS_RPMB_RESP_AUTH_DATA_READ);
+        memcpy(out_frames[i].data,
+               r->storage + ((uint32_t)addr + i) * UFS_RPMB_DATA_LEN,
+               UFS_RPMB_DATA_LEN);
+    }
+    ufs_rpmb_calc_hmac(r, out_frames, in_frames,
+                       out_frames[in_frames - 1].key_mac);
+    r->resp = out_frames[in_frames - 1];
+}
+
 static void ufs_rpmb_handle_req(UfsHc *u, UfsRpmbRegion *r,
                                 const UfsRpmbFrame *frames,
                                 uint32_t nframes)
@@ -43,7 +186,27 @@ static void ufs_rpmb_handle_req(UfsHc *u, UfsRpmbRegion *r,
     uint16_t result = UFS_RPMB_RES_GENERAL_FAILURE;
     bool sign_resp = false;
 
+    /*
+     * JESD220H Sections 12.4.7.3.1 & 12.4.7.3.3 state that any request other
+     * than Result read request (0x0005) overwrites the Result Register of the
+     * RPMB region, and Section 12.4.7.3.4 (Figure 12.8) specifies that when an
+     * Authenticated Data Read request (0x0004) is received, the device checks
+     * the address and block count, fetches the data, sets Result, and computes
+     * the MAC for the 0x0400 response (with Stuff bytes [0:195] and Write
+     * counter [500:503] set to 0...00h). Because normal multi-frame reads
+     * defer ufs_rpmb_do_read() until SECURITY PROTOCOL IN supplies the
+     * allocation frame count, if a Result read request (0x0005) arrives while
+     * read_pending is still set (no IN was issued), materialize the 1-frame
+     * 0x0400 response into r->resp and clear read_pending so the following
+     * 1-frame SECURITY PROTOCOL IN returns a complete, signed 0x0400 frame.
+     */
     if (req_type == UFS_RPMB_REQ_READ_RESULT) {
+        if (r->read_pending) {
+            UfsRpmbFrame tmp = { 0 };
+
+            ufs_rpmb_do_read(u, r, 1, &tmp);
+            r->read_pending = false;
+        }
         trace_ufs_rpmb_request(req_type, lduw_be_p(&r->resp.result));
         return;
     }
@@ -85,6 +248,23 @@ static void ufs_rpmb_handle_req(UfsHc *u, UfsRpmbRegion *r,
         sign_resp = true;
         break;
 
+    case UFS_RPMB_REQ_AUTH_DATA_WRITE:
+        r->resp.addr = req->addr;
+        r->resp.req_resp = cpu_to_be16(UFS_RPMB_RESP_AUTH_DATA_WRITE);
+        result = ufs_rpmb_do_write(u, r, frames, nframes);
+        r->resp.write_counter = cpu_to_be32(r->write_counter);
+        sign_resp = r->key_set;
+        break;
+
+    case UFS_RPMB_REQ_AUTH_DATA_READ:
+        r->read_pending = true;
+        memcpy(r->resp.nonce, req->nonce, UFS_RPMB_NONCE_LEN);
+        r->resp.addr = req->addr;
+        r->resp.block_count = req->block_count;
+        r->resp.req_resp = cpu_to_be16(UFS_RPMB_RESP_AUTH_DATA_READ);
+        trace_ufs_rpmb_request(req_type, 0);
+        return;
+
     default:
         r->resp.req_resp = cpu_to_be16(req_type);
         result = UFS_RPMB_RES_GENERAL_FAILURE;
@@ -213,6 +393,40 @@ UfsReqResult ufs_rpmb_sec_in(UfsHc *u, UfsRequest *req, 
uint8_t *scsi_status,
     r = &u->rpmb[region];
     in_frames = alloc_len / UFS_RPMB_FRAME_LEN;
 
+    if (r->read_pending) {
+        g_autofree UfsRpmbFrame *out_frames = NULL;
+        uint16_t req_blk_cnt = lduw_be_p(&r->resp.block_count);
+
+        /*
+         * JESD220H Section 12.4.7.2 (Table 12.17; JESD220F Table 12.22)
+         * specifies an expected data transfer length of 512 * Block Count
+         * for Authenticated Data Read responses. When the request frame
+         * left block_count as 0 (as allowed by the Linux RPMB subsystem
+         * and hw/sd/sd.c), use the transfer frame count from SECURITY
+         * PROTOCOL IN.
+         */
+        if (req_blk_cnt > 0 && in_frames != req_blk_cnt) {
+            scsi_build_sense(sense_buf, SENSE_CODE(INVALID_FIELD));
+            *scsi_status = CHECK_CONDITION;
+            return UFS_REQUEST_SUCCESS;
+        }
+        if (!ufs_fit_data_buffer(req, alloc_len)) {
+            return UFS_REQUEST_MISMATCH_DATA_BUF;
+        }
+
+        r->read_pending = false;
+        out_frames = g_new0(UfsRpmbFrame, in_frames);
+        ufs_rpmb_do_read(u, r, in_frames, out_frames);
+
+        if (dma_buf_read(out_frames, alloc_len, &residual, req->sg,
+                         MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
+            return UFS_REQUEST_FAIL;
+        }
+        *transferred_len = alloc_len - residual;
+        *scsi_status = GOOD;
+        return UFS_REQUEST_SUCCESS;
+    }
+
     if (in_frames != 1) {
         scsi_build_sense(sense_buf, SENSE_CODE(INVALID_FIELD));
         *scsi_status = CHECK_CONDITION;
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index bc0f2ce4eb..42fa374fca 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -3382,6 +3382,11 @@ void ufs_unrealize(UfsHc *u)
             u->cq[i] = NULL;
         }
     }
+
+    for (int i = 0; i < UFS_RPMB_MAX_REGIONS; i++) {
+        g_free(u->rpmb[i].storage);
+        u->rpmb[i].storage = NULL;
+    }
 }
 
 static bool ufs_bus_check_address(BusState *qbus, DeviceState *qdev,
-- 
2.56.0.307.g5fe7bd680c-goog


Reply via email to