The UFS device model creates the RPMB well-known logical unit (W-LUN
0xc4), but leaves bLUEnable set to 0 in RpmbUnitDescriptor, leaves
bSecurityLU set to 0 in DeviceDescriptor, and rejects SECURITY PROTOCOL
IN and SECURITY PROTOCOL OUT commands with ILLEGAL REQUEST.

Set bSecurityLU to 0x01 in DeviceDescriptor (JESD220H Table 14.4),
enable four 4 MiB RPMB regions (32 * 128 KiB each, 256-byte logical
blocks) in RpmbUnitDescriptor, and populate the big-endian block count
fields during unit descriptor reads using shared UFS_RPMB_* geometry
constants. Add per-region state in UfsHc with an embedded Result
Register frame, wire SECURITY PROTOCOL IN and SECURITY PROTOCOL OUT CDB
validation, PRDT buffer capacity validation via ufs_fit_data_buffer(),
and Result Read (0x0005) handling in hw/ufs/rpmb.c per JESD220H Section
12.4.6.1, and clear volatile RPMB state in ufs_hce_reset(). Requests
not yet implemented return UFS_RPMB_RES_GENERAL_FAILURE (0x0001).

Signed-off-by: Stanley Jhu <[email protected]>
---
 hw/ufs/lu.c         |  34 +++++++++
 hw/ufs/meson.build  |   2 +-
 hw/ufs/rpmb.c       | 170 ++++++++++++++++++++++++++++++++++++++++++++
 hw/ufs/trace-events |   3 +
 hw/ufs/ufs.c        |  21 +++++-
 hw/ufs/ufs.h        |  24 +++++++
 6 files changed, 250 insertions(+), 4 deletions(-)
 create mode 100644 hw/ufs/rpmb.c

diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index 4fe1247547..0e99ec5f7b 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -1137,6 +1137,40 @@ static UfsReqResult ufs_emulate_scsi_cmd(UfsLu *lu, 
UfsRequest *req)
                                    true);
         scsi_status = GOOD;
         break;
