hw/ufs: Support for UFS logical unit

This commit adds support for ufs logical unit.
The LU handles processing for the SCSI command,
unit descriptor query request.

This commit enables the UFS device to process
IO requests.

Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-id: beacc504376ab6a14b1a3830bb3c69382cf6aebc.1693980783.git.jeuk20.kim@gmail.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Jeuk Kim 2023-09-06 16:43:50 +09:00 committed by Stefan Hajnoczi
parent 329f166244
commit 2a8b36a496
6 changed files with 1761 additions and 7 deletions

1445
hw/ufs/lu.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c'))
system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c', 'lu.c'))

View file

@ -12,6 +12,31 @@ ufs_exec_scsi_cmd(uint32_t slot, uint8_t lun, uint8_t opcode) "slot %"PRIu32", l
ufs_exec_query_cmd(uint32_t slot, uint8_t opcode) "slot %"PRIu32", opcode 0x%"PRIx8""
ufs_process_uiccmd(uint32_t uiccmd, uint32_t ucmdarg1, uint32_t ucmdarg2, uint32_t ucmdarg3) "uiccmd 0x%"PRIx32", ucmdarg1 0x%"PRIx32", ucmdarg2 0x%"PRIx32", ucmdarg3 0x%"PRIx32""
# lu.c
ufs_scsi_check_condition(uint32_t tag, uint8_t key, uint8_t asc, uint8_t ascq) "Command complete tag=0x%x sense=%d/%d/%d"
ufs_scsi_read_complete(uint32_t tag, size_t size) "Data ready tag=0x%x len=%zd"
ufs_scsi_read_data_count(uint32_t sector_count) "Read sector_count=%d"
ufs_scsi_read_data_invalid(void) "Data transfer direction invalid"
ufs_scsi_write_complete_noio(uint32_t tag, size_t size) "Write complete tag=0x%x more=%zd"
ufs_scsi_write_data_invalid(void) "Data transfer direction invalid"
ufs_scsi_emulate_vpd_page_00(size_t xfer) "Inquiry EVPD[Supported pages] buffer size %zd"
ufs_scsi_emulate_vpd_page_80_not_supported(void) "Inquiry EVPD[Serial number] not supported"
ufs_scsi_emulate_vpd_page_80(size_t xfer) "Inquiry EVPD[Serial number] buffer size %zd"
ufs_scsi_emulate_vpd_page_87(size_t xfer) "Inquiry EVPD[Mode Page Policy] buffer size %zd"
ufs_scsi_emulate_mode_sense(int cmd, int page, size_t xfer, int control) "Mode Sense(%d) (page %d, xfer %zd, page_control %d)"
ufs_scsi_emulate_read_data(int buflen) "Read buf_len=%d"
ufs_scsi_emulate_write_data(int buflen) "Write buf_len=%d"
ufs_scsi_emulate_command_START_STOP(void) "START STOP UNIT"
ufs_scsi_emulate_command_FORMAT_UNIT(void) "FORMAT UNIT"
ufs_scsi_emulate_command_SEND_DIAGNOSTIC(void) "SEND DIAGNOSTIC"
ufs_scsi_emulate_command_SAI_16(void) "SAI READ CAPACITY(16)"
ufs_scsi_emulate_command_SAI_unsupported(void) "Unsupported Service Action In"
ufs_scsi_emulate_command_MODE_SELECT_10(size_t xfer) "Mode Select(10) (len %zd)"
ufs_scsi_emulate_command_VERIFY(int bytchk) "Verify (bytchk %d)"
ufs_scsi_emulate_command_UNKNOWN(int cmd, const char *name) "Unknown SCSI command (0x%2.2x=%s)"
ufs_scsi_dma_command_READ(uint64_t lba, uint32_t len) "Read (block %" PRIu64 ", count %u)"
ufs_scsi_dma_command_WRITE(uint64_t lba, int len) "Write (block %" PRIu64 ", count %u)"
# error condition
ufs_err_dma_read_utrd(uint32_t slot, uint64_t addr) "failed to read utrd. UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64""
ufs_err_dma_read_req_upiu(uint32_t slot, uint64_t addr) "failed to read req upiu. UTRLDBR slot %"PRIu32", request upiu addr %"PRIu64""

View file

