Message ID | 20230616065825epcms2p5682ea0edcd1fb0366a344708bea875cb@epcms2p5 (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | hw/ufs: Add Universal Flash Storage (UFS) support | expand |
On Fri, Jun 16, 2023 at 03:58:25PM +0900, Jeuk Kim wrote: > This commit makes the UFS device support query > and nop out transfer requests. > > The next patch would be support for UFS logical > unit and scsi command transfer request. > > Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com> > --- > hw/ufs/ufs.c | 968 ++++++++++++++++++++++++++++++++++++++++++++++++++- > hw/ufs/ufs.h | 45 +++ > 2 files changed, 1012 insertions(+), 1 deletion(-) > > diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c > index 9dba1073a8..10ecc8cd7b 100644 > --- a/hw/ufs/ufs.c > +++ b/hw/ufs/ufs.c > @@ -19,6 +19,233 @@ > #define UFS_MAX_NUTRS 32 > #define UFS_MAX_NUTMRS 8 > > +static MemTxResult ufs_addr_read(UfsHc *u, hwaddr addr, void *buf, int size) > +{ > + uint32_t cap = ldl_le_p(&u->reg.cap); > + hwaddr hi = addr + size - 1; > + > + if (hi < addr) { > + return MEMTX_DECODE_ERROR; > + } > + > + if (!FIELD_EX32(cap, CAP, 64AS) && (hi >> 32)) { > + return MEMTX_DECODE_ERROR; > + } > + > + return pci_dma_read(PCI_DEVICE(u), addr, buf, size); > +} > + > +static MemTxResult ufs_addr_write(UfsHc *u, hwaddr addr, const void *buf, > + int size) > +{ > + uint32_t cap = ldl_le_p(&u->reg.cap); > + hwaddr hi = addr + size - 1; > + if (hi < addr) { > + return MEMTX_DECODE_ERROR; > + } > + > + if (!FIELD_EX32(cap, CAP, 64AS) && (hi >> 32)) { > + return MEMTX_DECODE_ERROR; > + } > + > + return pci_dma_write(PCI_DEVICE(u), addr, buf, size); > +} > + > +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result); > + > +static inline hwaddr ufs_get_utrd_addr(UfsHc *u, uint32_t slot) > +{ > + uint32_t utrlba = ldl_le_p(&u->reg.utrlba); > + uint32_t utrlbau = ldl_le_p(&u->reg.utrlbau); > + hwaddr utrl_base_addr = (((hwaddr)utrlbau) << 32) + utrlba; > + hwaddr utrd_addr = utrl_base_addr + slot * sizeof(UtpTransferReqDesc); > + > + return utrd_addr; > +} > + > +static inline hwaddr ufs_get_req_upiu_base_addr(const UtpTransferReqDesc *utrd) > +{ > + uint32_t cmd_desc_base_addr_lo = > + le32_to_cpu(utrd->command_desc_base_addr_lo); > + uint32_t cmd_desc_base_addr_hi = > + le32_to_cpu(utrd->command_desc_base_addr_hi); > + > + return (((hwaddr)cmd_desc_base_addr_hi) << 32) + cmd_desc_base_addr_lo; > +} > + > +static inline hwaddr ufs_get_rsp_upiu_base_addr(const UtpTransferReqDesc *utrd) > +{ > + hwaddr req_upiu_base_addr = ufs_get_req_upiu_base_addr(utrd); > + uint32_t rsp_upiu_byte_off = > + le16_to_cpu(utrd->response_upiu_offset) * sizeof(uint32_t); > + return req_upiu_base_addr + rsp_upiu_byte_off; > +} > + > +static MemTxResult ufs_dma_read_utrd(UfsRequest *req) > +{ > + UfsHc *u = req->hc; > + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); > + MemTxResult ret; > + > + ret = ufs_addr_read(u, utrd_addr, &req->utrd, sizeof(req->utrd)); > + if (ret) { > + trace_ufs_err_dma_read_utrd(req->slot, utrd_addr); > + } > + return ret; > +} > + > +static MemTxResult ufs_dma_read_req_upiu(UfsRequest *req) > +{ > + UfsHc *u = req->hc; > + hwaddr req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); > + UtpUpiuReq *req_upiu = &req->req_upiu; > + uint32_t copy_size; > + uint16_t data_segment_length; > + MemTxResult ret; > + > + /* > + * To know the size of the req_upiu, we need to read the > + * data_segment_length in the header first. > + */ > + ret = ufs_addr_read(u, req_upiu_base_addr, &req_upiu->header, > + sizeof(UtpUpiuHeader)); > + if (ret) { > + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); > + return ret; > + } > + data_segment_length = be16_to_cpu(req_upiu->header.data_segment_length); > + > + copy_size = sizeof(UtpUpiuHeader) + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + > + data_segment_length; > + > + ret = ufs_addr_read(u, req_upiu_base_addr, &req->req_upiu, copy_size); > + if (ret) { > + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); > + } > + return ret; > +} > + > +static MemTxResult ufs_dma_read_prdt(UfsRequest *req) > +{ > + UfsHc *u = req->hc; > + uint16_t prdt_len = le16_to_cpu(req->utrd.prd_table_length); > + uint16_t prdt_byte_off = > + le16_to_cpu(req->utrd.prd_table_offset) * sizeof(uint32_t); > + uint32_t prdt_size = prdt_len * sizeof(UfshcdSgEntry); > + UfshcdSgEntry *prd_entries; > + hwaddr req_upiu_base_addr, prdt_base_addr; > + int err; > + > + assert(!req->sg); > + > + if (prdt_len == 0) { > + return MEMTX_OK; > + } > + > + prd_entries = g_new(UfshcdSgEntry, prdt_size); > + if (!prd_entries) { > + trace_ufs_err_memory_allocation(); > + return MEMTX_ERROR; > + } > + > + req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); > + prdt_base_addr = req_upiu_base_addr + prdt_byte_off; > + > + err = ufs_addr_read(u, prdt_base_addr, prd_entries, prdt_size); > + if (err) { > + trace_ufs_err_dma_read_prdt(req->slot, prdt_base_addr); > + return err; prd_entries is leaked. I suggest using g_autofree to avoid manual g_free() calls in return paths. > + } > + > + req->sg = g_malloc0(sizeof(QEMUSGList)); > + if (!req->sg) { > + trace_ufs_err_memory_allocation(); > + g_free(prd_entries); > + return MEMTX_ERROR; > + } > + pci_dma_sglist_init(req->sg, PCI_DEVICE(u), prdt_len); > + > + for (uint16_t i = 0; i < prdt_len; ++i) { > + hwaddr data_dma_addr = le64_to_cpu(prd_entries[i].addr); > + int32_t data_byte_count = le32_to_cpu(prd_entries[i].size) + 1; > + qemu_sglist_add(req->sg, data_dma_addr, data_byte_count); > + } > + g_free(prd_entries); > + > + return MEMTX_OK; > +} > + > +static MemTxResult ufs_dma_read_upiu(UfsRequest *req) > +{ > + MemTxResult ret; > + > + ret = ufs_dma_read_utrd(req); > + if (ret) { > + return ret; > + } > + > + ret = ufs_dma_read_req_upiu(req); > + if (ret) { > + return ret; > + } > + > + ret = ufs_dma_read_prdt(req); > + if (ret) { > + return ret; > + } > + > + return 0; > +} > + > +static MemTxResult ufs_dma_write_utrd(UfsRequest *req) > +{ > + UfsHc *u = req->hc; > + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); > + MemTxResult ret; > + > + ret = ufs_addr_write(u, utrd_addr, &req->utrd, sizeof(req->utrd)); > + if (ret) { > + trace_ufs_err_dma_write_utrd(req->slot, utrd_addr); > + } > + return ret; > +} > + > +static MemTxResult ufs_dma_write_rsp_upiu(UfsRequest *req) > +{ > + UfsHc *u = req->hc; > + hwaddr rsp_upiu_base_addr = ufs_get_rsp_upiu_base_addr(&req->utrd); > + uint32_t rsp_upiu_byte_len = > + le16_to_cpu(req->utrd.response_upiu_length) * sizeof(uint32_t); > + uint16_t data_segment_length = > + be16_to_cpu(req->rsp_upiu.header.data_segment_length); > + uint32_t copy_size = sizeof(UtpUpiuHeader) + > + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + > + data_segment_length; > + MemTxResult ret; > + > + if (copy_size > rsp_upiu_byte_len) { > + copy_size = rsp_upiu_byte_len; > + } > + > + ret = ufs_addr_write(u, rsp_upiu_base_addr, &req->rsp_upiu, copy_size); > + if (ret) { > + trace_ufs_err_dma_write_rsp_upiu(req->slot, rsp_upiu_base_addr); > + } > + return ret; > +} > + > +static MemTxResult ufs_dma_write_upiu(UfsRequest *req) > +{ > + MemTxResult ret; > + > + ret = ufs_dma_write_rsp_upiu(req); > + if (ret) { > + return ret; > + } > + > + return ufs_dma_write_utrd(req); > +} > + > static void ufs_irq_check(UfsHc *u) > { > PCIDevice *pci = PCI_DEVICE(u); > @@ -34,6 +261,36 @@ static void ufs_irq_check(UfsHc *u) > } > } > > +static void ufs_process_db(UfsHc *u, uint64_t val) > +{ > + uint32_t slot; > + uint32_t nutrs = u->params.nutrs; > + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); > + UfsRequest *req; > + > + val &= ~utrldbr; > + if (!val) { > + return; > + } > + stl_le_p(&u->reg.utrldbr, utrldbr | val); > + > + slot = find_first_bit(&val, nutrs); > + > + while (slot < nutrs) { > + req = &u->req_list[slot]; > + if (req->state != UFS_REQUEST_IDLE) { > + trace_ufs_err_utrl_slot_busy(req->slot); > + return; > + } > + > + trace_ufs_process_db(slot); > + req->state = UFS_REQUEST_READY; > + slot = find_next_bit(&val, nutrs, slot + 1); > + } > + > + qemu_bh_schedule(u->doorbell_bh); > +} > + > static void ufs_process_uiccmd(UfsHc *u, uint32_t val) > { > uint32_t is = ldl_le_p(&u->reg.is); > @@ -85,6 +342,7 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) > uint32_t is = ldl_le_p(&u->reg.is); > uint32_t hcs = ldl_le_p(&u->reg.hcs); > uint32_t hce = ldl_le_p(&u->reg.hce); > + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); > uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); > uint32_t utrlba, utmrlba; > > @@ -119,7 +377,9 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) > stl_le_p(&u->reg.utrlbau, data); > break; > case A_UTRLDBR: > - /* Not yet supported */ > + ufs_process_db(u, data); > + utrldbr |= data; > + stl_le_p(&u->reg.utrldbr, utrldbr); > break; > case A_UTRLRSR: > stl_le_p(&u->reg.utrlrsr, data); > @@ -199,6 +459,632 @@ static const MemoryRegionOps ufs_mmio_ops = { > }, > }; > > +static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, > + uint8_t flags, uint8_t response, > + uint8_t scsi_status, > + uint16_t data_segment_length) > +{ > + memcpy(&req->rsp_upiu.header, &req->req_upiu.header, sizeof(UtpUpiuHeader)); > + req->rsp_upiu.header.trans_type = trans_type; > + req->rsp_upiu.header.flags = flags; > + req->rsp_upiu.header.response = response; > + req->rsp_upiu.header.scsi_status = scsi_status; > + req->rsp_upiu.header.data_segment_length = cpu_to_be16(data_segment_length); > +} > + > +static UfsReqResult ufs_exec_nop_cmd(UfsRequest *req) > +{ > + trace_ufs_exec_nop_cmd(req->slot); > + ufs_build_upiu_header(req, UPIU_TRANSACTION_NOP_IN, 0, 0, 0, 0); > + return UFS_REQUEST_SUCCESS; > +} > + > +/* > + * This defines the permission of flags based on their IDN. There are some > + * things that are declared read-only, which is inconsistent with the ufs spec, > + * because we want to return an error for features that are not yet supported. > + */ > +static const int flag_permission[QUERY_FLAG_IDN_COUNT] = { > + [QUERY_FLAG_IDN_FDEVICEINIT] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET, > + /* Write protection is not supported */ > + [QUERY_FLAG_IDN_PERMANENT_WPE] = UFS_QUERY_FLAG_READ, > + [QUERY_FLAG_IDN_PWR_ON_WPE] = UFS_QUERY_FLAG_READ, > + [QUERY_FLAG_IDN_BKOPS_EN] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | > + UFS_QUERY_FLAG_CLEAR | UFS_QUERY_FLAG_TOGGLE, > + [QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE] = > + UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | UFS_QUERY_FLAG_CLEAR | > + UFS_QUERY_FLAG_TOGGLE, > + /* Purge Operation is not supported */ > + [QUERY_FLAG_IDN_PURGE_ENABLE] = UFS_QUERY_FLAG_NONE, > + /* Refresh Operation is not supported */ > + [QUERY_FLAG_IDN_REFRESH_ENABLE] = UFS_QUERY_FLAG_NONE, > + /* Physical Resource Removal is not supported */ > + [QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL] = UFS_QUERY_FLAG_READ, > + [QUERY_FLAG_IDN_BUSY_RTC] = UFS_QUERY_FLAG_READ, > + [QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE] = UFS_QUERY_FLAG_READ, > + /* Write Booster is not supported */ > + [QUERY_FLAG_IDN_WB_EN] = UFS_QUERY_FLAG_READ, > + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN] = UFS_QUERY_FLAG_READ, > + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8] = UFS_QUERY_FLAG_READ, > +}; > + > +static inline QueryRespCode ufs_flag_check_idn_valid(uint8_t idn, int op) > +{ > + if (idn >= QUERY_FLAG_IDN_COUNT) { > + return QUERY_RESULT_INVALID_IDN; > + } > + > + if (!(flag_permission[idn] & op)) { > + if (op == UFS_QUERY_FLAG_READ) { > + trace_ufs_err_query_flag_not_readable(idn); > + return QUERY_RESULT_NOT_READABLE; > + } > + trace_ufs_err_query_flag_not_writable(idn); > + return QUERY_RESULT_NOT_WRITEABLE; > + } > + > + return QUERY_RESULT_SUCCESS; > +} > + > +static const int attr_permission[QUERY_ATTR_IDN_COUNT] = { > + /* booting is not supported */ > + [QUERY_ATTR_IDN_BOOT_LU_EN] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_POWER_MODE] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_ACTIVE_ICC_LVL] = > + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_OOO_DATA_EN] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_BKOPS_STATUS] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_PURGE_STATUS] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_MAX_DATA_IN] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_MAX_DATA_OUT] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_DYN_CAP_NEEDED] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_REF_CLK_FREQ] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_CONF_DESC_LOCK] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_MAX_NUM_OF_RTT] = > + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_EE_CONTROL] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_EE_STATUS] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_SECONDS_PASSED] = UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_CNTX_CONF] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_FFU_STATUS] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_PSA_STATE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_PSA_DATA_SIZE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, > + [QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_CASE_ROUGH_TEMP] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_HIGH_TEMP_BOUND] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_LOW_TEMP_BOUND] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_THROTTLING_STATUS] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_WB_FLUSH_STATUS] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, > + /* refresh operation is not supported */ > + [QUERY_ATTR_IDN_REFRESH_STATUS] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_REFRESH_FREQ] = UFS_QUERY_ATTR_READ, > + [QUERY_ATTR_IDN_REFRESH_UNIT] = UFS_QUERY_ATTR_READ, > +}; > + > +static inline QueryRespCode ufs_attr_check_idn_valid(uint8_t idn, int op) > +{ > + if (idn >= QUERY_ATTR_IDN_COUNT) { > + return QUERY_RESULT_INVALID_IDN; > + } > + > + if (!(attr_permission[idn] & op)) { > + if (op == UFS_QUERY_ATTR_READ) { > + trace_ufs_err_query_attr_not_readable(idn); > + return QUERY_RESULT_NOT_READABLE; > + } > + trace_ufs_err_query_attr_not_writable(idn); > + return QUERY_RESULT_NOT_WRITEABLE; > + } > + > + return QUERY_RESULT_SUCCESS; > +} > + > +static QueryRespCode ufs_exec_query_flag(UfsRequest *req, int op) > +{ > + UfsHc *u = req->hc; > + uint8_t idn = req->req_upiu.qr.idn; > + uint32_t value; > + QueryRespCode ret; > + > + ret = ufs_flag_check_idn_valid(idn, op); > + if (ret) { > + return ret; > + } > + > + value = *(((uint8_t *)&u->flags) + idn); > + if (idn == QUERY_FLAG_IDN_FDEVICEINIT) { > + value = 0; > + } else if (op == UFS_QUERY_FLAG_READ) { > + value = *(((uint8_t *)&u->flags) + idn); This value was already loaded a few lines above. > + } else if (op == UFS_QUERY_FLAG_SET) { > + value = 1; > + } else if (op == UFS_QUERY_FLAG_CLEAR) { > + value = 0; > + } else if (op == UFS_QUERY_FLAG_TOGGLE) { > + value = !value; > + } else { > + trace_ufs_err_query_invalid_opcode(op); > + return QUERY_RESULT_INVALID_OPCODE; > + } > + > + *(((uint8_t *)&u->flags) + idn) = value; > + req->rsp_upiu.qr.value = cpu_to_be32(value); > + return QUERY_RESULT_SUCCESS; > +} > + > +static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) > +{ > + switch (idn) { > + case QUERY_ATTR_IDN_BOOT_LU_EN: > + return u->attributes.boot_lun_en; > + case QUERY_ATTR_IDN_POWER_MODE: > + return u->attributes.current_power_mode; > + case QUERY_ATTR_IDN_ACTIVE_ICC_LVL: > + return u->attributes.active_icc_level; > + case QUERY_ATTR_IDN_OOO_DATA_EN: > + return u->attributes.out_of_order_data_en; > + case QUERY_ATTR_IDN_BKOPS_STATUS: > + return u->attributes.background_op_status; > + case QUERY_ATTR_IDN_PURGE_STATUS: > + return u->attributes.purge_status; > + case QUERY_ATTR_IDN_MAX_DATA_IN: > + return u->attributes.max_data_in_size; > + case QUERY_ATTR_IDN_MAX_DATA_OUT: > + return u->attributes.max_data_out_size; > + case QUERY_ATTR_IDN_DYN_CAP_NEEDED: > + return be32_to_cpu(u->attributes.dyn_cap_needed); > + case QUERY_ATTR_IDN_REF_CLK_FREQ: > + return u->attributes.ref_clk_freq; > + case QUERY_ATTR_IDN_CONF_DESC_LOCK: > + return u->attributes.config_descr_lock; > + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: > + return u->attributes.max_num_of_rtt; > + case QUERY_ATTR_IDN_EE_CONTROL: > + return be16_to_cpu(u->attributes.exception_event_control); > + case QUERY_ATTR_IDN_EE_STATUS: > + return be16_to_cpu(u->attributes.exception_event_status); > + case QUERY_ATTR_IDN_SECONDS_PASSED: > + return be32_to_cpu(u->attributes.seconds_passed); > + case QUERY_ATTR_IDN_CNTX_CONF: > + return be16_to_cpu(u->attributes.context_conf); > + case QUERY_ATTR_IDN_FFU_STATUS: > + return u->attributes.device_ffu_status; > + case QUERY_ATTR_IDN_PSA_STATE: > + return be32_to_cpu(u->attributes.psa_state); > + case QUERY_ATTR_IDN_PSA_DATA_SIZE: > + return u->attributes.psa_data_size; > + case QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME: > + return u->attributes.ref_clk_gating_wait_time; > + case QUERY_ATTR_IDN_CASE_ROUGH_TEMP: > + return u->attributes.device_case_rough_temperaure; > + case QUERY_ATTR_IDN_HIGH_TEMP_BOUND: > + return u->attributes.device_too_high_temp_boundary; > + case QUERY_ATTR_IDN_LOW_TEMP_BOUND: > + return u->attributes.device_too_low_temp_boundary; > + case QUERY_ATTR_IDN_THROTTLING_STATUS: > + return u->attributes.throttling_status; > + case QUERY_ATTR_IDN_WB_FLUSH_STATUS: > + return u->attributes.wb_buffer_flush_status; > + case QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE: > + return u->attributes.available_wb_buffer_size; > + case QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST: > + return u->attributes.wb_buffer_life_time_est; > + case QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE: > + return be32_to_cpu(u->attributes.current_wb_buffer_size); > + case QUERY_ATTR_IDN_REFRESH_STATUS: > + return u->attributes.refresh_status; > + case QUERY_ATTR_IDN_REFRESH_FREQ: > + return u->attributes.refresh_freq; > + case QUERY_ATTR_IDN_REFRESH_UNIT: > + return u->attributes.refresh_unit; > + } > + return 0; > +} > + > +static void ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value) > +{ > + switch (idn) { > + case QUERY_ATTR_IDN_ACTIVE_ICC_LVL: > + u->attributes.active_icc_level = value; > + break; > + case QUERY_ATTR_IDN_MAX_DATA_IN: > + u->attributes.max_data_in_size = value; > + break; > + case QUERY_ATTR_IDN_MAX_DATA_OUT: > + u->attributes.max_data_out_size = value; > + break; > + case QUERY_ATTR_IDN_REF_CLK_FREQ: > + u->attributes.ref_clk_freq = value; > + break; > + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: > + u->attributes.max_num_of_rtt = value; > + break; > + case QUERY_ATTR_IDN_EE_CONTROL: > + u->attributes.exception_event_control = cpu_to_be16(value); > + break; > + case QUERY_ATTR_IDN_SECONDS_PASSED: > + u->attributes.seconds_passed = cpu_to_be32(value); > + break; > + case QUERY_ATTR_IDN_PSA_STATE: > + u->attributes.psa_state = value; > + break; > + case QUERY_ATTR_IDN_PSA_DATA_SIZE: > + u->attributes.psa_data_size = cpu_to_be32(value); > + break; > + } > +} > + > +static QueryRespCode ufs_exec_query_attr(UfsRequest *req, int op) > +{ > + UfsHc *u = req->hc; > + uint8_t idn = req->req_upiu.qr.idn; > + uint32_t value; > + QueryRespCode ret; > + > + ret = ufs_attr_check_idn_valid(idn, op); > + if (ret) { > + return ret; > + } > + > + if (op == UFS_QUERY_ATTR_READ) { > + value = ufs_read_attr_value(u, idn); > + } else { > + value = be32_to_cpu(req->req_upiu.qr.value); > + ufs_write_attr_value(u, idn, value); > + } > + > + req->rsp_upiu.qr.value = cpu_to_be32(value); > + return QUERY_RESULT_SUCCESS; > +} > + > +static const RpmbUnitDescriptor rpmb_unit_desc = { > + .length = sizeof(RpmbUnitDescriptor), > + .descriptor_idn = 2, > + .unit_index = UFS_UPIU_RPMB_WLUN, > + .lu_enable = 0, > +}; > + > +static QueryRespCode ufs_read_unit_desc(UfsRequest *req) > +{ > + uint8_t lun = req->req_upiu.qr.index; > + > + if (lun != UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) { > + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun); > + return QUERY_RESULT_INVALID_INDEX; > + } > + > + if (lun == UFS_UPIU_RPMB_WLUN) { > + memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.length); > + } else { > + /* unit descriptor is not yet supported */ > + return QUERY_RESULT_INVALID_INDEX; > + } > + > + return QUERY_RESULT_SUCCESS; > +} > + > +static const StringDescriptor manufacturer_str_desc = { > + .length = 0x12, > + .descriptor_idn = QUERY_DESC_IDN_STRING, > + .UC = { 'S', 'A', 'M', 'S', 'U', 'N', 'G' }, What is the endianness of these 16-bit characters? I noticed endianness issues in several other places but will not audit the patch exhaustively. Please review all guest-visible fields carefully and add cpu_to_leXX()/cpu_to_beXX() where necessary. > +}; > + > +static const StringDescriptor product_name_str_desc = { > + .length = 0x22, > + .descriptor_idn = QUERY_DESC_IDN_STRING, > + .UC = { 'Q', 'E', 'M', 'U', '-', 'U', 'F', 'S' }, > +}; > + > +static const StringDescriptor product_rev_level_str_desc = { > + .length = 0x0a, > + .descriptor_idn = QUERY_DESC_IDN_STRING, > + .UC = { '0', '0', '0', '1' }, > +}; > + > +static const StringDescriptor null_str_desc = { > + .length = 0x02, > + .descriptor_idn = QUERY_DESC_IDN_STRING, > +}; > + > +static QueryRespCode ufs_read_string_desc(UfsRequest *req) > +{ > + UfsHc *u = req->hc; > + uint8_t index = req->req_upiu.qr.index; > + if (index == u->device_desc.manufacturer_name) { > + memcpy(&req->rsp_upiu.qr.data, &manufacturer_str_desc, > + manufacturer_str_desc.length); > + } else if (index == u->device_desc.product_name) { > + memcpy(&req->rsp_upiu.qr.data, &product_name_str_desc, > + product_name_str_desc.length); > + } else if (index == u->device_desc.serial_number) { > + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); > + } else if (index == u->device_desc.oem_id) { > + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); > + } else if (index == u->device_desc.product_revision_level) { > + memcpy(&req->rsp_upiu.qr.data, &product_rev_level_str_desc, > + product_rev_level_str_desc.length); > + } else { > + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, index); > + return QUERY_RESULT_INVALID_INDEX; > + } > + return QUERY_RESULT_SUCCESS; > +} > + > +static const InterconnectDescriptor interconnect_desc = { > + .length = sizeof(InterconnectDescriptor), > + .descriptor_idn = QUERY_DESC_IDN_INTERCONNECT, > + .bcd_unipro_version = 0x180, > + .bcd_mphy_version = 0x410, What is the endianness of these two 16-bit fields? > +}; > + > +static QueryRespCode ufs_read_desc(UfsRequest *req) > +{ > + UfsHc *u = req->hc; > + QueryRespCode status; > + uint8_t idn = req->req_upiu.qr.idn; > + uint16_t length = be16_to_cpu(req->req_upiu.qr.length); > + > + switch (idn) { > + case QUERY_DESC_IDN_DEVICE: > + memcpy(&req->rsp_upiu.qr.data, &u->device_desc, sizeof(u->device_desc)); > + status = QUERY_RESULT_SUCCESS; > + break; > + case QUERY_DESC_IDN_UNIT: > + status = ufs_read_unit_desc(req); > + break; > + case QUERY_DESC_IDN_GEOMETRY: > + memcpy(&req->rsp_upiu.qr.data, &u->geometry_desc, > + sizeof(u->geometry_desc)); > + status = QUERY_RESULT_SUCCESS; > + break; > + case QUERY_DESC_IDN_INTERCONNECT: { > + memcpy(&req->rsp_upiu.qr.data, &interconnect_desc, > + sizeof(interconnect_desc)); > + status = QUERY_RESULT_SUCCESS; > + break; > + } > + case QUERY_DESC_IDN_STRING: > + status = ufs_read_string_desc(req); > + break; > + case QUERY_DESC_IDN_POWER: > + /* mocking of power descriptor is not supported */ > + memset(&req->rsp_upiu.qr.data, 0, sizeof(PowerParametersDescriptor)); > + req->rsp_upiu.qr.data[0] = sizeof(PowerParametersDescriptor); > + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_POWER; > + status = QUERY_RESULT_SUCCESS; > + break; > + case QUERY_DESC_IDN_HEALTH: > + /* mocking of health descriptor is not supported */ > + memset(&req->rsp_upiu.qr.data, 0, sizeof(DeviceHealthDescriptor)); > + req->rsp_upiu.qr.data[0] = sizeof(DeviceHealthDescriptor); > + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_HEALTH; > + status = QUERY_RESULT_SUCCESS; > + break; > + default: > + length = 0; > + trace_ufs_err_query_invalid_idn(req->req_upiu.qr.opcode, idn); > + status = QUERY_RESULT_INVALID_IDN; > + } > + > + if (length > req->rsp_upiu.qr.data[0]) { > + length = req->rsp_upiu.qr.data[0]; > + } > + req->rsp_upiu.qr.opcode = req->req_upiu.qr.opcode; > + req->rsp_upiu.qr.idn = req->req_upiu.qr.idn; > + req->rsp_upiu.qr.index = req->req_upiu.qr.index; > + req->rsp_upiu.qr.selector = req->req_upiu.qr.selector; > + req->rsp_upiu.qr.length = cpu_to_be16(length); > + > + return status; > +} > + > +static QueryRespCode ufs_exec_query_read(UfsRequest *req) > +{ > + QueryRespCode status; > + switch (req->req_upiu.qr.opcode) { > + case UPIU_QUERY_OPCODE_NOP: > + status = QUERY_RESULT_SUCCESS; > + break; > + case UPIU_QUERY_OPCODE_READ_DESC: > + status = ufs_read_desc(req); > + break; > + case UPIU_QUERY_OPCODE_READ_ATTR: > + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_READ); > + break; > + case UPIU_QUERY_OPCODE_READ_FLAG: > + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_READ); > + break; > + default: > + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); > + status = QUERY_RESULT_INVALID_OPCODE; > + break; > + } > + > + return status; > +} > + > +static QueryRespCode ufs_exec_query_write(UfsRequest *req) > +{ > + QueryRespCode status; > + switch (req->req_upiu.qr.opcode) { > + case UPIU_QUERY_OPCODE_NOP: > + status = QUERY_RESULT_SUCCESS; > + break; > + case UPIU_QUERY_OPCODE_WRITE_DESC: > + /* write descriptor is not supported */ > + status = QUERY_RESULT_NOT_WRITEABLE; > + break; > + case UPIU_QUERY_OPCODE_WRITE_ATTR: > + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_WRITE); > + break; > + case UPIU_QUERY_OPCODE_SET_FLAG: > + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_SET); > + break; > + case UPIU_QUERY_OPCODE_CLEAR_FLAG: > + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_CLEAR); > + break; > + case UPIU_QUERY_OPCODE_TOGGLE_FLAG: > + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_TOGGLE); > + break; > + default: > + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); > + status = QUERY_RESULT_INVALID_OPCODE; > + break; > + } > + > + return status; > +} > + > +static UfsReqResult ufs_exec_query_cmd(UfsRequest *req) > +{ > + uint8_t query_func = req->req_upiu.header.query_func; > + uint16_t data_segment_length; > + QueryRespCode status; > + > + trace_ufs_exec_query_cmd(req->slot, req->req_upiu.qr.opcode); > + if (query_func == UPIU_QUERY_FUNC_STANDARD_READ_REQUEST) { > + status = ufs_exec_query_read(req); > + } else if (query_func == UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST) { > + status = ufs_exec_query_write(req); > + } else { > + status = QUERY_RESULT_GENERAL_FAILURE; > + } > + > + data_segment_length = be16_to_cpu(req->rsp_upiu.qr.length); > + ufs_build_upiu_header(req, UPIU_TRANSACTION_QUERY_RSP, 0, status, 0, > + data_segment_length); > + > + if (status != QUERY_RESULT_SUCCESS) { > + return UFS_REQUEST_ERROR; > + } > + return UFS_REQUEST_SUCCESS; > +} > + > +static void ufs_exec_req(UfsRequest *req) > +{ > + UfsReqResult req_result; > + > + if (ufs_dma_read_upiu(req)) { > + return; > + } > + > + switch (req->req_upiu.header.trans_type) { > + case UPIU_TRANSACTION_NOP_OUT: > + req_result = ufs_exec_nop_cmd(req); > + break; > + case UPIU_TRANSACTION_COMMAND: > + /* Not yet implemented */ > + req_result = UFS_REQUEST_ERROR; > + break; > + case UPIU_TRANSACTION_QUERY_REQ: > + req_result = ufs_exec_query_cmd(req); > + break; > + default: > + trace_ufs_err_invalid_trans_code(req->slot, > + req->req_upiu.header.trans_type); > + req_result = UFS_REQUEST_ERROR; > + } > + > + ufs_complete_req(req, req_result); > +} > + > +static void ufs_process_req(void *opaque) > +{ > + UfsHc *u = opaque; > + UfsRequest *req; > + int slot; > + > + for (slot = 0; slot < u->params.nutrs; slot++) { > + req = &u->req_list[slot]; > + > + if (req->state != UFS_REQUEST_READY) { > + continue; > + } > + trace_ufs_process_req(slot); > + req->state = UFS_REQUEST_RUNNING; > + > + ufs_exec_req(req); > + } > +} > + > +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result) > +{ > + UfsHc *u = req->hc; > + assert(req->state == UFS_REQUEST_RUNNING); > + > + if (req_result == UFS_REQUEST_SUCCESS) { > + req->utrd.header.dword_2 = OCS_SUCCESS; > + } else { > + req->utrd.header.dword_2 = OCS_INVALID_CMD_TABLE_ATTR; > + } How does byte-swapping work in your patch series? This looks like native endian but it should be little-endian. > + > + trace_ufs_complete_req(req->slot); > + req->state = UFS_REQUEST_COMPLETE; > + qemu_bh_schedule(u->complete_bh); > +} > + > +static void ufs_clear_req(UfsRequest *req) > +{ > + if (req->sg != NULL) { > + qemu_sglist_destroy(req->sg); > + g_free(req->sg); > + req->sg = NULL; > + } > + > + memset(&req->utrd, 0, sizeof(req->utrd)); > + memset(&req->req_upiu, 0, sizeof(req->req_upiu)); > + memset(&req->rsp_upiu, 0, sizeof(req->rsp_upiu)); > +} > + > +static void ufs_sendback_req(void *opaque) > +{ > + UfsHc *u = opaque; > + UfsRequest *req; > + int slot; > + > + for (slot = 0; slot < u->params.nutrs; slot++) { > + uint32_t is = ldl_le_p(&u->reg.is); > + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); > + uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); > + > + req = &u->req_list[slot]; > + > + if (req->state != UFS_REQUEST_COMPLETE) { > + continue; > + } > + > + if (ufs_dma_write_upiu(req)) { > + continue; Does this error handling work? It looks to me like a failed DMA transaction will repeat every time ufs_sendback_req() is called instead of stopping the request (changing req->state). > + } > + > + /* > + * TODO: UTP Transfer Request Interrupt Aggregation Control is not yet > + * supported > + */ > + if (req->utrd.header.dword_2 != OCS_SUCCESS || > + req->utrd.header.dword_0 & UTP_REQ_DESC_INT_CMD) { > + is = FIELD_DP32(is, IS, UTRCS, 1); > + } > + > + utrldbr &= ~(1 << slot); > + utrlcnr |= (1 << slot); > + > + stl_le_p(&u->reg.is, is); > + stl_le_p(&u->reg.utrldbr, utrldbr); > + stl_le_p(&u->reg.utrlcnr, utrlcnr); > + > + trace_ufs_sendback_req(req->slot); > + > + ufs_clear_req(req); > + req->state = UFS_REQUEST_IDLE; > + } > + > + ufs_irq_check(u); > +} > + > static bool ufs_check_constraints(UfsHc *u, Error **errp) > { > if (u->params.nutrs > UFS_MAX_NUTRS) { > @@ -232,6 +1118,23 @@ static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) > u->irq = pci_allocate_irq(pci_dev); > } > > +static void ufs_init_state(UfsHc *u) > +{ > + u->req_list = g_new0(UfsRequest, u->params.nutrs); > + > + for (int i = 0; i < u->params.nutrs; i++) { > + u->req_list[i].hc = u; > + u->req_list[i].slot = i; > + u->req_list[i].sg = NULL; > + u->req_list[i].state = UFS_REQUEST_IDLE; > + } > + > + u->doorbell_bh = qemu_bh_new_guarded(ufs_process_req, u, > + &DEVICE(u)->mem_reentrancy_guard); > + u->complete_bh = qemu_bh_new_guarded(ufs_sendback_req, u, > + &DEVICE(u)->mem_reentrancy_guard); > +} > + > static void ufs_init_hc(UfsHc *u) > { > uint32_t cap = 0; > @@ -249,6 +1152,54 @@ static void ufs_init_hc(UfsHc *u) > cap = FIELD_DP32(cap, CAP, CS, 0); > stl_le_p(&u->reg.cap, cap); > stl_le_p(&u->reg.ver, UFS_SPEC_VER); > + > + memset(&u->device_desc, 0, sizeof(DeviceDescriptor)); > + u->device_desc.length = sizeof(DeviceDescriptor); > + u->device_desc.descriptor_idn = QUERY_DESC_IDN_DEVICE; > + u->device_desc.device_sub_class = 0x01; > + u->device_desc.number_lu = 0x00; > + u->device_desc.number_wlu = 0x04; > + /* 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.spec_version = cpu_to_be16(0x0310); Can UFS_SPEC_VER be used here instead of hardcoding a magic number? > + u->device_desc.manufacturer_name = 0x00; > + u->device_desc.product_name = 0x01; > + u->device_desc.serial_number = 0x02; > + u->device_desc.oem_id = 0x03; > + u->device_desc.ud_0_base_offset = 0x16; > + u->device_desc.ud_config_p_length = 0x1A; > + u->device_desc.device_rtt_cap = 0x02; > + u->device_desc.queue_depth = u->params.nutrs; > + u->device_desc.product_revision_level = 0x04; > + u->device_desc.extended_ufs_features_support = 0x00; > + > + memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor)); > + u->geometry_desc.length = sizeof(GeometryDescriptor); > + u->geometry_desc.descriptor_idn = QUERY_DESC_IDN_GEOMETRY; > + u->geometry_desc.total_raw_device_capacity = 0; > + u->geometry_desc.max_number_lu = (UFS_MAX_LUS == 32) ? 0x1 : 0x0; > + u->geometry_desc.segment_size = cpu_to_be32(0x2000); /* 4KB */ > + u->geometry_desc.allocation_unit_size = 0x1; /* 4KB */ > + u->geometry_desc.min_addr_block_size = 0x8; /* 4KB */ > + u->geometry_desc.max_in_buffer_size = 0x8; > + u->geometry_desc.max_out_buffer_size = 0x8; > + u->geometry_desc.rpmb_read_write_size = 0x40; > + u->geometry_desc.data_ordering = > + 0x0; /* out-of-order data transfer is not supported */ > + u->geometry_desc.max_context_id_number = 0x5; > + u->geometry_desc.supported_memory_types = cpu_to_be16(0x8001); > + > + memset(&u->attributes, 0, sizeof(u->attributes)); > + u->attributes.max_data_in_size = 0x08; > + u->attributes.max_data_out_size = 0x08; > + u->attributes.ref_clk_freq = 0x01; /* 26 MHz */ > + /* configure descriptor is not supported */ > + u->attributes.config_descr_lock = 0x01; > + u->attributes.max_num_of_rtt = 0x02; > + > + memset(&u->flags, 0, sizeof(u->flags)); > + u->flags.permanently_disable_fw_update = 1; > } > > static void ufs_realize(PCIDevice *pci_dev, Error **errp) > @@ -259,10 +1210,24 @@ static void ufs_realize(PCIDevice *pci_dev, Error **errp) > return; > } > > + ufs_init_state(u); > ufs_init_hc(u); > ufs_init_pci(u, pci_dev); > } > > +static void ufs_exit(PCIDevice *pci_dev) > +{ > + UfsHc *u = UFS(pci_dev); > + > + qemu_bh_delete(u->doorbell_bh); > + qemu_bh_delete(u->complete_bh); > + > + for (int i = 0; i < u->params.nutrs; i++) { > + ufs_clear_req(&u->req_list[i]); > + } > + g_free(u->req_list); > +} > + > static Property ufs_props[] = { > DEFINE_PROP_STRING("serial", UfsHc, params.serial), > DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), > @@ -281,6 +1246,7 @@ static void ufs_class_init(ObjectClass *oc, void *data) > PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc); > > pc->realize = ufs_realize; > + pc->exit = ufs_exit; > pc->class_id = PCI_CLASS_STORAGE_UFS; > > set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); > diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h > index 3c28f4e62d..5d4fd818f9 100644 > --- a/hw/ufs/ufs.h > +++ b/hw/ufs/ufs.h > @@ -18,6 +18,31 @@ > #define UFS_MAX_LUS 32 > #define UFS_LOGICAL_BLK_SIZE 4096 > > +typedef enum UfsRequestState { > + UFS_REQUEST_IDLE = 0, > + UFS_REQUEST_READY = 1, > + UFS_REQUEST_RUNNING = 2, > + UFS_REQUEST_COMPLETE = 3, > +} UfsRequestState; > + > +typedef enum UfsReqResult { > + UFS_REQUEST_SUCCESS = 0, > + UFS_REQUEST_ERROR = 1, > +} UfsReqResult; > + > +typedef struct UfsRequest { > + struct UfsHc *hc; > + UfsRequestState state; > + int slot; > + > + UtpTransferReqDesc utrd; > + UtpUpiuReq req_upiu; > + UtpUpiuRsp rsp_upiu; > + > + /* for scsi command */ > + QEMUSGList *sg; > +} UfsRequest; > + > typedef struct UfsParams { > char *serial; > uint8_t nutrs; /* Number of UTP Transfer Request Slots */ > @@ -30,6 +55,12 @@ typedef struct UfsHc { > UfsReg reg; > UfsParams params; > uint32_t reg_size; > + UfsRequest *req_list; > + > + DeviceDescriptor device_desc; > + GeometryDescriptor geometry_desc; > + Attributes attributes; > + Flags flags; > > qemu_irq irq; > QEMUBH *doorbell_bh; > @@ -39,4 +70,18 @@ typedef struct UfsHc { > #define TYPE_UFS "ufs" > #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) > > +typedef enum UfsQueryFlagPerm { > + UFS_QUERY_FLAG_NONE = 0x0, > + UFS_QUERY_FLAG_READ = 0x1, > + UFS_QUERY_FLAG_SET = 0x2, > + UFS_QUERY_FLAG_CLEAR = 0x4, > + UFS_QUERY_FLAG_TOGGLE = 0x8, > +} UfsQueryFlagPerm; > + > +typedef enum UfsQueryAttrPerm { > + UFS_QUERY_ATTR_NONE = 0x0, > + UFS_QUERY_ATTR_READ = 0x1, > + UFS_QUERY_ATTR_WRITE = 0x2, > +} UfsQueryAttrPerm; > + > #endif /* HW_UFS_UFS_H */ > -- > 2.34.1 >
On Fri, Jun 19, 2023, Stefan Hajnoczi wrote: >On Fri, Jun 16, 2023 at 03:58:25PM +0900, Jeuk Kim wrote: >> This commit makes the UFS device support query >> and nop out transfer requests. >> >> The next patch would be support for UFS logical >> unit and scsi command transfer request. >> >> Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com> >> --- >> hw/ufs/ufs.c | 968 ++++++++++++++++++++++++++++++++++++++++++++++++++- >> hw/ufs/ufs.h | 45 +++ >> 2 files changed, 1012 insertions(+), 1 deletion(-) >> >> diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c >> index 9dba1073a8..10ecc8cd7b 100644 >> --- a/hw/ufs/ufs.c >> +++ b/hw/ufs/ufs.c >> @@ -19,6 +19,233 @@ >> #define UFS_MAX_NUTRS 32 >> #define UFS_MAX_NUTMRS 8 >> >> +static MemTxResult ufs_addr_read(UfsHc *u, hwaddr addr, void *buf, int size) >> +{ >> + uint32_t cap = ldl_le_p(&u->reg.cap); >> + hwaddr hi = addr + size - 1; >> + >> + if (hi < addr) { >> + return MEMTX_DECODE_ERROR; >> + } >> + >> + if (!FIELD_EX32(cap, CAP, 64AS) && (hi >> 32)) { >> + return MEMTX_DECODE_ERROR; >> + } >> + >> + return pci_dma_read(PCI_DEVICE(u), addr, buf, size); >> +} >> + >> +static MemTxResult ufs_addr_write(UfsHc *u, hwaddr addr, const void *buf, >> + int size) >> +{ >> + uint32_t cap = ldl_le_p(&u->reg.cap); >> + hwaddr hi = addr + size - 1; >> + if (hi < addr) { >> + return MEMTX_DECODE_ERROR; >> + } >> + >> + if (!FIELD_EX32(cap, CAP, 64AS) && (hi >> 32)) { >> + return MEMTX_DECODE_ERROR; >> + } >> + >> + return pci_dma_write(PCI_DEVICE(u), addr, buf, size); >> +} >> + >> +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result); >> + >> +static inline hwaddr ufs_get_utrd_addr(UfsHc *u, uint32_t slot) >> +{ >> + uint32_t utrlba = ldl_le_p(&u->reg.utrlba); >> + uint32_t utrlbau = ldl_le_p(&u->reg.utrlbau); >> + hwaddr utrl_base_addr = (((hwaddr)utrlbau) << 32) + utrlba; >> + hwaddr utrd_addr = utrl_base_addr + slot * sizeof(UtpTransferReqDesc); >> + >> + return utrd_addr; >> +} >> + >> +static inline hwaddr ufs_get_req_upiu_base_addr(const UtpTransferReqDesc *utrd) >> +{ >> + uint32_t cmd_desc_base_addr_lo = >> + le32_to_cpu(utrd->command_desc_base_addr_lo); >> + uint32_t cmd_desc_base_addr_hi = >> + le32_to_cpu(utrd->command_desc_base_addr_hi); >> + >> + return (((hwaddr)cmd_desc_base_addr_hi) << 32) + cmd_desc_base_addr_lo; >> +} >> + >> +static inline hwaddr ufs_get_rsp_upiu_base_addr(const UtpTransferReqDesc *utrd) >> +{ >> + hwaddr req_upiu_base_addr = ufs_get_req_upiu_base_addr(utrd); >> + uint32_t rsp_upiu_byte_off = >> + le16_to_cpu(utrd->response_upiu_offset) * sizeof(uint32_t); >> + return req_upiu_base_addr + rsp_upiu_byte_off; >> +} >> + >> +static MemTxResult ufs_dma_read_utrd(UfsRequest *req) >> +{ >> + UfsHc *u = req->hc; >> + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); >> + MemTxResult ret; >> + >> + ret = ufs_addr_read(u, utrd_addr, &req->utrd, sizeof(req->utrd)); >> + if (ret) { >> + trace_ufs_err_dma_read_utrd(req->slot, utrd_addr); >> + } >> + return ret; >> +} >> + >> +static MemTxResult ufs_dma_read_req_upiu(UfsRequest *req) >> +{ >> + UfsHc *u = req->hc; >> + hwaddr req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); >> + UtpUpiuReq *req_upiu = &req->req_upiu; >> + uint32_t copy_size; >> + uint16_t data_segment_length; >> + MemTxResult ret; >> + >> + /* >> + * To know the size of the req_upiu, we need to read the >> + * data_segment_length in the header first. >> + */ >> + ret = ufs_addr_read(u, req_upiu_base_addr, &req_upiu->header, >> + sizeof(UtpUpiuHeader)); >> + if (ret) { >> + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); >> + return ret; >> + } >> + data_segment_length = be16_to_cpu(req_upiu->https://protect2.fireeye.com/v1/url?k=62228ed9-3dbea7f3-62230596-000babe598f7-b6885126c3989767&q=1&e=d20f362c-c592-4425-86c7-2f65b07a0d0e&u=http%3A%2F%2Fheader.data%2F_segment_length); >> + >> + copy_size = sizeof(UtpUpiuHeader) + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + >> + data_segment_length; >> + >> + ret = ufs_addr_read(u, req_upiu_base_addr, &req->req_upiu, copy_size); >> + if (ret) { >> + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); >> + } >> + return ret; >> +} >> + >> +static MemTxResult ufs_dma_read_prdt(UfsRequest *req) >> +{ >> + UfsHc *u = req->hc; >> + uint16_t prdt_len = le16_to_cpu(req->utrd.prd_table_length); >> + uint16_t prdt_byte_off = >> + le16_to_cpu(req->utrd.prd_table_offset) * sizeof(uint32_t); >> + uint32_t prdt_size = prdt_len * sizeof(UfshcdSgEntry); >> + UfshcdSgEntry *prd_entries; >> + hwaddr req_upiu_base_addr, prdt_base_addr; >> + int err; >> + >> + assert(!req->sg); >> + >> + if (prdt_len == 0) { >> + return MEMTX_OK; >> + } >> + >> + prd_entries = g_new(UfshcdSgEntry, prdt_size); >> + if (!prd_entries) { >> + trace_ufs_err_memory_allocation(); >> + return MEMTX_ERROR; >> + } >> + >> + req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); >> + prdt_base_addr = req_upiu_base_addr + prdt_byte_off; >> + >> + err = ufs_addr_read(u, prdt_base_addr, prd_entries, prdt_size); >> + if (err) { >> + trace_ufs_err_dma_read_prdt(req->slot, prdt_base_addr); >> + return err; > >prd_entries is leaked. I suggest using g_autofree to avoid manual >g_free() calls in return paths. > Thanks, I'll fix it to use g_autofree. >> + } >> + >> + req->sg = g_malloc0(sizeof(QEMUSGList)); >> + if (!req->sg) { >> + trace_ufs_err_memory_allocation(); >> + g_free(prd_entries); >> + return MEMTX_ERROR; >> + } >> + pci_dma_sglist_init(req->sg, PCI_DEVICE(u), prdt_len); >> + >> + for (uint16_t i = 0; i < prdt_len; ++i) { >> + hwaddr data_dma_addr = le64_to_cpu(prd_entries[i].addr); >> + int32_t data_byte_count = le32_to_cpu(prd_entries[i].size) + 1; >> + qemu_sglist_add(req->sg, data_dma_addr, data_byte_count); >> + } >> + g_free(prd_entries); >> + >> + return MEMTX_OK; >> +} >> + >> +static MemTxResult ufs_dma_read_upiu(UfsRequest *req) >> +{ >> + MemTxResult ret; >> + >> + ret = ufs_dma_read_utrd(req); >> + if (ret) { >> + return ret; >> + } >> + >> + ret = ufs_dma_read_req_upiu(req); >> + if (ret) { >> + return ret; >> + } >> + >> + ret = ufs_dma_read_prdt(req); >> + if (ret) { >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static MemTxResult ufs_dma_write_utrd(UfsRequest *req) >> +{ >> + UfsHc *u = req->hc; >> + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); >> + MemTxResult ret; >> + >> + ret = ufs_addr_write(u, utrd_addr, &req->utrd, sizeof(req->utrd)); >> + if (ret) { >> + trace_ufs_err_dma_write_utrd(req->slot, utrd_addr); >> + } >> + return ret; >> +} >> + >> +static MemTxResult ufs_dma_write_rsp_upiu(UfsRequest *req) >> +{ >> + UfsHc *u = req->hc; >> + hwaddr rsp_upiu_base_addr = ufs_get_rsp_upiu_base_addr(&req->utrd); >> + uint32_t rsp_upiu_byte_len = >> + le16_to_cpu(req->utrd.response_upiu_length) * sizeof(uint32_t); >> + uint16_t data_segment_length = >> + be16_to_cpu(req->rsp_upiu.header.data_segment_length); >> + uint32_t copy_size = sizeof(UtpUpiuHeader) + >> + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + >> + data_segment_length; >> + MemTxResult ret; >> + >> + if (copy_size > rsp_upiu_byte_len) { >> + copy_size = rsp_upiu_byte_len; >> + } >> + >> + ret = ufs_addr_write(u, rsp_upiu_base_addr, &req->rsp_upiu, copy_size); >> + if (ret) { >> + trace_ufs_err_dma_write_rsp_upiu(req->slot, rsp_upiu_base_addr); >> + } >> + return ret; >> +} >> + >> +static MemTxResult ufs_dma_write_upiu(UfsRequest *req) >> +{ >> + MemTxResult ret; >> + >> + ret = ufs_dma_write_rsp_upiu(req); >> + if (ret) { >> + return ret; >> + } >> + >> + return ufs_dma_write_utrd(req); >> +} >> + >> static void ufs_irq_check(UfsHc *u) >> { >> PCIDevice *pci = PCI_DEVICE(u); >> @@ -34,6 +261,36 @@ static void ufs_irq_check(UfsHc *u) >> } >> } >> >> +static void ufs_process_db(UfsHc *u, uint64_t val) >> +{ >> + uint32_t slot; >> + uint32_t nutrs = u->params.nutrs; >> + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); >> + UfsRequest *req; >> + >> + val &= ~utrldbr; >> + if (!val) { >> + return; >> + } >> + stl_le_p(&u->reg.utrldbr, utrldbr | val); >> + >> + slot = find_first_bit(&val, nutrs); >> + >> + while (slot < nutrs) { >> + req = &u->req_list[slot]; >> + if (req->state != UFS_REQUEST_IDLE) { >> + trace_ufs_err_utrl_slot_busy(req->slot); >> + return; >> + } >> + >> + trace_ufs_process_db(slot); >> + req->state = UFS_REQUEST_READY; >> + slot = find_next_bit(&val, nutrs, slot + 1); >> + } >> + >> + qemu_bh_schedule(u->doorbell_bh); >> +} >> + >> static void ufs_process_uiccmd(UfsHc *u, uint32_t val) >> { >> uint32_t is = ldl_le_p(&u->reg.is); >> @@ -85,6 +342,7 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) >> uint32_t is = ldl_le_p(&u->reg.is); >> uint32_t hcs = ldl_le_p(&u->reg.hcs); >> uint32_t hce = ldl_le_p(&u->reg.hce); >> + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); >> uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); >> uint32_t utrlba, utmrlba; >> >> @@ -119,7 +377,9 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) >> stl_le_p(&u->reg.utrlbau, data); >> break; >> case A_UTRLDBR: >> - /* Not yet supported */ >> + ufs_process_db(u, data); >> + utrldbr |= data; >> + stl_le_p(&u->reg.utrldbr, utrldbr); >> break; >> case A_UTRLRSR: >> stl_le_p(&u->reg.utrlrsr, data); >> @@ -199,6 +459,632 @@ static const MemoryRegionOps ufs_mmio_ops = { >> }, >> }; >> >> +static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, >> + uint8_t flags, uint8_t response, >> + uint8_t scsi_status, >> + uint16_t data_segment_length) >> +{ >> + memcpy(&req->rsp_upiu.header, &req->req_upiu.header, sizeof(UtpUpiuHeader)); >> + req->rsp_upiu.header.trans_type = trans_type; >> + req->rsp_upiu.header.flags = flags; >> + req->rsp_upiu.header.response = response; >> + req->rsp_upiu.header.scsi_status = scsi_status; >> + req->rsp_upiu.header.data_segment_length = cpu_to_be16(data_segment_length); >> +} >> + >> +static UfsReqResult ufs_exec_nop_cmd(UfsRequest *req) >> +{ >> + trace_ufs_exec_nop_cmd(req->slot); >> + ufs_build_upiu_header(req, UPIU_TRANSACTION_NOP_IN, 0, 0, 0, 0); >> + return UFS_REQUEST_SUCCESS; >> +} >> + >> +/* >> + * This defines the permission of flags based on their IDN. There are some >> + * things that are declared read-only, which is inconsistent with the ufs spec, >> + * because we want to return an error for features that are not yet supported. >> + */ >> +static const int flag_permission[QUERY_FLAG_IDN_COUNT] = { >> + [QUERY_FLAG_IDN_FDEVICEINIT] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET, >> + /* Write protection is not supported */ >> + [QUERY_FLAG_IDN_PERMANENT_WPE] = UFS_QUERY_FLAG_READ, >> + [QUERY_FLAG_IDN_PWR_ON_WPE] = UFS_QUERY_FLAG_READ, >> + [QUERY_FLAG_IDN_BKOPS_EN] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | >> + UFS_QUERY_FLAG_CLEAR | UFS_QUERY_FLAG_TOGGLE, >> + [QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE] = >> + UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | UFS_QUERY_FLAG_CLEAR | >> + UFS_QUERY_FLAG_TOGGLE, >> + /* Purge Operation is not supported */ >> + [QUERY_FLAG_IDN_PURGE_ENABLE] = UFS_QUERY_FLAG_NONE, >> + /* Refresh Operation is not supported */ >> + [QUERY_FLAG_IDN_REFRESH_ENABLE] = UFS_QUERY_FLAG_NONE, >> + /* Physical Resource Removal is not supported */ >> + [QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL] = UFS_QUERY_FLAG_READ, >> + [QUERY_FLAG_IDN_BUSY_RTC] = UFS_QUERY_FLAG_READ, >> + [QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE] = UFS_QUERY_FLAG_READ, >> + /* Write Booster is not supported */ >> + [QUERY_FLAG_IDN_WB_EN] = UFS_QUERY_FLAG_READ, >> + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN] = UFS_QUERY_FLAG_READ, >> + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8] = UFS_QUERY_FLAG_READ, >> +}; >> + >> +static inline QueryRespCode ufs_flag_check_idn_valid(uint8_t idn, int op) >> +{ >> + if (idn >= QUERY_FLAG_IDN_COUNT) { >> + return QUERY_RESULT_INVALID_IDN; >> + } >> + >> + if (!(flag_permission[idn] & op)) { >> + if (op == UFS_QUERY_FLAG_READ) { >> + trace_ufs_err_query_flag_not_readable(idn); >> + return QUERY_RESULT_NOT_READABLE; >> + } >> + trace_ufs_err_query_flag_not_writable(idn); >> + return QUERY_RESULT_NOT_WRITEABLE; >> + } >> + >> + return QUERY_RESULT_SUCCESS; >> +} >> + >> +static const int attr_permission[QUERY_ATTR_IDN_COUNT] = { >> + /* booting is not supported */ >> + [QUERY_ATTR_IDN_BOOT_LU_EN] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_POWER_MODE] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_ACTIVE_ICC_LVL] = >> + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_OOO_DATA_EN] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_BKOPS_STATUS] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_PURGE_STATUS] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_MAX_DATA_IN] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_MAX_DATA_OUT] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_DYN_CAP_NEEDED] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_REF_CLK_FREQ] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_CONF_DESC_LOCK] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_MAX_NUM_OF_RTT] = >> + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_EE_CONTROL] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_EE_STATUS] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_SECONDS_PASSED] = UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_CNTX_CONF] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_FFU_STATUS] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_PSA_STATE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_PSA_DATA_SIZE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, >> + [QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_CASE_ROUGH_TEMP] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_HIGH_TEMP_BOUND] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_LOW_TEMP_BOUND] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_THROTTLING_STATUS] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_WB_FLUSH_STATUS] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, >> + /* refresh operation is not supported */ >> + [QUERY_ATTR_IDN_REFRESH_STATUS] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_REFRESH_FREQ] = UFS_QUERY_ATTR_READ, >> + [QUERY_ATTR_IDN_REFRESH_UNIT] = UFS_QUERY_ATTR_READ, >> +}; >> + >> +static inline QueryRespCode ufs_attr_check_idn_valid(uint8_t idn, int op) >> +{ >> + if (idn >= QUERY_ATTR_IDN_COUNT) { >> + return QUERY_RESULT_INVALID_IDN; >> + } >> + >> + if (!(attr_permission[idn] & op)) { >> + if (op == UFS_QUERY_ATTR_READ) { >> + trace_ufs_err_query_attr_not_readable(idn); >> + return QUERY_RESULT_NOT_READABLE; >> + } >> + trace_ufs_err_query_attr_not_writable(idn); >> + return QUERY_RESULT_NOT_WRITEABLE; >> + } >> + >> + return QUERY_RESULT_SUCCESS; >> +} >> + >> +static QueryRespCode ufs_exec_query_flag(UfsRequest *req, int op) >> +{ >> + UfsHc *u = req->hc; >> + uint8_t idn = req->req_upiu.qr.idn; >> + uint32_t value; >> + QueryRespCode ret; >> + >> + ret = ufs_flag_check_idn_valid(idn, op); >> + if (ret) { >> + return ret; >> + } >> + >> + value = *(((uint8_t *)&u->flags) + idn); >> + if (idn == QUERY_FLAG_IDN_FDEVICEINIT) { >> + value = 0; >> + } else if (op == UFS_QUERY_FLAG_READ) { >> + value = *(((uint8_t *)&u->flags) + idn); > >This value was already loaded a few lines above. > Okay, I'll remove the line above. >> + } else if (op == UFS_QUERY_FLAG_SET) { >> + value = 1; >> + } else if (op == UFS_QUERY_FLAG_CLEAR) { >> + value = 0; >> + } else if (op == UFS_QUERY_FLAG_TOGGLE) { >> + value = !value; >> + } else { >> + trace_ufs_err_query_invalid_opcode(op); >> + return QUERY_RESULT_INVALID_OPCODE; >> + } >> + >> + *(((uint8_t *)&u->flags) + idn) = value; >> + req->rsp_upiu.qr.value = cpu_to_be32(value); >> + return QUERY_RESULT_SUCCESS; >> +} >> + >> +static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) >> +{ >> + switch (idn) { >> + case QUERY_ATTR_IDN_BOOT_LU_EN: >> + return u->attributes.boot_lun_en; >> + case QUERY_ATTR_IDN_POWER_MODE: >> + return u->attributes.current_power_mode; >> + case QUERY_ATTR_IDN_ACTIVE_ICC_LVL: >> + return u->attributes.active_icc_level; >> + case QUERY_ATTR_IDN_OOO_DATA_EN: >> + return u->attributes.out_of_order_data_en; >> + case QUERY_ATTR_IDN_BKOPS_STATUS: >> + return u->attributes.background_op_status; >> + case QUERY_ATTR_IDN_PURGE_STATUS: >> + return u->attributes.purge_status; >> + case QUERY_ATTR_IDN_MAX_DATA_IN: >> + return u->attributes.max_data_in_size; >> + case QUERY_ATTR_IDN_MAX_DATA_OUT: >> + return u->attributes.max_data_out_size; >> + case QUERY_ATTR_IDN_DYN_CAP_NEEDED: >> + return be32_to_cpu(u->attributes.dyn_cap_needed); >> + case QUERY_ATTR_IDN_REF_CLK_FREQ: >> + return u->attributes.ref_clk_freq; >> + case QUERY_ATTR_IDN_CONF_DESC_LOCK: >> + return u->attributes.config_descr_lock; >> + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: >> + return u->attributes.max_num_of_rtt; >> + case QUERY_ATTR_IDN_EE_CONTROL: >> + return be16_to_cpu(u->attributes.exception_event_control); >> + case QUERY_ATTR_IDN_EE_STATUS: >> + return be16_to_cpu(u->attributes.exception_event_status); >> + case QUERY_ATTR_IDN_SECONDS_PASSED: >> + return be32_to_cpu(u->attributes.seconds_passed); >> + case QUERY_ATTR_IDN_CNTX_CONF: >> + return be16_to_cpu(u->attributes.context_conf); >> + case QUERY_ATTR_IDN_FFU_STATUS: >> + return u->attributes.device_ffu_status; >> + case QUERY_ATTR_IDN_PSA_STATE: >> + return be32_to_cpu(u->attributes.psa_state); >> + case QUERY_ATTR_IDN_PSA_DATA_SIZE: >> + return u->attributes.psa_data_size; >> + case QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME: >> + return u->attributes.ref_clk_gating_wait_time; >> + case QUERY_ATTR_IDN_CASE_ROUGH_TEMP: >> + return u->attributes.device_case_rough_temperaure; >> + case QUERY_ATTR_IDN_HIGH_TEMP_BOUND: >> + return u->attributes.device_too_high_temp_boundary; >> + case QUERY_ATTR_IDN_LOW_TEMP_BOUND: >> + return u->attributes.device_too_low_temp_boundary; >> + case QUERY_ATTR_IDN_THROTTLING_STATUS: >> + return u->attributes.throttling_status; >> + case QUERY_ATTR_IDN_WB_FLUSH_STATUS: >> + return u->attributes.wb_buffer_flush_status; >> + case QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE: >> + return u->attributes.available_wb_buffer_size; >> + case QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST: >> + return u->attributes.wb_buffer_life_time_est; >> + case QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE: >> + return be32_to_cpu(u->attributes.current_wb_buffer_size); >> + case QUERY_ATTR_IDN_REFRESH_STATUS: >> + return u->attributes.refresh_status; >> + case QUERY_ATTR_IDN_REFRESH_FREQ: >> + return u->attributes.refresh_freq; >> + case QUERY_ATTR_IDN_REFRESH_UNIT: >> + return u->attributes.refresh_unit; >> + } >> + return 0; >> +} >> + >> +static void ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value) >> +{ >> + switch (idn) { >> + case QUERY_ATTR_IDN_ACTIVE_ICC_LVL: >> + u->attributes.active_icc_level = value; >> + break; >> + case QUERY_ATTR_IDN_MAX_DATA_IN: >> + u->attributes.max_data_in_size = value; >> + break; >> + case QUERY_ATTR_IDN_MAX_DATA_OUT: >> + u->attributes.max_data_out_size = value; >> + break; >> + case QUERY_ATTR_IDN_REF_CLK_FREQ: >> + u->attributes.ref_clk_freq = value; >> + break; >> + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: >> + u->attributes.max_num_of_rtt = value; >> + break; >> + case QUERY_ATTR_IDN_EE_CONTROL: >> + u->attributes.exception_event_control = cpu_to_be16(value); >> + break; >> + case QUERY_ATTR_IDN_SECONDS_PASSED: >> + u->attributes.seconds_passed = cpu_to_be32(value); >> + break; >> + case QUERY_ATTR_IDN_PSA_STATE: >> + u->attributes.psa_state = value; >> + break; >> + case QUERY_ATTR_IDN_PSA_DATA_SIZE: >> + u->attributes.psa_data_size = cpu_to_be32(value); >> + break; >> + } >> +} >> + >> +static QueryRespCode ufs_exec_query_attr(UfsRequest *req, int op) >> +{ >> + UfsHc *u = req->hc; >> + uint8_t idn = req->req_upiu.qr.idn; >> + uint32_t value; >> + QueryRespCode ret; >> + >> + ret = ufs_attr_check_idn_valid(idn, op); >> + if (ret) { >> + return ret; >> + } >> + >> + if (op == UFS_QUERY_ATTR_READ) { >> + value = ufs_read_attr_value(u, idn); >> + } else { >> + value = be32_to_cpu(req->req_upiu.qr.value); >> + ufs_write_attr_value(u, idn, value); >> + } >> + >> + req->rsp_upiu.qr.value = cpu_to_be32(value); >> + return QUERY_RESULT_SUCCESS; >> +} >> + >> +static const RpmbUnitDescriptor rpmb_unit_desc = { >> + .length = sizeof(RpmbUnitDescriptor), >> + .descriptor_idn = 2, >> + .unit_index = UFS_UPIU_RPMB_WLUN, >> + .lu_enable = 0, >> +}; >> + >> +static QueryRespCode ufs_read_unit_desc(UfsRequest *req) >> +{ >> + uint8_t lun = req->req_upiu.qr.index; >> + >> + if (lun != UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) { >> + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun); >> + return QUERY_RESULT_INVALID_INDEX; >> + } >> + >> + if (lun == UFS_UPIU_RPMB_WLUN) { >> + memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.length); >> + } else { >> + /* unit descriptor is not yet supported */ >> + return QUERY_RESULT_INVALID_INDEX; >> + } >> + >> + return QUERY_RESULT_SUCCESS; >> +} >> + >> +static const StringDescriptor manufacturer_str_desc = { >> + .length = 0x12, >> + .descriptor_idn = QUERY_DESC_IDN_STRING, >> + .UC = { 'S', 'A', 'M', 'S', 'U', 'N', 'G' }, > >What is the endianness of these 16-bit characters? I noticed endianness >issues in several other places but will not audit the patch >exhaustively. Please review all guest-visible fields carefully and add >cpu_to_leXX()/cpu_to_beXX() where necessary. > I'm sorry, lack of consideration for endianness. It should be big endian. I'll carefully review all the guest-visible fields defined in block/ufs.h and add cpu_to_leXX() / cpu_to_beXX(). Thanks. >> +}; >> + >> +static const StringDescriptor product_name_str_desc = { >> + .length = 0x22, >> + .descriptor_idn = QUERY_DESC_IDN_STRING, >> + .UC = { 'Q', 'E', 'M', 'U', '-', 'U', 'F', 'S' }, >> +}; >> + >> +static const StringDescriptor product_rev_level_str_desc = { >> + .length = 0x0a, >> + .descriptor_idn = QUERY_DESC_IDN_STRING, >> + .UC = { '0', '0', '0', '1' }, >> +}; >> + >> +static const StringDescriptor null_str_desc = { >> + .length = 0x02, >> + .descriptor_idn = QUERY_DESC_IDN_STRING, >> +}; >> + >> +static QueryRespCode ufs_read_string_desc(UfsRequest *req) >> +{ >> + UfsHc *u = req->hc; >> + uint8_t index = req->req_upiu.qr.index; >> + if (index == u->device_desc.manufacturer_name) { >> + memcpy(&req->rsp_upiu.qr.data, &manufacturer_str_desc, >> + manufacturer_str_desc.length); >> + } else if (index == u->device_desc.product_name) { >> + memcpy(&req->rsp_upiu.qr.data, &product_name_str_desc, >> + product_name_str_desc.length); >> + } else if (index == u->device_desc.serial_number) { >> + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); >> + } else if (index == u->device_desc.oem_id) { >> + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); >> + } else if (index == u->device_desc.product_revision_level) { >> + memcpy(&req->rsp_upiu.qr.data, &product_rev_level_str_desc, >> + product_rev_level_str_desc.length); >> + } else { >> + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, index); >> + return QUERY_RESULT_INVALID_INDEX; >> + } >> + return QUERY_RESULT_SUCCESS; >> +} >> + >> +static const InterconnectDescriptor interconnect_desc = { >> + .length = sizeof(InterconnectDescriptor), >> + .descriptor_idn = QUERY_DESC_IDN_INTERCONNECT, >> + .bcd_unipro_version = 0x180, >> + .bcd_mphy_version = 0x410, > >What is the endianness of these two 16-bit fields? > It should be big endian too. I'll fix it as well. >> +}; >> + >> +static QueryRespCode ufs_read_desc(UfsRequest *req) >> +{ >> + UfsHc *u = req->hc; >> + QueryRespCode status; >> + uint8_t idn = req->req_upiu.qr.idn; >> + uint16_t length = be16_to_cpu(req->req_upiu.qr.length); >> + >> + switch (idn) { >> + case QUERY_DESC_IDN_DEVICE: >> + memcpy(&req->rsp_upiu.qr.data, &u->device_desc, sizeof(u->device_desc)); >> + status = QUERY_RESULT_SUCCESS; >> + break; >> + case QUERY_DESC_IDN_UNIT: >> + status = ufs_read_unit_desc(req); >> + break; >> + case QUERY_DESC_IDN_GEOMETRY: >> + memcpy(&req->rsp_upiu.qr.data, &u->geometry_desc, >> + sizeof(u->geometry_desc)); >> + status = QUERY_RESULT_SUCCESS; >> + break; >> + case QUERY_DESC_IDN_INTERCONNECT: { >> + memcpy(&req->rsp_upiu.qr.data, &interconnect_desc, >> + sizeof(interconnect_desc)); >> + status = QUERY_RESULT_SUCCESS; >> + break; >> + } >> + case QUERY_DESC_IDN_STRING: >> + status = ufs_read_string_desc(req); >> + break; >> + case QUERY_DESC_IDN_POWER: >> + /* mocking of power descriptor is not supported */ >> + memset(&req->rsp_upiu.qr.data, 0, sizeof(PowerParametersDescriptor)); >> + req->rsp_upiu.qr.data[0] = sizeof(PowerParametersDescriptor); >> + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_POWER; >> + status = QUERY_RESULT_SUCCESS; >> + break; >> + case QUERY_DESC_IDN_HEALTH: >> + /* mocking of health descriptor is not supported */ >> + memset(&req->rsp_upiu.qr.data, 0, sizeof(DeviceHealthDescriptor)); >> + req->rsp_upiu.qr.data[0] = sizeof(DeviceHealthDescriptor); >> + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_HEALTH; >> + status = QUERY_RESULT_SUCCESS; >> + break; >> + default: >> + length = 0; >> + trace_ufs_err_query_invalid_idn(req->req_upiu.qr.opcode, idn); >> + status = QUERY_RESULT_INVALID_IDN; >> + } >> + >> + if (length > req->rsp_upiu.qr.data[0]) { >> + length = req->rsp_upiu.qr.data[0]; >> + } >> + req->rsp_upiu.qr.opcode = req->req_upiu.qr.opcode; >> + req->rsp_upiu.qr.idn = req->req_upiu.qr.idn; >> + req->rsp_upiu.qr.index = req->req_upiu.qr.index; >> + req->rsp_upiu.qr.selector = req->req_upiu.qr.selector; >> + req->rsp_upiu.qr.length = cpu_to_be16(length); >> + >> + return status; >> +} >> + >> +static QueryRespCode ufs_exec_query_read(UfsRequest *req) >> +{ >> + QueryRespCode status; >> + switch (req->req_upiu.qr.opcode) { >> + case UPIU_QUERY_OPCODE_NOP: >> + status = QUERY_RESULT_SUCCESS; >> + break; >> + case UPIU_QUERY_OPCODE_READ_DESC: >> + status = ufs_read_desc(req); >> + break; >> + case UPIU_QUERY_OPCODE_READ_ATTR: >> + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_READ); >> + break; >> + case UPIU_QUERY_OPCODE_READ_FLAG: >> + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_READ); >> + break; >> + default: >> + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); >> + status = QUERY_RESULT_INVALID_OPCODE; >> + break; >> + } >> + >> + return status; >> +} >> + >> +static QueryRespCode ufs_exec_query_write(UfsRequest *req) >> +{ >> + QueryRespCode status; >> + switch (req->req_upiu.qr.opcode) { >> + case UPIU_QUERY_OPCODE_NOP: >> + status = QUERY_RESULT_SUCCESS; >> + break; >> + case UPIU_QUERY_OPCODE_WRITE_DESC: >> + /* write descriptor is not supported */ >> + status = QUERY_RESULT_NOT_WRITEABLE; >> + break; >> + case UPIU_QUERY_OPCODE_WRITE_ATTR: >> + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_WRITE); >> + break; >> + case UPIU_QUERY_OPCODE_SET_FLAG: >> + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_SET); >> + break; >> + case UPIU_QUERY_OPCODE_CLEAR_FLAG: >> + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_CLEAR); >> + break; >> + case UPIU_QUERY_OPCODE_TOGGLE_FLAG: >> + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_TOGGLE); >> + break; >> + default: >> + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); >> + status = QUERY_RESULT_INVALID_OPCODE; >> + break; >> + } >> + >> + return status; >> +} >> + >> +static UfsReqResult ufs_exec_query_cmd(UfsRequest *req) >> +{ >> + uint8_t query_func = req->req_upiu.header.query_func; >> + uint16_t data_segment_length; >> + QueryRespCode status; >> + >> + trace_ufs_exec_query_cmd(req->slot, req->req_upiu.qr.opcode); >> + if (query_func == UPIU_QUERY_FUNC_STANDARD_READ_REQUEST) { >> + status = ufs_exec_query_read(req); >> + } else if (query_func == UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST) { >> + status = ufs_exec_query_write(req); >> + } else { >> + status = QUERY_RESULT_GENERAL_FAILURE; >> + } >> + >> + data_segment_length = be16_to_cpu(req->rsp_upiu.qr.length); >> + ufs_build_upiu_header(req, UPIU_TRANSACTION_QUERY_RSP, 0, status, 0, >> + data_segment_length); >> + >> + if (status != QUERY_RESULT_SUCCESS) { >> + return UFS_REQUEST_ERROR; >> + } >> + return UFS_REQUEST_SUCCESS; >> +} >> + >> +static void ufs_exec_req(UfsRequest *req) >> +{ >> + UfsReqResult req_result; >> + >> + if (ufs_dma_read_upiu(req)) { >> + return; >> + } >> + >> + switch (req->req_upiu.header.trans_type) { >> + case UPIU_TRANSACTION_NOP_OUT: >> + req_result = ufs_exec_nop_cmd(req); >> + break; >> + case UPIU_TRANSACTION_COMMAND: >> + /* Not yet implemented */ >> + req_result = UFS_REQUEST_ERROR; >> + break; >> + case UPIU_TRANSACTION_QUERY_REQ: >> + req_result = ufs_exec_query_cmd(req); >> + break; >> + default: >> + trace_ufs_err_invalid_trans_code(req->slot, >> + req->req_upiu.header.trans_type); >> + req_result = UFS_REQUEST_ERROR; >> + } >> + >> + ufs_complete_req(req, req_result); >> +} >> + >> +static void ufs_process_req(void *opaque) >> +{ >> + UfsHc *u = opaque; >> + UfsRequest *req; >> + int slot; >> + >> + for (slot = 0; slot < u->params.nutrs; slot++) { >> + req = &u->req_list[slot]; >> + >> + if (req->state != UFS_REQUEST_READY) { >> + continue; >> + } >> + trace_ufs_process_req(slot); >> + req->state = UFS_REQUEST_RUNNING; >> + >> + ufs_exec_req(req); >> + } >> +} >> + >> +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result) >> +{ >> + UfsHc *u = req->hc; >> + assert(req->state == UFS_REQUEST_RUNNING); >> + >> + if (req_result == UFS_REQUEST_SUCCESS) { >> + req->utrd.header.dword_2 = OCS_SUCCESS; >> + } else { >> + req->utrd.header.dword_2 = OCS_INVALID_CMD_TABLE_ATTR; >> + } > >How does byte-swapping work in your patch series? This looks like native >endian but it should be little-endian. > You are right. It should be litte-endian. I'll review all fields that use uint16_t and uint32_t and use the appropriate endian conversion function. >> + >> + trace_ufs_complete_req(req->slot); >> + req->state = UFS_REQUEST_COMPLETE; >> + qemu_bh_schedule(u->complete_bh); >> +} >> + >> +static void ufs_clear_req(UfsRequest *req) >> +{ >> + if (req->sg != NULL) { >> + qemu_sglist_destroy(req->sg); >> + g_free(req->sg); >> + req->sg = NULL; >> + } >> + >> + memset(&req->utrd, 0, sizeof(req->utrd)); >> + memset(&req->req_upiu, 0, sizeof(req->req_upiu)); >> + memset(&req->rsp_upiu, 0, sizeof(req->rsp_upiu)); >> +} >> + >> +static void ufs_sendback_req(void *opaque) >> +{ >> + UfsHc *u = opaque; >> + UfsRequest *req; >> + int slot; >> + >> + for (slot = 0; slot < u->params.nutrs; slot++) { >> + uint32_t is = ldl_le_p(&u->reg.is); >> + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); >> + uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); >> + >> + req = &u->req_list[slot]; >> + >> + if (req->state != UFS_REQUEST_COMPLETE) { >> + continue; >> + } >> + >> + if (ufs_dma_write_upiu(req)) { >> + continue; > >Does this error handling work? It looks to me like a failed DMA >transaction will repeat every time ufs_sendback_req() is called instead >of stopping the request (changing req->state). > This should be fixed. I'll introduce the UFS_REQUEST_ERROR state to prevent re-execution for DMA error situations. >> + } >> + >> + /* >> + * TODO: UTP Transfer Request Interrupt Aggregation Control is not yet >> + * supported >> + */ >> + if (req->utrd.header.dword_2 != OCS_SUCCESS || >> + req->utrd.header.dword_0 & UTP_REQ_DESC_INT_CMD) { >> + is = FIELD_DP32(is, IS, UTRCS, 1); >> + } >> + >> + utrldbr &= ~(1 << slot); >> + utrlcnr |= (1 << slot); >> + >> + stl_le_p(&u->https://protect2.fireeye.com/v1/url?k=914d23dd-ced10af7-914ca892-000babe598f7-e6ca9abf6ef07b74&q=1&e=d20f362c-c592-4425-86c7-2f65b07a0d0e&u=http%3A%2F%2Freg.is%2F, is); >> + stl_le_p(&u->reg.utrldbr, utrldbr); >> + stl_le_p(&u->reg.utrlcnr, utrlcnr); >> + >> + trace_ufs_sendback_req(req->slot); >> + >> + ufs_clear_req(req); >> + req->state = UFS_REQUEST_IDLE; >> + } >> + >> + ufs_irq_check(u); >> +} >> + >> static bool ufs_check_constraints(UfsHc *u, Error **errp) >> { >> if (u->params.nutrs > UFS_MAX_NUTRS) { >> @@ -232,6 +1118,23 @@ static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) >> u->irq = pci_allocate_irq(pci_dev); >> } >> >> +static void ufs_init_state(UfsHc *u) >> +{ >> + u->req_list = g_new0(UfsRequest, u->params.nutrs); >> + >> + for (int i = 0; i < u->params.nutrs; i++) { >> + u->req_list[i].hc = u; >> + u->req_list[i].slot = i; >> + u->req_list[i].sg = NULL; >> + u->req_list[i].state = UFS_REQUEST_IDLE; >> + } >> + >> + u->doorbell_bh = qemu_bh_new_guarded(ufs_process_req, u, >> + &DEVICE(u)->mem_reentrancy_guard); >> + u->complete_bh = qemu_bh_new_guarded(ufs_sendback_req, u, >> + &DEVICE(u)->mem_reentrancy_guard); >> +} >> + >> static void ufs_init_hc(UfsHc *u) >> { >> uint32_t cap = 0; >> @@ -249,6 +1152,54 @@ static void ufs_init_hc(UfsHc *u) >> cap = FIELD_DP32(cap, CAP, CS, 0); >> stl_le_p(&u->reg.cap, cap); >> stl_le_p(&u->reg.ver, UFS_SPEC_VER); >> + >> + memset(&u->device_desc, 0, sizeof(DeviceDescriptor)); >> + u->device_desc.length = sizeof(DeviceDescriptor); >> + u->device_desc.descriptor_idn = QUERY_DESC_IDN_DEVICE; >> + u->device_desc.device_sub_class = 0x01; >> + u->device_desc.number_lu = 0x00; >> + u->device_desc.number_wlu = 0x04; >> + /* 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.spec_version = cpu_to_be16(0x0310); > >Can UFS_SPEC_VER be used here instead of hardcoding a magic number? > Okay, I'll fix it. >> + u->device_desc.manufacturer_name = 0x00; >> + u->device_desc.product_name = 0x01; >> + u->device_desc.serial_number = 0x02; >> + u->device_desc.oem_id = 0x03; >> + u->device_desc.ud_0_base_offset = 0x16; >> + u->device_desc.ud_config_p_length = 0x1A; >> + u->device_desc.device_rtt_cap = 0x02; >> + u->device_desc.queue_depth = u->params.nutrs; >> + u->device_desc.product_revision_level = 0x04; >> + u->device_desc.extended_ufs_features_support = 0x00; >> + >> + memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor)); >> + u->geometry_desc.length = sizeof(GeometryDescriptor); >> + u->geometry_desc.descriptor_idn = QUERY_DESC_IDN_GEOMETRY; >> + u->geometry_desc.total_raw_device_capacity = 0; >> + u->geometry_desc.max_number_lu = (UFS_MAX_LUS == 32) ? 0x1 : 0x0; >> + u->geometry_desc.segment_size = cpu_to_be32(0x2000); /* 4KB */ >> + u->geometry_desc.allocation_unit_size = 0x1; /* 4KB */ >> + u->geometry_desc.min_addr_block_size = 0x8; /* 4KB */ >> + u->geometry_desc.max_in_buffer_size = 0x8; >> + u->geometry_desc.max_out_buffer_size = 0x8; >> + u->geometry_desc.rpmb_read_write_size = 0x40; >> + u->geometry_desc.data_ordering = >> + 0x0; /* out-of-order data transfer is not supported */ >> + u->geometry_desc.max_context_id_number = 0x5; >> + u->geometry_desc.supported_memory_types = cpu_to_be16(0x8001); >> + >> + memset(&u->attributes, 0, sizeof(u->attributes)); >> + u->attributes.max_data_in_size = 0x08; >> + u->attributes.max_data_out_size = 0x08; >> + u->attributes.ref_clk_freq = 0x01; /* 26 MHz */ >> + /* configure descriptor is not supported */ >> + u->attributes.config_descr_lock = 0x01; >> + u->attributes.max_num_of_rtt = 0x02; >> + >> + memset(&u->flags, 0, sizeof(u->flags)); >> + u->flags.permanently_disable_fw_update = 1; >> } >> >> static void ufs_realize(PCIDevice *pci_dev, Error **errp) >> @@ -259,10 +1210,24 @@ static void ufs_realize(PCIDevice *pci_dev, Error **errp) >> return; >> } >> >> + ufs_init_state(u); >> ufs_init_hc(u); >> ufs_init_pci(u, pci_dev); >> } >> >> +static void ufs_exit(PCIDevice *pci_dev) >> +{ >> + UfsHc *u = UFS(pci_dev); >> + >> + qemu_bh_delete(u->doorbell_bh); >> + qemu_bh_delete(u->complete_bh); >> + >> + for (int i = 0; i < u->params.nutrs; i++) { >> + ufs_clear_req(&u->req_list[i]); >> + } >> + g_free(u->req_list); >> +} >> + >> static Property ufs_props[] = { >> DEFINE_PROP_STRING("serial", UfsHc, params.serial), >> DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), >> @@ -281,6 +1246,7 @@ static void ufs_class_init(ObjectClass *oc, void *data) >> PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc); >> >> pc->realize = ufs_realize; >> + pc->exit = ufs_exit; >> pc->class_id = PCI_CLASS_STORAGE_UFS; >> >> set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); >> diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h >> index 3c28f4e62d..5d4fd818f9 100644 >> --- a/hw/ufs/ufs.h >> +++ b/hw/ufs/ufs.h >> @@ -18,6 +18,31 @@ >> #define UFS_MAX_LUS 32 >> #define UFS_LOGICAL_BLK_SIZE 4096 >> >> +typedef enum UfsRequestState { >> + UFS_REQUEST_IDLE = 0, >> + UFS_REQUEST_READY = 1, >> + UFS_REQUEST_RUNNING = 2, >> + UFS_REQUEST_COMPLETE = 3, >> +} UfsRequestState; >> + >> +typedef enum UfsReqResult { >> + UFS_REQUEST_SUCCESS = 0, >> + UFS_REQUEST_ERROR = 1, >> +} UfsReqResult; >> + >> +typedef struct UfsRequest { >> + struct UfsHc *hc; >> + UfsRequestState state; >> + int slot; >> + >> + UtpTransferReqDesc utrd; >> + UtpUpiuReq req_upiu; >> + UtpUpiuRsp rsp_upiu; >> + >> + /* for scsi command */ >> + QEMUSGList *sg; >> +} UfsRequest; >> + >> typedef struct UfsParams { >> char *serial; >> uint8_t nutrs; /* Number of UTP Transfer Request Slots */ >> @@ -30,6 +55,12 @@ typedef struct UfsHc { >> UfsReg reg; >> UfsParams params; >> uint32_t reg_size; >> + UfsRequest *req_list; >> + >> + DeviceDescriptor device_desc; >> + GeometryDescriptor geometry_desc; >> + Attributes attributes; >> + Flags flags; >> >> qemu_irq irq; >> QEMUBH *doorbell_bh; >> @@ -39,4 +70,18 @@ typedef struct UfsHc { >> #define TYPE_UFS "ufs" >> #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) >> >> +typedef enum UfsQueryFlagPerm { >> + UFS_QUERY_FLAG_NONE = 0x0, >> + UFS_QUERY_FLAG_READ = 0x1, >> + UFS_QUERY_FLAG_SET = 0x2, >> + UFS_QUERY_FLAG_CLEAR = 0x4, >> + UFS_QUERY_FLAG_TOGGLE = 0x8, >> +} UfsQueryFlagPerm; >> + >> +typedef enum UfsQueryAttrPerm { >> + UFS_QUERY_ATTR_NONE = 0x0, >> + UFS_QUERY_ATTR_READ = 0x1, >> + UFS_QUERY_ATTR_WRITE = 0x2, >> +} UfsQueryAttrPerm; >> + >> #endif /* HW_UFS_UFS_H */ >> -- >> 2.34.1 >>
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c index 9dba1073a8..10ecc8cd7b 100644 --- a/hw/ufs/ufs.c +++ b/hw/ufs/ufs.c @@ -19,6 +19,233 @@ #define UFS_MAX_NUTRS 32 #define UFS_MAX_NUTMRS 8 +static MemTxResult ufs_addr_read(UfsHc *u, hwaddr addr, void *buf, int size) +{ + uint32_t cap = ldl_le_p(&u->reg.cap); + hwaddr hi = addr + size - 1; + + if (hi < addr) { + return MEMTX_DECODE_ERROR; + } + + if (!FIELD_EX32(cap, CAP, 64AS) && (hi >> 32)) { + return MEMTX_DECODE_ERROR; + } + + return pci_dma_read(PCI_DEVICE(u), addr, buf, size); +} + +static MemTxResult ufs_addr_write(UfsHc *u, hwaddr addr, const void *buf, + int size) +{ + uint32_t cap = ldl_le_p(&u->reg.cap); + hwaddr hi = addr + size - 1; + if (hi < addr) { + return MEMTX_DECODE_ERROR; + } + + if (!FIELD_EX32(cap, CAP, 64AS) && (hi >> 32)) { + return MEMTX_DECODE_ERROR; + } + + return pci_dma_write(PCI_DEVICE(u), addr, buf, size); +} + +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result); + +static inline hwaddr ufs_get_utrd_addr(UfsHc *u, uint32_t slot) +{ + uint32_t utrlba = ldl_le_p(&u->reg.utrlba); + uint32_t utrlbau = ldl_le_p(&u->reg.utrlbau); + hwaddr utrl_base_addr = (((hwaddr)utrlbau) << 32) + utrlba; + hwaddr utrd_addr = utrl_base_addr + slot * sizeof(UtpTransferReqDesc); + + return utrd_addr; +} + +static inline hwaddr ufs_get_req_upiu_base_addr(const UtpTransferReqDesc *utrd) +{ + uint32_t cmd_desc_base_addr_lo = + le32_to_cpu(utrd->command_desc_base_addr_lo); + uint32_t cmd_desc_base_addr_hi = + le32_to_cpu(utrd->command_desc_base_addr_hi); + + return (((hwaddr)cmd_desc_base_addr_hi) << 32) + cmd_desc_base_addr_lo; +} + +static inline hwaddr ufs_get_rsp_upiu_base_addr(const UtpTransferReqDesc *utrd) +{ + hwaddr req_upiu_base_addr = ufs_get_req_upiu_base_addr(utrd); + uint32_t rsp_upiu_byte_off = + le16_to_cpu(utrd->response_upiu_offset) * sizeof(uint32_t); + return req_upiu_base_addr + rsp_upiu_byte_off; +} + +static MemTxResult ufs_dma_read_utrd(UfsRequest *req) +{ + UfsHc *u = req->hc; + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); + MemTxResult ret; + + ret = ufs_addr_read(u, utrd_addr, &req->utrd, sizeof(req->utrd)); + if (ret) { + trace_ufs_err_dma_read_utrd(req->slot, utrd_addr); + } + return ret; +} + +static MemTxResult ufs_dma_read_req_upiu(UfsRequest *req) +{ + UfsHc *u = req->hc; + hwaddr req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); + UtpUpiuReq *req_upiu = &req->req_upiu; + uint32_t copy_size; + uint16_t data_segment_length; + MemTxResult ret; + + /* + * To know the size of the req_upiu, we need to read the + * data_segment_length in the header first. + */ + ret = ufs_addr_read(u, req_upiu_base_addr, &req_upiu->header, + sizeof(UtpUpiuHeader)); + if (ret) { + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); + return ret; + } + data_segment_length = be16_to_cpu(req_upiu->header.data_segment_length); + + copy_size = sizeof(UtpUpiuHeader) + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + + data_segment_length; + + ret = ufs_addr_read(u, req_upiu_base_addr, &req->req_upiu, copy_size); + if (ret) { + trace_ufs_err_dma_read_req_upiu(req->slot, req_upiu_base_addr); + } + return ret; +} + +static MemTxResult ufs_dma_read_prdt(UfsRequest *req) +{ + UfsHc *u = req->hc; + uint16_t prdt_len = le16_to_cpu(req->utrd.prd_table_length); + uint16_t prdt_byte_off = + le16_to_cpu(req->utrd.prd_table_offset) * sizeof(uint32_t); + uint32_t prdt_size = prdt_len * sizeof(UfshcdSgEntry); + UfshcdSgEntry *prd_entries; + hwaddr req_upiu_base_addr, prdt_base_addr; + int err; + + assert(!req->sg); + + if (prdt_len == 0) { + return MEMTX_OK; + } + + prd_entries = g_new(UfshcdSgEntry, prdt_size); + if (!prd_entries) { + trace_ufs_err_memory_allocation(); + return MEMTX_ERROR; + } + + req_upiu_base_addr = ufs_get_req_upiu_base_addr(&req->utrd); + prdt_base_addr = req_upiu_base_addr + prdt_byte_off; + + err = ufs_addr_read(u, prdt_base_addr, prd_entries, prdt_size); + if (err) { + trace_ufs_err_dma_read_prdt(req->slot, prdt_base_addr); + return err; + } + + req->sg = g_malloc0(sizeof(QEMUSGList)); + if (!req->sg) { + trace_ufs_err_memory_allocation(); + g_free(prd_entries); + return MEMTX_ERROR; + } + pci_dma_sglist_init(req->sg, PCI_DEVICE(u), prdt_len); + + for (uint16_t i = 0; i < prdt_len; ++i) { + hwaddr data_dma_addr = le64_to_cpu(prd_entries[i].addr); + int32_t data_byte_count = le32_to_cpu(prd_entries[i].size) + 1; + qemu_sglist_add(req->sg, data_dma_addr, data_byte_count); + } + g_free(prd_entries); + + return MEMTX_OK; +} + +static MemTxResult ufs_dma_read_upiu(UfsRequest *req) +{ + MemTxResult ret; + + ret = ufs_dma_read_utrd(req); + if (ret) { + return ret; + } + + ret = ufs_dma_read_req_upiu(req); + if (ret) { + return ret; + } + + ret = ufs_dma_read_prdt(req); + if (ret) { + return ret; + } + + return 0; +} + +static MemTxResult ufs_dma_write_utrd(UfsRequest *req) +{ + UfsHc *u = req->hc; + hwaddr utrd_addr = ufs_get_utrd_addr(u, req->slot); + MemTxResult ret; + + ret = ufs_addr_write(u, utrd_addr, &req->utrd, sizeof(req->utrd)); + if (ret) { + trace_ufs_err_dma_write_utrd(req->slot, utrd_addr); + } + return ret; +} + +static MemTxResult ufs_dma_write_rsp_upiu(UfsRequest *req) +{ + UfsHc *u = req->hc; + hwaddr rsp_upiu_base_addr = ufs_get_rsp_upiu_base_addr(&req->utrd); + uint32_t rsp_upiu_byte_len = + le16_to_cpu(req->utrd.response_upiu_length) * sizeof(uint32_t); + uint16_t data_segment_length = + be16_to_cpu(req->rsp_upiu.header.data_segment_length); + uint32_t copy_size = sizeof(UtpUpiuHeader) + + UFS_TRANSACTION_SPECIFIC_FIELD_SIZE + + data_segment_length; + MemTxResult ret; + + if (copy_size > rsp_upiu_byte_len) { + copy_size = rsp_upiu_byte_len; + } + + ret = ufs_addr_write(u, rsp_upiu_base_addr, &req->rsp_upiu, copy_size); + if (ret) { + trace_ufs_err_dma_write_rsp_upiu(req->slot, rsp_upiu_base_addr); + } + return ret; +} + +static MemTxResult ufs_dma_write_upiu(UfsRequest *req) +{ + MemTxResult ret; + + ret = ufs_dma_write_rsp_upiu(req); + if (ret) { + return ret; + } + + return ufs_dma_write_utrd(req); +} + static void ufs_irq_check(UfsHc *u) { PCIDevice *pci = PCI_DEVICE(u); @@ -34,6 +261,36 @@ static void ufs_irq_check(UfsHc *u) } } +static void ufs_process_db(UfsHc *u, uint64_t val) +{ + uint32_t slot; + uint32_t nutrs = u->params.nutrs; + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); + UfsRequest *req; + + val &= ~utrldbr; + if (!val) { + return; + } + stl_le_p(&u->reg.utrldbr, utrldbr | val); + + slot = find_first_bit(&val, nutrs); + + while (slot < nutrs) { + req = &u->req_list[slot]; + if (req->state != UFS_REQUEST_IDLE) { + trace_ufs_err_utrl_slot_busy(req->slot); + return; + } + + trace_ufs_process_db(slot); + req->state = UFS_REQUEST_READY; + slot = find_next_bit(&val, nutrs, slot + 1); + } + + qemu_bh_schedule(u->doorbell_bh); +} + static void ufs_process_uiccmd(UfsHc *u, uint32_t val) { uint32_t is = ldl_le_p(&u->reg.is); @@ -85,6 +342,7 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) uint32_t is = ldl_le_p(&u->reg.is); uint32_t hcs = ldl_le_p(&u->reg.hcs); uint32_t hce = ldl_le_p(&u->reg.hce); + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); uint32_t utrlba, utmrlba; @@ -119,7 +377,9 @@ static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) stl_le_p(&u->reg.utrlbau, data); break; case A_UTRLDBR: - /* Not yet supported */ + ufs_process_db(u, data); + utrldbr |= data; + stl_le_p(&u->reg.utrldbr, utrldbr); break; case A_UTRLRSR: stl_le_p(&u->reg.utrlrsr, data); @@ -199,6 +459,632 @@ static const MemoryRegionOps ufs_mmio_ops = { }, }; +static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type, + uint8_t flags, uint8_t response, + uint8_t scsi_status, + uint16_t data_segment_length) +{ + memcpy(&req->rsp_upiu.header, &req->req_upiu.header, sizeof(UtpUpiuHeader)); + req->rsp_upiu.header.trans_type = trans_type; + req->rsp_upiu.header.flags = flags; + req->rsp_upiu.header.response = response; + req->rsp_upiu.header.scsi_status = scsi_status; + req->rsp_upiu.header.data_segment_length = cpu_to_be16(data_segment_length); +} + +static UfsReqResult ufs_exec_nop_cmd(UfsRequest *req) +{ + trace_ufs_exec_nop_cmd(req->slot); + ufs_build_upiu_header(req, UPIU_TRANSACTION_NOP_IN, 0, 0, 0, 0); + return UFS_REQUEST_SUCCESS; +} + +/* + * This defines the permission of flags based on their IDN. There are some + * things that are declared read-only, which is inconsistent with the ufs spec, + * because we want to return an error for features that are not yet supported. + */ +static const int flag_permission[QUERY_FLAG_IDN_COUNT] = { + [QUERY_FLAG_IDN_FDEVICEINIT] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET, + /* Write protection is not supported */ + [QUERY_FLAG_IDN_PERMANENT_WPE] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_PWR_ON_WPE] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_BKOPS_EN] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | + UFS_QUERY_FLAG_CLEAR | UFS_QUERY_FLAG_TOGGLE, + [QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE] = + UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | UFS_QUERY_FLAG_CLEAR | + UFS_QUERY_FLAG_TOGGLE, + /* Purge Operation is not supported */ + [QUERY_FLAG_IDN_PURGE_ENABLE] = UFS_QUERY_FLAG_NONE, + /* Refresh Operation is not supported */ + [QUERY_FLAG_IDN_REFRESH_ENABLE] = UFS_QUERY_FLAG_NONE, + /* Physical Resource Removal is not supported */ + [QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_BUSY_RTC] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE] = UFS_QUERY_FLAG_READ, + /* Write Booster is not supported */ + [QUERY_FLAG_IDN_WB_EN] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN] = UFS_QUERY_FLAG_READ, + [QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8] = UFS_QUERY_FLAG_READ, +}; + +static inline QueryRespCode ufs_flag_check_idn_valid(uint8_t idn, int op) +{ + if (idn >= QUERY_FLAG_IDN_COUNT) { + return QUERY_RESULT_INVALID_IDN; + } + + if (!(flag_permission[idn] & op)) { + if (op == UFS_QUERY_FLAG_READ) { + trace_ufs_err_query_flag_not_readable(idn); + return QUERY_RESULT_NOT_READABLE; + } + trace_ufs_err_query_flag_not_writable(idn); + return QUERY_RESULT_NOT_WRITEABLE; + } + + return QUERY_RESULT_SUCCESS; +} + +static const int attr_permission[QUERY_ATTR_IDN_COUNT] = { + /* booting is not supported */ + [QUERY_ATTR_IDN_BOOT_LU_EN] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_POWER_MODE] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_ACTIVE_ICC_LVL] = + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_OOO_DATA_EN] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_BKOPS_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_PURGE_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_MAX_DATA_IN] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_MAX_DATA_OUT] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_DYN_CAP_NEEDED] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_REF_CLK_FREQ] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_CONF_DESC_LOCK] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_MAX_NUM_OF_RTT] = + UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_EE_CONTROL] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_EE_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_SECONDS_PASSED] = UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_CNTX_CONF] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_FFU_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_PSA_STATE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_PSA_DATA_SIZE] = UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE, + [QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_CASE_ROUGH_TEMP] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_HIGH_TEMP_BOUND] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_LOW_TEMP_BOUND] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_THROTTLING_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_WB_FLUSH_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE] = UFS_QUERY_ATTR_READ, + /* refresh operation is not supported */ + [QUERY_ATTR_IDN_REFRESH_STATUS] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_REFRESH_FREQ] = UFS_QUERY_ATTR_READ, + [QUERY_ATTR_IDN_REFRESH_UNIT] = UFS_QUERY_ATTR_READ, +}; + +static inline QueryRespCode ufs_attr_check_idn_valid(uint8_t idn, int op) +{ + if (idn >= QUERY_ATTR_IDN_COUNT) { + return QUERY_RESULT_INVALID_IDN; + } + + if (!(attr_permission[idn] & op)) { + if (op == UFS_QUERY_ATTR_READ) { + trace_ufs_err_query_attr_not_readable(idn); + return QUERY_RESULT_NOT_READABLE; + } + trace_ufs_err_query_attr_not_writable(idn); + return QUERY_RESULT_NOT_WRITEABLE; + } + + return QUERY_RESULT_SUCCESS; +} + +static QueryRespCode ufs_exec_query_flag(UfsRequest *req, int op) +{ + UfsHc *u = req->hc; + uint8_t idn = req->req_upiu.qr.idn; + uint32_t value; + QueryRespCode ret; + + ret = ufs_flag_check_idn_valid(idn, op); + if (ret) { + return ret; + } + + value = *(((uint8_t *)&u->flags) + idn); + if (idn == QUERY_FLAG_IDN_FDEVICEINIT) { + value = 0; + } else if (op == UFS_QUERY_FLAG_READ) { + value = *(((uint8_t *)&u->flags) + idn); + } else if (op == UFS_QUERY_FLAG_SET) { + value = 1; + } else if (op == UFS_QUERY_FLAG_CLEAR) { + value = 0; + } else if (op == UFS_QUERY_FLAG_TOGGLE) { + value = !value; + } else { + trace_ufs_err_query_invalid_opcode(op); + return QUERY_RESULT_INVALID_OPCODE; + } + + *(((uint8_t *)&u->flags) + idn) = value; + req->rsp_upiu.qr.value = cpu_to_be32(value); + return QUERY_RESULT_SUCCESS; +} + +static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn) +{ + switch (idn) { + case QUERY_ATTR_IDN_BOOT_LU_EN: + return u->attributes.boot_lun_en; + case QUERY_ATTR_IDN_POWER_MODE: + return u->attributes.current_power_mode; + case QUERY_ATTR_IDN_ACTIVE_ICC_LVL: + return u->attributes.active_icc_level; + case QUERY_ATTR_IDN_OOO_DATA_EN: + return u->attributes.out_of_order_data_en; + case QUERY_ATTR_IDN_BKOPS_STATUS: + return u->attributes.background_op_status; + case QUERY_ATTR_IDN_PURGE_STATUS: + return u->attributes.purge_status; + case QUERY_ATTR_IDN_MAX_DATA_IN: + return u->attributes.max_data_in_size; + case QUERY_ATTR_IDN_MAX_DATA_OUT: + return u->attributes.max_data_out_size; + case QUERY_ATTR_IDN_DYN_CAP_NEEDED: + return be32_to_cpu(u->attributes.dyn_cap_needed); + case QUERY_ATTR_IDN_REF_CLK_FREQ: + return u->attributes.ref_clk_freq; + case QUERY_ATTR_IDN_CONF_DESC_LOCK: + return u->attributes.config_descr_lock; + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: + return u->attributes.max_num_of_rtt; + case QUERY_ATTR_IDN_EE_CONTROL: + return be16_to_cpu(u->attributes.exception_event_control); + case QUERY_ATTR_IDN_EE_STATUS: + return be16_to_cpu(u->attributes.exception_event_status); + case QUERY_ATTR_IDN_SECONDS_PASSED: + return be32_to_cpu(u->attributes.seconds_passed); + case QUERY_ATTR_IDN_CNTX_CONF: + return be16_to_cpu(u->attributes.context_conf); + case QUERY_ATTR_IDN_FFU_STATUS: + return u->attributes.device_ffu_status; + case QUERY_ATTR_IDN_PSA_STATE: + return be32_to_cpu(u->attributes.psa_state); + case QUERY_ATTR_IDN_PSA_DATA_SIZE: + return u->attributes.psa_data_size; + case QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME: + return u->attributes.ref_clk_gating_wait_time; + case QUERY_ATTR_IDN_CASE_ROUGH_TEMP: + return u->attributes.device_case_rough_temperaure; + case QUERY_ATTR_IDN_HIGH_TEMP_BOUND: + return u->attributes.device_too_high_temp_boundary; + case QUERY_ATTR_IDN_LOW_TEMP_BOUND: + return u->attributes.device_too_low_temp_boundary; + case QUERY_ATTR_IDN_THROTTLING_STATUS: + return u->attributes.throttling_status; + case QUERY_ATTR_IDN_WB_FLUSH_STATUS: + return u->attributes.wb_buffer_flush_status; + case QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE: + return u->attributes.available_wb_buffer_size; + case QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST: + return u->attributes.wb_buffer_life_time_est; + case QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE: + return be32_to_cpu(u->attributes.current_wb_buffer_size); + case QUERY_ATTR_IDN_REFRESH_STATUS: + return u->attributes.refresh_status; + case QUERY_ATTR_IDN_REFRESH_FREQ: + return u->attributes.refresh_freq; + case QUERY_ATTR_IDN_REFRESH_UNIT: + return u->attributes.refresh_unit; + } + return 0; +} + +static void ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value) +{ + switch (idn) { + case QUERY_ATTR_IDN_ACTIVE_ICC_LVL: + u->attributes.active_icc_level = value; + break; + case QUERY_ATTR_IDN_MAX_DATA_IN: + u->attributes.max_data_in_size = value; + break; + case QUERY_ATTR_IDN_MAX_DATA_OUT: + u->attributes.max_data_out_size = value; + break; + case QUERY_ATTR_IDN_REF_CLK_FREQ: + u->attributes.ref_clk_freq = value; + break; + case QUERY_ATTR_IDN_MAX_NUM_OF_RTT: + u->attributes.max_num_of_rtt = value; + break; + case QUERY_ATTR_IDN_EE_CONTROL: + u->attributes.exception_event_control = cpu_to_be16(value); + break; + case QUERY_ATTR_IDN_SECONDS_PASSED: + u->attributes.seconds_passed = cpu_to_be32(value); + break; + case QUERY_ATTR_IDN_PSA_STATE: + u->attributes.psa_state = value; + break; + case QUERY_ATTR_IDN_PSA_DATA_SIZE: + u->attributes.psa_data_size = cpu_to_be32(value); + break; + } +} + +static QueryRespCode ufs_exec_query_attr(UfsRequest *req, int op) +{ + UfsHc *u = req->hc; + uint8_t idn = req->req_upiu.qr.idn; + uint32_t value; + QueryRespCode ret; + + ret = ufs_attr_check_idn_valid(idn, op); + if (ret) { + return ret; + } + + if (op == UFS_QUERY_ATTR_READ) { + value = ufs_read_attr_value(u, idn); + } else { + value = be32_to_cpu(req->req_upiu.qr.value); + ufs_write_attr_value(u, idn, value); + } + + req->rsp_upiu.qr.value = cpu_to_be32(value); + return QUERY_RESULT_SUCCESS; +} + +static const RpmbUnitDescriptor rpmb_unit_desc = { + .length = sizeof(RpmbUnitDescriptor), + .descriptor_idn = 2, + .unit_index = UFS_UPIU_RPMB_WLUN, + .lu_enable = 0, +}; + +static QueryRespCode ufs_read_unit_desc(UfsRequest *req) +{ + uint8_t lun = req->req_upiu.qr.index; + + if (lun != UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) { + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun); + return QUERY_RESULT_INVALID_INDEX; + } + + if (lun == UFS_UPIU_RPMB_WLUN) { + memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.length); + } else { + /* unit descriptor is not yet supported */ + return QUERY_RESULT_INVALID_INDEX; + } + + return QUERY_RESULT_SUCCESS; +} + +static const StringDescriptor manufacturer_str_desc = { + .length = 0x12, + .descriptor_idn = QUERY_DESC_IDN_STRING, + .UC = { 'S', 'A', 'M', 'S', 'U', 'N', 'G' }, +}; + +static const StringDescriptor product_name_str_desc = { + .length = 0x22, + .descriptor_idn = QUERY_DESC_IDN_STRING, + .UC = { 'Q', 'E', 'M', 'U', '-', 'U', 'F', 'S' }, +}; + +static const StringDescriptor product_rev_level_str_desc = { + .length = 0x0a, + .descriptor_idn = QUERY_DESC_IDN_STRING, + .UC = { '0', '0', '0', '1' }, +}; + +static const StringDescriptor null_str_desc = { + .length = 0x02, + .descriptor_idn = QUERY_DESC_IDN_STRING, +}; + +static QueryRespCode ufs_read_string_desc(UfsRequest *req) +{ + UfsHc *u = req->hc; + uint8_t index = req->req_upiu.qr.index; + if (index == u->device_desc.manufacturer_name) { + memcpy(&req->rsp_upiu.qr.data, &manufacturer_str_desc, + manufacturer_str_desc.length); + } else if (index == u->device_desc.product_name) { + memcpy(&req->rsp_upiu.qr.data, &product_name_str_desc, + product_name_str_desc.length); + } else if (index == u->device_desc.serial_number) { + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); + } else if (index == u->device_desc.oem_id) { + memcpy(&req->rsp_upiu.qr.data, &null_str_desc, null_str_desc.length); + } else if (index == u->device_desc.product_revision_level) { + memcpy(&req->rsp_upiu.qr.data, &product_rev_level_str_desc, + product_rev_level_str_desc.length); + } else { + trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, index); + return QUERY_RESULT_INVALID_INDEX; + } + return QUERY_RESULT_SUCCESS; +} + +static const InterconnectDescriptor interconnect_desc = { + .length = sizeof(InterconnectDescriptor), + .descriptor_idn = QUERY_DESC_IDN_INTERCONNECT, + .bcd_unipro_version = 0x180, + .bcd_mphy_version = 0x410, +}; + +static QueryRespCode ufs_read_desc(UfsRequest *req) +{ + UfsHc *u = req->hc; + QueryRespCode status; + uint8_t idn = req->req_upiu.qr.idn; + uint16_t length = be16_to_cpu(req->req_upiu.qr.length); + + switch (idn) { + case QUERY_DESC_IDN_DEVICE: + memcpy(&req->rsp_upiu.qr.data, &u->device_desc, sizeof(u->device_desc)); + status = QUERY_RESULT_SUCCESS; + break; + case QUERY_DESC_IDN_UNIT: + status = ufs_read_unit_desc(req); + break; + case QUERY_DESC_IDN_GEOMETRY: + memcpy(&req->rsp_upiu.qr.data, &u->geometry_desc, + sizeof(u->geometry_desc)); + status = QUERY_RESULT_SUCCESS; + break; + case QUERY_DESC_IDN_INTERCONNECT: { + memcpy(&req->rsp_upiu.qr.data, &interconnect_desc, + sizeof(interconnect_desc)); + status = QUERY_RESULT_SUCCESS; + break; + } + case QUERY_DESC_IDN_STRING: + status = ufs_read_string_desc(req); + break; + case QUERY_DESC_IDN_POWER: + /* mocking of power descriptor is not supported */ + memset(&req->rsp_upiu.qr.data, 0, sizeof(PowerParametersDescriptor)); + req->rsp_upiu.qr.data[0] = sizeof(PowerParametersDescriptor); + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_POWER; + status = QUERY_RESULT_SUCCESS; + break; + case QUERY_DESC_IDN_HEALTH: + /* mocking of health descriptor is not supported */ + memset(&req->rsp_upiu.qr.data, 0, sizeof(DeviceHealthDescriptor)); + req->rsp_upiu.qr.data[0] = sizeof(DeviceHealthDescriptor); + req->rsp_upiu.qr.data[1] = QUERY_DESC_IDN_HEALTH; + status = QUERY_RESULT_SUCCESS; + break; + default: + length = 0; + trace_ufs_err_query_invalid_idn(req->req_upiu.qr.opcode, idn); + status = QUERY_RESULT_INVALID_IDN; + } + + if (length > req->rsp_upiu.qr.data[0]) { + length = req->rsp_upiu.qr.data[0]; + } + req->rsp_upiu.qr.opcode = req->req_upiu.qr.opcode; + req->rsp_upiu.qr.idn = req->req_upiu.qr.idn; + req->rsp_upiu.qr.index = req->req_upiu.qr.index; + req->rsp_upiu.qr.selector = req->req_upiu.qr.selector; + req->rsp_upiu.qr.length = cpu_to_be16(length); + + return status; +} + +static QueryRespCode ufs_exec_query_read(UfsRequest *req) +{ + QueryRespCode status; + switch (req->req_upiu.qr.opcode) { + case UPIU_QUERY_OPCODE_NOP: + status = QUERY_RESULT_SUCCESS; + break; + case UPIU_QUERY_OPCODE_READ_DESC: + status = ufs_read_desc(req); + break; + case UPIU_QUERY_OPCODE_READ_ATTR: + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_READ); + break; + case UPIU_QUERY_OPCODE_READ_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_READ); + break; + default: + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); + status = QUERY_RESULT_INVALID_OPCODE; + break; + } + + return status; +} + +static QueryRespCode ufs_exec_query_write(UfsRequest *req) +{ + QueryRespCode status; + switch (req->req_upiu.qr.opcode) { + case UPIU_QUERY_OPCODE_NOP: + status = QUERY_RESULT_SUCCESS; + break; + case UPIU_QUERY_OPCODE_WRITE_DESC: + /* write descriptor is not supported */ + status = QUERY_RESULT_NOT_WRITEABLE; + break; + case UPIU_QUERY_OPCODE_WRITE_ATTR: + status = ufs_exec_query_attr(req, UFS_QUERY_ATTR_WRITE); + break; + case UPIU_QUERY_OPCODE_SET_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_SET); + break; + case UPIU_QUERY_OPCODE_CLEAR_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_CLEAR); + break; + case UPIU_QUERY_OPCODE_TOGGLE_FLAG: + status = ufs_exec_query_flag(req, UFS_QUERY_FLAG_TOGGLE); + break; + default: + trace_ufs_err_query_invalid_opcode(req->req_upiu.qr.opcode); + status = QUERY_RESULT_INVALID_OPCODE; + break; + } + + return status; +} + +static UfsReqResult ufs_exec_query_cmd(UfsRequest *req) +{ + uint8_t query_func = req->req_upiu.header.query_func; + uint16_t data_segment_length; + QueryRespCode status; + + trace_ufs_exec_query_cmd(req->slot, req->req_upiu.qr.opcode); + if (query_func == UPIU_QUERY_FUNC_STANDARD_READ_REQUEST) { + status = ufs_exec_query_read(req); + } else if (query_func == UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST) { + status = ufs_exec_query_write(req); + } else { + status = QUERY_RESULT_GENERAL_FAILURE; + } + + data_segment_length = be16_to_cpu(req->rsp_upiu.qr.length); + ufs_build_upiu_header(req, UPIU_TRANSACTION_QUERY_RSP, 0, status, 0, + data_segment_length); + + if (status != QUERY_RESULT_SUCCESS) { + return UFS_REQUEST_ERROR; + } + return UFS_REQUEST_SUCCESS; +} + +static void ufs_exec_req(UfsRequest *req) +{ + UfsReqResult req_result; + + if (ufs_dma_read_upiu(req)) { + return; + } + + switch (req->req_upiu.header.trans_type) { + case UPIU_TRANSACTION_NOP_OUT: + req_result = ufs_exec_nop_cmd(req); + break; + case UPIU_TRANSACTION_COMMAND: + /* Not yet implemented */ + req_result = UFS_REQUEST_ERROR; + break; + case UPIU_TRANSACTION_QUERY_REQ: + req_result = ufs_exec_query_cmd(req); + break; + default: + trace_ufs_err_invalid_trans_code(req->slot, + req->req_upiu.header.trans_type); + req_result = UFS_REQUEST_ERROR; + } + + ufs_complete_req(req, req_result); +} + +static void ufs_process_req(void *opaque) +{ + UfsHc *u = opaque; + UfsRequest *req; + int slot; + + for (slot = 0; slot < u->params.nutrs; slot++) { + req = &u->req_list[slot]; + + if (req->state != UFS_REQUEST_READY) { + continue; + } + trace_ufs_process_req(slot); + req->state = UFS_REQUEST_RUNNING; + + ufs_exec_req(req); + } +} + +static void ufs_complete_req(UfsRequest *req, UfsReqResult req_result) +{ + UfsHc *u = req->hc; + assert(req->state == UFS_REQUEST_RUNNING); + + if (req_result == UFS_REQUEST_SUCCESS) { + req->utrd.header.dword_2 = OCS_SUCCESS; + } else { + req->utrd.header.dword_2 = OCS_INVALID_CMD_TABLE_ATTR; + } + + trace_ufs_complete_req(req->slot); + req->state = UFS_REQUEST_COMPLETE; + qemu_bh_schedule(u->complete_bh); +} + +static void ufs_clear_req(UfsRequest *req) +{ + if (req->sg != NULL) { + qemu_sglist_destroy(req->sg); + g_free(req->sg); + req->sg = NULL; + } + + memset(&req->utrd, 0, sizeof(req->utrd)); + memset(&req->req_upiu, 0, sizeof(req->req_upiu)); + memset(&req->rsp_upiu, 0, sizeof(req->rsp_upiu)); +} + +static void ufs_sendback_req(void *opaque) +{ + UfsHc *u = opaque; + UfsRequest *req; + int slot; + + for (slot = 0; slot < u->params.nutrs; slot++) { + uint32_t is = ldl_le_p(&u->reg.is); + uint32_t utrldbr = ldl_le_p(&u->reg.utrldbr); + uint32_t utrlcnr = ldl_le_p(&u->reg.utrlcnr); + + req = &u->req_list[slot]; + + if (req->state != UFS_REQUEST_COMPLETE) { + continue; + } + + if (ufs_dma_write_upiu(req)) { + continue; + } + + /* + * TODO: UTP Transfer Request Interrupt Aggregation Control is not yet + * supported + */ + if (req->utrd.header.dword_2 != OCS_SUCCESS || + req->utrd.header.dword_0 & UTP_REQ_DESC_INT_CMD) { + is = FIELD_DP32(is, IS, UTRCS, 1); + } + + utrldbr &= ~(1 << slot); + utrlcnr |= (1 << slot); + + stl_le_p(&u->reg.is, is); + stl_le_p(&u->reg.utrldbr, utrldbr); + stl_le_p(&u->reg.utrlcnr, utrlcnr); + + trace_ufs_sendback_req(req->slot); + + ufs_clear_req(req); + req->state = UFS_REQUEST_IDLE; + } + + ufs_irq_check(u); +} + static bool ufs_check_constraints(UfsHc *u, Error **errp) { if (u->params.nutrs > UFS_MAX_NUTRS) { @@ -232,6 +1118,23 @@ static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) u->irq = pci_allocate_irq(pci_dev); } +static void ufs_init_state(UfsHc *u) +{ + u->req_list = g_new0(UfsRequest, u->params.nutrs); + + for (int i = 0; i < u->params.nutrs; i++) { + u->req_list[i].hc = u; + u->req_list[i].slot = i; + u->req_list[i].sg = NULL; + u->req_list[i].state = UFS_REQUEST_IDLE; + } + + u->doorbell_bh = qemu_bh_new_guarded(ufs_process_req, u, + &DEVICE(u)->mem_reentrancy_guard); + u->complete_bh = qemu_bh_new_guarded(ufs_sendback_req, u, + &DEVICE(u)->mem_reentrancy_guard); +} + static void ufs_init_hc(UfsHc *u) { uint32_t cap = 0; @@ -249,6 +1152,54 @@ static void ufs_init_hc(UfsHc *u) cap = FIELD_DP32(cap, CAP, CS, 0); stl_le_p(&u->reg.cap, cap); stl_le_p(&u->reg.ver, UFS_SPEC_VER); + + memset(&u->device_desc, 0, sizeof(DeviceDescriptor)); + u->device_desc.length = sizeof(DeviceDescriptor); + u->device_desc.descriptor_idn = QUERY_DESC_IDN_DEVICE; + u->device_desc.device_sub_class = 0x01; + u->device_desc.number_lu = 0x00; + u->device_desc.number_wlu = 0x04; + /* 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.spec_version = cpu_to_be16(0x0310); + u->device_desc.manufacturer_name = 0x00; + u->device_desc.product_name = 0x01; + u->device_desc.serial_number = 0x02; + u->device_desc.oem_id = 0x03; + u->device_desc.ud_0_base_offset = 0x16; + u->device_desc.ud_config_p_length = 0x1A; + u->device_desc.device_rtt_cap = 0x02; + u->device_desc.queue_depth = u->params.nutrs; + u->device_desc.product_revision_level = 0x04; + u->device_desc.extended_ufs_features_support = 0x00; + + memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor)); + u->geometry_desc.length = sizeof(GeometryDescriptor); + u->geometry_desc.descriptor_idn = QUERY_DESC_IDN_GEOMETRY; + u->geometry_desc.total_raw_device_capacity = 0; + u->geometry_desc.max_number_lu = (UFS_MAX_LUS == 32) ? 0x1 : 0x0; + u->geometry_desc.segment_size = cpu_to_be32(0x2000); /* 4KB */ + u->geometry_desc.allocation_unit_size = 0x1; /* 4KB */ + u->geometry_desc.min_addr_block_size = 0x8; /* 4KB */ + u->geometry_desc.max_in_buffer_size = 0x8; + u->geometry_desc.max_out_buffer_size = 0x8; + u->geometry_desc.rpmb_read_write_size = 0x40; + u->geometry_desc.data_ordering = + 0x0; /* out-of-order data transfer is not supported */ + u->geometry_desc.max_context_id_number = 0x5; + u->geometry_desc.supported_memory_types = cpu_to_be16(0x8001); + + memset(&u->attributes, 0, sizeof(u->attributes)); + u->attributes.max_data_in_size = 0x08; + u->attributes.max_data_out_size = 0x08; + u->attributes.ref_clk_freq = 0x01; /* 26 MHz */ + /* configure descriptor is not supported */ + u->attributes.config_descr_lock = 0x01; + u->attributes.max_num_of_rtt = 0x02; + + memset(&u->flags, 0, sizeof(u->flags)); + u->flags.permanently_disable_fw_update = 1; } static void ufs_realize(PCIDevice *pci_dev, Error **errp) @@ -259,10 +1210,24 @@ static void ufs_realize(PCIDevice *pci_dev, Error **errp) return; } + ufs_init_state(u); ufs_init_hc(u); ufs_init_pci(u, pci_dev); } +static void ufs_exit(PCIDevice *pci_dev) +{ + UfsHc *u = UFS(pci_dev); + + qemu_bh_delete(u->doorbell_bh); + qemu_bh_delete(u->complete_bh); + + for (int i = 0; i < u->params.nutrs; i++) { + ufs_clear_req(&u->req_list[i]); + } + g_free(u->req_list); +} + static Property ufs_props[] = { DEFINE_PROP_STRING("serial", UfsHc, params.serial), DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), @@ -281,6 +1246,7 @@ static void ufs_class_init(ObjectClass *oc, void *data) PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc); pc->realize = ufs_realize; + pc->exit = ufs_exit; pc->class_id = PCI_CLASS_STORAGE_UFS; set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h index 3c28f4e62d..5d4fd818f9 100644 --- a/hw/ufs/ufs.h +++ b/hw/ufs/ufs.h @@ -18,6 +18,31 @@ #define UFS_MAX_LUS 32 #define UFS_LOGICAL_BLK_SIZE 4096 +typedef enum UfsRequestState { + UFS_REQUEST_IDLE = 0, + UFS_REQUEST_READY = 1, + UFS_REQUEST_RUNNING = 2, + UFS_REQUEST_COMPLETE = 3, +} UfsRequestState; + +typedef enum UfsReqResult { + UFS_REQUEST_SUCCESS = 0, + UFS_REQUEST_ERROR = 1, +} UfsReqResult; + +typedef struct UfsRequest { + struct UfsHc *hc; + UfsRequestState state; + int slot; + + UtpTransferReqDesc utrd; + UtpUpiuReq req_upiu; + UtpUpiuRsp rsp_upiu; + + /* for scsi command */ + QEMUSGList *sg; +} UfsRequest; + typedef struct UfsParams { char *serial; uint8_t nutrs; /* Number of UTP Transfer Request Slots */ @@ -30,6 +55,12 @@ typedef struct UfsHc { UfsReg reg; UfsParams params; uint32_t reg_size; + UfsRequest *req_list; + + DeviceDescriptor device_desc; + GeometryDescriptor geometry_desc; + Attributes attributes; + Flags flags; qemu_irq irq; QEMUBH *doorbell_bh; @@ -39,4 +70,18 @@ typedef struct UfsHc { #define TYPE_UFS "ufs" #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS) +typedef enum UfsQueryFlagPerm { + UFS_QUERY_FLAG_NONE = 0x0, + UFS_QUERY_FLAG_READ = 0x1, + UFS_QUERY_FLAG_SET = 0x2, + UFS_QUERY_FLAG_CLEAR = 0x4, + UFS_QUERY_FLAG_TOGGLE = 0x8, +} UfsQueryFlagPerm; + +typedef enum UfsQueryAttrPerm { + UFS_QUERY_ATTR_NONE = 0x0, + UFS_QUERY_ATTR_READ = 0x1, + UFS_QUERY_ATTR_WRITE = 0x2, +} UfsQueryAttrPerm; + #endif /* HW_UFS_UFS_H */
This commit makes the UFS device support query and nop out transfer requests. The next patch would be support for UFS logical unit and scsi command transfer request. Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com> --- hw/ufs/ufs.c | 968 ++++++++++++++++++++++++++++++++++++++++++++++++++- hw/ufs/ufs.h | 45 +++ 2 files changed, 1012 insertions(+), 1 deletion(-)