Change-Id: Id59ac950f37d7f4f2642daf324d501da1ee622de Signed-off-by: Giacomo Travaglini <giacomo.travaglini@arm.com> Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/40775 Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com> Maintainer: Andreas Sandberg <andreas.sandberg@arm.com> Tested-by: kokoro <noreply+kokoro@google.com>
2307 lines
75 KiB
C++
2307 lines
75 KiB
C++
/*
|
|
* Copyright (c) 2013-2015 ARM Limited
|
|
* All rights reserved
|
|
*
|
|
* The license below extends only to copyright in the software and shall
|
|
* not be construed as granting a license to any other intellectual
|
|
* property including but not limited to intellectual property relating
|
|
* to a hardware implementation of the functionality of the software
|
|
* licensed hereunder. You may use the software subject to the license
|
|
* terms below provided that you ensure that this notice is replicated
|
|
* unmodified and in its entirety in all distributions of the software,
|
|
* modified or unmodified, in source code or in binary form.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met: redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer;
|
|
* redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution;
|
|
* neither the name of the copyright holders nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/** @file
|
|
* This is a simulation model for a UFS interface
|
|
* The UFS interface consists of a host controller and (at least) one device.
|
|
* The device can contain multiple logic units.
|
|
* To make this interface as usefull as possible for future development, the
|
|
* decision has been made to split the UFS functionality from the SCSI
|
|
* functionality. The class UFS SCSIDevice can therefor be used as a starting
|
|
* point for creating a more generic SCSI device. This has as a consequence
|
|
* that the UFSHostDevice class contains functionality from both the host
|
|
* controller and the device. The current UFS standard (1.1) allows only one
|
|
* device, and up to 8 logic units. the logic units only handle the SCSI part
|
|
* of the command, and the device mainly the UFS part. Yet the split between
|
|
* the SCSIresume function and the SCSICMDHandle might seem a bit awkward.
|
|
* The SCSICMDHandle function is in essence a SCSI reply generator, and it
|
|
* distils the essential information from the command. A disktransfer cannot
|
|
* be made from this position because the scatter gather list is not included
|
|
* in the SCSI command, but in the Transfer Request descriptor. The device
|
|
* needs to manage the data transfer. This file is build up as follows: first
|
|
* the UFSSCSIDevice functions will be presented; then the UFSHostDevice
|
|
* functions. The UFSHostDevice functions are split in three parts: UFS
|
|
* transaction flow, data write transfer and data read transfer. The
|
|
* functions are then ordered in the order in which a transfer takes place.
|
|
*/
|
|
|
|
/**
|
|
* Reference material can be found at the JEDEC website:
|
|
* UFS standard
|
|
* http://www.jedec.org/standards-documents/results/jesd220
|
|
* UFS HCI specification
|
|
* http://www.jedec.org/standards-documents/results/jesd223
|
|
*/
|
|
|
|
#include "dev/arm/ufs_device.hh"
|
|
|
|
/**
|
|
* Constructor and destructor functions of UFSHCM device
|
|
*/
|
|
UFSHostDevice::UFSSCSIDevice::UFSSCSIDevice(const UFSHostDeviceParams &p,
|
|
uint32_t lun_id, const Callback &transfer_cb,
|
|
const Callback &read_cb):
|
|
SimObject(p),
|
|
flashDisk(p.image[lun_id]),
|
|
flashDevice(p.internalflash[lun_id]),
|
|
blkSize(p.img_blk_size),
|
|
lunAvail(p.image.size()),
|
|
diskSize(flashDisk->size()),
|
|
capacityLower((diskSize - 1) & 0xffffffff),
|
|
capacityUpper((diskSize - SectorSize) >> 32),
|
|
lunID(lun_id),
|
|
transferCompleted(false),
|
|
readCompleted(false),
|
|
totalRead(0),
|
|
totalWrite(0),
|
|
amountOfWriteTransfers(0),
|
|
amountOfReadTransfers(0)
|
|
{
|
|
/**
|
|
* These callbacks are used to communicate the events that are
|
|
* triggered upstream; e.g. from the Memory Device to the UFS SCSI Device
|
|
* or from the UFS SCSI device to the UFS host.
|
|
*/
|
|
signalDone = transfer_cb;
|
|
memReadCallback = [this]() { readCallback(); };
|
|
deviceReadCallback = read_cb;
|
|
memWriteCallback = [this]() { SSDWriteDone(); };
|
|
|
|
/**
|
|
* make ascii out of lun_id (and add more characters)
|
|
* UFS allows up to 8 logic units, so the numbering should work out
|
|
*/
|
|
uint32_t temp_id = ((lun_id | 0x30) << 24) | 0x3A4449;
|
|
lunInfo.dWord0 = 0x02060000; //data
|
|
lunInfo.dWord1 = 0x0200001F;
|
|
lunInfo.vendor0 = 0x484D5241; //ARMH (HMRA)
|
|
lunInfo.vendor1 = 0x424D4143; //CAMB (BMAC)
|
|
lunInfo.product0 = 0x356D6567; //gem5 (5meg)
|
|
lunInfo.product1 = 0x4D534655; //UFSM (MSFU)
|
|
lunInfo.product2 = 0x4C45444F; //ODEL (LEDO)
|
|
lunInfo.product3 = temp_id; // ID:"lun_id" ("lun_id":DI)
|
|
lunInfo.productRevision = 0x01000000; //0x01
|
|
|
|
DPRINTF(UFSHostDevice, "Logic unit %d assumes that %d logic units are"
|
|
" present in the system\n", lunID, lunAvail);
|
|
DPRINTF(UFSHostDevice,"The disksize of lun: %d should be %d blocks\n",
|
|
lunID, diskSize);
|
|
flashDevice->initializeMemory(diskSize, SectorSize);
|
|
}
|
|
|
|
|
|
/**
|
|
* These pages are SCSI specific. For more information refer to:
|
|
* Universal Flash Storage (UFS) JESD220 FEB 2011 (JEDEC)
|
|
* http://www.jedec.org/standards-documents/results/jesd220
|
|
*/
|
|
const unsigned int UFSHostDevice::UFSSCSIDevice::controlPage[3] =
|
|
{0x01400A0A, 0x00000000,
|
|
0x0000FFFF};
|
|
const unsigned int UFSHostDevice::UFSSCSIDevice::recoveryPage[3] =
|
|
{0x03800A01, 0x00000000,
|
|
0xFFFF0003};
|
|
const unsigned int UFSHostDevice::UFSSCSIDevice::cachingPage[5] =
|
|
{0x00011208, 0x00000000,
|
|
0x00000000, 0x00000020,
|
|
0x00000000};
|
|
|
|
UFSHostDevice::UFSSCSIDevice::~UFSSCSIDevice() {}
|
|
|
|
/**
|
|
* UFS specific SCSI handling function.
|
|
* The following attributes may still be added: SCSI format unit,
|
|
* Send diagnostic and UNMAP;
|
|
* Synchronize Cache and buffer read/write could not be tested yet
|
|
* All parameters can be found in:
|
|
* Universal Flash Storage (UFS) JESD220 FEB 2011 (JEDEC)
|
|
* http://www.jedec.org/standards-documents/results/jesd220
|
|
* (a JEDEC acount may be required {free of charge})
|
|
*/
|
|
|
|
struct UFSHostDevice::SCSIReply
|
|
UFSHostDevice::UFSSCSIDevice::SCSICMDHandle(uint32_t* SCSI_msg)
|
|
{
|
|
struct SCSIReply scsi_out;
|
|
scsi_out.reset();
|
|
|
|
/**
|
|
* Create the standard SCSI reponse information
|
|
* These values might changes over the course of a transfer
|
|
*/
|
|
scsi_out.message.header.dWord0 = UPIUHeaderDataIndWord0 |
|
|
lunID << 16;
|
|
scsi_out.message.header.dWord1 = UPIUHeaderDataIndWord1;
|
|
scsi_out.message.header.dWord2 = UPIUHeaderDataIndWord2;
|
|
statusCheck(SCSIGood, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.LUN = lunID;
|
|
scsi_out.status = SCSIGood;
|
|
|
|
DPRINTF(UFSHostDevice, "SCSI command:%2x\n", SCSI_msg[4]);
|
|
/**Determine what the message is and fill the response packet*/
|
|
|
|
switch (SCSI_msg[4] & 0xFF) {
|
|
|
|
case SCSIInquiry: {
|
|
/**
|
|
* SCSI inquiry: tell about this specific logic unit
|
|
*/
|
|
scsi_out.msgSize = 36;
|
|
scsi_out.message.dataMsg.resize(9);
|
|
|
|
for (uint8_t count = 0; count < 9; count++)
|
|
scsi_out.message.dataMsg[count] =
|
|
(reinterpret_cast<uint32_t*> (&lunInfo))[count];
|
|
} break;
|
|
|
|
case SCSIRead6: {
|
|
/**
|
|
* Read command. Number indicates the length of the command.
|
|
*/
|
|
scsi_out.expectMore = 0x02;
|
|
scsi_out.msgSize = 0;
|
|
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned. Apart from that it only has
|
|
* information in five bits of the first byte that is relevant
|
|
* for this field.
|
|
*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(tempptr);
|
|
uint64_t read_offset = betoh(tmp) & 0x1FFFFF;
|
|
|
|
uint32_t read_size = tempptr[4];
|
|
|
|
|
|
scsi_out.msgSize = read_size * blkSize;
|
|
scsi_out.offset = read_offset * blkSize;
|
|
|
|
if ((read_offset + read_size) > diskSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
DPRINTF(UFSHostDevice, "Read6 offset: 0x%8x, for %d blocks\n",
|
|
read_offset, read_size);
|
|
|
|
/**
|
|
* Renew status check, for the request may have been illegal
|
|
*/
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIRead10: {
|
|
scsi_out.expectMore = 0x02;
|
|
scsi_out.msgSize = 0;
|
|
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned.*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]);
|
|
uint64_t read_offset = betoh(tmp);
|
|
|
|
uint16_t tmpsize = *reinterpret_cast<uint16_t*>(&tempptr[7]);
|
|
uint32_t read_size = betoh(tmpsize);
|
|
|
|
scsi_out.msgSize = read_size * blkSize;
|
|
scsi_out.offset = read_offset * blkSize;
|
|
|
|
if ((read_offset + read_size) > diskSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
DPRINTF(UFSHostDevice, "Read10 offset: 0x%8x, for %d blocks\n",
|
|
read_offset, read_size);
|
|
|
|
/**
|
|
* Renew status check, for the request may have been illegal
|
|
*/
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIRead16: {
|
|
scsi_out.expectMore = 0x02;
|
|
scsi_out.msgSize = 0;
|
|
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned.*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]);
|
|
uint64_t read_offset = betoh(tmp);
|
|
|
|
tmp = *reinterpret_cast<uint32_t*>(&tempptr[6]);
|
|
read_offset = (read_offset << 32) | betoh(tmp);
|
|
|
|
tmp = *reinterpret_cast<uint32_t*>(&tempptr[10]);
|
|
uint32_t read_size = betoh(tmp);
|
|
|
|
scsi_out.msgSize = read_size * blkSize;
|
|
scsi_out.offset = read_offset * blkSize;
|
|
|
|
if ((read_offset + read_size) > diskSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
DPRINTF(UFSHostDevice, "Read16 offset: 0x%8x, for %d blocks\n",
|
|
read_offset, read_size);
|
|
|
|
/**
|
|
* Renew status check, for the request may have been illegal
|
|
*/
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIReadCapacity10: {
|
|
/**
|
|
* read the capacity of the device
|
|
*/
|
|
scsi_out.msgSize = 8;
|
|
scsi_out.message.dataMsg.resize(2);
|
|
scsi_out.message.dataMsg[0] =
|
|
betoh(capacityLower);//last block
|
|
scsi_out.message.dataMsg[1] = betoh(blkSize);//blocksize
|
|
|
|
} break;
|
|
case SCSIReadCapacity16: {
|
|
scsi_out.msgSize = 32;
|
|
scsi_out.message.dataMsg.resize(8);
|
|
scsi_out.message.dataMsg[0] =
|
|
betoh(capacityUpper);//last block
|
|
scsi_out.message.dataMsg[1] =
|
|
betoh(capacityLower);//last block
|
|
scsi_out.message.dataMsg[2] = betoh(blkSize);//blocksize
|
|
scsi_out.message.dataMsg[3] = 0x00;//
|
|
scsi_out.message.dataMsg[4] = 0x00;//reserved
|
|
scsi_out.message.dataMsg[5] = 0x00;//reserved
|
|
scsi_out.message.dataMsg[6] = 0x00;//reserved
|
|
scsi_out.message.dataMsg[7] = 0x00;//reserved
|
|
|
|
} break;
|
|
|
|
case SCSIReportLUNs: {
|
|
/**
|
|
* Find out how many Logic Units this device has.
|
|
*/
|
|
scsi_out.msgSize = (lunAvail * 8) + 8;//list + overhead
|
|
scsi_out.message.dataMsg.resize(2 * lunAvail + 2);
|
|
scsi_out.message.dataMsg[0] = (lunAvail * 8) << 24;//LUN listlength
|
|
scsi_out.message.dataMsg[1] = 0x00;
|
|
|
|
for (uint8_t count = 0; count < lunAvail; count++) {
|
|
//LUN "count"
|
|
scsi_out.message.dataMsg[2 + 2 * count] = (count & 0x7F) << 8;
|
|
scsi_out.message.dataMsg[3 + 2 * count] = 0x00;
|
|
}
|
|
|
|
} break;
|
|
|
|
case SCSIStartStop: {
|
|
//Just acknowledge; not deemed relevant ATM
|
|
scsi_out.msgSize = 0;
|
|
|
|
} break;
|
|
|
|
case SCSITestUnitReady: {
|
|
//Just acknowledge; not deemed relevant ATM
|
|
scsi_out.msgSize = 0;
|
|
|
|
} break;
|
|
|
|
case SCSIVerify10: {
|
|
/**
|
|
* See if the blocks that the host plans to request are in range of
|
|
* the device.
|
|
*/
|
|
scsi_out.msgSize = 0;
|
|
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned.*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]);
|
|
uint64_t read_offset = betoh(tmp);
|
|
|
|
uint16_t tmpsize = *reinterpret_cast<uint16_t*>(&tempptr[7]);
|
|
uint32_t read_size = betoh(tmpsize);
|
|
|
|
if ((read_offset + read_size) > diskSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
/**
|
|
* Renew status check, for the request may have been illegal
|
|
*/
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIWrite6: {
|
|
/**
|
|
* Write command.
|
|
*/
|
|
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned. Apart from that it only has
|
|
* information in five bits of the first byte that is relevant
|
|
* for this field.
|
|
*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(tempptr);
|
|
uint64_t write_offset = betoh(tmp) & 0x1FFFFF;
|
|
|
|
uint32_t write_size = tempptr[4];
|
|
|
|
scsi_out.msgSize = write_size * blkSize;
|
|
scsi_out.offset = write_offset * blkSize;
|
|
scsi_out.expectMore = 0x01;
|
|
|
|
if ((write_offset + write_size) > diskSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
DPRINTF(UFSHostDevice, "Write6 offset: 0x%8x, for %d blocks\n",
|
|
write_offset, write_size);
|
|
|
|
/**
|
|
* Renew status check, for the request may have been illegal
|
|
*/
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIWrite10: {
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned.*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]);
|
|
uint64_t write_offset = betoh(tmp);
|
|
|
|
uint16_t tmpsize = *reinterpret_cast<uint16_t*>(&tempptr[7]);
|
|
uint32_t write_size = betoh(tmpsize);
|
|
|
|
scsi_out.msgSize = write_size * blkSize;
|
|
scsi_out.offset = write_offset * blkSize;
|
|
scsi_out.expectMore = 0x01;
|
|
|
|
if ((write_offset + write_size) > diskSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
DPRINTF(UFSHostDevice, "Write10 offset: 0x%8x, for %d blocks\n",
|
|
write_offset, write_size);
|
|
|
|
/**
|
|
* Renew status check, for the request may have been illegal
|
|
*/
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIWrite16: {
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned.*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]);
|
|
uint64_t write_offset = betoh(tmp);
|
|
|
|
tmp = *reinterpret_cast<uint32_t*>(&tempptr[6]);
|
|
write_offset = (write_offset << 32) | betoh(tmp);
|
|
|
|
tmp = *reinterpret_cast<uint32_t*>(&tempptr[10]);
|
|
uint32_t write_size = betoh(tmp);
|
|
|
|
scsi_out.msgSize = write_size * blkSize;
|
|
scsi_out.offset = write_offset * blkSize;
|
|
scsi_out.expectMore = 0x01;
|
|
|
|
if ((write_offset + write_size) > diskSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
DPRINTF(UFSHostDevice, "Write16 offset: 0x%8x, for %d blocks\n",
|
|
write_offset, write_size);
|
|
|
|
/**
|
|
* Renew status check, for the request may have been illegal
|
|
*/
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIFormatUnit: {//not yet verified
|
|
scsi_out.msgSize = 0;
|
|
scsi_out.expectMore = 0x01;
|
|
|
|
} break;
|
|
|
|
case SCSISendDiagnostic: {//not yet verified
|
|
scsi_out.msgSize = 0;
|
|
|
|
} break;
|
|
|
|
case SCSISynchronizeCache: {
|
|
//do we have cache (we don't have cache at this moment)
|
|
//TODO: here will synchronization happen when cache is modelled
|
|
scsi_out.msgSize = 0;
|
|
|
|
} break;
|
|
|
|
//UFS SCSI additional command set for full functionality
|
|
case SCSIModeSelect10:
|
|
//TODO:
|
|
//scsi_out.expectMore = 0x01;//not supported due to modepage support
|
|
//code isn't dead, code suggest what is to be done when implemented
|
|
break;
|
|
|
|
case SCSIModeSense6: case SCSIModeSense10: {
|
|
/**
|
|
* Get more discriptive information about the SCSI functionality
|
|
* within this logic unit.
|
|
*/
|
|
if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x0A) {//control page
|
|
scsi_out.message.dataMsg.resize((sizeof(controlPage) >> 2) + 2);
|
|
scsi_out.message.dataMsg[0] = 0x00000A00;//control page code
|
|
scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8
|
|
|
|
for (uint8_t count = 0; count < 3; count++)
|
|
scsi_out.message.dataMsg[2 + count] = controlPage[count];
|
|
|
|
scsi_out.msgSize = 20;
|
|
DPRINTF(UFSHostDevice, "CONTROL page\n");
|
|
|
|
} else if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x01) {//recovery page
|
|
scsi_out.message.dataMsg.resize((sizeof(recoveryPage) >> 2)
|
|
+ 2);
|
|
|
|
scsi_out.message.dataMsg[0] = 0x00000100;//recovery page code
|
|
scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8
|
|
|
|
for (uint8_t count = 0; count < 3; count++)
|
|
scsi_out.message.dataMsg[2 + count] = recoveryPage[count];
|
|
|
|
scsi_out.msgSize = 20;
|
|
DPRINTF(UFSHostDevice, "RECOVERY page\n");
|
|
|
|
} else if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x08) {//caching page
|
|
|
|
scsi_out.message.dataMsg.resize((sizeof(cachingPage) >> 2) + 2);
|
|
scsi_out.message.dataMsg[0] = 0x00001200;//caching page code
|
|
scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8
|
|
|
|
for (uint8_t count = 0; count < 5; count++)
|
|
scsi_out.message.dataMsg[2 + count] = cachingPage[count];
|
|
|
|
scsi_out.msgSize = 20;
|
|
DPRINTF(UFSHostDevice, "CACHE page\n");
|
|
|
|
} else if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x3F) {//ALL the pages!
|
|
|
|
scsi_out.message.dataMsg.resize(((sizeof(controlPage) +
|
|
sizeof(recoveryPage) +
|
|
sizeof(cachingPage)) >> 2)
|
|
+ 2);
|
|
scsi_out.message.dataMsg[0] = 0x00003200;//all page code
|
|
scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8
|
|
|
|
for (uint8_t count = 0; count < 3; count++)
|
|
scsi_out.message.dataMsg[2 + count] = recoveryPage[count];
|
|
|
|
for (uint8_t count = 0; count < 5; count++)
|
|
scsi_out.message.dataMsg[5 + count] = cachingPage[count];
|
|
|
|
for (uint8_t count = 0; count < 3; count++)
|
|
scsi_out.message.dataMsg[10 + count] = controlPage[count];
|
|
|
|
scsi_out.msgSize = 52;
|
|
DPRINTF(UFSHostDevice, "Return ALL the pages!!!\n");
|
|
|
|
} else inform("Wrong mode page requested\n");
|
|
|
|
scsi_out.message.dataCount = scsi_out.msgSize << 24;
|
|
} break;
|
|
|
|
case SCSIRequestSense: {
|
|
scsi_out.msgSize = 0;
|
|
|
|
} break;
|
|
|
|
case SCSIUnmap:break;//not yet verified
|
|
|
|
case SCSIWriteBuffer: {
|
|
scsi_out.expectMore = 0x01;
|
|
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned.*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]);
|
|
uint64_t write_offset = betoh(tmp) & 0xFFFFFF;
|
|
|
|
tmp = *reinterpret_cast<uint32_t*>(&tempptr[5]);
|
|
uint32_t write_size = betoh(tmp) & 0xFFFFFF;
|
|
|
|
scsi_out.msgSize = write_size;
|
|
scsi_out.offset = write_offset;
|
|
|
|
} break;
|
|
|
|
case SCSIReadBuffer: {
|
|
/**
|
|
* less trivial than normal read. Size is in bytes instead
|
|
* of blocks, and it is assumed (though not guaranteed) that
|
|
* reading is from cache.
|
|
*/
|
|
scsi_out.expectMore = 0x02;
|
|
|
|
uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]);
|
|
|
|
/**BE and not nicely aligned.*/
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]);
|
|
uint64_t read_offset = betoh(tmp) & 0xFFFFFF;
|
|
|
|
tmp = *reinterpret_cast<uint32_t*>(&tempptr[5]);
|
|
uint32_t read_size = betoh(tmp) & 0xFFFFFF;
|
|
|
|
scsi_out.msgSize = read_size;
|
|
scsi_out.offset = read_offset;
|
|
|
|
if ((read_offset + read_size) > capacityLower * blkSize)
|
|
scsi_out.status = SCSIIllegalRequest;
|
|
|
|
DPRINTF(UFSHostDevice, "Read buffer location: 0x%8x\n",
|
|
read_offset);
|
|
DPRINTF(UFSHostDevice, "Number of bytes: 0x%8x\n", read_size);
|
|
|
|
statusCheck(scsi_out.status, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
|
|
} break;
|
|
|
|
case SCSIMaintenanceIn: {
|
|
/**
|
|
* linux sends this command three times from kernel 3.9 onwards,
|
|
* UFS does not support it, nor does this model. Linux knows this,
|
|
* but tries anyway (useful for some SD card types).
|
|
* Lets make clear we don't want it and just ignore it.
|
|
*/
|
|
DPRINTF(UFSHostDevice, "Ignoring Maintenance In command\n");
|
|
statusCheck(SCSIIllegalRequest, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
scsi_out.msgSize = 0;
|
|
} break;
|
|
|
|
default: {
|
|
statusCheck(SCSIIllegalRequest, scsi_out.senseCode);
|
|
scsi_out.senseSize = scsi_out.senseCode[0];
|
|
scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood :
|
|
SCSICheckCondition;
|
|
scsi_out.msgSize = 0;
|
|
inform("Unsupported scsi message type: %2x\n", SCSI_msg[4] & 0xFF);
|
|
inform("0x%8x\n", SCSI_msg[0]);
|
|
inform("0x%8x\n", SCSI_msg[1]);
|
|
inform("0x%8x\n", SCSI_msg[2]);
|
|
inform("0x%8x\n", SCSI_msg[3]);
|
|
inform("0x%8x\n", SCSI_msg[4]);
|
|
} break;
|
|
}
|
|
|
|
return scsi_out;
|
|
}
|
|
|
|
/**
|
|
* SCSI status check function. generic device test, creates sense codes
|
|
* Future versions may include TODO: device checks, which is why this is
|
|
* in a separate function.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::statusCheck(uint8_t status,
|
|
uint8_t* sensecodelist)
|
|
{
|
|
for (uint8_t count = 0; count < 19; count++)
|
|
sensecodelist[count] = 0;
|
|
|
|
sensecodelist[0] = 18; //sense length
|
|
sensecodelist[1] = 0x70; //we send a valid frame
|
|
sensecodelist[3] = status & 0xF; //mask to be sure + sensecode
|
|
sensecodelist[8] = 0x1F; //data length
|
|
}
|
|
|
|
/**
|
|
* read from the flashdisk
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::readFlash(uint8_t* readaddr, uint64_t offset,
|
|
uint32_t size)
|
|
{
|
|
/** read from image, and get to memory */
|
|
for (int count = 0; count < (size / SectorSize); count++)
|
|
flashDisk->read(&(readaddr[SectorSize*count]), (offset /
|
|
SectorSize) + count);
|
|
}
|
|
|
|
/**
|
|
* Write to the flashdisk
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::writeFlash(uint8_t* writeaddr, uint64_t offset,
|
|
uint32_t size)
|
|
{
|
|
/** Get from fifo and write to image*/
|
|
for (int count = 0; count < (size / SectorSize); count++)
|
|
flashDisk->write(&(writeaddr[SectorSize * count]),
|
|
(offset / SectorSize) + count);
|
|
}
|
|
|
|
/**
|
|
* Constructor for the UFS Host device
|
|
*/
|
|
|
|
UFSHostDevice::UFSHostDevice(const UFSHostDeviceParams &p) :
|
|
DmaDevice(p),
|
|
pioAddr(p.pio_addr),
|
|
pioSize(0x0FFF),
|
|
pioDelay(p.pio_latency),
|
|
intNum(p.int_num),
|
|
gic(p.gic),
|
|
lunAvail(p.image.size()),
|
|
UFSSlots(p.ufs_slots - 1),
|
|
readPendingNum(0),
|
|
writePendingNum(0),
|
|
activeDoorbells(0),
|
|
pendingDoorbells(0),
|
|
countInt(0),
|
|
transferTrack(0),
|
|
taskCommandTrack(0),
|
|
idlePhaseStart(0),
|
|
stats(this),
|
|
SCSIResumeEvent([this]{ SCSIStart(); }, name()),
|
|
UTPEvent([this]{ finalUTP(); }, name())
|
|
{
|
|
DPRINTF(UFSHostDevice, "The hostcontroller hosts %d Logic units\n",
|
|
lunAvail);
|
|
UFSDevice.resize(lunAvail);
|
|
|
|
for (int count = 0; count < lunAvail; count++) {
|
|
UFSDevice[count] = new UFSSCSIDevice(p, count,
|
|
[this]() { LUNSignal(); },
|
|
[this]() { readCallback(); });
|
|
}
|
|
|
|
if (UFSSlots > 31)
|
|
warn("UFSSlots = %d, this will results in %d command slots",
|
|
UFSSlots, (UFSSlots & 0x1F));
|
|
|
|
if ((UFSSlots & 0x1F) == 0)
|
|
fatal("Number of UFS command slots should be between 1 and 32.");
|
|
|
|
setValues();
|
|
}
|
|
|
|
UFSHostDevice::
|
|
UFSHostDeviceStats::UFSHostDeviceStats(UFSHostDevice *parent)
|
|
: Stats::Group(parent, "UFSDiskHost"),
|
|
ADD_STAT(currentSCSIQueue,
|
|
"Most up to date length of the command queue"),
|
|
ADD_STAT(currentReadSSDQueue,
|
|
"Most up to date length of the read SSD queue"),
|
|
ADD_STAT(currentWriteSSDQueue,
|
|
"Most up to date length of the write SSD queue"),
|
|
/** Amount of data read/written */
|
|
ADD_STAT(totalReadSSD, "Number of bytes read from SSD"),
|
|
ADD_STAT(totalWrittenSSD, "Number of bytes written to SSD"),
|
|
ADD_STAT(totalReadDiskTransactions,"Number of transactions from disk"),
|
|
ADD_STAT(totalWriteDiskTransactions, "Number of transactions to disk"),
|
|
ADD_STAT(totalReadUFSTransactions, "Number of transactions from device"),
|
|
ADD_STAT(totalWriteUFSTransactions, "Number of transactions to device"),
|
|
/** Average bandwidth for reads and writes */
|
|
ADD_STAT(averageReadSSDBW, "Average read bandwidth (bytes/s)",
|
|
totalReadSSD / simSeconds),
|
|
ADD_STAT(averageWriteSSDBW, "Average write bandwidth (bytes/s)",
|
|
totalWrittenSSD / simSeconds),
|
|
ADD_STAT(averageSCSIQueue, "Average command queue length"),
|
|
ADD_STAT(averageReadSSDQueue, "Average read queue length"),
|
|
ADD_STAT(averageWriteSSDQueue, "Average write queue length"),
|
|
/** Number of doorbells rung*/
|
|
ADD_STAT(curDoorbell, "Most up to date number of doorbells used",
|
|
parent->activeDoorbells),
|
|
ADD_STAT(maxDoorbell, "Maximum number of doorbells utilized"),
|
|
ADD_STAT(averageDoorbell, "Average number of Doorbells used"),
|
|
/** Latency*/
|
|
ADD_STAT(transactionLatency, "Histogram of transaction times"),
|
|
ADD_STAT(idleTimes, "Histogram of idle times")
|
|
{
|
|
using namespace Stats;
|
|
|
|
// Register the stats
|
|
/** Queue lengths */
|
|
currentSCSIQueue
|
|
.flags(none);
|
|
currentReadSSDQueue
|
|
.flags(none);
|
|
currentWriteSSDQueue
|
|
.flags(none);
|
|
|
|
/** Amount of data read/written */
|
|
totalReadSSD
|
|
.flags(none);
|
|
|
|
totalWrittenSSD
|
|
.flags(none);
|
|
|
|
totalReadDiskTransactions
|
|
.flags(none);
|
|
totalWriteDiskTransactions
|
|
.flags(none);
|
|
totalReadUFSTransactions
|
|
.flags(none);
|
|
totalWriteUFSTransactions
|
|
.flags(none);
|
|
|
|
/** Average bandwidth for reads and writes */
|
|
averageReadSSDBW
|
|
.flags(nozero);
|
|
|
|
averageWriteSSDBW
|
|
.flags(nozero);
|
|
|
|
averageSCSIQueue
|
|
.flags(nozero);
|
|
averageReadSSDQueue
|
|
.flags(nozero);
|
|
averageWriteSSDQueue
|
|
.flags(nozero);
|
|
|
|
/** Number of doorbells rung*/
|
|
curDoorbell
|
|
.flags(none);
|
|
|
|
maxDoorbell
|
|
.flags(none);
|
|
averageDoorbell
|
|
.flags(nozero);
|
|
|
|
/** Latency*/
|
|
transactionLatency
|
|
.init(100)
|
|
.flags(pdf);
|
|
|
|
idleTimes
|
|
.init(100)
|
|
.flags(pdf);
|
|
}
|
|
|
|
/**
|
|
* Register init
|
|
*/
|
|
void UFSHostDevice::setValues()
|
|
{
|
|
/**
|
|
* The capability register is built up as follows:
|
|
* 31-29 RES; Testmode support; O3 delivery; 64 bit addr;
|
|
* 23-19 RES; 18-16 #TM Req slots; 15-5 RES;4-0 # TR slots
|
|
*/
|
|
UFSHCIMem.HCCAP = 0x06070000 | (UFSSlots & 0x1F);
|
|
UFSHCIMem.HCversion = 0x00010000; //version is 1.0
|
|
UFSHCIMem.HCHCDDID = 0xAA003C3C;// Arbitrary number
|
|
UFSHCIMem.HCHCPMID = 0x41524D48; //ARMH (not an official MIPI number)
|
|
UFSHCIMem.TRUTRLDBR = 0x00;
|
|
UFSHCIMem.TMUTMRLDBR = 0x00;
|
|
UFSHCIMem.CMDUICCMDR = 0x00;
|
|
// We can process CMD, TM, TR, device present
|
|
UFSHCIMem.ORHostControllerStatus = 0x08;
|
|
UFSHCIMem.TRUTRLBA = 0x00;
|
|
UFSHCIMem.TRUTRLBAU = 0x00;
|
|
UFSHCIMem.TMUTMRLBA = 0x00;
|
|
UFSHCIMem.TMUTMRLBAU = 0x00;
|
|
}
|
|
|
|
/**
|
|
* Determine address ranges
|
|
*/
|
|
|
|
AddrRangeList
|
|
UFSHostDevice::getAddrRanges() const
|
|
{
|
|
AddrRangeList ranges;
|
|
ranges.push_back(RangeSize(pioAddr, pioSize));
|
|
return ranges;
|
|
}
|
|
|
|
/**
|
|
* UFSHCD read register. This function allows the system to read the
|
|
* register entries
|
|
*/
|
|
|
|
Tick
|
|
UFSHostDevice::read(PacketPtr pkt)
|
|
{
|
|
uint32_t data = 0;
|
|
|
|
switch (pkt->getAddr() & 0xFF)
|
|
{
|
|
|
|
case regControllerCapabilities:
|
|
data = UFSHCIMem.HCCAP;
|
|
break;
|
|
|
|
case regUFSVersion:
|
|
data = UFSHCIMem.HCversion;
|
|
break;
|
|
|
|
case regControllerDEVID:
|
|
data = UFSHCIMem.HCHCDDID;
|
|
break;
|
|
|
|
case regControllerPRODID:
|
|
data = UFSHCIMem.HCHCPMID;
|
|
break;
|
|
|
|
case regInterruptStatus:
|
|
data = UFSHCIMem.ORInterruptStatus;
|
|
UFSHCIMem.ORInterruptStatus = 0x00;
|
|
//TODO: Revise and extend
|
|
clearInterrupt();
|
|
break;
|
|
|
|
case regInterruptEnable:
|
|
data = UFSHCIMem.ORInterruptEnable;
|
|
break;
|
|
|
|
case regControllerStatus:
|
|
data = UFSHCIMem.ORHostControllerStatus;
|
|
break;
|
|
|
|
case regControllerEnable:
|
|
data = UFSHCIMem.ORHostControllerEnable;
|
|
break;
|
|
|
|
case regUICErrorCodePHYAdapterLayer:
|
|
data = UFSHCIMem.ORUECPA;
|
|
break;
|
|
|
|
case regUICErrorCodeDataLinkLayer:
|
|
data = UFSHCIMem.ORUECDL;
|
|
break;
|
|
|
|
case regUICErrorCodeNetworkLayer:
|
|
data = UFSHCIMem.ORUECN;
|
|
break;
|
|
|
|
case regUICErrorCodeTransportLayer:
|
|
data = UFSHCIMem.ORUECT;
|
|
break;
|
|
|
|
case regUICErrorCodeDME:
|
|
data = UFSHCIMem.ORUECDME;
|
|
break;
|
|
|
|
case regUTPTransferREQINTAGGControl:
|
|
data = UFSHCIMem.ORUTRIACR;
|
|
break;
|
|
|
|
case regUTPTransferREQListBaseL:
|
|
data = UFSHCIMem.TRUTRLBA;
|
|
break;
|
|
|
|
case regUTPTransferREQListBaseH:
|
|
data = UFSHCIMem.TRUTRLBAU;
|
|
break;
|
|
|
|
case regUTPTransferREQDoorbell:
|
|
data = UFSHCIMem.TRUTRLDBR;
|
|
break;
|
|
|
|
case regUTPTransferREQListClear:
|
|
data = UFSHCIMem.TRUTRLCLR;
|
|
break;
|
|
|
|
case regUTPTransferREQListRunStop:
|
|
data = UFSHCIMem.TRUTRLRSR;
|
|
break;
|
|
|
|
case regUTPTaskREQListBaseL:
|
|
data = UFSHCIMem.TMUTMRLBA;
|
|
break;
|
|
|
|
case regUTPTaskREQListBaseH:
|
|
data = UFSHCIMem.TMUTMRLBAU;
|
|
break;
|
|
|
|
case regUTPTaskREQDoorbell:
|
|
data = UFSHCIMem.TMUTMRLDBR;
|
|
break;
|
|
|
|
case regUTPTaskREQListClear:
|
|
data = UFSHCIMem.TMUTMRLCLR;
|
|
break;
|
|
|
|
case regUTPTaskREQListRunStop:
|
|
data = UFSHCIMem.TMUTMRLRSR;
|
|
break;
|
|
|
|
case regUICCommand:
|
|
data = UFSHCIMem.CMDUICCMDR;
|
|
break;
|
|
|
|
case regUICCommandArg1:
|
|
data = UFSHCIMem.CMDUCMDARG1;
|
|
break;
|
|
|
|
case regUICCommandArg2:
|
|
data = UFSHCIMem.CMDUCMDARG2;
|
|
break;
|
|
|
|
case regUICCommandArg3:
|
|
data = UFSHCIMem.CMDUCMDARG3;
|
|
break;
|
|
|
|
default:
|
|
data = 0x00;
|
|
break;
|
|
}
|
|
|
|
pkt->setLE<uint32_t>(data);
|
|
pkt->makeResponse();
|
|
return pioDelay;
|
|
}
|
|
|
|
/**
|
|
* UFSHCD write function. This function allows access to the writeable
|
|
* registers. If any function attempts to write value to an unwriteable
|
|
* register entry, then the value will not be written.
|
|
*/
|
|
Tick
|
|
UFSHostDevice::write(PacketPtr pkt)
|
|
{
|
|
assert(pkt->getSize() <= 4);
|
|
|
|
const uint32_t data = pkt->getUintX(ByteOrder::little);
|
|
|
|
switch (pkt->getAddr() & 0xFF)
|
|
{
|
|
case regControllerCapabilities://you shall not write to this
|
|
break;
|
|
|
|
case regUFSVersion://you shall not write to this
|
|
break;
|
|
|
|
case regControllerDEVID://you shall not write to this
|
|
break;
|
|
|
|
case regControllerPRODID://you shall not write to this
|
|
break;
|
|
|
|
case regInterruptStatus://you shall not write to this
|
|
break;
|
|
|
|
case regInterruptEnable:
|
|
UFSHCIMem.ORInterruptEnable = data;
|
|
break;
|
|
|
|
case regControllerStatus:
|
|
UFSHCIMem.ORHostControllerStatus = data;
|
|
break;
|
|
|
|
case regControllerEnable:
|
|
UFSHCIMem.ORHostControllerEnable = data;
|
|
break;
|
|
|
|
case regUICErrorCodePHYAdapterLayer:
|
|
UFSHCIMem.ORUECPA = data;
|
|
break;
|
|
|
|
case regUICErrorCodeDataLinkLayer:
|
|
UFSHCIMem.ORUECDL = data;
|
|
break;
|
|
|
|
case regUICErrorCodeNetworkLayer:
|
|
UFSHCIMem.ORUECN = data;
|
|
break;
|
|
|
|
case regUICErrorCodeTransportLayer:
|
|
UFSHCIMem.ORUECT = data;
|
|
break;
|
|
|
|
case regUICErrorCodeDME:
|
|
UFSHCIMem.ORUECDME = data;
|
|
break;
|
|
|
|
case regUTPTransferREQINTAGGControl:
|
|
UFSHCIMem.ORUTRIACR = data;
|
|
break;
|
|
|
|
case regUTPTransferREQListBaseL:
|
|
UFSHCIMem.TRUTRLBA = data;
|
|
if (((UFSHCIMem.TRUTRLBA | UFSHCIMem.TRUTRLBAU) != 0x00) &&
|
|
((UFSHCIMem.TMUTMRLBA | UFSHCIMem.TMUTMRLBAU)!= 0x00))
|
|
UFSHCIMem.ORHostControllerStatus |= UICCommandReady;
|
|
break;
|
|
|
|
case regUTPTransferREQListBaseH:
|
|
UFSHCIMem.TRUTRLBAU = data;
|
|
if (((UFSHCIMem.TRUTRLBA | UFSHCIMem.TRUTRLBAU) != 0x00) &&
|
|
((UFSHCIMem.TMUTMRLBA | UFSHCIMem.TMUTMRLBAU) != 0x00))
|
|
UFSHCIMem.ORHostControllerStatus |= UICCommandReady;
|
|
break;
|
|
|
|
case regUTPTransferREQDoorbell:
|
|
if (!(UFSHCIMem.TRUTRLDBR) && data)
|
|
stats.idleTimes.sample(curTick() - idlePhaseStart);
|
|
UFSHCIMem.TRUTRLDBR |= data;
|
|
requestHandler();
|
|
break;
|
|
|
|
case regUTPTransferREQListClear:
|
|
UFSHCIMem.TRUTRLCLR = data;
|
|
break;
|
|
|
|
case regUTPTransferREQListRunStop:
|
|
UFSHCIMem.TRUTRLRSR = data;
|
|
break;
|
|
|
|
case regUTPTaskREQListBaseL:
|
|
UFSHCIMem.TMUTMRLBA = data;
|
|
if (((UFSHCIMem.TRUTRLBA | UFSHCIMem.TRUTRLBAU) != 0x00) &&
|
|
((UFSHCIMem.TMUTMRLBA | UFSHCIMem.TMUTMRLBAU) != 0x00))
|
|
UFSHCIMem.ORHostControllerStatus |= UICCommandReady;
|
|
break;
|
|
|
|
case regUTPTaskREQListBaseH:
|
|
UFSHCIMem.TMUTMRLBAU = data;
|
|
if (((UFSHCIMem.TRUTRLBA | UFSHCIMem.TRUTRLBAU) != 0x00) &&
|
|
((UFSHCIMem.TMUTMRLBA | UFSHCIMem.TMUTMRLBAU) != 0x00))
|
|
UFSHCIMem.ORHostControllerStatus |= UICCommandReady;
|
|
break;
|
|
|
|
case regUTPTaskREQDoorbell:
|
|
UFSHCIMem.TMUTMRLDBR |= data;
|
|
requestHandler();
|
|
break;
|
|
|
|
case regUTPTaskREQListClear:
|
|
UFSHCIMem.TMUTMRLCLR = data;
|
|
break;
|
|
|
|
case regUTPTaskREQListRunStop:
|
|
UFSHCIMem.TMUTMRLRSR = data;
|
|
break;
|
|
|
|
case regUICCommand:
|
|
UFSHCIMem.CMDUICCMDR = data;
|
|
requestHandler();
|
|
break;
|
|
|
|
case regUICCommandArg1:
|
|
UFSHCIMem.CMDUCMDARG1 = data;
|
|
break;
|
|
|
|
case regUICCommandArg2:
|
|
UFSHCIMem.CMDUCMDARG2 = data;
|
|
break;
|
|
|
|
case regUICCommandArg3:
|
|
UFSHCIMem.CMDUCMDARG3 = data;
|
|
break;
|
|
|
|
default:break;//nothing happens, you try to access a register that
|
|
//does not exist
|
|
|
|
}
|
|
|
|
pkt->makeResponse();
|
|
return pioDelay;
|
|
}
|
|
|
|
/**
|
|
* Request handler. Determines where the request comes from and initiates the
|
|
* appropriate actions accordingly.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::requestHandler()
|
|
{
|
|
Addr address = 0x00;
|
|
int mask = 0x01;
|
|
int size;
|
|
int count = 0;
|
|
struct taskStart task_info;
|
|
struct transferStart transferstart_info;
|
|
transferstart_info.done = 0;
|
|
|
|
/**
|
|
* step1 determine what called us
|
|
* step2 determine where to get it
|
|
* Look for any request of which we where not yet aware
|
|
*/
|
|
while (((UFSHCIMem.CMDUICCMDR > 0x00) |
|
|
((UFSHCIMem.TMUTMRLDBR ^ taskCommandTrack) > 0x00) |
|
|
((UFSHCIMem.TRUTRLDBR ^ transferTrack) > 0x00)) ) {
|
|
|
|
if (UFSHCIMem.CMDUICCMDR > 0x00) {
|
|
/**
|
|
* Command; general control of the Host controller.
|
|
* no DMA transfer needed
|
|
*/
|
|
commandHandler();
|
|
UFSHCIMem.ORInterruptStatus |= UICCommandCOMPL;
|
|
generateInterrupt();
|
|
UFSHCIMem.CMDUICCMDR = 0x00;
|
|
return; //command, nothing more we can do
|
|
|
|
} else if ((UFSHCIMem.TMUTMRLDBR ^ taskCommandTrack) > 0x00) {
|
|
/**
|
|
* Task; flow control, meant for the device/Logic unit
|
|
* DMA transfer is needed, flash will not be approached
|
|
*/
|
|
size = sizeof(UTPUPIUTaskReq);
|
|
/**Find the position that is not handled yet*/
|
|
count = findLsbSet((UFSHCIMem.TMUTMRLDBR ^ taskCommandTrack));
|
|
address = UFSHCIMem.TMUTMRLBAU;
|
|
//<-64 bit
|
|
address = (count * size) + (address << 32) +
|
|
UFSHCIMem.TMUTMRLBA;
|
|
taskCommandTrack |= mask << count;
|
|
|
|
inform("UFSmodel received a task from the system; this might"
|
|
" lead to untested behaviour.\n");
|
|
|
|
task_info.mask = mask << count;
|
|
task_info.address = address;
|
|
task_info.size = size;
|
|
task_info.done = UFSHCIMem.TMUTMRLDBR;
|
|
taskInfo.push_back(task_info);
|
|
taskEventQueue.push_back(
|
|
EventFunctionWrapper([this]{ taskStart(); }, name()));
|
|
writeDevice(&taskEventQueue.back(), false, address, size,
|
|
reinterpret_cast<uint8_t*>
|
|
(&taskInfo.back().destination), 0, 0);
|
|
|
|
} else if ((UFSHCIMem.TRUTRLDBR ^ transferTrack) > 0x00) {
|
|
/**
|
|
* Transfer; Data transfer from or to the disk. There will be DMA
|
|
* transfers, and the flash might be approached. Further
|
|
* commands, are needed to specify the exact command.
|
|
*/
|
|
size = sizeof(UTPTransferReqDesc);
|
|
/**Find the position that is not handled yet*/
|
|
count = findLsbSet((UFSHCIMem.TRUTRLDBR ^ transferTrack));
|
|
address = UFSHCIMem.TRUTRLBAU;
|
|
//<-64 bit
|
|
address = (count * size) + (address << 32) + UFSHCIMem.TRUTRLBA;
|
|
|
|
transferTrack |= mask << count;
|
|
DPRINTF(UFSHostDevice, "Doorbell register: 0x%8x select #:"
|
|
" 0x%8x completion info: 0x%8x\n", UFSHCIMem.TRUTRLDBR,
|
|
count, transferstart_info.done);
|
|
|
|
transferstart_info.done = UFSHCIMem.TRUTRLDBR;
|
|
|
|
/**stats**/
|
|
transactionStart[count] = curTick(); //note the start time
|
|
++activeDoorbells;
|
|
stats.maxDoorbell = (stats.maxDoorbell.value() < activeDoorbells)
|
|
? activeDoorbells : stats.maxDoorbell.value();
|
|
stats.averageDoorbell = stats.maxDoorbell.value();
|
|
|
|
/**
|
|
* step3 start transfer
|
|
* step4 register information; allowing the host to respond in
|
|
* the end
|
|
*/
|
|
transferstart_info.mask = mask << count;
|
|
transferstart_info.address = address;
|
|
transferstart_info.size = size;
|
|
transferstart_info.done = UFSHCIMem.TRUTRLDBR;
|
|
transferStartInfo.push_back(transferstart_info);
|
|
|
|
/**Deleted in readDone, queued in finalUTP*/
|
|
transferStartInfo.back().destination = new struct
|
|
UTPTransferReqDesc;
|
|
DPRINTF(UFSHostDevice, "Initial transfer start: 0x%8x\n",
|
|
transferstart_info.done);
|
|
transferEventQueue.push_back(
|
|
EventFunctionWrapper([this]{ transferStart(); }, name()));
|
|
|
|
if (transferEventQueue.size() < 2) {
|
|
writeDevice(&transferEventQueue.front(), false,
|
|
address, size, reinterpret_cast<uint8_t*>
|
|
(transferStartInfo.front().destination),0, 0);
|
|
DPRINTF(UFSHostDevice, "Transfer scheduled\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Task start event
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::taskStart()
|
|
{
|
|
DPRINTF(UFSHostDevice, "Task start");
|
|
taskHandler(&taskInfo.front().destination, taskInfo.front().mask,
|
|
taskInfo.front().address, taskInfo.front().size);
|
|
taskInfo.pop_front();
|
|
taskEventQueue.pop_front();
|
|
}
|
|
|
|
/**
|
|
* Transfer start event
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::transferStart()
|
|
{
|
|
DPRINTF(UFSHostDevice, "Enter transfer event\n");
|
|
transferHandler(transferStartInfo.front().destination,
|
|
transferStartInfo.front().mask,
|
|
transferStartInfo.front().address,
|
|
transferStartInfo.front().size,
|
|
transferStartInfo.front().done);
|
|
|
|
transferStartInfo.pop_front();
|
|
DPRINTF(UFSHostDevice, "Transfer queue size at end of event: "
|
|
"0x%8x\n", transferEventQueue.size());
|
|
}
|
|
|
|
/**
|
|
* Handles the commands that are given. At this point in time, not many
|
|
* commands have been implemented in the driver.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::commandHandler()
|
|
{
|
|
if (UFSHCIMem.CMDUICCMDR == 0x16) {
|
|
UFSHCIMem.ORHostControllerStatus |= 0x0F;//link startup
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the tasks that are given. At this point in time, not many tasks
|
|
* have been implemented in the driver.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::taskHandler(struct UTPUPIUTaskReq* request_in,
|
|
uint32_t req_pos, Addr finaladdress, uint32_t
|
|
finalsize)
|
|
{
|
|
/**
|
|
* For now, just unpack and acknowledge the task without doing anything.
|
|
* TODO Implement UFS tasks.
|
|
*/
|
|
inform("taskHandler\n");
|
|
inform("%8x\n", request_in->header.dWord0);
|
|
inform("%8x\n", request_in->header.dWord1);
|
|
inform("%8x\n", request_in->header.dWord2);
|
|
|
|
request_in->header.dWord2 &= 0xffffff00;
|
|
|
|
UFSHCIMem.TMUTMRLDBR &= ~(req_pos);
|
|
taskCommandTrack &= ~(req_pos);
|
|
UFSHCIMem.ORInterruptStatus |= UTPTaskREQCOMPL;
|
|
|
|
readDevice(true, finaladdress, finalsize, reinterpret_cast<uint8_t*>
|
|
(request_in), true, NULL);
|
|
|
|
}
|
|
|
|
/**
|
|
* Obtains the SCSI command (if any)
|
|
* Two possibilities: if it contains a SCSI command, then it is a usable
|
|
* message; if it doesnt contain a SCSI message, then it can't be handeld
|
|
* by this code.
|
|
* This is the second stage of the transfer. We have the information about
|
|
* where the next command can be found and what the type of command is. The
|
|
* actions that are needed from the device its side are: get the information
|
|
* and store the information such that we can reply.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::transferHandler(struct UTPTransferReqDesc* request_in,
|
|
int req_pos, Addr finaladdress, uint32_t
|
|
finalsize, uint32_t done)
|
|
{
|
|
|
|
Addr cmd_desc_addr = 0x00;
|
|
|
|
|
|
//acknowledge handling of the message
|
|
DPRINTF(UFSHostDevice, "SCSI message detected\n");
|
|
request_in->header.dWord2 &= 0xffffff00;
|
|
SCSIInfo.RequestIn = request_in;
|
|
SCSIInfo.reqPos = req_pos;
|
|
SCSIInfo.finalAddress = finaladdress;
|
|
SCSIInfo.finalSize = finalsize;
|
|
SCSIInfo.destination.resize(request_in->PRDTableOffset * 4
|
|
+ request_in->PRDTableLength * sizeof(UFSHCDSGEntry));
|
|
SCSIInfo.done = done;
|
|
|
|
assert(!SCSIResumeEvent.scheduled());
|
|
/**
|
|
*Get the UTP command that has the SCSI command
|
|
*/
|
|
cmd_desc_addr = request_in->commandDescBaseAddrHi;
|
|
cmd_desc_addr = (cmd_desc_addr << 32) |
|
|
(request_in->commandDescBaseAddrLo & 0xffffffff);
|
|
|
|
writeDevice(&SCSIResumeEvent, false, cmd_desc_addr,
|
|
SCSIInfo.destination.size(), &SCSIInfo.destination[0],0, 0);
|
|
|
|
DPRINTF(UFSHostDevice, "SCSI scheduled\n");
|
|
|
|
transferEventQueue.pop_front();
|
|
}
|
|
|
|
/**
|
|
* Obtain LUN and put it in the right LUN queue. Each LUN has its own queue
|
|
* of commands that need to be executed. This is the first instance where it
|
|
* can be determined which Logic unit should handle the transfer. Then check
|
|
* wether it should wait and queue or if it can continue.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::SCSIStart()
|
|
{
|
|
DPRINTF(UFSHostDevice, "SCSI message on hold until ready\n");
|
|
uint32_t LUN = SCSIInfo.destination[2];
|
|
UFSDevice[LUN]->SCSIInfoQueue.push_back(SCSIInfo);
|
|
|
|
DPRINTF(UFSHostDevice, "SCSI queue %d has %d elements\n", LUN,
|
|
UFSDevice[LUN]->SCSIInfoQueue.size());
|
|
|
|
/**There are 32 doorbells, so at max there can be 32 transactions*/
|
|
if (UFSDevice[LUN]->SCSIInfoQueue.size() < 2) //LUN is available
|
|
SCSIResume(LUN);
|
|
|
|
else if (UFSDevice[LUN]->SCSIInfoQueue.size() > 32)
|
|
panic("SCSI queue is getting too big %d\n", UFSDevice[LUN]->
|
|
SCSIInfoQueue.size());
|
|
|
|
/**
|
|
* First transfer is done, fetch the next;
|
|
* At this point, the device is busy, not the HC
|
|
*/
|
|
if (!transferEventQueue.empty()) {
|
|
|
|
/**
|
|
* loading next data packet in case Another LUN
|
|
* is approached in the mean time
|
|
*/
|
|
writeDevice(&transferEventQueue.front(), false,
|
|
transferStartInfo.front().address,
|
|
transferStartInfo.front().size, reinterpret_cast<uint8_t*>
|
|
(transferStartInfo.front().destination), 0, 0);
|
|
|
|
DPRINTF(UFSHostDevice, "Transfer scheduled");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the transfer requests that are given.
|
|
* There can be three types of transfer. SCSI specific, Reads and writes
|
|
* apart from the data transfer, this also generates its own reply (UPIU
|
|
* response). Information for this reply is stored in transferInfo and will
|
|
* be used in transferDone
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::SCSIResume(uint32_t lun_id)
|
|
{
|
|
DPRINTF(UFSHostDevice, "SCSIresume\n");
|
|
if (UFSDevice[lun_id]->SCSIInfoQueue.empty())
|
|
panic("No SCSI message scheduled lun:%d Doorbell: 0x%8x", lun_id,
|
|
UFSHCIMem.TRUTRLDBR);
|
|
|
|
/**old info, lets form it such that we can understand it*/
|
|
struct UTPTransferReqDesc* request_in = UFSDevice[lun_id]->
|
|
SCSIInfoQueue.front().RequestIn;
|
|
|
|
uint32_t req_pos = UFSDevice[lun_id]->SCSIInfoQueue.front().reqPos;
|
|
|
|
Addr finaladdress = UFSDevice[lun_id]->SCSIInfoQueue.front().
|
|
finalAddress;
|
|
|
|
uint32_t finalsize = UFSDevice[lun_id]->SCSIInfoQueue.front().finalSize;
|
|
|
|
uint32_t* transfercommand = reinterpret_cast<uint32_t*>
|
|
(&(UFSDevice[lun_id]->SCSIInfoQueue.front().destination[0]));
|
|
|
|
DPRINTF(UFSHostDevice, "Task tag: 0x%8x\n", transfercommand[0]>>24);
|
|
/**call logic unit to handle SCSI command*/
|
|
request_out_datain = UFSDevice[(transfercommand[0] & 0xFF0000) >> 16]->
|
|
SCSICMDHandle(transfercommand);
|
|
|
|
DPRINTF(UFSHostDevice, "LUN: %d\n", request_out_datain.LUN);
|
|
|
|
/**
|
|
* build response stating that it was succesful
|
|
* command completion, Logic unit number, and Task tag
|
|
*/
|
|
request_in->header.dWord0 = ((request_in->header.dWord0 >> 24) == 0x21)
|
|
? 0x36 : 0x21;
|
|
UFSDevice[lun_id]->transferInfo.requestOut.header.dWord0 =
|
|
request_in->header.dWord0 | (request_out_datain.LUN << 8)
|
|
| (transfercommand[0] & 0xFF000000);
|
|
/**SCSI status reply*/
|
|
UFSDevice[lun_id]->transferInfo.requestOut.header.dWord1 = 0x00000000 |
|
|
(request_out_datain.status << 24);
|
|
/**segment size + EHS length (see UFS standard ch7)*/
|
|
UFSDevice[lun_id]->transferInfo.requestOut.header.dWord2 = 0x00000000 |
|
|
((request_out_datain.senseSize + 2) << 24) | 0x05;
|
|
/**amount of data that will follow*/
|
|
UFSDevice[lun_id]->transferInfo.requestOut.senseDataLen =
|
|
request_out_datain.senseSize;
|
|
|
|
//data
|
|
for (uint8_t count = 0; count<request_out_datain.senseSize; count++) {
|
|
UFSDevice[lun_id]->transferInfo.requestOut.senseData[count] =
|
|
request_out_datain.senseCode[count + 1];
|
|
}
|
|
|
|
/*
|
|
* At position defined by "request_in->PRDTableOffset" (counting 32 bit
|
|
* words) in array "transfercommand" we have a scatter gather list, which
|
|
* is usefull to us if we interpreted it as a UFSHCDSGEntry structure.
|
|
*/
|
|
struct UFSHCDSGEntry* sglist = reinterpret_cast<UFSHCDSGEntry*>
|
|
(&(transfercommand[(request_in->PRDTableOffset)]));
|
|
|
|
uint32_t length = request_in->PRDTableLength;
|
|
DPRINTF(UFSHostDevice, "# PRDT entries: %d\n", length);
|
|
|
|
Addr response_addr = request_in->commandDescBaseAddrHi;
|
|
response_addr = (response_addr << 32) |
|
|
((request_in->commandDescBaseAddrLo +
|
|
(request_in->responseUPIULength << 2)) & 0xffffffff);
|
|
|
|
/**transferdone information packet filling*/
|
|
UFSDevice[lun_id]->transferInfo.responseStartAddr = response_addr;
|
|
UFSDevice[lun_id]->transferInfo.reqPos = req_pos;
|
|
UFSDevice[lun_id]->transferInfo.size = finalsize;
|
|
UFSDevice[lun_id]->transferInfo.address = finaladdress;
|
|
UFSDevice[lun_id]->transferInfo.destination = reinterpret_cast<uint8_t*>
|
|
(UFSDevice[lun_id]->SCSIInfoQueue.front().RequestIn);
|
|
UFSDevice[lun_id]->transferInfo.finished = true;
|
|
UFSDevice[lun_id]->transferInfo.lunID = request_out_datain.LUN;
|
|
|
|
/**
|
|
* In this part the data that needs to be transfered will be initiated
|
|
* and the chain of DMA (and potentially) disk transactions will be
|
|
* started.
|
|
*/
|
|
if (request_out_datain.expectMore == 0x01) {
|
|
/**write transfer*/
|
|
manageWriteTransfer(request_out_datain.LUN, request_out_datain.offset,
|
|
length, sglist);
|
|
|
|
} else if (request_out_datain.expectMore == 0x02) {
|
|
/**read transfer*/
|
|
manageReadTransfer(request_out_datain.msgSize, request_out_datain.LUN,
|
|
request_out_datain.offset, length, sglist);
|
|
|
|
} else {
|
|
/**not disk related transfer, SCSI maintanance*/
|
|
uint32_t count = 0;
|
|
uint32_t size_accum = 0;
|
|
DPRINTF(UFSHostDevice, "Data DMA size: 0x%8x\n",
|
|
request_out_datain.msgSize);
|
|
|
|
/**Transport the SCSI reponse data according to the SG list*/
|
|
while ((length > count) && size_accum
|
|
< (request_out_datain.msgSize - 1) &&
|
|
(request_out_datain.msgSize != 0x00)) {
|
|
Addr SCSI_start = sglist[count].upperAddr;
|
|
SCSI_start = (SCSI_start << 32) |
|
|
(sglist[count].baseAddr & 0xFFFFFFFF);
|
|
DPRINTF(UFSHostDevice, "Data DMA start: 0x%8x\n", SCSI_start);
|
|
DPRINTF(UFSHostDevice, "Data DMA size: 0x%8x\n",
|
|
(sglist[count].size + 1));
|
|
/**
|
|
* safetynet; it has been shown that sg list may be optimistic in
|
|
* the amount of data allocated, which can potentially lead to
|
|
* some garbage data being send over. Hence this construction
|
|
* that finds the least amount of data that needs to be
|
|
* transfered.
|
|
*/
|
|
uint32_t size_to_send = sglist[count].size + 1;
|
|
|
|
if (request_out_datain.msgSize < (size_to_send + size_accum))
|
|
size_to_send = request_out_datain.msgSize - size_accum;
|
|
|
|
readDevice(false, SCSI_start, size_to_send,
|
|
reinterpret_cast<uint8_t*>
|
|
(&(request_out_datain.message.dataMsg[size_accum])),
|
|
false, NULL);
|
|
|
|
size_accum += size_to_send;
|
|
DPRINTF(UFSHostDevice, "Total remaining: 0x%8x,accumulated so far"
|
|
" : 0x%8x\n", (request_out_datain.msgSize - size_accum),
|
|
size_accum);
|
|
|
|
++count;
|
|
DPRINTF(UFSHostDevice, "Transfer #: %d\n", count);
|
|
}
|
|
|
|
/**Go to the next stage of the answering process*/
|
|
transferDone(response_addr, req_pos, UFSDevice[lun_id]->
|
|
transferInfo.requestOut, finalsize, finaladdress,
|
|
reinterpret_cast<uint8_t*>(request_in), true, lun_id);
|
|
}
|
|
|
|
DPRINTF(UFSHostDevice, "SCSI resume done\n");
|
|
}
|
|
|
|
/**
|
|
* Find finished transfer. Callback function. One of the LUNs is done with
|
|
* the disk transfer and reports back to the controller. This function finds
|
|
* out who it was, and calls transferDone.
|
|
*/
|
|
void
|
|
UFSHostDevice::LUNSignal()
|
|
{
|
|
uint8_t this_lun = 0;
|
|
|
|
//while we haven't found the right lun, keep searching
|
|
while ((this_lun < lunAvail) && !UFSDevice[this_lun]->finishedCommand())
|
|
++this_lun;
|
|
|
|
if (this_lun < lunAvail) {
|
|
//Clear signal.
|
|
UFSDevice[this_lun]->clearSignal();
|
|
//found it; call transferDone
|
|
transferDone(UFSDevice[this_lun]->transferInfo.responseStartAddr,
|
|
UFSDevice[this_lun]->transferInfo.reqPos,
|
|
UFSDevice[this_lun]->transferInfo.requestOut,
|
|
UFSDevice[this_lun]->transferInfo.size,
|
|
UFSDevice[this_lun]->transferInfo.address,
|
|
UFSDevice[this_lun]->transferInfo.destination,
|
|
UFSDevice[this_lun]->transferInfo.finished,
|
|
UFSDevice[this_lun]->transferInfo.lunID);
|
|
}
|
|
|
|
else
|
|
panic("no LUN finished in tick %d\n", curTick());
|
|
}
|
|
|
|
/**
|
|
* Transfer done. When the data transfer is done, this function ensures
|
|
* that the application is notified.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::transferDone(Addr responseStartAddr, uint32_t req_pos,
|
|
struct UTPUPIURSP request_out, uint32_t size,
|
|
Addr address, uint8_t* destination,
|
|
bool finished, uint32_t lun_id)
|
|
{
|
|
/**Test whether SCSI queue hasn't popped prematurely*/
|
|
if (UFSDevice[lun_id]->SCSIInfoQueue.empty())
|
|
panic("No SCSI message scheduled lun:%d Doorbell: 0x%8x", lun_id,
|
|
UFSHCIMem.TRUTRLDBR);
|
|
|
|
DPRINTF(UFSHostDevice, "DMA start: 0x%8x; DMA size: 0x%8x\n",
|
|
responseStartAddr, sizeof(request_out));
|
|
|
|
struct transferStart lastinfo;
|
|
lastinfo.mask = req_pos;
|
|
lastinfo.done = finished;
|
|
lastinfo.address = address;
|
|
lastinfo.size = size;
|
|
lastinfo.destination = reinterpret_cast<UTPTransferReqDesc*>
|
|
(destination);
|
|
lastinfo.lun_id = lun_id;
|
|
|
|
transferEnd.push_back(lastinfo);
|
|
|
|
DPRINTF(UFSHostDevice, "Transfer done start\n");
|
|
|
|
readDevice(false, responseStartAddr, sizeof(request_out),
|
|
reinterpret_cast<uint8_t*>
|
|
(&(UFSDevice[lun_id]->transferInfo.requestOut)),
|
|
true, &UTPEvent);
|
|
}
|
|
|
|
/**
|
|
* finalUTP. Second part of the transfer done event.
|
|
* this sends the final response: the UTP response. After this transaction
|
|
* the doorbell shall be cleared, and the interupt shall be set.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::finalUTP()
|
|
{
|
|
uint32_t lun_id = transferEnd.front().lun_id;
|
|
|
|
UFSDevice[lun_id]->SCSIInfoQueue.pop_front();
|
|
DPRINTF(UFSHostDevice, "SCSIInfoQueue size: %d, lun: %d\n",
|
|
UFSDevice[lun_id]->SCSIInfoQueue.size(), lun_id);
|
|
|
|
/**stats**/
|
|
if (UFSHCIMem.TRUTRLDBR & transferEnd.front().mask) {
|
|
uint8_t count = 0;
|
|
while (!(transferEnd.front().mask & (0x1 << count)))
|
|
++count;
|
|
stats.transactionLatency.sample(curTick() -
|
|
transactionStart[count]);
|
|
}
|
|
|
|
/**Last message that will be transfered*/
|
|
readDevice(true, transferEnd.front().address,
|
|
transferEnd.front().size, reinterpret_cast<uint8_t*>
|
|
(transferEnd.front().destination), true, NULL);
|
|
|
|
/**clean and ensure that the tracker is updated*/
|
|
transferTrack &= ~(transferEnd.front().mask);
|
|
--activeDoorbells;
|
|
++pendingDoorbells;
|
|
garbage.push_back(transferEnd.front().destination);
|
|
transferEnd.pop_front();
|
|
DPRINTF(UFSHostDevice, "UTP handled\n");
|
|
|
|
/**stats**/
|
|
stats.averageDoorbell = stats.maxDoorbell.value();
|
|
|
|
DPRINTF(UFSHostDevice, "activeDoorbells: %d, pendingDoorbells: %d,"
|
|
" garbage: %d, TransferEvent: %d\n", activeDoorbells,
|
|
pendingDoorbells, garbage.size(), transferEventQueue.size());
|
|
|
|
/**This is the moment that the device is available again*/
|
|
if (!UFSDevice[lun_id]->SCSIInfoQueue.empty())
|
|
SCSIResume(lun_id);
|
|
}
|
|
|
|
/**
|
|
* Read done handling function, is only initiated at the end of a transaction
|
|
*/
|
|
void
|
|
UFSHostDevice::readDone()
|
|
{
|
|
DPRINTF(UFSHostDevice, "Read done start\n");
|
|
--readPendingNum;
|
|
|
|
/**Garbage collection; sort out the allocated UTP descriptor*/
|
|
if (garbage.size() > 0) {
|
|
delete garbage.front();
|
|
garbage.pop_front();
|
|
}
|
|
|
|
/**done, generate interrupt if we havent got one already*/
|
|
if (!(UFSHCIMem.ORInterruptStatus & 0x01)) {
|
|
UFSHCIMem.ORInterruptStatus |= UTPTransferREQCOMPL;
|
|
generateInterrupt();
|
|
}
|
|
|
|
|
|
if (!readDoneEvent.empty()) {
|
|
readDoneEvent.pop_front();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* set interrupt and sort out the doorbell register.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::generateInterrupt()
|
|
{
|
|
/**just to keep track of the transactions*/
|
|
countInt++;
|
|
|
|
/**step5 clear doorbell*/
|
|
UFSHCIMem.TRUTRLDBR &= transferTrack;
|
|
pendingDoorbells = 0;
|
|
DPRINTF(UFSHostDevice, "Clear doorbell %X\n", UFSHCIMem.TRUTRLDBR);
|
|
|
|
checkDrain();
|
|
|
|
/**step6 raise interrupt*/
|
|
gic->sendInt(intNum);
|
|
DPRINTF(UFSHostDevice, "Send interrupt @ transaction: 0x%8x!\n",
|
|
countInt);
|
|
}
|
|
|
|
/**
|
|
* Clear interrupt
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::clearInterrupt()
|
|
{
|
|
gic->clearInt(intNum);
|
|
DPRINTF(UFSHostDevice, "Clear interrupt: 0x%8x!\n", countInt);
|
|
|
|
checkDrain();
|
|
|
|
if (!(UFSHCIMem.TRUTRLDBR)) {
|
|
idlePhaseStart = curTick();
|
|
}
|
|
/**end of a transaction*/
|
|
}
|
|
|
|
/**
|
|
* Important to understand about the transfer flow:
|
|
* We have basically three stages, The "system memory" stage, the "device
|
|
* buffer" stage and the "disk" stage. In this model we assume an infinite
|
|
* buffer, or a buffer that is big enough to store all the data in the
|
|
* biggest transaction. Between the three stages are two queues. Those queues
|
|
* store the messages to simulate their transaction from one stage to the
|
|
* next. The manage{Action} function fills up one of the queues and triggers
|
|
* the first event, which causes a chain reaction of events executed once
|
|
* they pass through their queues. For a write action the stages are ordered
|
|
* "system memory", "device buffer" and "disk", whereas the read transfers
|
|
* happen "disk", "device buffer" and "system memory". The dma action in the
|
|
* dma device is written from a bus perspective whereas this model is written
|
|
* from a device perspective. To avoid confusion, the translation between the
|
|
* two has been made in the writeDevice and readDevice funtions.
|
|
*/
|
|
|
|
|
|
/**
|
|
* Dma transaction function: write device. Note that the dma action is
|
|
* from a device perspective, while this function is from an initiator
|
|
* perspective
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::writeDevice(Event* additional_action, bool toDisk, Addr
|
|
start, int size, uint8_t* destination, uint64_t
|
|
SCSIDiskOffset, uint32_t lun_id)
|
|
{
|
|
DPRINTF(UFSHostDevice, "Write transaction Start: 0x%8x; Size: %d\n",
|
|
start, size);
|
|
|
|
/**check whether transfer is all the way to the flash*/
|
|
if (toDisk) {
|
|
++writePendingNum;
|
|
|
|
while (!writeDoneEvent.empty() && (writeDoneEvent.front().when()
|
|
< curTick()))
|
|
writeDoneEvent.pop_front();
|
|
|
|
writeDoneEvent.push_back(
|
|
EventFunctionWrapper([this]{ writeDone(); },
|
|
name()));
|
|
assert(!writeDoneEvent.back().scheduled());
|
|
|
|
/**destination is an offset here since we are writing to a disk*/
|
|
struct transferInfo new_transfer;
|
|
new_transfer.offset = SCSIDiskOffset;
|
|
new_transfer.size = size;
|
|
new_transfer.lunID = lun_id;
|
|
new_transfer.filePointer = 0;
|
|
SSDWriteinfo.push_back(new_transfer);
|
|
|
|
/**allocate appropriate buffer*/
|
|
SSDWriteinfo.back().buffer.resize(size);
|
|
|
|
/**transaction*/
|
|
dmaPort.dmaAction(MemCmd::ReadReq, start, size,
|
|
&writeDoneEvent.back(),
|
|
&SSDWriteinfo.back().buffer[0], 0);
|
|
//yes, a readreq at a write device function is correct.
|
|
DPRINTF(UFSHostDevice, "Write to disk scheduled\n");
|
|
|
|
} else {
|
|
assert(!additional_action->scheduled());
|
|
dmaPort.dmaAction(MemCmd::ReadReq, start, size,
|
|
additional_action, destination, 0);
|
|
DPRINTF(UFSHostDevice, "Write scheduled\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Manage write transfer. Manages correct transfer flow and makes sure that
|
|
* the queues are filled on time
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::manageWriteTransfer(uint8_t LUN, uint64_t offset, uint32_t
|
|
sg_table_length, struct UFSHCDSGEntry*
|
|
sglist)
|
|
{
|
|
struct writeToDiskBurst next_packet;
|
|
|
|
next_packet.SCSIDiskOffset = offset;
|
|
|
|
UFSDevice[LUN]->setTotalWrite(sg_table_length);
|
|
|
|
/**
|
|
* Break-up the transactions into actions defined by the scatter gather
|
|
* list.
|
|
*/
|
|
for (uint32_t count = 0; count < sg_table_length; count++) {
|
|
next_packet.start = sglist[count].upperAddr;
|
|
next_packet.start = (next_packet.start << 32) |
|
|
(sglist[count].baseAddr & 0xFFFFFFFF);
|
|
next_packet.LUN = LUN;
|
|
DPRINTF(UFSHostDevice, "Write data DMA start: 0x%8x\n",
|
|
next_packet.start);
|
|
DPRINTF(UFSHostDevice, "Write data DMA size: 0x%8x\n",
|
|
(sglist[count].size + 1));
|
|
assert(sglist[count].size > 0);
|
|
|
|
if (count != 0)
|
|
next_packet.SCSIDiskOffset = next_packet.SCSIDiskOffset +
|
|
(sglist[count - 1].size + 1);
|
|
|
|
next_packet.size = sglist[count].size + 1;
|
|
|
|
/**If the queue is empty, the transaction should be initiated*/
|
|
if (dmaWriteInfo.empty())
|
|
writeDevice(NULL, true, next_packet.start, next_packet.size,
|
|
NULL, next_packet.SCSIDiskOffset, next_packet.LUN);
|
|
else
|
|
DPRINTF(UFSHostDevice, "Write not initiated queue: %d\n",
|
|
dmaWriteInfo.size());
|
|
|
|
dmaWriteInfo.push_back(next_packet);
|
|
DPRINTF(UFSHostDevice, "Write Location: 0x%8x\n",
|
|
next_packet.SCSIDiskOffset);
|
|
|
|
DPRINTF(UFSHostDevice, "Write transfer #: 0x%8x\n", count + 1);
|
|
|
|
/** stats **/
|
|
stats.totalWrittenSSD += (sglist[count].size + 1);
|
|
}
|
|
|
|
/**stats**/
|
|
++stats.totalWriteUFSTransactions;
|
|
}
|
|
|
|
/**
|
|
* Write done handling function. Is only initiated when the flash is directly
|
|
* approached
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::writeDone()
|
|
{
|
|
/**DMA is done, information no longer needed*/
|
|
assert(dmaWriteInfo.size() > 0);
|
|
dmaWriteInfo.pop_front();
|
|
assert(SSDWriteinfo.size() > 0);
|
|
uint32_t lun = SSDWriteinfo.front().lunID;
|
|
|
|
/**If there is nothing on the way, we need to start the events*/
|
|
DPRINTF(UFSHostDevice, "Write done entered, queue: %d\n",
|
|
UFSDevice[lun]->SSDWriteDoneInfo.size());
|
|
/**Write the disk*/
|
|
UFSDevice[lun]->writeFlash(&SSDWriteinfo.front().buffer[0],
|
|
SSDWriteinfo.front().offset,
|
|
SSDWriteinfo.front().size);
|
|
|
|
/**
|
|
* Move to the second queue, enter the logic unit
|
|
* This is where the disk is approached and the flash transaction is
|
|
* handled SSDWriteDone will take care of the timing
|
|
*/
|
|
UFSDevice[lun]->SSDWriteDoneInfo.push_back(SSDWriteinfo.front());
|
|
SSDWriteinfo.pop_front();
|
|
|
|
--writePendingNum;
|
|
/**so far, only the DMA part has been handled, lets do the disk delay*/
|
|
UFSDevice[lun]->SSDWriteStart();
|
|
|
|
/** stats **/
|
|
stats.currentWriteSSDQueue = UFSDevice[lun]->SSDWriteDoneInfo.size();
|
|
stats.averageWriteSSDQueue = UFSDevice[lun]->SSDWriteDoneInfo.size();
|
|
++stats.totalWriteDiskTransactions;
|
|
|
|
/**initiate the next dma action (if any)*/
|
|
if (!dmaWriteInfo.empty())
|
|
writeDevice(NULL, true, dmaWriteInfo.front().start,
|
|
dmaWriteInfo.front().size, NULL,
|
|
dmaWriteInfo.front().SCSIDiskOffset,
|
|
dmaWriteInfo.front().LUN);
|
|
DPRINTF(UFSHostDevice, "Write done end\n");
|
|
}
|
|
|
|
/**
|
|
* SSD write start. Starts the write action in the timing model
|
|
*/
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::SSDWriteStart()
|
|
{
|
|
assert(SSDWriteDoneInfo.size() > 0);
|
|
flashDevice->writeMemory(
|
|
SSDWriteDoneInfo.front().offset,
|
|
SSDWriteDoneInfo.front().size, memWriteCallback);
|
|
|
|
SSDWriteDoneInfo.pop_front();
|
|
|
|
DPRINTF(UFSHostDevice, "Write is started; left in queue: %d\n",
|
|
SSDWriteDoneInfo.size());
|
|
}
|
|
|
|
|
|
/**
|
|
* SSDisk write done
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::SSDWriteDone()
|
|
{
|
|
DPRINTF(UFSHostDevice, "Write disk, aiming for %d messages, %d so far\n",
|
|
totalWrite, amountOfWriteTransfers);
|
|
|
|
//we have done one extra transfer
|
|
++amountOfWriteTransfers;
|
|
|
|
/**test whether call was correct*/
|
|
assert(totalWrite >= amountOfWriteTransfers && totalWrite != 0);
|
|
|
|
/**are we there yet? (did we do everything)*/
|
|
if (totalWrite == amountOfWriteTransfers) {
|
|
DPRINTF(UFSHostDevice, "Write transactions finished\n");
|
|
totalWrite = 0;
|
|
amountOfWriteTransfers = 0;
|
|
|
|
//Callback UFS Host
|
|
setSignal();
|
|
signalDone();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Dma transaction function: read device. Notice that the dma action is from
|
|
* a device perspective, while this function is from an initiator perspective
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::readDevice(bool lastTransfer, Addr start, uint32_t size,
|
|
uint8_t* destination, bool no_cache, Event*
|
|
additional_action)
|
|
{
|
|
DPRINTF(UFSHostDevice, "Read start: 0x%8x; Size: %d, data[0]: 0x%8x\n",
|
|
start, size, (reinterpret_cast<uint32_t *>(destination))[0]);
|
|
|
|
/** check wether interrupt is needed */
|
|
if (lastTransfer) {
|
|
++readPendingNum;
|
|
readDoneEvent.push_back(
|
|
EventFunctionWrapper([this]{ readDone(); },
|
|
name()));
|
|
assert(!readDoneEvent.back().scheduled());
|
|
dmaPort.dmaAction(MemCmd::WriteReq, start, size,
|
|
&readDoneEvent.back(), destination, 0);
|
|
//yes, a writereq at a read device function is correct.
|
|
|
|
} else {
|
|
if (additional_action != NULL)
|
|
assert(!additional_action->scheduled());
|
|
|
|
dmaPort.dmaAction(MemCmd::WriteReq, start, size,
|
|
additional_action, destination, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Manage read transfer. Manages correct transfer flow and makes sure that
|
|
* the queues are filled on time
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::manageReadTransfer(uint32_t size, uint32_t LUN, uint64_t
|
|
offset, uint32_t sg_table_length,
|
|
struct UFSHCDSGEntry* sglist)
|
|
{
|
|
uint32_t size_accum = 0;
|
|
|
|
DPRINTF(UFSHostDevice, "Data READ size: %d\n", size);
|
|
|
|
/**
|
|
* Break-up the transactions into actions defined by the scatter gather
|
|
* list.
|
|
*/
|
|
for (uint32_t count = 0; count < sg_table_length; count++) {
|
|
struct transferInfo new_transfer;
|
|
new_transfer.offset = sglist[count].upperAddr;
|
|
new_transfer.offset = (new_transfer.offset << 32) |
|
|
(sglist[count].baseAddr & 0xFFFFFFFF);
|
|
new_transfer.filePointer = offset + size_accum;
|
|
new_transfer.size = (sglist[count].size + 1);
|
|
new_transfer.lunID = LUN;
|
|
|
|
DPRINTF(UFSHostDevice, "Data READ start: 0x%8x; size: %d\n",
|
|
new_transfer.offset, new_transfer.size);
|
|
|
|
UFSDevice[LUN]->SSDReadInfo.push_back(new_transfer);
|
|
UFSDevice[LUN]->SSDReadInfo.back().buffer.resize(sglist[count].size
|
|
+ 1);
|
|
|
|
/**
|
|
* The disk image is read here; but the action is simultated later
|
|
* You can see this as the preparation stage, whereas later is the
|
|
* simulation phase.
|
|
*/
|
|
UFSDevice[LUN]->readFlash(&UFSDevice[LUN]->
|
|
SSDReadInfo.back().buffer[0],
|
|
offset + size_accum,
|
|
sglist[count].size + 1);
|
|
|
|
size_accum += (sglist[count].size + 1);
|
|
|
|
DPRINTF(UFSHostDevice, "Transfer %d; Remaining: 0x%8x, Accumulated:"
|
|
" 0x%8x\n", (count + 1), (size-size_accum), size_accum);
|
|
|
|
/** stats **/
|
|
stats.totalReadSSD += (sglist[count].size + 1);
|
|
stats.currentReadSSDQueue = UFSDevice[LUN]->SSDReadInfo.size();
|
|
stats.averageReadSSDQueue = UFSDevice[LUN]->SSDReadInfo.size();
|
|
}
|
|
|
|
UFSDevice[LUN]->SSDReadStart(sg_table_length);
|
|
|
|
/** stats **/
|
|
++stats.totalReadUFSTransactions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* SSDisk start read; this function was created to keep the interfaces
|
|
* between the layers simpler. Without this function UFSHost would need to
|
|
* know about the flashdevice.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::SSDReadStart(uint32_t total_read)
|
|
{
|
|
totalRead = total_read;
|
|
for (uint32_t number_handled = 0; number_handled < SSDReadInfo.size();
|
|
number_handled++) {
|
|
/**
|
|
* Load all the read request to the Memory device.
|
|
* It will call back when done.
|
|
*/
|
|
flashDevice->readMemory(SSDReadInfo.front().filePointer,
|
|
SSDReadInfo.front().size, memReadCallback);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* SSDisk read done
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::SSDReadDone()
|
|
{
|
|
DPRINTF(UFSHostDevice, "SSD read done at lun %d, Aiming for %d messages,"
|
|
" %d so far\n", lunID, totalRead, amountOfReadTransfers);
|
|
|
|
if (totalRead == amountOfReadTransfers) {
|
|
totalRead = 0;
|
|
amountOfReadTransfers = 0;
|
|
|
|
/**Callback: transferdone*/
|
|
setSignal();
|
|
signalDone();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Read callback, on the way from the disk to the DMA. Called by the flash
|
|
* layer. Intermediate step to the host layer
|
|
*/
|
|
void
|
|
UFSHostDevice::UFSSCSIDevice::readCallback()
|
|
{
|
|
++amountOfReadTransfers;
|
|
|
|
/**Callback; make sure data is transfered upstream:
|
|
* UFSHostDevice::readCallback
|
|
*/
|
|
setReadSignal();
|
|
deviceReadCallback();
|
|
|
|
//Are we done yet?
|
|
SSDReadDone();
|
|
}
|
|
|
|
/**
|
|
* Read callback, on the way from the disk to the DMA. Called by the UFSSCSI
|
|
* layer.
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::readCallback()
|
|
{
|
|
DPRINTF(UFSHostDevice, "Read Callback\n");
|
|
uint8_t this_lun = 0;
|
|
|
|
//while we haven't found the right lun, keep searching
|
|
while ((this_lun < lunAvail) && !UFSDevice[this_lun]->finishedRead())
|
|
++this_lun;
|
|
|
|
DPRINTF(UFSHostDevice, "Found LUN %d messages pending for clean: %d\n",
|
|
this_lun, SSDReadPending.size());
|
|
|
|
if (this_lun < lunAvail) {
|
|
//Clear signal.
|
|
UFSDevice[this_lun]->clearReadSignal();
|
|
SSDReadPending.push_back(UFSDevice[this_lun]->SSDReadInfo.front());
|
|
UFSDevice[this_lun]->SSDReadInfo.pop_front();
|
|
readGarbageEventQueue.push_back(
|
|
EventFunctionWrapper([this]{ readGarbage(); }, name()));
|
|
|
|
//make sure the queue is popped a the end of the dma transaction
|
|
readDevice(false, SSDReadPending.front().offset,
|
|
SSDReadPending.front().size,
|
|
&SSDReadPending.front().buffer[0], false,
|
|
&readGarbageEventQueue.back());
|
|
|
|
/**stats*/
|
|
++stats.totalReadDiskTransactions;
|
|
}
|
|
else
|
|
panic("no read finished in tick %d\n", curTick());
|
|
}
|
|
|
|
/**
|
|
* After a disk read DMA transfer, the structure needs to be freed. This is
|
|
* done in this function.
|
|
*/
|
|
void
|
|
UFSHostDevice::readGarbage()
|
|
{
|
|
DPRINTF(UFSHostDevice, "Clean read data, %d\n", SSDReadPending.size());
|
|
SSDReadPending.pop_front();
|
|
readGarbageEventQueue.pop_front();
|
|
}
|
|
|
|
/**
|
|
* Serialize; needed to make checkpoints
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::serialize(CheckpointOut &cp) const
|
|
{
|
|
DmaDevice::serialize(cp);
|
|
|
|
const uint8_t* temp_HCI_mem = reinterpret_cast<const uint8_t*>(&UFSHCIMem);
|
|
SERIALIZE_ARRAY(temp_HCI_mem, sizeof(HCIMem));
|
|
|
|
uint32_t lun_avail = lunAvail;
|
|
SERIALIZE_SCALAR(lun_avail);
|
|
}
|
|
|
|
|
|
/**
|
|
* Unserialize; needed to restore from checkpoints
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::unserialize(CheckpointIn &cp)
|
|
{
|
|
DmaDevice::unserialize(cp);
|
|
uint8_t* temp_HCI_mem = reinterpret_cast<uint8_t*>(&UFSHCIMem);
|
|
UNSERIALIZE_ARRAY(temp_HCI_mem, sizeof(HCIMem));
|
|
|
|
uint32_t lun_avail;
|
|
UNSERIALIZE_SCALAR(lun_avail);
|
|
assert(lunAvail == lun_avail);
|
|
}
|
|
|
|
|
|
/**
|
|
* Drain; needed to enable checkpoints
|
|
*/
|
|
|
|
DrainState
|
|
UFSHostDevice::drain()
|
|
{
|
|
if (UFSHCIMem.TRUTRLDBR) {
|
|
DPRINTF(UFSHostDevice, "UFSDevice is draining...\n");
|
|
return DrainState::Draining;
|
|
} else {
|
|
DPRINTF(UFSHostDevice, "UFSDevice drained\n");
|
|
return DrainState::Drained;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checkdrain; needed to enable checkpoints
|
|
*/
|
|
|
|
void
|
|
UFSHostDevice::checkDrain()
|
|
{
|
|
if (drainState() != DrainState::Draining)
|
|
return;
|
|
|
|
if (UFSHCIMem.TRUTRLDBR) {
|
|
DPRINTF(UFSHostDevice, "UFSDevice is still draining; with %d active"
|
|
" doorbells\n", activeDoorbells);
|
|
} else {
|
|
DPRINTF(UFSHostDevice, "UFSDevice is done draining\n");
|
|
signalDrainDone();
|
|
}
|
|
}
|