@ -8,6 +8,19 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/**
* Reference Specs: https://www.jedec.org/, 3.1
*
* Usage
* -----
*
* Add options:
* -drive file=<file>,if=none,id=<drive_id>
* -device ufs,serial=<serial>,id=<bus_name>, \
* nutrs=<N[optional]>,nutmrs=<N[optional]>
* -device ufs-lu,drive=<drive_id>,bus=<bus_name>
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "migration/vmstate.h"
@ -420,6 +433,19 @@ static const MemoryRegionOps ufs_mmio_ops = {
},
};
static QEMUSGList *ufs_get_sg_list(SCSIRequest *scsi_req)
{
UfsRequest *req = scsi_req->hba_private;
return req->sg;
}
static void ufs_build_upiu_sense_data(UfsRequest *req, SCSIRequest *scsi_req)
{
req->rsp_upiu.sr.sense_data_len = cpu_to_be16(scsi_req->sense_len);
assert(scsi_req->sense_len <= SCSI_SENSE_LEN);
memcpy(req->rsp_upiu.sr.sense_data, scsi_req->sense, scsi_req->sense_len);
}
static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type,
uint8_t flags, uint8_t response,
uint8_t scsi_status,
@ -433,6 +459,98 @@ static void ufs_build_upiu_header(UfsRequest *req, uint8_t trans_type,
req->rsp_upiu.header.data_segment_length = cpu_to_be16(data_segment_length);
}
static void ufs_scsi_command_complete(SCSIRequest *scsi_req, size_t resid)
{
UfsRequest *req = scsi_req->hba_private;
int16_t status = scsi_req->status;
uint32_t expected_len = be32_to_cpu(req->req_upiu.sc.exp_data_transfer_len);
uint32_t transfered_len = scsi_req->cmd.xfer - resid;
uint8_t flags = 0, response = UFS_COMMAND_RESULT_SUCESS;
uint16_t data_segment_length;
if (expected_len > transfered_len) {
req->rsp_upiu.sr.residual_transfer_count =
cpu_to_be32(expected_len - transfered_len);
flags |= UFS_UPIU_FLAG_UNDERFLOW;
} else if (expected_len < transfered_len) {
req->rsp_upiu.sr.residual_transfer_count =
cpu_to_be32(transfered_len - expected_len);
flags |= UFS_UPIU_FLAG_OVERFLOW;
}
if (status != 0) {
ufs_build_upiu_sense_data(req, scsi_req);
response = UFS_COMMAND_RESULT_FAIL;
}
data_segment_length = cpu_to_be16(scsi_req->sense_len +
sizeof(req->rsp_upiu.sr.sense_data_len));
ufs_build_upiu_header(req, UFS_UPIU_TRANSACTION_RESPONSE, flags, response,
status, data_segment_length);
ufs_complete_req(req, UFS_REQUEST_SUCCESS);
scsi_req->hba_private = NULL;
scsi_req_unref(scsi_req);
}
static const struct SCSIBusInfo ufs_scsi_info = {
.tcq = true,
.max_target = 0,
.max_lun = UFS_MAX_LUS,
.max_channel = 0,
.get_sg_list = ufs_get_sg_list,
.complete = ufs_scsi_command_complete,
};
static UfsReqResult ufs_exec_scsi_cmd(UfsRequest *req)
{
UfsHc *u = req->hc;
uint8_t lun = req->req_upiu.header.lun;
uint8_t task_tag = req->req_upiu.header.task_tag;
SCSIDevice *dev = NULL;
trace_ufs_exec_scsi_cmd(req->slot, lun, req->req_upiu.sc.cdb[0]);
if (!is_wlun(lun)) {
if (lun >= u->device_desc.number_lu) {
trace_ufs_err_scsi_cmd_invalid_lun(lun);
return UFS_REQUEST_FAIL;
} else if (u->lus[lun] == NULL) {
trace_ufs_err_scsi_cmd_invalid_lun(lun);
return UFS_REQUEST_FAIL;
}
}
switch (lun) {
case UFS_UPIU_REPORT_LUNS_WLUN:
dev = &u->report_wlu->qdev;
break;
case UFS_UPIU_UFS_DEVICE_WLUN:
dev = &u->dev_wlu->qdev;
break;
case UFS_UPIU_BOOT_WLUN:
dev = &u->boot_wlu->qdev;
break;
case UFS_UPIU_RPMB_WLUN:
dev = &u->rpmb_wlu->qdev;
break;
default:
dev = &u->lus[lun]->qdev;
}
SCSIRequest *scsi_req = scsi_req_new(
dev, task_tag, lun, req->req_upiu.sc.cdb, UFS_CDB_SIZE, req);
uint32_t len = scsi_req_enqueue(scsi_req);
if (len) {
scsi_req_continue(scsi_req);
}
return UFS_REQUEST_NO_COMPLETE;
}
static UfsReqResult ufs_exec_nop_cmd(UfsRequest *req)
{
trace_ufs_exec_nop_cmd(req->slot);
@ -716,9 +834,11 @@ static const RpmbUnitDescriptor rpmb_unit_desc = {
static QueryRespCode ufs_read_unit_desc(UfsRequest *req)
{
UfsHc *u = req->hc;
uint8_t lun = req->req_upiu.qr.index;
if (lun != UFS_UPIU_RPMB_WLUN && lun > UFS_MAX_LUS) {
if (lun != UFS_UPIU_RPMB_WLUN &&
(lun > UFS_MAX_LUS || u->lus[lun] == NULL)) {
trace_ufs_err_query_invalid_index(req->req_upiu.qr.opcode, lun);
return UFS_QUERY_RESULT_INVALID_INDEX;
}
@ -726,8 +846,8 @@ static QueryRespCode ufs_read_unit_desc(UfsRequest *req)
if (lun == UFS_UPIU_RPMB_WLUN) {
memcpy(&req->rsp_upiu.qr.data, &rpmb_unit_desc, rpmb_unit_desc.length);
} else {
/* unit descriptor is not yet supported */
return UFS_QUERY_RESULT_INVALID_INDEX;
memcpy(&req->rsp_upiu.qr.data, &u->lus[lun]->unit_desc,
sizeof(u->lus[lun]->unit_desc));
}
return UFS_QUERY_RESULT_SUCCESS;
@ -977,8 +1097,7 @@ static void ufs_exec_req(UfsRequest *req)
req_result = ufs_exec_nop_cmd(req);
break;
case UFS_UPIU_TRANSACTION_COMMAND:
/* Not yet implemented */
req_result = UFS_REQUEST_FAIL;
req_result = ufs_exec_scsi_cmd(req);
break;
case UFS_UPIU_TRANSACTION_QUERY_REQ:
req_result = ufs_exec_query_cmd(req);
@ -989,7 +1108,14 @@ static void ufs_exec_req(UfsRequest *req)
req_result = UFS_REQUEST_FAIL;
}
ufs_complete_req(req, req_result);
/*
* The ufs_complete_req for scsi commands is handled by the
* ufs_scsi_command_complete() callback function. Therefore, to avoid
* duplicate processing, ufs_complete_req() is not called for scsi commands.
*/
if (req_result != UFS_REQUEST_NO_COMPLETE) {
ufs_complete_req(req, req_result);
}
}
static void ufs_process_req(void *opaque)
@ -1191,6 +1317,28 @@ static void ufs_init_hc(UfsHc *u)
u->flags.permanently_disable_fw_update = 1;
}
static bool ufs_init_wlu(UfsHc *u, UfsWLu **wlu, uint8_t wlun, Error **errp)
{
UfsWLu *new_wlu = UFSWLU(qdev_new(TYPE_UFS_WLU));
qdev_prop_set_uint32(DEVICE(new_wlu), "lun", wlun);
/*
* The well-known lu shares the same bus as the normal lu. If the well-known
* lu writes the same channel value as the normal lu, the report will be
* made not only for the normal lu but also for the well-known lu at
* REPORT_LUN time. To prevent this, the channel value of normal lu is fixed
* to 0 and the channel value of well-known lu is fixed to 1.
*/
qdev_prop_set_uint32(DEVICE(new_wlu), "channel", 1);
if (!qdev_realize_and_unref(DEVICE(new_wlu), BUS(&u->bus), errp)) {
return false;
}
*wlu = new_wlu;
return true;
}
static void ufs_realize(PCIDevice *pci_dev, Error **errp)
{
UfsHc *u = UFS(pci_dev);
@ -1199,15 +1347,55 @@ static void ufs_realize(PCIDevice *pci_dev, Error **errp)
return;
}
qbus_init(&u->bus, sizeof(UfsBus), TYPE_UFS_BUS, &pci_dev->qdev,
u->parent_obj.qdev.id);
u->bus.parent_bus.info = &ufs_scsi_info;
ufs_init_state(u);
ufs_init_hc(u);
ufs_init_pci(u, pci_dev);
if (!ufs_init_wlu(u, &u->report_wlu, UFS_UPIU_REPORT_LUNS_WLUN, errp)) {
return;
}
if (!ufs_init_wlu(u, &u->dev_wlu, UFS_UPIU_UFS_DEVICE_WLUN, errp)) {
return;
}
if (!ufs_init_wlu(u, &u->boot_wlu, UFS_UPIU_BOOT_WLUN, errp)) {
return;
}
if (!ufs_init_wlu(u, &u->rpmb_wlu, UFS_UPIU_RPMB_WLUN, errp)) {
return;
}
}
static void ufs_exit(PCIDevice *pci_dev)
{
UfsHc *u = UFS(pci_dev);
if (u->dev_wlu) {
object_unref(OBJECT(u->dev_wlu));
u->dev_wlu = NULL;
}
if (u->report_wlu) {
object_unref(OBJECT(u->report_wlu));
u->report_wlu = NULL;
}
if (u->rpmb_wlu) {
object_unref(OBJECT(u->rpmb_wlu));
u->rpmb_wlu = NULL;
}
if (u->boot_wlu) {
object_unref(OBJECT(u->boot_wlu));
u->boot_wlu = NULL;
}
qemu_bh_delete(u->doorbell_bh);
qemu_bh_delete(u->complete_bh);
@ -1246,6 +1434,49 @@ static void ufs_class_init(ObjectClass *oc, void *data)
dc->vmsd = &ufs_vmstate;
}
static bool ufs_bus_check_address(BusState *qbus, DeviceState *qdev,
Error **errp)
{
SCSIDevice *dev = SCSI_DEVICE(qdev);
UfsBusClass *ubc = UFS_BUS_GET_CLASS(qbus);
UfsHc *u = UFS(qbus->parent);
if (strcmp(object_get_typename(OBJECT(dev)), TYPE_UFS_WLU) == 0) {
if (dev->lun != UFS_UPIU_REPORT_LUNS_WLUN &&
dev->lun != UFS_UPIU_UFS_DEVICE_WLUN &&
dev->lun != UFS_UPIU_BOOT_WLUN && dev->lun != UFS_UPIU_RPMB_WLUN) {
error_setg(errp, "bad well-known lun: %d", dev->lun);
return false;
}
if ((dev->lun == UFS_UPIU_REPORT_LUNS_WLUN && u->report_wlu != NULL) ||
(dev->lun == UFS_UPIU_UFS_DEVICE_WLUN && u->dev_wlu != NULL) ||
(dev->lun == UFS_UPIU_BOOT_WLUN && u->boot_wlu != NULL) ||
(dev->lun == UFS_UPIU_RPMB_WLUN && u->rpmb_wlu != NULL)) {
error_setg(errp, "well-known lun %d already exists", dev->lun);
return false;
}
return true;
}
if (strcmp(object_get_typename(OBJECT(dev)), TYPE_UFS_LU) != 0) {
error_setg(errp, "%s cannot be connected to ufs-bus",
object_get_typename(OBJECT(dev)));
return false;
}
return ubc->parent_check_address(qbus, qdev, errp);
}
static void ufs_bus_class_init(ObjectClass *class, void *data)
{
BusClass *bc = BUS_CLASS(class);
UfsBusClass *ubc = UFS_BUS_CLASS(class);
ubc->parent_check_address = bc->check_address;
bc->check_address = ufs_bus_check_address;
}
static const TypeInfo ufs_info = {
.name = TYPE_UFS,
.parent = TYPE_PCI_DEVICE,
@ -1254,9 +1485,18 @@ static const TypeInfo ufs_info = {
.interfaces = (InterfaceInfo[]){ { INTERFACE_PCIE_DEVICE }, {} },
};
static const TypeInfo ufs_bus_info = {
.name = TYPE_UFS_BUS,
.parent = TYPE_SCSI_BUS,
.class_init = ufs_bus_class_init,
.class_size = sizeof(UfsBusClass),
.instance_size = sizeof(UfsBus),
};
static void ufs_register_types(void)
{
type_register_static(&ufs_info);
type_register_static(&ufs_bus_info);
}
type_init(ufs_register_types)

View file

@ -18,6 +18,18 @@
#define UFS_MAX_LUS 32
#define UFS_BLOCK_SIZE 4096
typedef struct UfsBusClass {
BusClass parent_class;
bool (*parent_check_address)(BusState *bus, DeviceState *dev, Error **errp);
} UfsBusClass;
typedef struct UfsBus {
SCSIBus parent_bus;
} UfsBus;
#define TYPE_UFS_BUS "ufs-bus"
DECLARE_OBJ_CHECKERS(UfsBus, UfsBusClass, UFS_BUS, TYPE_UFS_BUS)
typedef enum UfsRequestState {
UFS_REQUEST_IDLE = 0,
UFS_REQUEST_READY = 1,
@ -29,6 +41,7 @@ typedef enum UfsRequestState {
typedef enum UfsReqResult {
UFS_REQUEST_SUCCESS = 0,
UFS_REQUEST_FAIL = 1,
UFS_REQUEST_NO_COMPLETE = 2,
} UfsReqResult;
typedef struct UfsRequest {
@ -44,6 +57,17 @@ typedef struct UfsRequest {
QEMUSGList *sg;
} UfsRequest;
typedef struct UfsLu {
SCSIDevice qdev;
uint8_t lun;
UnitDescriptor unit_desc;
} UfsLu;
typedef struct UfsWLu {
SCSIDevice qdev;
uint8_t lun;
} UfsWLu;
typedef struct UfsParams {
char *serial;
uint8_t nutrs; /* Number of UTP Transfer Request Slots */
@ -52,12 +76,18 @@ typedef struct UfsParams {
typedef struct UfsHc {
PCIDevice parent_obj;
UfsBus bus;
MemoryRegion iomem;
UfsReg reg;
UfsParams params;
uint32_t reg_size;
UfsRequest *req_list;
UfsLu *lus[UFS_MAX_LUS];
UfsWLu *report_wlu;
UfsWLu *dev_wlu;
UfsWLu *boot_wlu;
UfsWLu *rpmb_wlu;
DeviceDescriptor device_desc;
GeometryDescriptor geometry_desc;
Attributes attributes;
@ -71,6 +101,12 @@ typedef struct UfsHc {
#define TYPE_UFS "ufs"
#define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS)
#define TYPE_UFS_LU "ufs-lu"
#define UFSLU(obj) OBJECT_CHECK(UfsLu, (obj), TYPE_UFS_LU)
#define TYPE_UFS_WLU "ufs-wlu"
#define UFSWLU(obj) OBJECT_CHECK(UfsWLu, (obj), TYPE_UFS_WLU)
typedef enum UfsQueryFlagPerm {
UFS_QUERY_FLAG_NONE = 0x0,
UFS_QUERY_FLAG_READ = 0x1,
@ -85,4 +121,11 @@ typedef enum UfsQueryAttrPerm {
UFS_QUERY_ATTR_WRITE = 0x2,
} UfsQueryAttrPerm;
static inline bool is_wlun(uint8_t lun)
{
return (lun == UFS_UPIU_REPORT_LUNS_WLUN ||
lun == UFS_UPIU_UFS_DEVICE_WLUN || lun == UFS_UPIU_BOOT_WLUN ||
lun == UFS_UPIU_RPMB_WLUN);
}
#endif /* HW_UFS_UFS_H */

View file

@ -231,6 +231,7 @@
#define MODE_PAGE_FLEXIBLE_DISK_GEOMETRY 0x05
#define MODE_PAGE_CACHING 0x08
#define MODE_PAGE_AUDIO_CTL 0x0e
#define MODE_PAGE_CONTROL 0x0a
#define MODE_PAGE_POWER 0x1a
#define MODE_PAGE_FAULT_FAIL 0x1c
#define MODE_PAGE_TO_PROTECT 0x1d