Add ufstest_rpmb_read_write() to test single-frame and multi-frame
(N = 4) authenticated data writes and reads with HMAC-SHA256
verification, Result Read (0x0005) both after a multi-frame read and
while a pending 0x0004 read has had no SECURITY PROTOCOL IN (verifying
zeroed stuff bytes and zero write_counter per JESD220H Figure 12.8),
residual underflow accounting, JESD220H Sections 12.4.7.3.3 and
12.4.7.3.4 check ordering and UFS_RPMB_RES_GENERAL_FAILURE (0x0001)
cases, multi-frame reads past region end, SECURITY PROTOCOL IN block
count mismatch sense reporting, multi-region key and data isolation
across regions 0..3, short-PRDT rejection while read_pending is set,
and HCE 1->0->1 reset clearing volatile state while preserving keys,
write counters, and stored blocks.

Signed-off-by: Stanley Jhu <[email protected]>
---
 tests/qtest/ufs-test.c | 569 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 569 insertions(+)

diff --git a/tests/qtest/ufs-test.c b/tests/qtest/ufs-test.c
index 9fce5fff82..c118ee86be 100644
--- a/tests/qtest/ufs-test.c
+++ b/tests/qtest/ufs-test.c
@@ -4361,6 +4361,573 @@ static void ufstest_rpmb_key_counter(void *obj, void 
*data,
     ufs_exit(ufs, alloc);
 }
 
