aboutsummaryrefslogtreecommitdiff
path: root/hw/ufs/ufs.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/ufs/ufs.c')
-rw-r--r--hw/ufs/ufs.c252
1 files changed, 246 insertions, 6 deletions
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 56a8ec2..0ecedb9 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -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)