-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
hw/ufs: Initial commit for emulated Universal-Flash-Storage
Universal Flash Storage (UFS) is a high-performance mass storage device with a serial interface. It is primarily used as a high-performance data storage device for embedded applications. This commit contains code for UFS device to be recognized as a UFS PCI device. Patches to handle UFS logical unit and Transfer Request will follow. Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Message-id: 10232660d462ee5cd10cf673f1a9a1205fc8276c.1693980783.git.jeuk20.kim@gmail.com Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
- Loading branch information
1 parent
1f14c91
commit bc4e68d
Showing
14 changed files
with
1,461 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
config UFS_PCI | ||
bool | ||
default y if PCI_DEVICES | ||
depends on PCI |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
system_ss.add(when: 'CONFIG_UFS_PCI', if_true: files('ufs.c')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# ufs.c | ||
ufs_irq_raise(void) "INTx" | ||
ufs_irq_lower(void) "INTx" | ||
ufs_mmio_read(uint64_t addr, uint64_t data, unsigned size) "addr 0x%"PRIx64" data 0x%"PRIx64" size %d" | ||
ufs_mmio_write(uint64_t addr, uint64_t data, unsigned size) "addr 0x%"PRIx64" data 0x%"PRIx64" size %d" | ||
ufs_process_db(uint32_t slot) "UTRLDBR slot %"PRIu32"" | ||
ufs_process_req(uint32_t slot) "UTRLDBR slot %"PRIu32"" | ||
ufs_complete_req(uint32_t slot) "UTRLDBR slot %"PRIu32"" | ||
ufs_sendback_req(uint32_t slot) "UTRLDBR slot %"PRIu32"" | ||
ufs_exec_nop_cmd(uint32_t slot) "UTRLDBR slot %"PRIu32"" | ||
ufs_exec_scsi_cmd(uint32_t slot, uint8_t lun, uint8_t opcode) "slot %"PRIu32", lun 0x%"PRIx8", opcode 0x%"PRIx8"" | ||
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"" | ||
|
||
# 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"" | ||
ufs_err_dma_read_prdt(uint32_t slot, uint64_t addr) "failed to read prdt. UTRLDBR slot %"PRIu32", prdt addr %"PRIu64"" | ||
ufs_err_dma_write_utrd(uint32_t slot, uint64_t addr) "failed to write utrd. UTRLDBR slot %"PRIu32", UTRD dma addr %"PRIu64"" | ||
ufs_err_dma_write_rsp_upiu(uint32_t slot, uint64_t addr) "failed to write rsp upiu. UTRLDBR slot %"PRIu32", response upiu addr %"PRIu64"" | ||
ufs_err_utrl_slot_busy(uint32_t slot) "UTRLDBR slot %"PRIu32" is busy" | ||
ufs_err_unsupport_register_offset(uint32_t offset) "Register offset 0x%"PRIx32" is not yet supported" | ||
ufs_err_invalid_register_offset(uint32_t offset) "Register offset 0x%"PRIx32" is invalid" | ||
ufs_err_scsi_cmd_invalid_lun(uint8_t lun) "scsi command has invalid lun: 0x%"PRIx8"" | ||
ufs_err_query_flag_not_readable(uint8_t idn) "query flag idn 0x%"PRIx8" is denied to read" | ||
ufs_err_query_flag_not_writable(uint8_t idn) "query flag idn 0x%"PRIx8" is denied to write" | ||
ufs_err_query_attr_not_readable(uint8_t idn) "query attribute idn 0x%"PRIx8" is denied to read" | ||
ufs_err_query_attr_not_writable(uint8_t idn) "query attribute idn 0x%"PRIx8" is denied to write" | ||
ufs_err_query_invalid_opcode(uint8_t opcode) "query request has invalid opcode. opcode: 0x%"PRIx8"" | ||
ufs_err_query_invalid_idn(uint8_t opcode, uint8_t idn) "query request has invalid idn. opcode: 0x%"PRIx8", idn 0x%"PRIx8"" | ||
ufs_err_query_invalid_index(uint8_t opcode, uint8_t index) "query request has invalid index. opcode: 0x%"PRIx8", index 0x%"PRIx8"" | ||
ufs_err_invalid_trans_code(uint32_t slot, uint8_t trans_code) "request upiu has invalid transaction code. slot: %"PRIu32", trans_code: 0x%"PRIx8"" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
#include "trace/trace-hw_ufs.h" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
/* | ||
* QEMU Universal Flash Storage (UFS) Controller | ||
* | ||
* Copyright (c) 2023 Samsung Electronics Co., Ltd. All rights reserved. | ||
* | ||
* Written by Jeuk Kim <jeuk20.kim@samsung.com> | ||
* | ||
* SPDX-License-Identifier: GPL-2.0-or-later | ||
*/ | ||
|
||
#include "qemu/osdep.h" | ||
#include "qapi/error.h" | ||
#include "migration/vmstate.h" | ||
#include "trace.h" | ||
#include "ufs.h" | ||
|
||
/* The QEMU-UFS device follows spec version 3.1 */ | ||
#define UFS_SPEC_VER 0x00000310 | ||
#define UFS_MAX_NUTRS 32 | ||
#define UFS_MAX_NUTMRS 8 | ||
|
||
static void ufs_irq_check(UfsHc *u) | ||
{ | ||
PCIDevice *pci = PCI_DEVICE(u); | ||
|
||
if ((u->reg.is & UFS_INTR_MASK) & u->reg.ie) { | ||
trace_ufs_irq_raise(); | ||
pci_irq_assert(pci); | ||
} else { | ||
trace_ufs_irq_lower(); | ||
pci_irq_deassert(pci); | ||
} | ||
} | ||
|
||
static void ufs_process_uiccmd(UfsHc *u, uint32_t val) | ||
{ | ||
trace_ufs_process_uiccmd(val, u->reg.ucmdarg1, u->reg.ucmdarg2, | ||
u->reg.ucmdarg3); | ||
/* | ||
* Only the essential uic commands for running drivers on Linux and Windows | ||
* are implemented. | ||
*/ | ||
switch (val) { | ||
case UFS_UIC_CMD_DME_LINK_STARTUP: | ||
u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, DP, 1); | ||
u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, UTRLRDY, 1); | ||
u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, UTMRLRDY, 1); | ||
u->reg.ucmdarg2 = UFS_UIC_CMD_RESULT_SUCCESS; | ||
break; | ||
/* TODO: Revisit it when Power Management is implemented */ | ||
case UFS_UIC_CMD_DME_HIBER_ENTER: | ||
u->reg.is = FIELD_DP32(u->reg.is, IS, UHES, 1); | ||
u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, UPMCRS, UFS_PWR_LOCAL); | ||
u->reg.ucmdarg2 = UFS_UIC_CMD_RESULT_SUCCESS; | ||
break; | ||
case UFS_UIC_CMD_DME_HIBER_EXIT: | ||
u->reg.is = FIELD_DP32(u->reg.is, IS, UHXS, 1); | ||
u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, UPMCRS, UFS_PWR_LOCAL); | ||
u->reg.ucmdarg2 = UFS_UIC_CMD_RESULT_SUCCESS; | ||
break; | ||
default: | ||
u->reg.ucmdarg2 = UFS_UIC_CMD_RESULT_FAILURE; | ||
} | ||
|
||
u->reg.is = FIELD_DP32(u->reg.is, IS, UCCS, 1); | ||
|
||
ufs_irq_check(u); | ||
} | ||
|
||
static void ufs_write_reg(UfsHc *u, hwaddr offset, uint32_t data, unsigned size) | ||
{ | ||
switch (offset) { | ||
case A_IS: | ||
u->reg.is &= ~data; | ||
ufs_irq_check(u); | ||
break; | ||
case A_IE: | ||
u->reg.ie = data; | ||
ufs_irq_check(u); | ||
break; | ||
case A_HCE: | ||
if (!FIELD_EX32(u->reg.hce, HCE, HCE) && FIELD_EX32(data, HCE, HCE)) { | ||
u->reg.hcs = FIELD_DP32(u->reg.hcs, HCS, UCRDY, 1); | ||
u->reg.hce = FIELD_DP32(u->reg.hce, HCE, HCE, 1); | ||
} else if (FIELD_EX32(u->reg.hce, HCE, HCE) && | ||
!FIELD_EX32(data, HCE, HCE)) { | ||
u->reg.hcs = 0; | ||
u->reg.hce = FIELD_DP32(u->reg.hce, HCE, HCE, 0); | ||
} | ||
break; | ||
case A_UTRLBA: | ||
u->reg.utrlba = data & R_UTRLBA_UTRLBA_MASK; | ||
break; | ||
case A_UTRLBAU: | ||
u->reg.utrlbau = data; | ||
break; | ||
case A_UTRLDBR: | ||
/* Not yet supported */ | ||
break; | ||
case A_UTRLRSR: | ||
u->reg.utrlrsr = data; | ||
break; | ||
case A_UTRLCNR: | ||
u->reg.utrlcnr &= ~data; | ||
break; | ||
case A_UTMRLBA: | ||
u->reg.utmrlba = data & R_UTMRLBA_UTMRLBA_MASK; | ||
break; | ||
case A_UTMRLBAU: | ||
u->reg.utmrlbau = data; | ||
break; | ||
case A_UICCMD: | ||
ufs_process_uiccmd(u, data); | ||
break; | ||
case A_UCMDARG1: | ||
u->reg.ucmdarg1 = data; | ||
break; | ||
case A_UCMDARG2: | ||
u->reg.ucmdarg2 = data; | ||
break; | ||
case A_UCMDARG3: | ||
u->reg.ucmdarg3 = data; | ||
break; | ||
case A_UTRLCLR: | ||
case A_UTMRLDBR: | ||
case A_UTMRLCLR: | ||
case A_UTMRLRSR: | ||
trace_ufs_err_unsupport_register_offset(offset); | ||
break; | ||
default: | ||
trace_ufs_err_invalid_register_offset(offset); | ||
break; | ||
} | ||
} | ||
|
||
static uint64_t ufs_mmio_read(void *opaque, hwaddr addr, unsigned size) | ||
{ | ||
UfsHc *u = (UfsHc *)opaque; | ||
uint8_t *ptr = (uint8_t *)&u->reg; | ||
uint64_t value; | ||
|
||
if (addr > sizeof(u->reg) - size) { | ||
trace_ufs_err_invalid_register_offset(addr); | ||
return 0; | ||
} | ||
|
||
value = *(uint32_t *)(ptr + addr); | ||
trace_ufs_mmio_read(addr, value, size); | ||
return value; | ||
} | ||
|
||
static void ufs_mmio_write(void *opaque, hwaddr addr, uint64_t data, | ||
unsigned size) | ||
{ | ||
UfsHc *u = (UfsHc *)opaque; | ||
|
||
if (addr > sizeof(u->reg) - size) { | ||
trace_ufs_err_invalid_register_offset(addr); | ||
return; | ||
} | ||
|
||
trace_ufs_mmio_write(addr, data, size); | ||
ufs_write_reg(u, addr, data, size); | ||
} | ||
|
||
static const MemoryRegionOps ufs_mmio_ops = { | ||
.read = ufs_mmio_read, | ||
.write = ufs_mmio_write, | ||
.endianness = DEVICE_LITTLE_ENDIAN, | ||
.impl = { | ||
.min_access_size = 4, | ||
.max_access_size = 4, | ||
}, | ||
}; | ||
|
||
static bool ufs_check_constraints(UfsHc *u, Error **errp) | ||
{ | ||
if (u->params.nutrs > UFS_MAX_NUTRS) { | ||
error_setg(errp, "nutrs must be less than or equal to %d", | ||
UFS_MAX_NUTRS); | ||
return false; | ||
} | ||
|
||
if (u->params.nutmrs > UFS_MAX_NUTMRS) { | ||
error_setg(errp, "nutmrs must be less than or equal to %d", | ||
UFS_MAX_NUTMRS); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
static void ufs_init_pci(UfsHc *u, PCIDevice *pci_dev) | ||
{ | ||
uint8_t *pci_conf = pci_dev->config; | ||
|
||
pci_conf[PCI_INTERRUPT_PIN] = 1; | ||
pci_config_set_prog_interface(pci_conf, 0x1); | ||
|
||
memory_region_init_io(&u->iomem, OBJECT(u), &ufs_mmio_ops, u, "ufs", | ||
u->reg_size); | ||
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &u->iomem); | ||
u->irq = pci_allocate_irq(pci_dev); | ||
} | ||
|
||
static void ufs_init_hc(UfsHc *u) | ||
{ | ||
uint32_t cap = 0; | ||
|
||
u->reg_size = pow2ceil(sizeof(UfsReg)); | ||
|
||
memset(&u->reg, 0, sizeof(u->reg)); | ||
cap = FIELD_DP32(cap, CAP, NUTRS, (u->params.nutrs - 1)); | ||
cap = FIELD_DP32(cap, CAP, RTT, 2); | ||
cap = FIELD_DP32(cap, CAP, NUTMRS, (u->params.nutmrs - 1)); | ||
cap = FIELD_DP32(cap, CAP, AUTOH8, 0); | ||
cap = FIELD_DP32(cap, CAP, 64AS, 1); | ||
cap = FIELD_DP32(cap, CAP, OODDS, 0); | ||
cap = FIELD_DP32(cap, CAP, UICDMETMS, 0); | ||
cap = FIELD_DP32(cap, CAP, CS, 0); | ||
u->reg.cap = cap; | ||
u->reg.ver = UFS_SPEC_VER; | ||
} | ||
|
||
static void ufs_realize(PCIDevice *pci_dev, Error **errp) | ||
{ | ||
UfsHc *u = UFS(pci_dev); | ||
|
||
if (!ufs_check_constraints(u, errp)) { | ||
return; | ||
} | ||
|
||
ufs_init_hc(u); | ||
ufs_init_pci(u, pci_dev); | ||
} | ||
|
||
static Property ufs_props[] = { | ||
DEFINE_PROP_STRING("serial", UfsHc, params.serial), | ||
DEFINE_PROP_UINT8("nutrs", UfsHc, params.nutrs, 32), | ||
DEFINE_PROP_UINT8("nutmrs", UfsHc, params.nutmrs, 8), | ||
DEFINE_PROP_END_OF_LIST(), | ||
}; | ||
|
||
static const VMStateDescription ufs_vmstate = { | ||
.name = "ufs", | ||
.unmigratable = 1, | ||
}; | ||
|
||
static void ufs_class_init(ObjectClass *oc, void *data) | ||
{ | ||
DeviceClass *dc = DEVICE_CLASS(oc); | ||
PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc); | ||
|
||
pc->realize = ufs_realize; | ||
pc->vendor_id = PCI_VENDOR_ID_REDHAT; | ||
pc->device_id = PCI_DEVICE_ID_REDHAT_UFS; | ||
pc->class_id = PCI_CLASS_STORAGE_UFS; | ||
|
||
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); | ||
dc->desc = "Universal Flash Storage"; | ||
device_class_set_props(dc, ufs_props); | ||
dc->vmsd = &ufs_vmstate; | ||
} | ||
|
||
static const TypeInfo ufs_info = { | ||
.name = TYPE_UFS, | ||
.parent = TYPE_PCI_DEVICE, | ||
.class_init = ufs_class_init, | ||
.instance_size = sizeof(UfsHc), | ||
.interfaces = (InterfaceInfo[]){ { INTERFACE_PCIE_DEVICE }, {} }, | ||
}; | ||
|
||
static void ufs_register_types(void) | ||
{ | ||
type_register_static(&ufs_info); | ||
} | ||
|
||
type_init(ufs_register_types) |
Oops, something went wrong.