diff options
Diffstat (limited to 'src/dev/arm')
-rw-r--r-- | src/dev/arm/SConscript | 3 | ||||
-rw-r--r-- | src/dev/arm/UFSHostDevice.py | 66 | ||||
-rw-r--r-- | src/dev/arm/ufs_device.cc | 2363 | ||||
-rw-r--r-- | src/dev/arm/ufs_device.hh | 1230 |
4 files changed, 3662 insertions, 0 deletions
diff --git a/src/dev/arm/SConscript b/src/dev/arm/SConscript index 09ee7b4e5..06398ed15 100644 --- a/src/dev/arm/SConscript +++ b/src/dev/arm/SConscript @@ -44,6 +44,7 @@ if env['TARGET_ISA'] == 'arm': SimObject('FlashDevice.py') SimObject('Gic.py') SimObject('RealView.py') + SimObject('UFSHostDevice.py') SimObject('EnergyCtrl.py') Source('a9scu.cc') @@ -64,6 +65,7 @@ if env['TARGET_ISA'] == 'arm': Source('rtc_pl031.cc') Source('timer_cpulocal.cc') Source('vgic.cc') + Source('ufs_device.cc') Source('energy_ctrl.cc') DebugFlag('AMBA') @@ -75,4 +77,5 @@ if env['TARGET_ISA'] == 'arm': DebugFlag('GIC') DebugFlag('RVCTRL') DebugFlag('EnergyCtrl') + DebugFlag('UFSHostDevice') DebugFlag('VGIC') diff --git a/src/dev/arm/UFSHostDevice.py b/src/dev/arm/UFSHostDevice.py new file mode 100644 index 000000000..f9369ebc9 --- /dev/null +++ b/src/dev/arm/UFSHostDevice.py @@ -0,0 +1,66 @@ +# 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. +# +# Authors: Rene de Jong +# +import sys +from m5.params import * +from m5.proxy import * +from Device import DmaDevice +from AbstractNVM import * + +class UFSHostDevice(DmaDevice): + type = 'UFSHostDevice' + cxx_header = "dev/arm/ufs_device.hh" + pio_addr = Param.Addr("Address for SCSI configuration slave interface") + pio_latency = Param.Latency("10ns", "Time between action and write/read \ + result by AMBA DMA Device") + gic = Param.BaseGic(Parent.any, "Gic to use for interrupting") + int_num = Param.UInt32("Interrupt number that connects to GIC") + img_blk_size = Param.UInt32(512, "Size of one image block in bytes") + # Every image that is added to the vector will generate a new logic unit + # in the UFS device; Theoretically (when using the driver from Linux + # kernel 3.9 onwards), this can be as many as eigth. Up to two have been + # tested. + image = VectorParam.DiskImage("Disk images") + # Every logic unit can have its own flash dimensions. So the number of + # images that have been provided in the image vector, should be equal to + # the number of flash objects that are created. Each logic unit can have + # its own flash dimensions; to allow the system to define a hetrogeneous + # storage system. + internalflash = VectorParam.AbstractNVM("Describes the internal flash") + ufs_slots = Param.UInt32(32, "Number of commands that can be queued in \ + the Host controller (min: 1, max: 32)") + diff --git a/src/dev/arm/ufs_device.cc b/src/dev/arm/ufs_device.cc new file mode 100644 index 000000000..d271656cd --- /dev/null +++ b/src/dev/arm/ufs_device.cc @@ -0,0 +1,2363 @@ +/* + * 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. + * + * Authors: Rene de Jong + */ + +/** @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, Callback *transfer_cb, + 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 = new MakeCallback<UFSSCSIDevice, + &UFSHostDevice::UFSSCSIDevice::readCallback>(this); + deviceReadCallback = read_cb; + memWriteCallback = new MakeCallback<UFSSCSIDevice, + &UFSHostDevice::UFSSCSIDevice::SSDWriteDone>(this); + + /** + * 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; + memset(&scsi_out, 0, sizeof(struct SCSIReply)); + + /** + * 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), + drainManager(NULL), + SCSIResumeEvent(this), + UTPEvent(this) +{ + DPRINTF(UFSHostDevice, "The hostcontroller hosts %d Logic units\n", + lunAvail); + UFSDevice.resize(lunAvail); + + transferDoneCallback = new MakeCallback<UFSHostDevice, + &UFSHostDevice::LUNSignal>(this); + memReadCallback = new MakeCallback<UFSHostDevice, + &UFSHostDevice::readCallback>(this); + + for(int count = 0; count < lunAvail; count++) { + UFSDevice[count] = new UFSSCSIDevice(p, count, transferDoneCallback, + memReadCallback); + } + + 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(); +} + +/** + * Create the parameters of this device + */ + +UFSHostDevice* +UFSHostDeviceParams::create() +{ + return new UFSHostDevice(this); +} + + +void +UFSHostDevice::regStats() +{ + using namespace Stats; + + std::string UFSHost_name = name() + ".UFSDiskHost"; + + // Register the stats + /** Queue lengths */ + stats.currentSCSIQueue + .name(UFSHost_name + ".currentSCSIQueue") + .desc("Most up to date length of the command queue") + .flags(none); + stats.currentReadSSDQueue + .name(UFSHost_name + ".currentReadSSDQueue") + .desc("Most up to date length of the read SSD queue") + .flags(none); + stats.currentWriteSSDQueue + .name(UFSHost_name + ".currentWriteSSDQueue") + .desc("Most up to date length of the write SSD queue") + .flags(none); + + /** Amount of data read/written */ + stats.totalReadSSD + .name(UFSHost_name + ".totalReadSSD") + .desc("Number of bytes read from SSD") + .flags(none); + + stats.totalWrittenSSD + .name(UFSHost_name + ".totalWrittenSSD") + .desc("Number of bytes written to SSD") + .flags(none); + + stats.totalReadDiskTransactions + .name(UFSHost_name + ".totalReadDiskTransactions") + .desc("Number of transactions from disk") + .flags(none); + stats.totalWriteDiskTransactions + .name(UFSHost_name + ".totalWriteDiskTransactions") + .desc("Number of transactions to disk") + .flags(none); + stats.totalReadUFSTransactions + .name(UFSHost_name + ".totalReadUFSTransactions") + .desc("Number of transactions from device") + .flags(none); + stats.totalWriteUFSTransactions + .name(UFSHost_name + ".totalWriteUFSTransactions") + .desc("Number of transactions to device") + .flags(none); + + /** Average bandwidth for reads and writes */ + stats.averageReadSSDBW + .name(UFSHost_name + ".averageReadSSDBandwidth") + .desc("Average read bandwidth (bytes/s)") + .flags(nozero); + + stats.averageReadSSDBW = stats.totalReadSSD / simSeconds; + + stats.averageWriteSSDBW + .name(UFSHost_name + ".averageWriteSSDBandwidth") + .desc("Average write bandwidth (bytes/s)") + .flags(nozero); + + stats.averageWriteSSDBW = stats.totalWrittenSSD / simSeconds; + + stats.averageSCSIQueue + .name(UFSHost_name + ".averageSCSIQueueLength") + .desc("Average command queue length") + .flags(nozero); + stats.averageReadSSDQueue + .name(UFSHost_name + ".averageReadSSDQueueLength") + .desc("Average read queue length") + .flags(nozero); + stats.averageWriteSSDQueue + .name(UFSHost_name + ".averageWriteSSDQueueLength") + .desc("Average write queue length") + .flags(nozero); + + /** Number of doorbells rung*/ + stats.curDoorbell + .name(UFSHost_name + ".curDoorbell") + .desc("Most up to date number of doorbells used") + .flags(none); + + stats.curDoorbell = activeDoorbells; + + stats.maxDoorbell + .name(UFSHost_name + ".maxDoorbell") + .desc("Maximum number of doorbells utilized") + .flags(none); + stats.averageDoorbell + .name(UFSHost_name + ".averageDoorbell") + .desc("Average number of Doorbells used") + .flags(nozero); + + /** Latency*/ + stats.transactionLatency + .init(100) + .name(UFSHost_name + ".transactionLatency") + .desc("Histogram of transaction times") + .flags(pdf); + + stats.idleTimes + .init(100) + .name(UFSHost_name + ".idlePeriods") + .desc("Histogram of idle times") + .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->set<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) +{ + uint32_t data = 0; + + switch (pkt->getSize()) { + + case 1: + data = pkt->get<uint8_t>(); + break; + + case 2: + data = pkt->get<uint16_t>(); + break; + + case 4: + data = pkt->get<uint32_t>(); + break; + + default: + panic("Undefined UFSHCD controller write size!\n"); + break; + } + + 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(this); + 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(this); + + 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); + + /**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); + + 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(this); + 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->process(); + } + +} + +/** + * 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(this); + 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->process(); + } + +} + +/** + * 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->process(); + + //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(this); + + //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(std::ostream &os) +{ + DmaDevice::serialize(os); + + uint8_t* temp_HCI_mem = reinterpret_cast<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(Checkpoint *cp, const std::string §ion) +{ + DmaDevice::unserialize(cp, section); + 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 + */ + +unsigned int +UFSHostDevice::drain(DrainManager *dm) +{ + unsigned int count = 0; + + // check pio, dma port, and doorbells + count = pioPort.drain(dm) + dmaPort.drain(dm); + + if (UFSHCIMem.TRUTRLDBR) { + count += 1; + drainManager = dm; + } else { + DPRINTF(UFSHostDevice, "UFSHostDevice in drained state\n"); + } + + if (count) { + DPRINTF(UFSHostDevice, "UFSDevice is draining...\n"); + setDrainState(Drainable::Draining); + } else { + DPRINTF(UFSHostDevice, "UFSDevice drained\n"); + setDrainState(Drainable::Drained); + } + return count; +} + +/** + * Checkdrain; needed to enable checkpoints + */ + +void +UFSHostDevice::checkDrain() +{ + if (drainManager == NULL) { + return; + } + + if (UFSHCIMem.TRUTRLDBR) { + DPRINTF(UFSHostDevice, "UFSDevice is still draining; with %d active" + " doorbells\n", activeDoorbells); + } else { + DPRINTF(UFSHostDevice, "UFSDevice is done draining\n"); + drainManager->signalDrainDone(); + drainManager = NULL; + } +} diff --git a/src/dev/arm/ufs_device.hh b/src/dev/arm/ufs_device.hh new file mode 100644 index 000000000..07f038175 --- /dev/null +++ b/src/dev/arm/ufs_device.hh @@ -0,0 +1,1230 @@ +/* + * 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. + * + * Authors: Rene de Jong + */ + + +/** @file + * This is a base class for UFS devices + * The UFS interface consists out of one host controller which connects a + * number of devices which together contain up to 8 logic units. A Logical + * Unit is an externally addressable, independent, processing entity that + * processes SCSI tasks (commands) and performs task management functions. + * The decision has been made to abstract the device away, and model the + * different logic units. Effectively this means that there is one Host layer + * which handles all the UFS commands (everything UTP, UPIU and UNIpro + * related) and a SCSI layer, which handles the SCSI interpretation and the + * interaction with the "disk" (effectively the LBA that that particular + * Logic Unit controls). Each Logic unit should therefor control a disk image + * and a timing model. The UFS protocol has three types of commands + * (explained later). Each has different phases and each phase need to wait + * for its particular data. This is the reason that this model contains a lot + * of events. For clarity, a state diagram in doxygen has been provided. To + * fully apreciate the stages that the model flows through, the states have + * been broken into three state diagrams. It is best if one imagines the + * command flow state machine to be happening in the UFSHost layer, and the + * other two to flow through all the layers of the model (UFS host, SCSI and + * NVM model). See it as a quarry, one state diagram is moving the crane into + * place, and the other ones are transporting the dirt down, or the valuables + * up. For complete information about the working of UFS please refer to + * http://www.jedec.org/standards-documents/results/jesd220 or + * http://www.jedec.org/standards-documents/results/jesd223 + * The documents are available free of charge, although you will need to have + * an acount. + */ + +/** UFS command flow state machine + *digraph CommandFlow{ + node [fontsize=10]; + IDLE -> transferHandler + [ label=" transfer/task/command request " fontsize=6]; + transferHandler -> command + [ label=" It is a command " fontsize=6]; + command -> IDLE + [ label=" Command done, no further action " fontsize=6]; + transferHandler -> taskStart + [ label=" It is a task " fontsize=6]; + taskStart -> finalUTP + [ label=" Task handled, now acknowledge (UFS) " fontsize=6]; + transferHandler -> transferStart + [ label=" It is a transfer " fontsize=6]; + transferStart -> SCSIResume + [ label=" Transfer, obtain the specific command " fontsize=6]; + SCSIResume -> DiskDataFlowPhase + [ label=" Disk data transfer (see other graphs) " fontsize=6]; + SCSIResume -> DeviceDataPhase + [ label=" Device info transfer (handled in SCSIResume) " + fontsize=6]; + DiskDataFlowPhase -> transferDone + [ label=" Transfer done, acknowledge SCSI command " fontsize=6]; + DeviceDataPhase -> transferDone + [ label=" Transfer done, acknowledge SCSI command " fontsize=6]; + transferDone -> finalUTP + [ label=" Transfer handled, now acknowledge (UFS) " fontsize=6]; + finalUTP -> readDone + [ label=" All handled, clear data structures " fontsize=6]; + readDone -> IDLE + [ label=" All handled, nothing outstanding " fontsize=6]; + readDone -> transferHandler + [ label=" All handled, handle next outstanding " fontsize=6]; + } + */ +/** UFS read transaction flow state machine + digraph readFlow{ + node [fontsize=10]; + getScatterGather -> commitReadFromDisk + [ label=" Put the information about the data transfer to the disk " + fontsize=6]; + commitReadFromDisk -> waitForReads + [ label=" Push the reads to the flashmodel and wait for callbacks " + fontsize=6]; + waitForReads -> pushToDMA + [ label=" Push to the DMA and wait for them to finish " fontsize=6]; + pushToDMA -> waitForReads + [ label=" Wait for the next disk event " fontsize=6]; + pushToDMA -> waitForDMA + [ label=" Wait for the last DMA transfer to finish " fontsize=6]; + waitForDMA -> finishTransfer + [ label=" Continue with the command flow " fontsize=6]; + } + */ +/** UFS write transaction flow state machine + digraph WriteFlow{ + node [fontsize=10]; + getScatterGather -> getFromDMA + [ label=" Put the transfer information to the DMA " fontsize=6]; + getFromDMA -> waitForDMA + [ label=" Wait for dma actions to arrive " fontsize=6]; + waitForDMA -> pushToDisk + [ label=" Push arrived DMA to disk " fontsize=6]; + pushToDisk -> waitForDMA + [ label=" Wait for next DMA action " fontsize=6]; + pushToDisk -> waitForDisk + [ label=" All DMA actions are done, wait for disk " fontsize=6]; + waitForDisk -> finishTransfer + [ label=" All transactions are done , continue the command flow " + fontsize=6]; + } + */ + +#ifndef __DEV_ARM_UFS_DEVICE_HH__ +#define __DEV_ARM_UFS_DEVICE_HH__ + +#include <deque> + +#include "base/addr_range.hh" +#include "base/bitfield.hh" +#include "base/statistics.hh" +#include "debug/UFSHostDevice.hh" +#include "dev/arm/abstract_nvm.hh" +#include "dev/arm/base_gic.hh" +#include "dev/disk_image.hh" +#include "dev/dma_device.hh" +#include "dev/io_device.hh" +#include "mem/packet.hh" +#include "mem/packet_access.hh" +#include "params/UFSHostDevice.hh" +#include "sim/serialize.hh" +#include "sim/stats.hh" + +/** + * Host controller layer: This is your Host controller + * This layer handles the UFS functionality. + * It tracks all the different transaction stages and uses + * the device layer and the flash layer to determine the transaction flow. + */ +class UFSHostDevice : public DmaDevice +{ + public: + + UFSHostDevice(const UFSHostDeviceParams* p); + + unsigned int drain(DrainManager *dm); + void checkDrain(); + void serialize(std::ostream &os); + void unserialize(Checkpoint *cp, const std::string §ion); + + private: + /** + * Host Controller Interface + * This is a set of registers that allow the driver to control the + * transactions to the flash devices. + * As defined in: + * http://www.jedec.org/standards-documents/results/jesd223 + */ + struct HCIMem { + /** + * Specify the host capabilities + */ + uint32_t HCCAP; + uint32_t HCversion; + uint32_t HCHCDDID; + uint32_t HCHCPMID; + + /** + * Operation and runtime registers + */ + uint32_t ORInterruptStatus; + uint32_t ORInterruptEnable; + uint32_t ORHostControllerStatus; + uint32_t ORHostControllerEnable; + uint32_t ORUECPA; + uint32_t ORUECDL; + uint32_t ORUECN; + uint32_t ORUECT; + uint32_t ORUECDME; + uint32_t ORUTRIACR; + + /** + * vendor specific register + */ + uint32_t vendorSpecific; + + /** + * Transfer control registers + */ + uint32_t TRUTRLBA; + uint32_t TRUTRLBAU; + uint32_t TRUTRLDBR; + uint32_t TRUTRLCLR; + uint32_t TRUTRLRSR; + + /** + * Task control registers + */ + uint32_t TMUTMRLBA; + uint32_t TMUTMRLBAU; + uint32_t TMUTMRLDBR; + uint32_t TMUTMRLCLR; + uint32_t TMUTMRLRSR; + + /** + * Command registers + */ + uint32_t CMDUICCMDR; + uint32_t CMDUCMDARG1; + uint32_t CMDUCMDARG2; + uint32_t CMDUCMDARG3; + }; + + /** + * All the data structures are defined in the UFS standard + * This standard be found at the JEDEC website free of charge + * (login required): + * http://www.jedec.org/standards-documents/results/jesd220 + */ + + /** + * struct UTPUPIUHeader - UPIU header structure + * dWord0: UPIU header DW-0 + * dWord1: UPIU header DW-1 + * dWord2: UPIU header DW-2 + */ + struct UTPUPIUHeader { + uint32_t dWord0; + uint32_t dWord1; + uint32_t dWord2; + }; + + /** + * struct UTPUPIURSP - Response UPIU structure + * header: UPIU header DW-0 to DW-2 + * residualTransferCount: Residual transfer count DW-3 + * reserved: Reserved DW-4 to DW-7 + * senseDataLen: Sense data length DW-8 U16 + * senseData: Sense data field DW-8 to DW-12 + */ + struct UTPUPIURSP { + struct UTPUPIUHeader header; + uint32_t residualTransferCount; + uint32_t reserved[4]; + uint16_t senseDataLen; + uint8_t senseData[18]; + }; + + /** + * struct UTPUPIUTaskReq - Task request UPIU structure + * header - UPIU header structure DW0 to DW-2 + * inputParam1: Input param 1 DW-3 + * inputParam2: Input param 2 DW-4 + * inputParam3: Input param 3 DW-5 + * reserved: Reserver DW-6 to DW-7 + */ + struct UTPUPIUTaskReq { + struct UTPUPIUHeader header; + uint32_t inputParam1; + uint32_t inputParam2; + uint32_t inputParam3; + uint32_t reserved[2]; + }; + + /** + * struct UFSHCDSGEntry - UFSHCI PRD Entry + * baseAddr: Lower 32bit physical address DW-0 + * upperAddr: Upper 32bit physical address DW-1 + * reserved: Reserved for future use DW-2 + * size: size of physical segment DW-3 + */ + struct UFSHCDSGEntry { + uint32_t baseAddr; + uint32_t upperAddr; + uint32_t reserved; + uint32_t size; + }; + + /** + * struct UTPTransferCMDDesc - UFS Commad Descriptor structure + * commandUPIU: Command UPIU Frame address + * responseUPIU: Response UPIU Frame address + * PRDTable: Physcial Region Descriptor + * All lengths as defined by JEDEC220 + */ + struct UTPTransferCMDDesc { + uint8_t commandUPIU[128]; + uint8_t responseUPIU[128]; + struct UFSHCDSGEntry PRDTable[128]; + }; + + /** + * UPIU tranfer message. + */ + struct UPIUMessage { + struct UTPUPIUHeader header; + uint32_t dataOffset; + uint32_t dataCount; + std::vector<uint32_t> dataMsg; + }; + + /** + * struct UTPTransferReqDesc - UTRD structure + * header: UTRD header DW-0 to DW-3 + * commandDescBaseAddrLo: UCD base address low DW-4 + * commandDescBaseAddrHi: UCD base address high DW-5 + * responseUPIULength: response UPIU length DW-6 + * responseUPIUOffset: response UPIU offset DW-6 + * PRDTableLength: Physical region descriptor length DW-7 + * PRDTableOffset: Physical region descriptor offset DW-7 + */ + struct UTPTransferReqDesc { + + /** + * struct RequestDescHeader + * dword0: Descriptor Header DW0 + * dword1: Descriptor Header DW1 + * dword2: Descriptor Header DW2 + * dword3: Descriptor Header DW3 + */ + struct RequestDescHeader { + uint32_t dWord0; + uint32_t dWord1; + uint32_t dWord2; + uint32_t dWord3; + } header; + + /* DW 4-5*/ + uint32_t commandDescBaseAddrLo; + uint32_t commandDescBaseAddrHi; + + /* DW 6 */ + uint16_t responseUPIULength; + uint16_t responseUPIUOffset; + + /* DW 7 */ + uint16_t PRDTableLength; + uint16_t PRDTableOffset; + }; + + /** + * SCSI reply structure. In here is all the information that is needed to + * build a SCSI reply. + */ + struct SCSIReply { + uint8_t status; + uint32_t msgSize; + uint8_t LUN; + struct UPIUMessage message; + uint8_t senseSize; + uint8_t expectMore;//0x01 is for writes, 0x02 is for reads + uint64_t offset; + uint8_t senseCode[19]; + }; + + /** + * Logic unit information structure. SCSI requires information of each LUN. + * This structure is defined in the SCSI standard, and can also be found in + * the UFS standard. http://www.jedec.org/standards-documents/results/jesd220 + */ + struct LUNInfo { + uint32_t dWord0; + uint32_t dWord1; + uint32_t vendor0; + uint32_t vendor1; + uint32_t product0; + uint32_t product1; + uint32_t product2; + uint32_t product3; + uint32_t productRevision; + }; + + /** + * Different events, and scenarios require different types of information. + * Keep in mind that for a read-from-disk transaction the host at first a + * datastructure fetches to determine where and what the command is, then the + * command fetches and the structure fetches to determine where the + * different read transactions should be placed and then transfers all the + * read fragments. It then answers to the original caller with two replies, + * one for the command, and one for UFS. Each of these stages trigger a + * different event, and each event needs to know what happened in the + * previous stage and what is going to happen in the current one. This + * happens also for writes, SCSI maintanance, UFS maintanance, command + * management and task management. + */ + + /** + * Transfer information. + * @filePointer this does not point to a file, but to a position on the disk + * image (which is from the software systems perspective a position in a file) + */ + struct transferInfo { + std::vector <uint8_t> buffer; + uint32_t size; + uint64_t offset; + uint32_t filePointer; + uint32_t lunID; + }; + + /** + * transfer completion info. + * This information is needed by transferDone to finish the transfer. + */ + struct transferDoneInfo { + Addr responseStartAddr; + uint32_t reqPos; + struct UTPUPIURSP requestOut; + uint32_t size; + Addr address; + uint8_t *destination; + bool finished; + uint32_t lunID; + }; + + /** + * Transfer start information. + */ + struct transferStart { + struct UTPTransferReqDesc* destination; + uint32_t mask; + Addr address; + uint32_t size; + uint32_t done; + uint32_t lun_id; + }; + + /** + * Task start information. This is for the device, so no lun id needed. + */ + struct taskStart { + struct UTPUPIUTaskReq destination; + uint32_t mask; + Addr address; + uint32_t size; + bool done; + }; + + /** + * After a SCSI command has been identified, the SCSI resume function will + * handle it. This information will provide context information. + */ + struct SCSIResumeInfo { + struct UTPTransferReqDesc* RequestIn; + int reqPos; + Addr finalAddress; + uint32_t finalSize; + std::vector <uint8_t> destination; + uint32_t done; + }; + + /** + * Disk transfer burst information. Needed to allow communication between the + * disk transactions and dma transactions. + */ + struct writeToDiskBurst { + Addr start; + uint64_t SCSIDiskOffset; + uint32_t size; + uint32_t LUN; + }; + + /** + * Statistics + */ + struct UFSHostDeviceStats { + /** Queue lengths */ + Stats::Scalar currentSCSIQueue; + Stats::Scalar currentReadSSDQueue; + Stats::Scalar currentWriteSSDQueue; + + /** Amount of data read/written */ + Stats::Scalar totalReadSSD; + Stats::Scalar totalWrittenSSD; + Stats::Scalar totalReadDiskTransactions; + Stats::Scalar totalWriteDiskTransactions; + Stats::Scalar totalReadUFSTransactions; + Stats::Scalar totalWriteUFSTransactions; + + /** Average bandwidth for reads and writes */ + Stats::Formula averageReadSSDBW; + Stats::Formula averageWriteSSDBW; + + /** Average Queue lengths*/ + Stats::Average averageSCSIQueue; + Stats::Average averageReadSSDQueue; + Stats::Average averageWriteSSDQueue; + + /** Number of doorbells rung*/ + Stats::Formula curDoorbell; + Stats::Scalar maxDoorbell; + Stats::Average averageDoorbell; + + /** Histogram of latencies*/ + Stats::Histogram transactionLatency; + Stats::Histogram idleTimes; + }; + + /** + * device layer: This is your Logic unit + * This layer implements the SCSI functionality of the UFS Device + * One logic unit controls one or more disk partitions + */ + class UFSSCSIDevice: SimObject + { + public: + /** + * Constructor and destructor + */ + UFSSCSIDevice(const UFSHostDeviceParams* p, uint32_t lun_id, Callback* + transfer_cb, Callback *read_cb); + ~UFSSCSIDevice(); + + /** + * SCSI command handle function; determines what the command is and + * returns a reply structure that allows the host device to continue + * with the transfer. + */ + struct SCSIReply SCSICMDHandle(uint32_t* SCSI_msg); + + /** + * Disk access functions. These will transfer the data from/to the disk + */ + + /** + * Read flash. read the data from the disk image. This function + * doesn't model timing behaviour + */ + void readFlash(uint8_t* readaddr, uint64_t offset, uint32_t size); + + /** + * Write flash. write the data to the disk image. This function + * doesn't model timing behaviour + */ + void writeFlash(uint8_t* writeaddr, uint64_t offset, uint32_t size); + + /** + * finished command. Probe to find out wether this logic unit + * finished its transfer and triggered the callback; The host needs + * this to handle the final part of the transaction. + */ + bool finishedCommand() const {return transferCompleted;}; + + /** + * Clear signal. Handle for the host to clear the transfer complete + * signal. + */ + void clearSignal() {transferCompleted = false;}; + + /** + * Finished read. Probe to find out which logic unit finished its + * read. This is needed, because multiple units can do transactions + * at the same time, and need to push back data at the right time in + * the right order. (because writes work the other way round, they do + * not need this mechanism) + */ + bool finishedRead() const {return readCompleted;}; + + /** + * Clear signal. Handle for the host to clear the read complete + * signal. + */ + void clearReadSignal() {readCompleted = false;}; + + /** + * Start the transactions to (and from) the disk + * The host will queue all the transactions. Once the next phase + * commences, this function should be started. + */ + void SSDReadStart(uint32_t total_read); + void SSDWriteStart(); + + /** + * Sets total amount of write transactions that needs to be made. + * First they need to be fetched via DMA, so this value is needed in + * a later stage. + */ + void setTotalWrite(uint32_t total_write) {totalWrite = total_write;}; + + /** + * End of transfer information + */ + transferDoneInfo transferInfo; + + /** + * Information message queues, as there can be multiple messages + * queued for handling in this system. These are the main + * communication interfaces between the Host and the device layers + */ + + /** + * SCSIInfoQueue: each LU handles its own SCSI commands. + */ + std::deque<struct SCSIResumeInfo> SCSIInfoQueue; + + /** + * SSDReadInfo: Structure from disk to dma, that contains data, and + * helper info to get it to the right place in the memory. + */ + std::deque<struct transferInfo> SSDReadInfo; + + /** + * SSDWriteDoneInfo: Structure from dma to disk, that contains data, + * and helper info to get it to the right place in the memory. + * The done is added because it is going to the last phase of the + * write transfer. + */ + std::deque<struct transferInfo> SSDWriteDoneInfo; + + private: + /** + * Functions to indicate that the action to the SSD has completed. + */ + /** + * Read Call back; This is the callback function for the memory model + */ + void readCallback(); + + /** + * SSD Read done; Determines if the final callback of the transaction + * should be made at the end of a read transfer. + */ + void SSDReadDone(); + + /** + * SSD Write Done; This is the callback function for the memory model. + */ + void SSDWriteDone(); + + /** + * Status of SCSI. This may be linked to a status check in the future. + * For now it (mainly) fills a data structure with sense information + * for a successfull transaction + */ + void statusCheck(uint8_t status, uint8_t* sensecodelist); + + /** + * set signal to indicate that the transaction has been completed. + */ + void setSignal() {transferCompleted = true;}; + + /** + * set signal to indicate that the read action has been completed + */ + void setReadSignal() {readCompleted = true;}; + + /** + * The objects this model links to. + * 1: the disk data model + * 2: the memory timing model + */ + DiskImage* flashDisk; + AbstractNVM* flashDevice; + + /** + * Logic unit dimensions + */ + const uint32_t blkSize; + const uint32_t lunAvail; + const uint64_t diskSize; + const uint32_t capacityLower; + const uint32_t capacityUpper; + + /** + * Logic unit info; needed for SCSI Info messages and LU + * identification + */ + struct LUNInfo lunInfo; + const uint32_t lunID; + + /** + * Signals to Host layer + * 1: signal for transaction completion + * 2: signal for read action completion + */ + bool transferCompleted; + bool readCompleted; + + /** + * Total amount transactions that need to be made + */ + uint32_t totalRead; + uint32_t totalWrite; + + /** + * transaction progress tracking + */ + uint32_t amountOfWriteTransfers; + uint32_t amountOfReadTransfers; + + /** + * Callbacks between Host and Device + */ + Callback* signalDone; + Callback* deviceReadCallback; + + /** + * Callbacks between Device and Memory + */ + Callback* memReadCallback; + Callback* memWriteCallback; + + /* + * Default response header layout. For more information refer to + * chapter 7 http://www.jedec.org/standards-documents/results/jesd220 + */ + static const unsigned int UPIUHeaderDataIndWord0 = 0x0000C022; + static const unsigned int UPIUHeaderDataIndWord1 = 0x00000000; + static const unsigned int UPIUHeaderDataIndWord2 = 0x40000000; + + /* + * SCSI mode pages values assigned in ufs_device.cc + * The mode pages give device specific information via the SCSI + * protocol. They are defined in + * http://www.jedec.org/standards-documents/results/jesd220 + */ + static const unsigned int controlPage[3]; + static const unsigned int recoveryPage[3]; + static const unsigned int cachingPage[5]; + + /* + * SCSI command set; defined in + * http://www.jedec.org/standards-documents/results/jesd220 + */ + enum SCSICommandSet { + SCSIInquiry = 0x12, + SCSIRead6 = 0x08, + SCSIRead10 = 0x28, + SCSIRead16 = 0x88, + SCSIReadCapacity10 = 0x25, + SCSIReadCapacity16 = 0x9E, + SCSIReportLUNs = 0xA0, + SCSIStartStop = 0x1B, + SCSITestUnitReady = 0x00, + SCSIVerify10 = 0x2F, + SCSIWrite6 = 0x0A, + SCSIWrite10 = 0x2A, + SCSIWrite16 = 0x8A, + SCSIFormatUnit = 0x04, + SCSISendDiagnostic = 0x1D, + SCSISynchronizeCache = 0x35, + //UFS SCSI additional command set for full functionality + SCSIModeSelect10 = 0x55, + SCSIModeSense6 = 0x1A, + SCSIModeSense10 = 0x5A, + SCSIRequestSense = 0x03, + SCSIUnmap = 0x42, + SCSIWriteBuffer = 0x3B, + SCSIReadBuffer = 0x3C, + //SCSI commands not supported by UFS; but Linux send them anyway + SCSIMaintenanceIn = 0xA3 + }; + + /* + * SCSI status codes; defined in + * http://www.jedec.org/standards-documents/results/jesd220 + */ + enum SCSIStatusCodes { + SCSIGood = 0x00, + SCSICheckCondition = 0x02, + SCSIConditionGood = 0x04, + SCSIBusy = 0x08, + SCSIIntermediateGood = 0x10, + SCSIIntermediatCGood = 0x14, + SCSIReservationConflict = 0x18, + SCSICommandTerminated = 0x22, + SCSITaskSetFull = 0x28, + SCSIACAActive = 0x30, + SCSITaskAborted = 0x40 + }; + + /* + * SCSI sense codes; defined in + * http://www.jedec.org/standards-documents/results/jesd220 + */ + enum SCSISenseCodes { + SCSINoSense = 0x00, + SCSIRecoverdError = 0x01, + SCSINotReady = 0x02, + SCSIMediumError = 0x03, + SCSIHardwareError = 0x04, + SCSIIllegalRequest = 0x05, + SCSIUnitAttention = 0x06, + SCSIDataProtect = 0x07, + SCSIBlankCheck = 0x08, + SCSIAbortedCommand = 0x0B, + SCSIVolumeOverflow = 0x0D, + SCSIMisCompare = 0x0E + }; + + }; + + //All access functions are inherrited; no need to make them public + /** + * Address range functions + */ + AddrRangeList getAddrRanges() const; + + /** + * register access functions + */ + Tick read(PacketPtr pkt); + Tick write(PacketPtr pkt); + // end of access functions + + /** + * Initialization function. Sets the sefault HCI register values + */ + void setValues(); + + /** + * Handler functions. Each function handles a different stage of the + * transfer. Note that the UFS protocol specifies three types of messages + * to the host (and devices): + * 1: Command (to Host specifically) + * 2: Task (to device; to control flow, not for data) + * 3: Transfer (to device; to transfer data) + */ + /** + * request handler. This function finds the cause of the request and + * triggers the right follow-up action (command handler, task handler, + * or transferhandler) + */ + void requestHandler(); + + /** + * Command handler function. Handles the command send to the Host + * controller + */ + void commandHandler(); + + /** + * Task Start function. Starts the task handler once the task data + * structure has arrived + */ + void taskStart(); + + /** + * Task handler function. Handles the tasks send to the devices + * because there are not many tasks implemented yet this is kept in the + * Host controller layer + */ + void taskHandler(struct UTPUPIUTaskReq* request_in, + uint32_t req_pos, Addr finaladdress, uint32_t finalsize); + + /** + * Transfer Start function. Starts the transfer handler once the transfer + * data structure has arrived + */ + void transferStart(); + + /** + * Transfer handler function. Handles the transfers send to the devices + * Important to understand here is that a Logic unit is not a device (a + * device can contain multiple logic units). This function analyses the + * first data structure that has been transfered. Which will tell the + * host to expect SCSI frames for the rest of the transaction. Note that + * the host has no indication whatsoever which LU to address. That will + * follow in the next transaction. + */ + void transferHandler(struct UTPTransferReqDesc* + request_in, int req_pos, Addr finaladdress, + uint32_t finalsize, uint32_t done); + + /** + * Transfer SCSI function. Determines which Logic unit to address and + * starts the SCSI resume function + */ + void SCSIStart(); + + /** + * Starts the scsi handling function in the apropriate Logic unit, + * prepares the right data transfer scheme and kicks it off. + */ + void SCSIResume(uint32_t lun_id); + + /** + * LU callback function to indicate that the action has completed. + */ + void LUNSignal(); + + /** + * transfer done, the beginning of the final stage of the transfer. + * Acknowledges UPIU frame and prepares the UTP response frame + */ + void 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); + /** + * final UTP, sends the last acknowledge data structure to the system; + * prepares the clean up functions. + */ + void finalUTP(); + + /** + * Interrupt control functions + */ + void clearInterrupt(); + void generateInterrupt(); + + /** + * DMA transfer functions + * These allow the host to push/pull the data to the memory + * The provided event indicates what the next phase it that will handle + * the obtained data, or what the follow up action is once the data has + * been pushed to the memory + */ + void writeDevice(Event* additional_action, bool toDisk, Addr start, + int size, uint8_t* destination, uint64_t SCSIDiskOffset, + uint32_t lun_id); + void readDevice(bool lastTransfer, Addr SCSIStart, uint32_t SCSISize, + uint8_t* SCSIDestination, bool no_cache, + Event* additional_action); + + /** + * Disk transfer management functions + * these set up the queues, and initiated them, leading to the data + * transaction timing model based on the scatter gather list constructed + * in SCSIresume. + */ + void manageWriteTransfer(uint8_t LUN, uint64_t offset, uint32_t + sg_table_length, struct UFSHCDSGEntry* sglist); + void manageReadTransfer(uint32_t size, uint32_t LUN, uint64_t offset, + uint32_t sg_table_length, + struct UFSHCDSGEntry* sglist); + + /** + * Read done + * Started at the end of a transaction after the last read action. Cleans + * up UTP descriptor and other remaining data structures. It also raises + * the interrupt. + */ + void readDone(); + + /** + * Write done + * After a DMA write with data intended for the disk, this function is + * called. It ensures that the disk image is modified, and that the + * correct timing function is triggered. + */ + void writeDone(); + + /** + * Read callback + * Call back function for the logic units to indicate the completion of + * a read action. Note that this is needed because the read functionality + * needs to push data structures back to the memory. + */ + void readCallback(); + + /** + * Read garbage + * A read from disk data structure can vary in size and is therefor + * allocated on creation. It can only be destroyed once that particular + * read action has completed. This function is called on completion of a + * read from disk action to handle this. + */ + void readGarbage(); + + /**register statistics*/ + void regStats(); + + /** + * Host controller information + */ + const Addr pioAddr; + const Addr pioSize; + const Tick pioDelay; + const int intNum; + BaseGic* gic; + const uint32_t lunAvail; + const uint8_t UFSSlots; + + /** + * Host controller memory + */ + HCIMem UFSHCIMem; + + /** + * Track number of DMA transactions in progress + */ + int readPendingNum; + int writePendingNum; + + /** + * Statistics helper variables + * Active doorbells indicates how many doorbells are in teh process of + * being handled. + * Pending doorbells have been handled and are waiting to be acknowledged + * by the host system. + * The doorbell register is 32 bits wide, so one byte is enough to keep + * track of the numbers + */ + uint8_t activeDoorbells; + uint8_t pendingDoorbells; + + /** + * interrupt verification + * This keeps track of the number of interrupts generated. It is usefull + * for debug purposes. Make sure that the implemented driver prints the + * number of interrupts it has handled so far to fully benefit from this + * feature. + */ + uint32_t countInt; + + /** + * Track the transfer + * This is allows the driver to "group" certain transfers together by + * using a tag in the UPIU. The messages with the same tag should be + * handled together, i.e. their doorbells should be cleared when they are + * all done. but we need to keep track of the ones we already handled, + * this integer shadows the doorbells to allow this behaviour. + */ + uint32_t transferTrack; + uint32_t taskCommandTrack; + + /** + * Helper for latency stats + * These variables keep track of the latency for every doorbell. + * Eventually the latenmcies will be put in a histogram. + */ + Tick transactionStart[32]; + Tick idlePhaseStart; + + /** + * drain manager + * Needed to be able to implement checkpoint functionality + */ + + DrainManager *drainManager; + + /** + * logic units connected to the UFS Host device + * Note again that the "device" as such is represented by one or multiple + * logic units. + */ + std::vector<UFSSCSIDevice*> UFSDevice; + + /** + * SCSI reply structure, used for direct answering. Might be refered to + * during the assembly of the reply (data, and response; e.g. if + * something goes wrong along the way, the reply will be different) + */ + struct SCSIReply request_out_datain; + + /** + * SCSI resume info + * information structure for SCSI resume. it keeps track of all the + * information that is needed to successfully complete the transaction + * (response addresses, communicated information so far, etc.). + */ + struct SCSIResumeInfo SCSIInfo; + + /** + * To finish the transaction one needs information about the original + * message. This is stored in this queue + * transferEnd uses the same structure as transferStartInfo, because all + * the information it needs is in there. It improves readability in the + * cc file. + */ + std::deque<struct transferStart> transferEnd; + + /** + * When a task/transfer is started it needs information about the + * task/transfer it is about to perform. This is defined in these + * structures. If multiple tasks/transfers are issued at the same time, + * they still need to be fetched one by one. They then need to be + * executed in the order specified by the UFS standard (least significant + * doorbell first). The tasks/transfers are placed in the queue in that + * specific order. + */ + std::deque<struct taskStart> taskInfo; + std::deque<struct transferStart> transferStartInfo; + + /** + * Information to get a DMA transaction + */ + std::deque<struct writeToDiskBurst> dmaWriteInfo; + + /** + * Information from DMA transaction to disk + */ + std::deque<struct transferInfo> SSDWriteinfo; + + /** + * Information from the Disk, waiting to be pushed to the DMA + */ + std::deque<struct transferInfo> SSDReadPending; + + /** + * garbage queue, ensure clearing of the allocated memory + */ + std::deque<struct UTPTransferReqDesc*> garbage; + + /** + * RequestHandler stats + */ + struct UFSHostDeviceStats stats; + + /** + * Transfer flow events + * Basically these events form two queues, one from memory to UFS device + * (DMA) and one from device to flash (SSD). The SSD "queue" is + * maintained by the flash and the lun classes and does not form a queue + * of events as such, but rather a queue of information. This can be done + * because the flow of the events is completely in the control of these + * classes. (Whereas in the DMA case we rely on an external class) + */ + std::deque<EventWrapper<UFSHostDevice, &UFSHostDevice::readDone> > + readDoneEvent; + std::deque<EventWrapper<UFSHostDevice, &UFSHostDevice::writeDone> > + writeDoneEvent; + + /** + * Callbacks for the logic units. One to indicate the completion of a + * transaction, the other one to indicate the completion of a read + * action. + */ + Callback* transferDoneCallback; + Callback* memReadCallback; + + /** + * The events that control the functionality. + * After a doorbell has been set, either a taskevent or a transfer event + * is scheduled. A transfer event might schedule a SCSI event, all events + * sequences end with an UTP event, which can be considered as the event + * which answers the doorbell. + */ + /** + * Wait for the SCSI specific data to arive + */ + EventWrapper<UFSHostDevice, &UFSHostDevice::SCSIStart> SCSIResumeEvent; + + /** + * Wait for the moment where we can send the last frame + */ + EventWrapper<UFSHostDevice, &UFSHostDevice::finalUTP> UTPEvent; + + /** + * Event after a read to clean up the UTP data structures + */ + std::deque<EventWrapper<UFSHostDevice, &UFSHostDevice::readGarbage> > + readGarbageEventQueue; + + /** + * Multiple tasks transfers can be scheduled at once for the device, the + * only thing we know for sure about them is that they will happen in a + * first come first serve order; hence we need to queue. + */ + std::deque<EventWrapper<UFSHostDevice, &UFSHostDevice::taskStart> > + taskEventQueue; + std::deque<EventWrapper<UFSHostDevice, &UFSHostDevice::transferStart> > + transferEventQueue; + + /** + * Bits of interest within UFS data packages + */ + static const unsigned int UTPTransferREQCOMPL = 0x01;//UFS_BIT(0) + static const unsigned int UTPTaskREQCOMPL = 0x200;//UFS_BIT(9) + static const unsigned int UICCommandCOMPL = 0x400;//UFS_BIT(10) + static const unsigned int UICCommandReady = 0x08;//UFS_BIT(3) + + /* + * UFSHCI Registers; please refer to + * http://www.jedec.org/standards-documents/results/jesd223 + * for their definition. + */ + enum UFSHCIRegisters { + regControllerCapabilities = 0x00, + regUFSVersion = 0x08, + regControllerDEVID = 0x10, + regControllerPRODID = 0x14, + regInterruptStatus = 0x20, + regInterruptEnable = 0x24, + regControllerStatus = 0x30, + regControllerEnable = 0x34, + regUICErrorCodePHYAdapterLayer = 0x38, + regUICErrorCodeDataLinkLayer = 0x3C, + regUICErrorCodeNetworkLayer = 0x40, + regUICErrorCodeTransportLayer = 0x44, + regUICErrorCodeDME = 0x48, + regUTPTransferREQINTAGGControl = 0x4C, + regUTPTransferREQListBaseL = 0x50, + regUTPTransferREQListBaseH = 0x54, + regUTPTransferREQDoorbell = 0x58, + regUTPTransferREQListClear = 0x5C, + regUTPTransferREQListRunStop = 0x60, + regUTPTaskREQListBaseL = 0x70, + regUTPTaskREQListBaseH = 0x74, + regUTPTaskREQDoorbell = 0x78, + regUTPTaskREQListClear = 0x7C, + regUTPTaskREQListRunStop = 0x80, + regUICCommand = 0x90, + regUICCommandArg1 = 0x94, + regUICCommandArg2 = 0x98, + regUICCommandArg3 = 0x9C + }; +}; + +#endif //__DEV_ARM_UFS_DEVICE_HH__ |