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

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)