+static void ufstest_rpmb_read_write(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    QUfs *ufs = obj;
+    UtpUpiuRsp rsp_upiu;
+    enum UtpOcsCodes ocs;
+    UfsRpmbFrame req, resp, zero_frame;
+    UfsRpmbFrame wframes[4], rframes[4];
+    uint8_t mac[UFS_RPMB_KEY_MAC_LEN];
+    uint8_t zero_mac[UFS_RPMB_KEY_MAC_LEN] = { 0 };
+    uint8_t cdb[UFS_CDB_SIZE];
+    const uint8_t *key = ufs_rpmb_test_key;
+    uint8_t region_keys[UFS_RPMB_MAX_REGIONS][UFS_RPMB_KEY_MAC_LEN];
+    int i, j, r;
+
+    ufs_init(ufs, alloc);
+    memset(&zero_frame, 0, sizeof(zero_frame));
+    ufs_rpmb_program_key(ufs, 0, key);
+
+    /* 1. Single-frame authenticated write (block 3, counter 0 -> 1) + read */
+    memset(&req, 0, sizeof(req));
+    for (i = 0; i < UFS_RPMB_DATA_LEN; i++) {
+        req.data[i] = (uint8_t)(0x5a ^ i);
+    }
+    stl_be_p(&req.write_counter, 0);
+    stw_be_p(&req.addr, 3);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    ufs_rpmb_compute_hmac(key, &req, 1, req.key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_WRITE);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(lduw_be_p(&resp.addr), ==, 3);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 1);
+    ufs_rpmb_compute_hmac(key, &resp, 1, mac);
+    g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0xc3, sizeof(req.nonce));
+    stw_be_p(&req.addr, 3);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_rpmb_sec_in_frames(ufs, 0, &resp, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_READ);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(lduw_be_p(&resp.addr), ==, 3);
+    g_assert_cmpuint(lduw_be_p(&resp.block_count), ==, 1);
+    g_assert_cmpmem(resp.nonce, UFS_RPMB_NONCE_LEN,
+                    req.nonce, UFS_RPMB_NONCE_LEN);
+    for (i = 0; i < UFS_RPMB_DATA_LEN; i++) {
+        g_assert_cmpuint(resp.data[i], ==, (uint8_t)(0x5a ^ i));
+    }
+    ufs_rpmb_compute_hmac(key, &resp, 1, mac);
+    g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    /* 2. Multi-frame authenticated write (N = 4, addr 10..13, wc 1 -> 2) */
+    memset(wframes, 0, sizeof(wframes));
+    for (i = 0; i < 4; i++) {
+        for (j = 0; j < UFS_RPMB_DATA_LEN; j++) {
+            wframes[i].data[j] = (uint8_t)((i + 1) * 0x11 + j);
+        }
+        stl_be_p(&wframes[i].write_counter, 1);
+        stw_be_p(&wframes[i].addr, 10);
+        stw_be_p(&wframes[i].block_count, 4);
+        stw_be_p(&wframes[i].req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    }
+    ufs_rpmb_compute_hmac(key, wframes, 4, wframes[3].key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, wframes, 4, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_WRITE);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(lduw_be_p(&resp.addr), ==, 10);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+    ufs_rpmb_compute_hmac(key, &resp, 1, mac);
+    g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    /* Multi-frame authenticated read (N = 4, addr 10..13) with MAC check */
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0xd4, sizeof(req.nonce));
+    stw_be_p(&req.addr, 10);
+    stw_be_p(&req.block_count, 4);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    memset(rframes, 0, sizeof(rframes));
+    ufs_rpmb_sec_in_frames(ufs, 0, rframes, 4, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    for (i = 0; i < 4; i++) {
+        g_assert_cmpuint(lduw_be_p(&rframes[i].req_resp), ==,
+                         UFS_RPMB_RESP_AUTH_DATA_READ);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].result), ==, UFS_RPMB_RES_OK);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].addr), ==, 10);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].block_count), ==, 4);
+        g_assert_cmpmem(rframes[i].nonce, UFS_RPMB_NONCE_LEN,
+                        req.nonce, UFS_RPMB_NONCE_LEN);
+        g_assert_cmpmem(rframes[i].data, UFS_RPMB_DATA_LEN,
+                        wframes[i].data, UFS_RPMB_DATA_LEN);
+        if (i < 3) {
+            g_assert_cmpmem(rframes[i].key_mac, UFS_RPMB_KEY_MAC_LEN,
+                            zero_mac, UFS_RPMB_KEY_MAC_LEN);
+        }
+    }
+    ufs_rpmb_compute_hmac(key, rframes, 4, mac);
+    g_assert_cmpmem(rframes[3].key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    /*
+     * READ_RESULT + 1-frame SECURITY PROTOCOL IN after a multi-frame
+     * AUTH_DATA_READ returns the latched result frame without failing the
+     * multi-frame block count check.
+     */
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpmem(resp.stuff, sizeof(resp.stuff),
+                    zero_frame.stuff, sizeof(resp.stuff));
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 0);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_READ);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(lduw_be_p(&resp.addr), ==, 10);
+    g_assert_cmpuint(lduw_be_p(&resp.block_count), ==, 4);
+    g_assert_cmpmem(&resp, sizeof(resp), &rframes[3], sizeof(resp));
+
+    /*
+     * Pending AUTH_DATA_READ (OUT 0x0004) immediately followed by READ_RESULT
+     * (OUT 0x0005) + 1-frame SECURITY PROTOCOL IN (512 bytes, with no prior IN
+     * issued for the read) returns a fully initialized, signed 0x0400 frame
+     * with stuff[0..195] == 0 and write_counter == 0 per JESD220H Figure 12.8.
+     */
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0xd9, sizeof(req.nonce));
+    stw_be_p(&req.addr, 3);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpmem(resp.stuff, sizeof(resp.stuff),
+                    zero_frame.stuff, sizeof(resp.stuff));
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 0);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_READ);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(lduw_be_p(&resp.addr), ==, 3);
+    g_assert_cmpuint(lduw_be_p(&resp.block_count), ==, 1);
+    g_assert_cmpmem(resp.nonce, UFS_RPMB_NONCE_LEN,
+                    req.nonce, UFS_RPMB_NONCE_LEN);
+    for (i = 0; i < UFS_RPMB_DATA_LEN; i++) {
+        g_assert_cmpuint(resp.data[i], ==, (uint8_t)(0x5a ^ i));
+    }
+    ufs_rpmb_compute_hmac(key, &resp, 1, mac);
+    g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    ufs_rpmb_build_cdb(cdb, SECURITY_PROTOCOL_IN, UFS_RPMB_SEC_PROTOCOL,
+                       UFS_RPMB_SEC_PROTOCOL_ID, sizeof(resp));
+    ocs = ufs_send_scsi_command(ufs, UFS_UPIU_RPMB_WLUN, cdb,
+                                NULL, 0, (uint8_t *)rframes,
+                                2 * sizeof(UfsRpmbFrame), &rsp_upiu);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_SUCCESS);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    g_assert_cmpuint(rsp_upiu.header.flags & UFS_UPIU_FLAG_UNDERFLOW, ==,
+                     UFS_UPIU_FLAG_UNDERFLOW);
+    g_assert_cmpuint(be32_to_cpu(rsp_upiu.sr.residual_transfer_count), ==,
+                     sizeof(UfsRpmbFrame));
+
+    /*
+     * 3. Write check order per JESD220H Section 12.4.7.3.3: address range
+     *    (0x0004) before block count > max (0x0001), MAC (0x0002), and write
+     *    counter (0x0003), plus N-frame read past region end -> 0x0004 on all
+     *    returned frames without out-of-bounds read.
+     */
+    memset(&req, 0, sizeof(req));
+    stl_be_p(&req.write_counter, 99);
+    stw_be_p(&req.addr, 32 * UFS_RPMB_BLOCKS_PER_UNIT);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    ufs_rpmb_compute_hmac(key, &req, 1, req.key_mac);
+    req.key_mac[0] ^= 0xff;
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_ADDR_FAILURE);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+
+    /* Bad address + block_count > bRPMB_ReadWriteSize (64) -> 0x0004 */
+    memset(&req, 0, sizeof(req));
+    stl_be_p(&req.write_counter, 2);
+    stw_be_p(&req.addr, 32 * UFS_RPMB_BLOCKS_PER_UNIT);
+    stw_be_p(&req.block_count, 65);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    ufs_rpmb_compute_hmac(key, &req, 1, req.key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_ADDR_FAILURE);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+
+    /* Good address + bad MAC (+ bad counter) -> 0x0002 */
+    memset(&req, 0, sizeof(req));
+    stl_be_p(&req.write_counter, 99);
+    stw_be_p(&req.addr, 0);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    ufs_rpmb_compute_hmac(key, &req, 1, req.key_mac);
+    req.key_mac[0] ^= 0xff;
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_AUTH_FAILURE);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+
+    /* Good address + good MAC + bad counter -> 0x0003 */
+    ufs_rpmb_compute_hmac(key, &req, 1, req.key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_COUNT_FAILURE);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+
+    memset(wframes, 0, sizeof(wframes));
+    for (i = 0; i < 2; i++) {
+        stl_be_p(&wframes[i].write_counter, 2);
+        stw_be_p(&wframes[i].addr, 32 * UFS_RPMB_BLOCKS_PER_UNIT - 1);
+        stw_be_p(&wframes[i].block_count, 2);
+        stw_be_p(&wframes[i].req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    }
+    ufs_rpmb_compute_hmac(key, wframes, 2, wframes[1].key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, wframes, 2, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_ADDR_FAILURE);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+
+    /* N-frame read near end of region (block_count=0 in OUT, N=2 in IN) */
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0xe5, sizeof(req.nonce));
+    stw_be_p(&req.addr, 32 * UFS_RPMB_BLOCKS_PER_UNIT - 1);
+    stw_be_p(&req.block_count, 0);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    memset(rframes, 0, sizeof(rframes));
+    ufs_rpmb_sec_in_frames(ufs, 0, rframes, 2, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_compute_hmac(key, &rframes[0], 1, mac);
+    for (i = 0; i < 2; i++) {
+        g_assert_cmpuint(lduw_be_p(&rframes[i].req_resp), ==,
+                         UFS_RPMB_RESP_AUTH_DATA_READ);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].result), ==,
+                         UFS_RPMB_RES_ADDR_FAILURE);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].addr), ==,
+                         32 * UFS_RPMB_BLOCKS_PER_UNIT - 1);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].block_count), ==, 2);
+        g_assert_cmpmem(rframes[i].nonce, UFS_RPMB_NONCE_LEN,
+                        req.nonce, UFS_RPMB_NONCE_LEN);
+        g_assert_cmpmem(rframes[i].key_mac, UFS_RPMB_KEY_MAC_LEN,
+                        mac, UFS_RPMB_KEY_MAC_LEN);
+    }
+
+    /* N-frame read near end of region (block_count=4 in OUT, N=4 in IN) */
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0xf6, sizeof(req.nonce));
+    stw_be_p(&req.addr, 32 * UFS_RPMB_BLOCKS_PER_UNIT - 2);
+    stw_be_p(&req.block_count, 4);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    memset(rframes, 0, sizeof(rframes));
+    ufs_rpmb_sec_in_frames(ufs, 0, rframes, 4, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_compute_hmac(key, &rframes[0], 1, mac);
+    for (i = 0; i < 4; i++) {
+        g_assert_cmpuint(lduw_be_p(&rframes[i].req_resp), ==,
+                         UFS_RPMB_RESP_AUTH_DATA_READ);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].result), ==,
+                         UFS_RPMB_RES_ADDR_FAILURE);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].addr), ==,
+                         32 * UFS_RPMB_BLOCKS_PER_UNIT - 2);
+        g_assert_cmpuint(lduw_be_p(&rframes[i].block_count), ==, 4);
+        g_assert_cmpmem(rframes[i].nonce, UFS_RPMB_NONCE_LEN,
+                        req.nonce, UFS_RPMB_NONCE_LEN);
+        g_assert_cmpmem(rframes[i].key_mac, UFS_RPMB_KEY_MAC_LEN,
+                        mac, UFS_RPMB_KEY_MAC_LEN);
+    }
+
+    /*
+     * Read check order per JESD220H Section 12.4.7.3.4: bad address +
+     * block_count > bRPMB_ReadWriteSize (64) -> 0x0004 (before 0x0001).
+     */
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0xf7, sizeof(req.nonce));
+    stw_be_p(&req.addr, 32 * UFS_RPMB_BLOCKS_PER_UNIT);
+    stw_be_p(&req.block_count, 65);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_READ);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_ADDR_FAILURE);
+
+    /*
+     * UFS_RPMB_RES_GENERAL_FAILURE (0x0001) paths:
+     * (a) 2-frame write with mismatched addr across frames
+     * (b) 1-frame write with block_count != nframes
+     * (c) 0x0004 read with valid addr and block_count > bRPMB_ReadWriteSize
+     * (d) unknown request type 0x0006
+     */
+    memset(wframes, 0, sizeof(wframes));
+    for (i = 0; i < 2; i++) {
+        stl_be_p(&wframes[i].write_counter, 2);
+        stw_be_p(&wframes[i].block_count, 2);
+        stw_be_p(&wframes[i].req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    }
+    stw_be_p(&wframes[0].addr, 4);
+    stw_be_p(&wframes[1].addr, 5);
+    ufs_rpmb_compute_hmac(key, wframes, 2, wframes[1].key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, wframes, 2, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_WRITE);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==,
+                     UFS_RPMB_RES_GENERAL_FAILURE);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+
+    memset(&req, 0, sizeof(req));
+    stl_be_p(&req.write_counter, 2);
+    stw_be_p(&req.addr, 4);
+    stw_be_p(&req.block_count, 2);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    ufs_rpmb_compute_hmac(key, &req, 1, req.key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_WRITE);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==,
+                     UFS_RPMB_RES_GENERAL_FAILURE);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 2);
+
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0xf8, sizeof(req.nonce));
+    stw_be_p(&req.addr, 0);
+    stw_be_p(&req.block_count, 65);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_READ);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==,
+                     UFS_RPMB_RES_GENERAL_FAILURE);
+
+    memset(&req, 0, sizeof(req));
+    stw_be_p(&req.req_resp, 0x0006);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==, 0x0006);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==,
+                     UFS_RPMB_RES_GENERAL_FAILURE);
+
+    /*
+     * 4. Authenticated data read SECURITY PROTOCOL IN block count mismatch
+     *    sense check.
+     */
+    memset(&req, 0, sizeof(req));
+    stw_be_p(&req.addr, 10);
+    stw_be_p(&req.block_count, 2);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_rpmb_sec_in_frames(ufs, 0, &resp, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, CHECK_CONDITION);
+    g_assert_cmpuint(rsp_upiu.sr.sense_data[2] & 0x0f, ==, ILLEGAL_REQUEST);
+    g_assert_cmpuint(rsp_upiu.sr.sense_data[12], ==, 0x24);
+    g_assert_cmpuint(rsp_upiu.sr.sense_data[13], ==, 0x00);
+
+    /*
+     * 5. Multi-region (regions 1..3) key programming and cross-region data
+     *    isolation at block 0.
+     */
+    memset(&req, 0, sizeof(req));
+    for (i = 0; i < UFS_RPMB_DATA_LEN; i++) {
+        req.data[i] = (uint8_t)(0xa5 ^ i);
+    }
+    stl_be_p(&req.write_counter, 2);
+    stw_be_p(&req.addr, 0);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    ufs_rpmb_compute_hmac(key, &req, 1, req.key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    ufs_rpmb_read_result(ufs, 0, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 3);
+
+    memcpy(region_keys[0], key, UFS_RPMB_KEY_MAC_LEN);
+    for (r = 1; r < UFS_RPMB_MAX_REGIONS; r++) {
+        for (i = 0; i < UFS_RPMB_KEY_MAC_LEN; i++) {
+            region_keys[r][i] = (uint8_t)(key[i] ^ (r * 0x33));
+        }
+        ufs_rpmb_program_key(ufs, r, region_keys[r]);
+    }
+
+    memset(&req, 0, sizeof(req));
+    for (i = 0; i < UFS_RPMB_DATA_LEN; i++) {
+        req.data[i] = (uint8_t)(0x3c ^ i);
+    }
+    stl_be_p(&req.write_counter, 0);
+    stw_be_p(&req.addr, 0);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_WRITE);
+    ufs_rpmb_compute_hmac(region_keys[1], &req, 1, req.key_mac);
+    ufs_rpmb_sec_out_frames(ufs, 1, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_rpmb_read_result(ufs, 1, &resp);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_WRITE);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 1);
+    ufs_rpmb_compute_hmac(region_keys[1], &resp, 1, mac);
+    g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    for (r = 0; r < UFS_RPMB_MAX_REGIONS; r++) {
+        memset(&req, 0, sizeof(req));
+        memset(req.nonce, (uint8_t)(0x90 + r), sizeof(req.nonce));
+        stw_be_p(&req.addr, 0);
+        stw_be_p(&req.block_count, 1);
+        stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+        ufs_rpmb_sec_out_frames(ufs, r, &req, 1, &rsp_upiu);
+        g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+        memset(&resp, 0, sizeof(resp));
+        ufs_rpmb_sec_in_frames(ufs, r, &resp, 1, &rsp_upiu);
+        g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+        g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                         UFS_RPMB_RESP_AUTH_DATA_READ);
+        g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+        for (i = 0; i < UFS_RPMB_DATA_LEN; i++) {
+            uint8_t expected = 0;
+
+            if (r == 0) {
+                expected = (uint8_t)(0xa5 ^ i);
+            } else if (r == 1) {
+                expected = (uint8_t)(0x3c ^ i);
+            }
+            g_assert_cmpuint(resp.data[i], ==, expected);
+        }
+        ufs_rpmb_compute_hmac(region_keys[r], &resp, 1, mac);
+        g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                        mac, UFS_RPMB_KEY_MAC_LEN);
+    }
+
+    /*
+     * 6. Short-PRDT SECURITY_PROTOCOL_IN (256 bytes) while read_pending is
+     *    set must return UFS_OCS_MISMATCH_DATA_BUF_SIZE without clearing
+     *    read_pending.
+     */
+    memset(&req, 0, sizeof(req));
+    stw_be_p(&req.addr, 0);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_rpmb_build_cdb(cdb, SECURITY_PROTOCOL_IN, UFS_RPMB_SEC_PROTOCOL,
+                       UFS_RPMB_SEC_PROTOCOL_ID, UFS_RPMB_FRAME_LEN);
+    ocs = ufs_rpmb_send_short_prdt(ufs, cdb, UFS_RPMB_FRAME_LEN, 256, false);
+    g_assert_cmpuint(ocs, ==, UFS_OCS_MISMATCH_DATA_BUF_SIZE);
+    /* read_pending stays armed until a valid IN or reset */
+    ufs_rpmb_sec_in_frames(ufs, 0, &resp, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    g_assert_cmpuint(lduw_be_p(&resp.req_resp), ==,
+                     UFS_RPMB_RESP_AUTH_DATA_READ);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+
+    /*
+     * 7. HCE 1->0->1 reset clears volatile read_pending and Result Register
+     *    state while keeping programmed keys, write counters, and stored
+     *    blocks intact.
+     */
+    memset(&req, 0, sizeof(req));
+    stw_be_p(&req.addr, 0);
+    stw_be_p(&req.block_count, 2);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 0, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0x5a, sizeof(req.nonce));
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_READ_COUNTER);
+    ufs_rpmb_sec_out_frames(ufs, 1, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    ufs_wreg(ufs, A_HCE, 0);
+    ufs_exit(ufs, alloc);
+    ufs_init(ufs, alloc);
+
+    /* read_pending was 2 frames on region 0, so 1-frame IN returns zero */
+    memset(&resp, 0xff, sizeof(resp));
+    ufs_rpmb_sec_in_frames(ufs, 0, &resp, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    g_assert_cmpmem(&resp, sizeof(resp), &zero_frame, sizeof(zero_frame));
+
+    memset(&resp, 0xff, sizeof(resp));
+    ufs_rpmb_sec_in_frames(ufs, 1, &resp, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    g_assert_cmpmem(&resp, sizeof(resp), &zero_frame, sizeof(zero_frame));
+
+    /* Key, write counter (1), and block 0 data on region 1 still persist */
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0x3c, sizeof(req.nonce));
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_READ_COUNTER);
+    ufs_rpmb_sec_out_frames(ufs, 1, &req, 1, &rsp_upiu);
+    ufs_rpmb_sec_in_frames(ufs, 1, &resp, 1, &rsp_upiu);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    g_assert_cmpuint(ldl_be_p(&resp.write_counter), ==, 1);
+    ufs_rpmb_compute_hmac(region_keys[1], &resp, 1, mac);
+    g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    memset(&req, 0, sizeof(req));
+    memset(req.nonce, 0x7e, sizeof(req.nonce));
+    stw_be_p(&req.addr, 0);
+    stw_be_p(&req.block_count, 1);
+    stw_be_p(&req.req_resp, UFS_RPMB_REQ_AUTH_DATA_READ);
+    ufs_rpmb_sec_out_frames(ufs, 1, &req, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+
+    memset(&resp, 0, sizeof(resp));
+    ufs_rpmb_sec_in_frames(ufs, 1, &resp, 1, &rsp_upiu);
+    g_assert_cmpuint(rsp_upiu.header.scsi_status, ==, GOOD);
+    g_assert_cmpuint(lduw_be_p(&resp.result), ==, UFS_RPMB_RES_OK);
+    for (i = 0; i < UFS_RPMB_DATA_LEN; i++) {
+        g_assert_cmpuint(resp.data[i], ==, (uint8_t)(0x3c ^ i));
+    }
+    ufs_rpmb_compute_hmac(region_keys[1], &resp, 1, mac);
+    g_assert_cmpmem(resp.key_mac, UFS_RPMB_KEY_MAC_LEN,
+                    mac, UFS_RPMB_KEY_MAC_LEN);
+
+    ufs_exit(ufs, alloc);
+}
+
 static void ufs_register_nodes(void)
 {
     const char *arch;
@@ -4458,6 +5025,8 @@ static void ufs_register_nodes(void)
                  &io_test_opts);
     qos_add_test("rpmb-key-counter", "ufs", ufstest_rpmb_key_counter,
                  &io_test_opts);
+    qos_add_test("rpmb-read-write", "ufs", ufstest_rpmb_read_write,
+                 &io_test_opts);
 }
 
 libqos_init(ufs_register_nodes);
-- 
2.56.0.307.g5fe7bd680c-goog


Reply via email to