@ -8,6 +8,19 @@
* SPDX - License - Identifier : GPL - 2.0 - or - later
* 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 "qemu/osdep.h"
# include "qapi/error.h"
# include "qapi/error.h"
# include "migration/vmstate.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 ,
static void ufs_build_upiu_header ( UfsRequest * req , uint8_t trans_type ,
uint8_t flags , uint8_t response ,
uint8_t flags , uint8_t response ,
uint8_t scsi_status ,
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 ) ;
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 )
static UfsReqResult ufs_exec_nop_cmd ( UfsRequest * req )
{
{
trace_ufs_exec_nop_cmd ( req - > slot ) ;
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 )
static QueryRespCode ufs_read_unit_desc ( UfsRequest * req )
{
{
UfsHc * u = req - > hc ;
uint8_t lun = req - > req_upiu . qr . index ;
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 ) ;
trace_ufs_err_query_invalid_index ( req - > req_upiu . qr . opcode , lun ) ;
return UFS_QUERY_RESULT_INVALID_INDEX ;
return UFS_QUERY_RESULT_INVALID_INDEX ;
}
}
@ -726,8 +846,8 @@ static QueryRespCode ufs_read_unit_desc(UfsRequest *req)
if ( lun = = UFS_UPIU_RPMB_WLUN ) {
if ( lun = = UFS_UPIU_RPMB_WLUN ) {
memcpy ( & req - > rsp_upiu . qr . data , & rpmb_unit_desc , rpmb_unit_desc . length ) ;
memcpy ( & req - > rsp_upiu . qr . data , & rpmb_unit_desc , rpmb_unit_desc . length ) ;
} else {
} else {
/* unit descriptor is not yet supported */
memcpy ( & req - > rsp_upiu . qr . data , & u - > lus [ lun ] - > unit_desc ,
return UFS_QUERY_RESULT_INVALID_INDEX ;
sizeof ( u - > lus [ lun ] - > unit_desc ) ) ;
}
}
return UFS_QUERY_RESULT_SUCCESS ;
return UFS_QUERY_RESULT_SUCCESS ;
@ -977,8 +1097,7 @@ static void ufs_exec_req(UfsRequest *req)
req_result = ufs_exec_nop_cmd ( req ) ;
req_result = ufs_exec_nop_cmd ( req ) ;
break ;
break ;
case UFS_UPIU_TRANSACTION_COMMAND :
case UFS_UPIU_TRANSACTION_COMMAND :
/* Not yet implemented */
req_result = ufs_exec_scsi_cmd ( req ) ;
req_result = UFS_REQUEST_FAIL ;
break ;
break ;
case UFS_UPIU_TRANSACTION_QUERY_REQ :
case UFS_UPIU_TRANSACTION_QUERY_REQ :
req_result = ufs_exec_query_cmd ( req ) ;
req_result = ufs_exec_query_cmd ( req ) ;
@ -989,7 +1108,14 @@ static void ufs_exec_req(UfsRequest *req)
req_result = UFS_REQUEST_FAIL ;
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 )
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 ;
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 )
static void ufs_realize ( PCIDevice * pci_dev , Error * * errp )
{
{
UfsHc * u = UFS ( pci_dev ) ;
UfsHc * u = UFS ( pci_dev ) ;
@ -1199,15 +1347,55 @@ static void ufs_realize(PCIDevice *pci_dev, Error **errp)
return ;
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_state ( u ) ;
ufs_init_hc ( u ) ;
ufs_init_hc ( u ) ;
ufs_init_pci ( u , pci_dev ) ;
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 )
static void ufs_exit ( PCIDevice * pci_dev )
{
{
UfsHc * u = UFS ( 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 - > doorbell_bh ) ;
qemu_bh_delete ( u - > complete_bh ) ;
qemu_bh_delete ( u - > complete_bh ) ;
@ -1246,6 +1434,49 @@ static void ufs_class_init(ObjectClass *oc, void *data)
dc - > vmsd = & ufs_vmstate ;
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 = {
static const TypeInfo ufs_info = {
. name = TYPE_UFS ,
. name = TYPE_UFS ,
. parent = TYPE_PCI_DEVICE ,
. parent = TYPE_PCI_DEVICE ,
@ -1254,9 +1485,18 @@ static const TypeInfo ufs_info = {
. interfaces = ( InterfaceInfo [ ] ) { { INTERFACE_PCIE_DEVICE } , { } } ,
. 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 )
static void ufs_register_types ( void )
{
{
type_register_static ( & ufs_info ) ;
type_register_static ( & ufs_info ) ;
type_register_static ( & ufs_bus_info ) ;
}
}
type_init ( ufs_register_types )
type_init ( ufs_register_types )