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