+    case SECURITY_PROTOCOL_IN:
+        if (lun == UFS_UPIU_RPMB_WLUN) {
+            uint32_t transferred_len = 0;
+            UfsReqResult ret;
+
+            ret = ufs_rpmb_sec_in(req->hc, req, &scsi_status, sense_buf,
+                                  &transferred_len);
+            if (ret != UFS_REQUEST_SUCCESS) {
+                return ret;
+            }
+            ufs_build_scsi_response_upiu(req, sense_buf, sizeof(sense_buf),
+                                         transferred_len, scsi_status);
+            return UFS_REQUEST_SUCCESS;
+        }
+        scsi_build_sense(sense_buf, SENSE_CODE(INVALID_OPCODE));
+        scsi_status = CHECK_CONDITION;
+        break;
+    case SECURITY_PROTOCOL_OUT:
+        if (lun == UFS_UPIU_RPMB_WLUN) {
+            uint32_t transferred_len = 0;
+            UfsReqResult ret;
+
+            ret = ufs_rpmb_sec_out(req->hc, req, &scsi_status, sense_buf,
+                                   &transferred_len);
+            if (ret != UFS_REQUEST_SUCCESS) {
+                return ret;
+            }
+            ufs_build_scsi_response_upiu(req, sense_buf, sizeof(sense_buf),
+                                         transferred_len, scsi_status);
+            return UFS_REQUEST_SUCCESS;
+        }
+        scsi_build_sense(sense_buf, SENSE_CODE(INVALID_OPCODE));
+        scsi_status = CHECK_CONDITION;
+        break;
     case START_STOP:
         /* TODO: Revisit it when Power Management is implemented */
         if (lun == UFS_UPIU_UFS_DEVICE_WLUN) {
diff --git a/hw/ufs/meson.build b/hw/ufs/meson.build
index a84627b96a..9e5e6ad913 100644
--- a/hw/ufs/meson.build
+++ b/hw/ufs/meson.build
@@ -1,3 +1,3 @@
-system_ss.add(when: 'CONFIG_UFS', if_true: files('ufs.c', 'lu.c'))
+system_ss.add(when: 'CONFIG_UFS', if_true: files('ufs.c', 'lu.c', 'rpmb.c'))
 system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs-pci.c'))
 system_ss.add(when: 'CONFIG_UFS_SYSBUS', if_true: files('ufs-sysbus.c'))
diff --git a/hw/ufs/rpmb.c b/hw/ufs/rpmb.c
new file mode 100644
index 0000000000..3032e0e098
--- /dev/null
+++ b/hw/ufs/rpmb.c
@@ -0,0 +1,170 @@
+/*
+ * QEMU UFS RPMB Well-Known Logical Unit Emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "ufs.h"
+
+static void ufs_rpmb_handle_req(UfsHc *u, UfsRpmbRegion *r,
+                                const UfsRpmbFrame *frames,
+                                uint32_t nframes)
+{
+    const UfsRpmbFrame *req = &frames[0];
+    uint16_t req_type = lduw_be_p(&req->req_resp);
+    uint16_t result = UFS_RPMB_RES_GENERAL_FAILURE;
+
+    if (req_type == UFS_RPMB_REQ_READ_RESULT) {
+        trace_ufs_rpmb_request(req_type, lduw_be_p(&r->resp.result));
+        return;
+    }
+
+    r->read_pending = false;
+    memset(&r->resp, 0, sizeof(r->resp));
+
+    switch (req_type) {
+    default:
+        r->resp.req_resp = cpu_to_be16(req_type);
+        result = UFS_RPMB_RES_GENERAL_FAILURE;
+        break;
+    }
+
+    if (r->write_counter == 0xffffffff) {
+        result |= UFS_RPMB_RES_COUNT_EXPIRED;
+    }
+    r->resp.result = cpu_to_be16(result);
+
+    trace_ufs_rpmb_request(req_type, result);
+}
+
+static bool ufs_rpmb_parse_cdb(const uint8_t *cdb, uint8_t *region_out,
+                               uint32_t *xfer_len_out, uint8_t *sense_buf)
+{
+    uint8_t sec_proto = cdb[1];
+    uint16_t spsp = lduw_be_p(&cdb[2]);
+    uint8_t region = (spsp >> 8) & 0xff;
+    uint8_t proto_id = spsp & 0xff;
+    bool inc_512 = (cdb[4] & 0x80) != 0;
+
+    /*
+     * JESD220H Section 12.4.6.1 (Table 12.14) and Section 12.4.7.1/.2
+     * (Tables 12.16/12.17; JESD220F Tables 12.21/12.22): when SECURITY
+     * PROTOCOL is ECh, INC_512 (CDB byte 4 bit 7) shall be set to 0;
+     * otherwise terminate with CHECK CONDITION / ILLEGAL REQUEST /
+     * INVALID FIELD IN CDB.
+     */
+    if (sec_proto != UFS_RPMB_SEC_PROTOCOL ||
+        proto_id != UFS_RPMB_SEC_PROTOCOL_ID ||
+        region >= UFS_RPMB_MAX_REGIONS || inc_512) {
+        scsi_build_sense(sense_buf, SENSE_CODE(INVALID_FIELD));
+        return false;
+    }
+
+    *region_out = region;
+    *xfer_len_out = ldl_be_p(&cdb[6]);
+    return true;
+}
+
+void ufs_rpmb_reset(UfsHc *u)
+{
+    for (int i = 0; i < UFS_RPMB_MAX_REGIONS; i++) {
+        u->rpmb[i].read_pending = false;
+        memset(&u->rpmb[i].resp, 0, sizeof(u->rpmb[i].resp));
+    }
+}
+
+UfsReqResult ufs_rpmb_sec_out(UfsHc *u, UfsRequest *req, uint8_t *scsi_status,
+                              uint8_t *sense_buf, uint32_t *transferred_len)
+{
+    g_autofree UfsRpmbFrame *frames = NULL;
+    dma_addr_t residual = 0;
+    uint8_t region;
+    uint32_t xfer_len;
+    uint32_t nframes;
+    uint8_t max_rw = u->geometry_desc.rpmb_read_write_size;
+    uint16_t req_type;
+
+    *transferred_len = 0;
+    if (!ufs_rpmb_parse_cdb(req->req_upiu.sc.cdb, &region, &xfer_len,
+                            sense_buf)) {
+        *scsi_status = CHECK_CONDITION;
+        return UFS_REQUEST_SUCCESS;
+    }
+    if (xfer_len == 0 || (xfer_len % UFS_RPMB_FRAME_LEN) != 0 ||
+        xfer_len > (uint32_t)max_rw * UFS_RPMB_FRAME_LEN) {
+        scsi_build_sense(sense_buf, SENSE_CODE(INVALID_FIELD));
+        *scsi_status = CHECK_CONDITION;
+        return UFS_REQUEST_SUCCESS;
+    }
+    if (!ufs_fit_data_buffer(req, xfer_len)) {
+        return UFS_REQUEST_MISMATCH_DATA_BUF;
+    }
+
+    nframes = xfer_len / UFS_RPMB_FRAME_LEN;
+    frames = g_new0(UfsRpmbFrame, nframes);
+    if (dma_buf_write(frames, xfer_len, &residual, req->sg,
+                      MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
+        return UFS_REQUEST_FAIL;
+    }
+
+    req_type = lduw_be_p(&frames[0].req_resp);
+    if (req_type != UFS_RPMB_REQ_AUTH_DATA_WRITE && nframes != 1) {
+        scsi_build_sense(sense_buf, SENSE_CODE(INVALID_FIELD));
+        *scsi_status = CHECK_CONDITION;
+        return UFS_REQUEST_SUCCESS;
+    }
+
+    ufs_rpmb_handle_req(u, &u->rpmb[region], frames, nframes);
+    *transferred_len = xfer_len - residual;
+    *scsi_status = GOOD;
+    return UFS_REQUEST_SUCCESS;
+}
+
+UfsReqResult ufs_rpmb_sec_in(UfsHc *u, UfsRequest *req, uint8_t *scsi_status,
+                             uint8_t *sense_buf, uint32_t *transferred_len)
+{
+    UfsRpmbRegion *r;
+    dma_addr_t residual = 0;
+    uint8_t region;
+    uint32_t alloc_len;
+    uint32_t in_frames;
+    uint8_t max_rw = u->geometry_desc.rpmb_read_write_size;
+
+    *transferred_len = 0;
+    if (!ufs_rpmb_parse_cdb(req->req_upiu.sc.cdb, &region, &alloc_len,
+                            sense_buf)) {
+        *scsi_status = CHECK_CONDITION;
+        return UFS_REQUEST_SUCCESS;
+    }
+    if (alloc_len < UFS_RPMB_FRAME_LEN ||
+        (alloc_len % UFS_RPMB_FRAME_LEN) != 0 ||
+        alloc_len > (uint32_t)max_rw * UFS_RPMB_FRAME_LEN) {
+        scsi_build_sense(sense_buf, SENSE_CODE(INVALID_FIELD));
+        *scsi_status = CHECK_CONDITION;
+        return UFS_REQUEST_SUCCESS;
+    }
+
+    r = &u->rpmb[region];
+    in_frames = alloc_len / UFS_RPMB_FRAME_LEN;
+
+    if (in_frames != 1) {
+        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;
+    }
+
+    if (dma_buf_read(&r->resp, 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;
+}
diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index e1d29d6670..cb7aaa765f 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -67,3 +67,6 @@ ufs_zbc_report_zones(uint8_t lun, uint64_t lba, uint32_t 
alloc_len, uint8_t opti
 ufs_zbc_zone_op(uint8_t lun, uint8_t action, uint64_t zone_id, bool all) "lun 
0x%"PRIx8", action 0x%"PRIx8", zone id %"PRIu64", all %d"
 ufs_zbc_write_rejected(uint8_t lun, uint64_t lba, uint32_t len, uint64_t wp, 
uint8_t cond, uint8_t asc, uint8_t ascq) "lun 0x%"PRIx8", lba %"PRIu64", len 
%"PRIu32", wp %"PRIu64", cond 0x%"PRIx8", asc 0x%"PRIx8", ascq 0x%"PRIx8""
 ufs_zbc_write_undone(uint8_t lun, uint64_t zone_start, uint64_t wp) "lun 
0x%"PRIx8", zone start %"PRIu64", wp back to %"PRIu64""
+
+# rpmb.c
+ufs_rpmb_request(uint16_t req, uint16_t result) "req 0x%04"PRIx16", result 
0x%04"PRIx16""
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index f8da7c9554..bc0f2ce4eb 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -961,6 +961,7 @@ static void ufs_hce_reset(UfsHc *u)
             scsi_device_purge_requests(u->lus[i]->scsi_dev, SENSE_CODE(RESET));
         }
     }
+    ufs_rpmb_reset(u);
 
     /* 3. Reset standard request list slots, doorbells, and status registers */
     for (i = 0; i < u->params.nutrs; i++) {
@@ -2325,9 +2326,18 @@ static QueryRespCode ufs_exec_query_attr(UfsRequest 
*req, int op)
 
 static const RpmbUnitDescriptor rpmb_unit_desc = {
     .length = sizeof(RpmbUnitDescriptor),
-    .descriptor_idn = 2,
+    .descriptor_idn = UFS_QUERY_DESC_IDN_UNIT,
     .unit_index = UFS_UPIU_RPMB_WLUN,
-    .lu_enable = 0,
+    .lu_enable = 0x01,
+    .memory_type = 0x0f,
+    .rpmb_region_enable = (1U << UFS_RPMB_MAX_REGIONS) - 1,
+    .logical_block_size = UFS_RPMB_LOGICAL_BLOCK_SIZE,
+    .rpmb_region_size = {
+        UFS_RPMB_REGION_UNITS,
+        UFS_RPMB_REGION_UNITS,
+        UFS_RPMB_REGION_UNITS,
+        UFS_RPMB_REGION_UNITS,
+    },
 };
 
 static QueryRespCode ufs_read_unit_desc(UfsRequest *req)
@@ -2342,7 +2352,11 @@ static QueryRespCode ufs_read_unit_desc(UfsRequest *req)
     }
 
     if (lun == UFS_UPIU_RPMB_WLUN) {
-        memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.length);
+        RpmbUnitDescriptor desc = rpmb_unit_desc;
+
+        desc.logical_block_count = cpu_to_be64(UFS_RPMB_TOTAL_BLOCKS);
+        desc.phy_mem_resource_count = cpu_to_be64(UFS_RPMB_TOTAL_BLOCKS);
+        memcpy(&req->rsp_upiu.qr.data, &desc, desc.length);
     } else {
         memcpy(&req->rsp_upiu.qr.data, &u->lus[lun]->unit_desc,
                sizeof(u->lus[lun]->unit_desc));
@@ -3162,6 +3176,7 @@ static void ufs_init_hc(UfsHc *u)
     /* TODO: Revisit it when Power Management is implemented */
     u->device_desc.init_power_mode = 0x01; /* Active Mode */
     u->device_desc.high_priority_lun = 0x7F; /* Same Priority */
+    u->device_desc.security_lu = 0x01;
     u->device_desc.spec_version = cpu_to_be16(UFS_SPEC_VER);
     u->device_desc.manufacturer_name = 0x00;
     u->device_desc.product_name = 0x01;
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index a1e63bd0c8..5120ade082 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -245,6 +245,24 @@ typedef struct UfsWb {
     uint64_t pinned_total_written_bytes;
 } UfsWb;
 
+/* Each region unit in bRPMBRegionXSize is 128 KiB = 512 x 256 B blocks */
+#define UFS_RPMB_REGION_UNITS       32
+#define UFS_RPMB_REGION_BLOCKS      (UFS_RPMB_REGION_UNITS * \
+                                     UFS_RPMB_BLOCKS_PER_UNIT)
+#define UFS_RPMB_REGION_BYTES       (UFS_RPMB_REGION_BLOCKS * \
+                                     UFS_RPMB_DATA_LEN)
+#define UFS_RPMB_TOTAL_BLOCKS       (UFS_RPMB_MAX_REGIONS * \
+                                     UFS_RPMB_REGION_BLOCKS)
+
+typedef struct UfsRpmbRegion {
+    uint8_t key[UFS_RPMB_KEY_MAC_LEN];
+    uint8_t *storage;
+    uint32_t write_counter;
+    bool key_set;
+    bool read_pending;
+    UfsRpmbFrame resp;
+} UfsRpmbRegion;
+
 typedef struct UfsHc {
     DeviceState *dev;
     AddressSpace *dma_as;
@@ -262,6 +280,7 @@ typedef struct UfsHc {
     UfsLu dev_wlu;
     UfsLu boot_wlu;
     UfsLu rpmb_wlu;
+    UfsRpmbRegion rpmb[UFS_RPMB_MAX_REGIONS];
     DeviceDescriptor device_desc;
     GeometryDescriptor geometry_desc;
     Attributes attributes;
@@ -412,6 +431,11 @@ void ufs_complete_req(UfsRequest *req, UfsReqResult 
req_result);
 bool ufs_fit_data_buffer(UfsRequest *req, uint64_t len);
 void ufs_wb_update_avail_buffer(UfsHc *u);
 void ufs_init_wlu(UfsLu *wlu, uint8_t wlun);
+UfsReqResult ufs_rpmb_sec_in(UfsHc *u, UfsRequest *req, uint8_t *scsi_status,
+                             uint8_t *sense_buf, uint32_t *transferred_len);
+void ufs_rpmb_reset(UfsHc *u);
+UfsReqResult ufs_rpmb_sec_out(UfsHc *u, UfsRequest *req, uint8_t *scsi_status,
+                              uint8_t *sense_buf, uint32_t *transferred_len);
 bool ufs_realize(UfsHc *u, DeviceState *dev, AddressSpace *dma_as,
                  Error **errp);
 void ufs_unrealize(UfsHc *u);
-- 
2.56.0.307.g5fe7bd680c-goog


Reply via email to