| /* | |
| * 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; | |
| scsi_out.reset(); | |
| /** | |
| * Create the standard SCSI reponse information | |
| * These values might changes over the course of a transfer | |
| */ | |
| scsi_out.message.header.dWord0 = UPIUHeaderDataIndWord0 | | |
| lunID << 16; | |
| scsi_out.message.header.dWord1 = UPIUHeaderDataIndWord1; | |
| scsi_out.message.header.dWord2 = UPIUHeaderDataIndWord2; | |
| statusCheck(SCSIGood, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.LUN = lunID; | |
| scsi_out.status = SCSIGood; | |
| DPRINTF(UFSHostDevice, "SCSI command:%2x\n", SCSI_msg[4]); | |
| /**Determine what the message is and fill the response packet*/ | |
| switch (SCSI_msg[4] & 0xFF) { | |
| case SCSIInquiry: { | |
| /** | |
| * SCSI inquiry: tell about this specific logic unit | |
| */ | |
| scsi_out.msgSize = 36; | |
| scsi_out.message.dataMsg.resize(9); | |
| for (uint8_t count = 0; count < 9; count++) | |
| scsi_out.message.dataMsg[count] = | |
| (reinterpret_cast<uint32_t*> (&lunInfo))[count]; | |
| } break; | |
| case SCSIRead6: { | |
| /** | |
| * Read command. Number indicates the length of the command. | |
| */ | |
| scsi_out.expectMore = 0x02; | |
| scsi_out.msgSize = 0; | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned. Apart from that it only has | |
| * information in five bits of the first byte that is relevant | |
| * for this field. | |
| */ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(tempptr); | |
| uint64_t read_offset = betoh(tmp) & 0x1FFFFF; | |
| uint32_t read_size = tempptr[4]; | |
| scsi_out.msgSize = read_size * blkSize; | |
| scsi_out.offset = read_offset * blkSize; | |
| if ((read_offset + read_size) > diskSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| DPRINTF(UFSHostDevice, "Read6 offset: 0x%8x, for %d blocks\n", | |
| read_offset, read_size); | |
| /** | |
| * Renew status check, for the request may have been illegal | |
| */ | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIRead10: { | |
| scsi_out.expectMore = 0x02; | |
| scsi_out.msgSize = 0; | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned.*/ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]); | |
| uint64_t read_offset = betoh(tmp); | |
| uint16_t tmpsize = *reinterpret_cast<uint16_t*>(&tempptr[7]); | |
| uint32_t read_size = betoh(tmpsize); | |
| scsi_out.msgSize = read_size * blkSize; | |
| scsi_out.offset = read_offset * blkSize; | |
| if ((read_offset + read_size) > diskSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| DPRINTF(UFSHostDevice, "Read10 offset: 0x%8x, for %d blocks\n", | |
| read_offset, read_size); | |
| /** | |
| * Renew status check, for the request may have been illegal | |
| */ | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIRead16: { | |
| scsi_out.expectMore = 0x02; | |
| scsi_out.msgSize = 0; | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned.*/ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]); | |
| uint64_t read_offset = betoh(tmp); | |
| tmp = *reinterpret_cast<uint32_t*>(&tempptr[6]); | |
| read_offset = (read_offset << 32) | betoh(tmp); | |
| tmp = *reinterpret_cast<uint32_t*>(&tempptr[10]); | |
| uint32_t read_size = betoh(tmp); | |
| scsi_out.msgSize = read_size * blkSize; | |
| scsi_out.offset = read_offset * blkSize; | |
| if ((read_offset + read_size) > diskSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| DPRINTF(UFSHostDevice, "Read16 offset: 0x%8x, for %d blocks\n", | |
| read_offset, read_size); | |
| /** | |
| * Renew status check, for the request may have been illegal | |
| */ | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIReadCapacity10: { | |
| /** | |
| * read the capacity of the device | |
| */ | |
| scsi_out.msgSize = 8; | |
| scsi_out.message.dataMsg.resize(2); | |
| scsi_out.message.dataMsg[0] = | |
| betoh(capacityLower);//last block | |
| scsi_out.message.dataMsg[1] = betoh(blkSize);//blocksize | |
| } break; | |
| case SCSIReadCapacity16: { | |
| scsi_out.msgSize = 32; | |
| scsi_out.message.dataMsg.resize(8); | |
| scsi_out.message.dataMsg[0] = | |
| betoh(capacityUpper);//last block | |
| scsi_out.message.dataMsg[1] = | |
| betoh(capacityLower);//last block | |
| scsi_out.message.dataMsg[2] = betoh(blkSize);//blocksize | |
| scsi_out.message.dataMsg[3] = 0x00;// | |
| scsi_out.message.dataMsg[4] = 0x00;//reserved | |
| scsi_out.message.dataMsg[5] = 0x00;//reserved | |
| scsi_out.message.dataMsg[6] = 0x00;//reserved | |
| scsi_out.message.dataMsg[7] = 0x00;//reserved | |
| } break; | |
| case SCSIReportLUNs: { | |
| /** | |
| * Find out how many Logic Units this device has. | |
| */ | |
| scsi_out.msgSize = (lunAvail * 8) + 8;//list + overhead | |
| scsi_out.message.dataMsg.resize(2 * lunAvail + 2); | |
| scsi_out.message.dataMsg[0] = (lunAvail * 8) << 24;//LUN listlength | |
| scsi_out.message.dataMsg[1] = 0x00; | |
| for (uint8_t count = 0; count < lunAvail; count++) { | |
| //LUN "count" | |
| scsi_out.message.dataMsg[2 + 2 * count] = (count & 0x7F) << 8; | |
| scsi_out.message.dataMsg[3 + 2 * count] = 0x00; | |
| } | |
| } break; | |
| case SCSIStartStop: { | |
| //Just acknowledge; not deemed relevant ATM | |
| scsi_out.msgSize = 0; | |
| } break; | |
| case SCSITestUnitReady: { | |
| //Just acknowledge; not deemed relevant ATM | |
| scsi_out.msgSize = 0; | |
| } break; | |
| case SCSIVerify10: { | |
| /** | |
| * See if the blocks that the host plans to request are in range of | |
| * the device. | |
| */ | |
| scsi_out.msgSize = 0; | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned.*/ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]); | |
| uint64_t read_offset = betoh(tmp); | |
| uint16_t tmpsize = *reinterpret_cast<uint16_t*>(&tempptr[7]); | |
| uint32_t read_size = betoh(tmpsize); | |
| if ((read_offset + read_size) > diskSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| /** | |
| * Renew status check, for the request may have been illegal | |
| */ | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIWrite6: { | |
| /** | |
| * Write command. | |
| */ | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned. Apart from that it only has | |
| * information in five bits of the first byte that is relevant | |
| * for this field. | |
| */ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(tempptr); | |
| uint64_t write_offset = betoh(tmp) & 0x1FFFFF; | |
| uint32_t write_size = tempptr[4]; | |
| scsi_out.msgSize = write_size * blkSize; | |
| scsi_out.offset = write_offset * blkSize; | |
| scsi_out.expectMore = 0x01; | |
| if ((write_offset + write_size) > diskSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| DPRINTF(UFSHostDevice, "Write6 offset: 0x%8x, for %d blocks\n", | |
| write_offset, write_size); | |
| /** | |
| * Renew status check, for the request may have been illegal | |
| */ | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIWrite10: { | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned.*/ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]); | |
| uint64_t write_offset = betoh(tmp); | |
| uint16_t tmpsize = *reinterpret_cast<uint16_t*>(&tempptr[7]); | |
| uint32_t write_size = betoh(tmpsize); | |
| scsi_out.msgSize = write_size * blkSize; | |
| scsi_out.offset = write_offset * blkSize; | |
| scsi_out.expectMore = 0x01; | |
| if ((write_offset + write_size) > diskSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| DPRINTF(UFSHostDevice, "Write10 offset: 0x%8x, for %d blocks\n", | |
| write_offset, write_size); | |
| /** | |
| * Renew status check, for the request may have been illegal | |
| */ | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIWrite16: { | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned.*/ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]); | |
| uint64_t write_offset = betoh(tmp); | |
| tmp = *reinterpret_cast<uint32_t*>(&tempptr[6]); | |
| write_offset = (write_offset << 32) | betoh(tmp); | |
| tmp = *reinterpret_cast<uint32_t*>(&tempptr[10]); | |
| uint32_t write_size = betoh(tmp); | |
| scsi_out.msgSize = write_size * blkSize; | |
| scsi_out.offset = write_offset * blkSize; | |
| scsi_out.expectMore = 0x01; | |
| if ((write_offset + write_size) > diskSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| DPRINTF(UFSHostDevice, "Write16 offset: 0x%8x, for %d blocks\n", | |
| write_offset, write_size); | |
| /** | |
| * Renew status check, for the request may have been illegal | |
| */ | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIFormatUnit: {//not yet verified | |
| scsi_out.msgSize = 0; | |
| scsi_out.expectMore = 0x01; | |
| } break; | |
| case SCSISendDiagnostic: {//not yet verified | |
| scsi_out.msgSize = 0; | |
| } break; | |
| case SCSISynchronizeCache: { | |
| //do we have cache (we don't have cache at this moment) | |
| //TODO: here will synchronization happen when cache is modelled | |
| scsi_out.msgSize = 0; | |
| } break; | |
| //UFS SCSI additional command set for full functionality | |
| case SCSIModeSelect10: | |
| //TODO: | |
| //scsi_out.expectMore = 0x01;//not supported due to modepage support | |
| //code isn't dead, code suggest what is to be done when implemented | |
| break; | |
| case SCSIModeSense6: case SCSIModeSense10: { | |
| /** | |
| * Get more discriptive information about the SCSI functionality | |
| * within this logic unit. | |
| */ | |
| if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x0A) {//control page | |
| scsi_out.message.dataMsg.resize((sizeof(controlPage) >> 2) + 2); | |
| scsi_out.message.dataMsg[0] = 0x00000A00;//control page code | |
| scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8 | |
| for (uint8_t count = 0; count < 3; count++) | |
| scsi_out.message.dataMsg[2 + count] = controlPage[count]; | |
| scsi_out.msgSize = 20; | |
| DPRINTF(UFSHostDevice, "CONTROL page\n"); | |
| } else if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x01) {//recovery page | |
| scsi_out.message.dataMsg.resize((sizeof(recoveryPage) >> 2) | |
| + 2); | |
| scsi_out.message.dataMsg[0] = 0x00000100;//recovery page code | |
| scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8 | |
| for (uint8_t count = 0; count < 3; count++) | |
| scsi_out.message.dataMsg[2 + count] = recoveryPage[count]; | |
| scsi_out.msgSize = 20; | |
| DPRINTF(UFSHostDevice, "RECOVERY page\n"); | |
| } else if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x08) {//caching page | |
| scsi_out.message.dataMsg.resize((sizeof(cachingPage) >> 2) + 2); | |
| scsi_out.message.dataMsg[0] = 0x00001200;//caching page code | |
| scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8 | |
| for (uint8_t count = 0; count < 5; count++) | |
| scsi_out.message.dataMsg[2 + count] = cachingPage[count]; | |
| scsi_out.msgSize = 20; | |
| DPRINTF(UFSHostDevice, "CACHE page\n"); | |
| } else if ((SCSI_msg[4] & 0x3F0000) >> 16 == 0x3F) {//ALL the pages! | |
| scsi_out.message.dataMsg.resize(((sizeof(controlPage) + | |
| sizeof(recoveryPage) + | |
| sizeof(cachingPage)) >> 2) | |
| + 2); | |
| scsi_out.message.dataMsg[0] = 0x00003200;//all page code | |
| scsi_out.message.dataMsg[1] = 0x00000000;//See JEDEC220 ch8 | |
| for (uint8_t count = 0; count < 3; count++) | |
| scsi_out.message.dataMsg[2 + count] = recoveryPage[count]; | |
| for (uint8_t count = 0; count < 5; count++) | |
| scsi_out.message.dataMsg[5 + count] = cachingPage[count]; | |
| for (uint8_t count = 0; count < 3; count++) | |
| scsi_out.message.dataMsg[10 + count] = controlPage[count]; | |
| scsi_out.msgSize = 52; | |
| DPRINTF(UFSHostDevice, "Return ALL the pages!!!\n"); | |
| } else inform("Wrong mode page requested\n"); | |
| scsi_out.message.dataCount = scsi_out.msgSize << 24; | |
| } break; | |
| case SCSIRequestSense: { | |
| scsi_out.msgSize = 0; | |
| } break; | |
| case SCSIUnmap:break;//not yet verified | |
| case SCSIWriteBuffer: { | |
| scsi_out.expectMore = 0x01; | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned.*/ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]); | |
| uint64_t write_offset = betoh(tmp) & 0xFFFFFF; | |
| tmp = *reinterpret_cast<uint32_t*>(&tempptr[5]); | |
| uint32_t write_size = betoh(tmp) & 0xFFFFFF; | |
| scsi_out.msgSize = write_size; | |
| scsi_out.offset = write_offset; | |
| } break; | |
| case SCSIReadBuffer: { | |
| /** | |
| * less trivial than normal read. Size is in bytes instead | |
| * of blocks, and it is assumed (though not guaranteed) that | |
| * reading is from cache. | |
| */ | |
| scsi_out.expectMore = 0x02; | |
| uint8_t* tempptr = reinterpret_cast<uint8_t*>(&SCSI_msg[4]); | |
| /**BE and not nicely aligned.*/ | |
| uint32_t tmp = *reinterpret_cast<uint32_t*>(&tempptr[2]); | |
| uint64_t read_offset = betoh(tmp) & 0xFFFFFF; | |
| tmp = *reinterpret_cast<uint32_t*>(&tempptr[5]); | |
| uint32_t read_size = betoh(tmp) & 0xFFFFFF; | |
| scsi_out.msgSize = read_size; | |
| scsi_out.offset = read_offset; | |
| if ((read_offset + read_size) > capacityLower * blkSize) | |
| scsi_out.status = SCSIIllegalRequest; | |
| DPRINTF(UFSHostDevice, "Read buffer location: 0x%8x\n", | |
| read_offset); | |
| DPRINTF(UFSHostDevice, "Number of bytes: 0x%8x\n", read_size); | |
| statusCheck(scsi_out.status, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| } break; | |
| case SCSIMaintenanceIn: { | |
| /** | |
| * linux sends this command three times from kernel 3.9 onwards, | |
| * UFS does not support it, nor does this model. Linux knows this, | |
| * but tries anyway (useful for some SD card types). | |
| * Lets make clear we don't want it and just ignore it. | |
| */ | |
| DPRINTF(UFSHostDevice, "Ignoring Maintenance In command\n"); | |
| statusCheck(SCSIIllegalRequest, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| scsi_out.msgSize = 0; | |
| } break; | |
| default: { | |
| statusCheck(SCSIIllegalRequest, scsi_out.senseCode); | |
| scsi_out.senseSize = scsi_out.senseCode[0]; | |
| scsi_out.status = (scsi_out.status == SCSIGood) ? SCSIGood : | |
| SCSICheckCondition; | |
| scsi_out.msgSize = 0; | |
| inform("Unsupported scsi message type: %2x\n", SCSI_msg[4] & 0xFF); | |
| inform("0x%8x\n", SCSI_msg[0]); | |
| inform("0x%8x\n", SCSI_msg[1]); | |
| inform("0x%8x\n", SCSI_msg[2]); | |
| inform("0x%8x\n", SCSI_msg[3]); | |
| inform("0x%8x\n", SCSI_msg[4]); | |
| } break; | |
| } | |
| return scsi_out; | |
| } | |
| /** | |
| * SCSI status check function. generic device test, creates sense codes | |
| * Future versions may include TODO: device checks, which is why this is | |
| * in a separate function. | |
| */ | |
| void | |
| UFSHostDevice::UFSSCSIDevice::statusCheck(uint8_t status, | |
| uint8_t* sensecodelist) | |
| { | |
| for (uint8_t count = 0; count < 19; count++) | |
| sensecodelist[count] = 0; | |
| sensecodelist[0] = 18; //sense length | |
| sensecodelist[1] = 0x70; //we send a valid frame | |
| sensecodelist[3] = status & 0xF; //mask to be sure + sensecode | |
| sensecodelist[8] = 0x1F; //data length | |
| } | |
| /** | |
| * read from the flashdisk | |
| */ | |
| void | |
| UFSHostDevice::UFSSCSIDevice::readFlash(uint8_t* readaddr, uint64_t offset, | |
| uint32_t size) | |
| { | |
| /** read from image, and get to memory */ | |
| for (int count = 0; count < (size / SectorSize); count++) | |
| flashDisk->read(&(readaddr[SectorSize*count]), (offset / | |
| SectorSize) + count); | |
| } | |
| /** | |
| * Write to the flashdisk | |
| */ | |
| void | |
| UFSHostDevice::UFSSCSIDevice::writeFlash(uint8_t* writeaddr, uint64_t offset, | |
| uint32_t size) | |
| { | |
| /** Get from fifo and write to image*/ | |
| for (int count = 0; count < (size / SectorSize); count++) | |
| flashDisk->write(&(writeaddr[SectorSize * count]), | |
| (offset / SectorSize) + count); | |
| } | |
| /** | |
| * Constructor for the UFS Host device | |
| */ | |
| UFSHostDevice::UFSHostDevice(const UFSHostDeviceParams* p) : | |
| DmaDevice(p), | |
| pioAddr(p->pio_addr), | |
| pioSize(0x0FFF), | |
| pioDelay(p->pio_latency), | |
| intNum(p->int_num), | |
| gic(p->gic), | |
| lunAvail(p->image.size()), | |
| UFSSlots(p->ufs_slots - 1), | |
| readPendingNum(0), | |
| writePendingNum(0), | |
| activeDoorbells(0), | |
| pendingDoorbells(0), | |
| countInt(0), | |
| transferTrack(0), | |
| taskCommandTrack(0), | |
| idlePhaseStart(0), | |
| SCSIResumeEvent([this]{ SCSIStart(); }, name()), | |
| UTPEvent([this]{ finalUTP(); }, name()) | |
| { | |
| 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() | |
| { | |
| DmaDevice::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->setLE<uint32_t>(data); | |
| pkt->makeResponse(); | |
| return pioDelay; | |
| } | |
| /** | |
| * UFSHCD write function. This function allows access to the writeable | |
| * registers. If any function attempts to write value to an unwriteable | |
| * register entry, then the value will not be written. | |
| */ | |
| Tick | |
| UFSHostDevice::write(PacketPtr pkt) | |
| { | |
| uint32_t data = 0; | |
| switch (pkt->getSize()) { | |
| case 1: | |
| data = pkt->getLE<uint8_t>(); | |
| break; | |
| case 2: | |
| data = pkt->getLE<uint16_t>(); | |
| break; | |
| case 4: | |
| data = pkt->getLE<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( | |
| EventFunctionWrapper([this]{ taskStart(); }, name())); | |
| writeDevice(&taskEventQueue.back(), false, address, size, | |
| reinterpret_cast<uint8_t*> | |
| (&taskInfo.back().destination), 0, 0); | |
| } else if ((UFSHCIMem.TRUTRLDBR ^ transferTrack) > 0x00) { | |
| /** | |
| * Transfer; Data transfer from or to the disk. There will be DMA | |
| * transfers, and the flash might be approached. Further | |
| * commands, are needed to specify the exact command. | |
| */ | |
| size = sizeof(UTPTransferReqDesc); | |
| /**Find the position that is not handled yet*/ | |
| count = findLsbSet((UFSHCIMem.TRUTRLDBR ^ transferTrack)); | |
| address = UFSHCIMem.TRUTRLBAU; | |
| //<-64 bit | |
| address = (count * size) + (address << 32) + UFSHCIMem.TRUTRLBA; | |
| transferTrack |= mask << count; | |
| DPRINTF(UFSHostDevice, "Doorbell register: 0x%8x select #:" | |
| " 0x%8x completion info: 0x%8x\n", UFSHCIMem.TRUTRLDBR, | |
| count, transferstart_info.done); | |
| transferstart_info.done = UFSHCIMem.TRUTRLDBR; | |
| /**stats**/ | |
| transactionStart[count] = curTick(); //note the start time | |
| ++activeDoorbells; | |
| stats.maxDoorbell = (stats.maxDoorbell.value() < activeDoorbells) | |
| ? activeDoorbells : stats.maxDoorbell.value(); | |
| stats.averageDoorbell = stats.maxDoorbell.value(); | |
| /** | |
| * step3 start transfer | |
| * step4 register information; allowing the host to respond in | |
| * the end | |
| */ | |
| transferstart_info.mask = mask << count; | |
| transferstart_info.address = address; | |
| transferstart_info.size = size; | |
| transferstart_info.done = UFSHCIMem.TRUTRLDBR; | |
| transferStartInfo.push_back(transferstart_info); | |
| /**Deleted in readDone, queued in finalUTP*/ | |
| transferStartInfo.back().destination = new struct | |
| UTPTransferReqDesc; | |
| DPRINTF(UFSHostDevice, "Initial transfer start: 0x%8x\n", | |
| transferstart_info.done); | |
| transferEventQueue.push_back( | |
| EventFunctionWrapper([this]{ transferStart(); }, name())); | |
| if (transferEventQueue.size() < 2) { | |
| writeDevice(&transferEventQueue.front(), false, | |
| address, size, reinterpret_cast<uint8_t*> | |
| (transferStartInfo.front().destination),0, 0); | |
| DPRINTF(UFSHostDevice, "Transfer scheduled\n"); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Task start event | |
| */ | |
| void | |
| UFSHostDevice::taskStart() | |
| { | |
| DPRINTF(UFSHostDevice, "Task start"); | |
| taskHandler(&taskInfo.front().destination, taskInfo.front().mask, | |
| taskInfo.front().address, taskInfo.front().size); | |
| taskInfo.pop_front(); | |
| taskEventQueue.pop_front(); | |
| } | |
| /** | |
| * Transfer start event | |
| */ | |
| void | |
| UFSHostDevice::transferStart() | |
| { | |
| DPRINTF(UFSHostDevice, "Enter transfer event\n"); | |
| transferHandler(transferStartInfo.front().destination, | |
| transferStartInfo.front().mask, | |
| transferStartInfo.front().address, | |
| transferStartInfo.front().size, | |
| transferStartInfo.front().done); | |
| transferStartInfo.pop_front(); | |
| DPRINTF(UFSHostDevice, "Transfer queue size at end of event: " | |
| "0x%8x\n", transferEventQueue.size()); | |
| } | |
| /** | |
| * Handles the commands that are given. At this point in time, not many | |
| * commands have been implemented in the driver. | |
| */ | |
| void | |
| UFSHostDevice::commandHandler() | |
| { | |
| if (UFSHCIMem.CMDUICCMDR == 0x16) { | |
| UFSHCIMem.ORHostControllerStatus |= 0x0F;//link startup | |
| } | |
| } | |
| /** | |
| * Handles the tasks that are given. At this point in time, not many tasks | |
| * have been implemented in the driver. | |
| */ | |
| void | |
| UFSHostDevice::taskHandler(struct UTPUPIUTaskReq* request_in, | |
| uint32_t req_pos, Addr finaladdress, uint32_t | |
| finalsize) | |
| { | |
| /** | |
| * For now, just unpack and acknowledge the task without doing anything. | |
| * TODO Implement UFS tasks. | |
| */ | |
| inform("taskHandler\n"); | |
| inform("%8x\n", request_in->header.dWord0); | |
| inform("%8x\n", request_in->header.dWord1); | |
| inform("%8x\n", request_in->header.dWord2); | |
| request_in->header.dWord2 &= 0xffffff00; | |
| UFSHCIMem.TMUTMRLDBR &= ~(req_pos); | |
| taskCommandTrack &= ~(req_pos); | |
| UFSHCIMem.ORInterruptStatus |= UTPTaskREQCOMPL; | |
| readDevice(true, finaladdress, finalsize, reinterpret_cast<uint8_t*> | |
| (request_in), true, NULL); | |
| } | |
| /** | |
| * Obtains the SCSI command (if any) | |
| * Two possibilities: if it contains a SCSI command, then it is a usable | |
| * message; if it doesnt contain a SCSI message, then it can't be handeld | |
| * by this code. | |
| * This is the second stage of the transfer. We have the information about | |
| * where the next command can be found and what the type of command is. The | |
| * actions that are needed from the device its side are: get the information | |
| * and store the information such that we can reply. | |
| */ | |
| void | |
| UFSHostDevice::transferHandler(struct UTPTransferReqDesc* request_in, | |
| int req_pos, Addr finaladdress, uint32_t | |
| finalsize, uint32_t done) | |
| { | |
| Addr cmd_desc_addr = 0x00; | |
| //acknowledge handling of the message | |
| DPRINTF(UFSHostDevice, "SCSI message detected\n"); | |
| request_in->header.dWord2 &= 0xffffff00; | |
| SCSIInfo.RequestIn = request_in; | |
| SCSIInfo.reqPos = req_pos; | |
| SCSIInfo.finalAddress = finaladdress; | |
| SCSIInfo.finalSize = finalsize; | |
| SCSIInfo.destination.resize(request_in->PRDTableOffset * 4 | |
| + request_in->PRDTableLength * sizeof(UFSHCDSGEntry)); | |
| SCSIInfo.done = done; | |
| assert(!SCSIResumeEvent.scheduled()); | |
| /** | |
| *Get the UTP command that has the SCSI command | |
| */ | |
| cmd_desc_addr = request_in->commandDescBaseAddrHi; | |
| cmd_desc_addr = (cmd_desc_addr << 32) | | |
| (request_in->commandDescBaseAddrLo & 0xffffffff); | |
| writeDevice(&SCSIResumeEvent, false, cmd_desc_addr, | |
| SCSIInfo.destination.size(), &SCSIInfo.destination[0],0, 0); | |
| DPRINTF(UFSHostDevice, "SCSI scheduled\n"); | |
| transferEventQueue.pop_front(); | |
| } | |
| /** | |
| * Obtain LUN and put it in the right LUN queue. Each LUN has its own queue | |
| * of commands that need to be executed. This is the first instance where it | |
| * can be determined which Logic unit should handle the transfer. Then check | |
| * wether it should wait and queue or if it can continue. | |
| */ | |
| void | |
| UFSHostDevice::SCSIStart() | |
| { | |
| DPRINTF(UFSHostDevice, "SCSI message on hold until ready\n"); | |
| uint32_t LUN = SCSIInfo.destination[2]; | |
| UFSDevice[LUN]->SCSIInfoQueue.push_back(SCSIInfo); | |
| DPRINTF(UFSHostDevice, "SCSI queue %d has %d elements\n", LUN, | |
| UFSDevice[LUN]->SCSIInfoQueue.size()); | |
| /**There are 32 doorbells, so at max there can be 32 transactions*/ | |
| if (UFSDevice[LUN]->SCSIInfoQueue.size() < 2) //LUN is available | |
| SCSIResume(LUN); | |
| else if (UFSDevice[LUN]->SCSIInfoQueue.size() > 32) | |
| panic("SCSI queue is getting too big %d\n", UFSDevice[LUN]-> | |
| SCSIInfoQueue.size()); | |
| /** | |
| * First transfer is done, fetch the next; | |
| * At this point, the device is busy, not the HC | |
| */ | |
| if (!transferEventQueue.empty()) { | |
| /** | |
| * loading next data packet in case Another LUN | |
| * is approached in the mean time | |
| */ | |
| writeDevice(&transferEventQueue.front(), false, | |
| transferStartInfo.front().address, | |
| transferStartInfo.front().size, reinterpret_cast<uint8_t*> | |
| (transferStartInfo.front().destination), 0, 0); | |
| DPRINTF(UFSHostDevice, "Transfer scheduled"); | |
| } | |
| } | |
| /** | |
| * Handles the transfer requests that are given. | |
| * There can be three types of transfer. SCSI specific, Reads and writes | |
| * apart from the data transfer, this also generates its own reply (UPIU | |
| * response). Information for this reply is stored in transferInfo and will | |
| * be used in transferDone | |
| */ | |
| void | |
| UFSHostDevice::SCSIResume(uint32_t lun_id) | |
| { | |
| DPRINTF(UFSHostDevice, "SCSIresume\n"); | |
| if (UFSDevice[lun_id]->SCSIInfoQueue.empty()) | |
| panic("No SCSI message scheduled lun:%d Doorbell: 0x%8x", lun_id, | |
| UFSHCIMem.TRUTRLDBR); | |
| /**old info, lets form it such that we can understand it*/ | |
| struct UTPTransferReqDesc* request_in = UFSDevice[lun_id]-> | |
| SCSIInfoQueue.front().RequestIn; | |
| uint32_t req_pos = UFSDevice[lun_id]->SCSIInfoQueue.front().reqPos; | |
| Addr finaladdress = UFSDevice[lun_id]->SCSIInfoQueue.front(). | |
| finalAddress; | |
| uint32_t finalsize = UFSDevice[lun_id]->SCSIInfoQueue.front().finalSize; | |
| uint32_t* transfercommand = reinterpret_cast<uint32_t*> | |
| (&(UFSDevice[lun_id]->SCSIInfoQueue.front().destination[0])); | |
| DPRINTF(UFSHostDevice, "Task tag: 0x%8x\n", transfercommand[0]>>24); | |
| /**call logic unit to handle SCSI command*/ | |
| request_out_datain = UFSDevice[(transfercommand[0] & 0xFF0000) >> 16]-> | |
| SCSICMDHandle(transfercommand); | |
| DPRINTF(UFSHostDevice, "LUN: %d\n", request_out_datain.LUN); | |
| /** | |
| * build response stating that it was succesful | |
| * command completion, Logic unit number, and Task tag | |
| */ | |
| request_in->header.dWord0 = ((request_in->header.dWord0 >> 24) == 0x21) | |
| ? 0x36 : 0x21; | |
| UFSDevice[lun_id]->transferInfo.requestOut.header.dWord0 = | |
| request_in->header.dWord0 | (request_out_datain.LUN << 8) | |
| | (transfercommand[0] & 0xFF000000); | |
| /**SCSI status reply*/ | |
| UFSDevice[lun_id]->transferInfo.requestOut.header.dWord1 = 0x00000000 | | |
| (request_out_datain.status << 24); | |
| /**segment size + EHS length (see UFS standard ch7)*/ | |
| UFSDevice[lun_id]->transferInfo.requestOut.header.dWord2 = 0x00000000 | | |
| ((request_out_datain.senseSize + 2) << 24) | 0x05; | |
| /**amount of data that will follow*/ | |
| UFSDevice[lun_id]->transferInfo.requestOut.senseDataLen = | |
| request_out_datain.senseSize; | |
| //data | |
| for (uint8_t count = 0; count<request_out_datain.senseSize; count++) { | |
| UFSDevice[lun_id]->transferInfo.requestOut.senseData[count] = | |
| request_out_datain.senseCode[count + 1]; | |
| } | |
| /* | |
| * At position defined by "request_in->PRDTableOffset" (counting 32 bit | |
| * words) in array "transfercommand" we have a scatter gather list, which | |
| * is usefull to us if we interpreted it as a UFSHCDSGEntry structure. | |
| */ | |
| struct UFSHCDSGEntry* sglist = reinterpret_cast<UFSHCDSGEntry*> | |
| (&(transfercommand[(request_in->PRDTableOffset)])); | |
| uint32_t length = request_in->PRDTableLength; | |
| DPRINTF(UFSHostDevice, "# PRDT entries: %d\n", length); | |
| Addr response_addr = request_in->commandDescBaseAddrHi; | |
| response_addr = (response_addr << 32) | | |
| ((request_in->commandDescBaseAddrLo + | |
| (request_in->responseUPIULength << 2)) & 0xffffffff); | |
| /**transferdone information packet filling*/ | |
| UFSDevice[lun_id]->transferInfo.responseStartAddr = response_addr; | |
| UFSDevice[lun_id]->transferInfo.reqPos = req_pos; | |
| UFSDevice[lun_id]->transferInfo.size = finalsize; | |
| UFSDevice[lun_id]->transferInfo.address = finaladdress; | |
| UFSDevice[lun_id]->transferInfo.destination = reinterpret_cast<uint8_t*> | |
| (UFSDevice[lun_id]->SCSIInfoQueue.front().RequestIn); | |
| UFSDevice[lun_id]->transferInfo.finished = true; | |
| UFSDevice[lun_id]->transferInfo.lunID = request_out_datain.LUN; | |
| /** | |
| * In this part the data that needs to be transfered will be initiated | |
| * and the chain of DMA (and potentially) disk transactions will be | |
| * started. | |
| */ | |
| if (request_out_datain.expectMore == 0x01) { | |
| /**write transfer*/ | |
| manageWriteTransfer(request_out_datain.LUN, request_out_datain.offset, | |
| length, sglist); | |
| } else if (request_out_datain.expectMore == 0x02) { | |
| /**read transfer*/ | |
| manageReadTransfer(request_out_datain.msgSize, request_out_datain.LUN, | |
| request_out_datain.offset, length, sglist); | |
| } else { | |
| /**not disk related transfer, SCSI maintanance*/ | |
| uint32_t count = 0; | |
| uint32_t size_accum = 0; | |
| DPRINTF(UFSHostDevice, "Data DMA size: 0x%8x\n", | |
| request_out_datain.msgSize); | |
| /**Transport the SCSI reponse data according to the SG list*/ | |
| while ((length > count) && size_accum | |
| < (request_out_datain.msgSize - 1) && | |
| (request_out_datain.msgSize != 0x00)) { | |
| Addr SCSI_start = sglist[count].upperAddr; | |
| SCSI_start = (SCSI_start << 32) | | |
| (sglist[count].baseAddr & 0xFFFFFFFF); | |
| DPRINTF(UFSHostDevice, "Data DMA start: 0x%8x\n", SCSI_start); | |
| DPRINTF(UFSHostDevice, "Data DMA size: 0x%8x\n", | |
| (sglist[count].size + 1)); | |
| /** | |
| * safetynet; it has been shown that sg list may be optimistic in | |
| * the amount of data allocated, which can potentially lead to | |
| * some garbage data being send over. Hence this construction | |
| * that finds the least amount of data that needs to be | |
| * transfered. | |
| */ | |
| uint32_t size_to_send = sglist[count].size + 1; | |
| if (request_out_datain.msgSize < (size_to_send + size_accum)) | |
| size_to_send = request_out_datain.msgSize - size_accum; | |
| readDevice(false, SCSI_start, size_to_send, | |
| reinterpret_cast<uint8_t*> | |
| (&(request_out_datain.message.dataMsg[size_accum])), | |
| false, NULL); | |
| size_accum += size_to_send; | |
| DPRINTF(UFSHostDevice, "Total remaining: 0x%8x,accumulated so far" | |
| " : 0x%8x\n", (request_out_datain.msgSize - size_accum), | |
| size_accum); | |
| ++count; | |
| DPRINTF(UFSHostDevice, "Transfer #: %d\n", count); | |
| } | |
| /**Go to the next stage of the answering process*/ | |
| transferDone(response_addr, req_pos, UFSDevice[lun_id]-> | |
| transferInfo.requestOut, finalsize, finaladdress, | |
| reinterpret_cast<uint8_t*>(request_in), true, lun_id); | |
| } | |
| DPRINTF(UFSHostDevice, "SCSI resume done\n"); | |
| } | |
| /** | |
| * Find finished transfer. Callback function. One of the LUNs is done with | |
| * the disk transfer and reports back to the controller. This function finds | |
| * out who it was, and calls transferDone. | |
| */ | |
| void | |
| UFSHostDevice::LUNSignal() | |
| { | |
| uint8_t this_lun = 0; | |
| //while we haven't found the right lun, keep searching | |
| while ((this_lun < lunAvail) && !UFSDevice[this_lun]->finishedCommand()) | |
| ++this_lun; | |
| if (this_lun < lunAvail) { | |
| //Clear signal. | |
| UFSDevice[this_lun]->clearSignal(); | |
| //found it; call transferDone | |
| transferDone(UFSDevice[this_lun]->transferInfo.responseStartAddr, | |
| UFSDevice[this_lun]->transferInfo.reqPos, | |
| UFSDevice[this_lun]->transferInfo.requestOut, | |
| UFSDevice[this_lun]->transferInfo.size, | |
| UFSDevice[this_lun]->transferInfo.address, | |
| UFSDevice[this_lun]->transferInfo.destination, | |
| UFSDevice[this_lun]->transferInfo.finished, | |
| UFSDevice[this_lun]->transferInfo.lunID); | |
| } | |
| else | |
| panic("no LUN finished in tick %d\n", curTick()); | |
| } | |
| /** | |
| * Transfer done. When the data transfer is done, this function ensures | |
| * that the application is notified. | |
| */ | |
| void | |
| UFSHostDevice::transferDone(Addr responseStartAddr, uint32_t req_pos, | |
| struct UTPUPIURSP request_out, uint32_t size, | |
| Addr address, uint8_t* destination, | |
| bool finished, uint32_t lun_id) | |
| { | |
| /**Test whether SCSI queue hasn't popped prematurely*/ | |
| if (UFSDevice[lun_id]->SCSIInfoQueue.empty()) | |
| panic("No SCSI message scheduled lun:%d Doorbell: 0x%8x", lun_id, | |
| UFSHCIMem.TRUTRLDBR); | |
| DPRINTF(UFSHostDevice, "DMA start: 0x%8x; DMA size: 0x%8x\n", | |
| responseStartAddr, sizeof(request_out)); | |
| struct transferStart lastinfo; | |
| lastinfo.mask = req_pos; | |
| lastinfo.done = finished; | |
| lastinfo.address = address; | |
| lastinfo.size = size; | |
| lastinfo.destination = reinterpret_cast<UTPTransferReqDesc*> | |
| (destination); | |
| lastinfo.lun_id = lun_id; | |
| transferEnd.push_back(lastinfo); | |
| DPRINTF(UFSHostDevice, "Transfer done start\n"); | |
| readDevice(false, responseStartAddr, sizeof(request_out), | |
| reinterpret_cast<uint8_t*> | |
| (&(UFSDevice[lun_id]->transferInfo.requestOut)), | |
| true, &UTPEvent); | |
| } | |
| /** | |
| * finalUTP. Second part of the transfer done event. | |
| * this sends the final response: the UTP response. After this transaction | |
| * the doorbell shall be cleared, and the interupt shall be set. | |
| */ | |
| void | |
| UFSHostDevice::finalUTP() | |
| { | |
| uint32_t lun_id = transferEnd.front().lun_id; | |
| UFSDevice[lun_id]->SCSIInfoQueue.pop_front(); | |
| DPRINTF(UFSHostDevice, "SCSIInfoQueue size: %d, lun: %d\n", | |
| UFSDevice[lun_id]->SCSIInfoQueue.size(), lun_id); | |
| /**stats**/ | |
| if (UFSHCIMem.TRUTRLDBR & transferEnd.front().mask) { | |
| uint8_t count = 0; | |
| while (!(transferEnd.front().mask & (0x1 << count))) | |
| ++count; | |
| stats.transactionLatency.sample(curTick() - | |
| transactionStart[count]); | |
| } | |
| /**Last message that will be transfered*/ | |
| readDevice(true, transferEnd.front().address, | |
| transferEnd.front().size, reinterpret_cast<uint8_t*> | |
| (transferEnd.front().destination), true, NULL); | |
| /**clean and ensure that the tracker is updated*/ | |
| transferTrack &= ~(transferEnd.front().mask); | |
| --activeDoorbells; | |
| ++pendingDoorbells; | |
| garbage.push_back(transferEnd.front().destination); | |
| transferEnd.pop_front(); | |
| DPRINTF(UFSHostDevice, "UTP handled\n"); | |
| /**stats**/ | |
| stats.averageDoorbell = stats.maxDoorbell.value(); | |
| DPRINTF(UFSHostDevice, "activeDoorbells: %d, pendingDoorbells: %d," | |
| " garbage: %d, TransferEvent: %d\n", activeDoorbells, | |
| pendingDoorbells, garbage.size(), transferEventQueue.size()); | |
| /**This is the moment that the device is available again*/ | |
| if (!UFSDevice[lun_id]->SCSIInfoQueue.empty()) | |
| SCSIResume(lun_id); | |
| } | |
| /** | |
| * Read done handling function, is only initiated at the end of a transaction | |
| */ | |
| void | |
| UFSHostDevice::readDone() | |
| { | |
| DPRINTF(UFSHostDevice, "Read done start\n"); | |
| --readPendingNum; | |
| /**Garbage collection; sort out the allocated UTP descriptor*/ | |
| if (garbage.size() > 0) { | |
| delete garbage.front(); | |
| garbage.pop_front(); | |
| } | |
| /**done, generate interrupt if we havent got one already*/ | |
| if (!(UFSHCIMem.ORInterruptStatus & 0x01)) { | |
| UFSHCIMem.ORInterruptStatus |= UTPTransferREQCOMPL; | |
| generateInterrupt(); | |
| } | |
| if (!readDoneEvent.empty()) { | |
| readDoneEvent.pop_front(); | |
| } | |
| } | |
| /** | |
| * set interrupt and sort out the doorbell register. | |
| */ | |
| void | |
| UFSHostDevice::generateInterrupt() | |
| { | |
| /**just to keep track of the transactions*/ | |
| countInt++; | |
| /**step5 clear doorbell*/ | |
| UFSHCIMem.TRUTRLDBR &= transferTrack; | |
| pendingDoorbells = 0; | |
| DPRINTF(UFSHostDevice, "Clear doorbell %X\n", UFSHCIMem.TRUTRLDBR); | |
| checkDrain(); | |
| /**step6 raise interrupt*/ | |
| gic->sendInt(intNum); | |
| DPRINTF(UFSHostDevice, "Send interrupt @ transaction: 0x%8x!\n", | |
| countInt); | |
| } | |
| /** | |
| * Clear interrupt | |
| */ | |
| void | |
| UFSHostDevice::clearInterrupt() | |
| { | |
| gic->clearInt(intNum); | |
| DPRINTF(UFSHostDevice, "Clear interrupt: 0x%8x!\n", countInt); | |
| checkDrain(); | |
| if (!(UFSHCIMem.TRUTRLDBR)) { | |
| idlePhaseStart = curTick(); | |
| } | |
| /**end of a transaction*/ | |
| } | |
| /** | |
| * Important to understand about the transfer flow: | |
| * We have basically three stages, The "system memory" stage, the "device | |
| * buffer" stage and the "disk" stage. In this model we assume an infinite | |
| * buffer, or a buffer that is big enough to store all the data in the | |
| * biggest transaction. Between the three stages are two queues. Those queues | |
| * store the messages to simulate their transaction from one stage to the | |
| * next. The manage{Action} function fills up one of the queues and triggers | |
| * the first event, which causes a chain reaction of events executed once | |
| * they pass through their queues. For a write action the stages are ordered | |
| * "system memory", "device buffer" and "disk", whereas the read transfers | |
| * happen "disk", "device buffer" and "system memory". The dma action in the | |
| * dma device is written from a bus perspective whereas this model is written | |
| * from a device perspective. To avoid confusion, the translation between the | |
| * two has been made in the writeDevice and readDevice funtions. | |
| */ | |
| /** | |
| * Dma transaction function: write device. Note that the dma action is | |
| * from a device perspective, while this function is from an initiator | |
| * perspective | |
| */ | |
| void | |
| UFSHostDevice::writeDevice(Event* additional_action, bool toDisk, Addr | |
| start, int size, uint8_t* destination, uint64_t | |
| SCSIDiskOffset, uint32_t lun_id) | |
| { | |
| DPRINTF(UFSHostDevice, "Write transaction Start: 0x%8x; Size: %d\n", | |
| start, size); | |
| /**check whether transfer is all the way to the flash*/ | |
| if (toDisk) { | |
| ++writePendingNum; | |
| while (!writeDoneEvent.empty() && (writeDoneEvent.front().when() | |
| < curTick())) | |
| writeDoneEvent.pop_front(); | |
| writeDoneEvent.push_back( | |
| EventFunctionWrapper([this]{ writeDone(); }, | |
| name())); | |
| assert(!writeDoneEvent.back().scheduled()); | |
| /**destination is an offset here since we are writing to a disk*/ | |
| struct transferInfo new_transfer; | |
| new_transfer.offset = SCSIDiskOffset; | |
| new_transfer.size = size; | |
| new_transfer.lunID = lun_id; | |
| new_transfer.filePointer = 0; | |
| SSDWriteinfo.push_back(new_transfer); | |
| /**allocate appropriate buffer*/ | |
| SSDWriteinfo.back().buffer.resize(size); | |
| /**transaction*/ | |
| dmaPort.dmaAction(MemCmd::ReadReq, start, size, | |
| &writeDoneEvent.back(), | |
| &SSDWriteinfo.back().buffer[0], 0); | |
| //yes, a readreq at a write device function is correct. | |
| DPRINTF(UFSHostDevice, "Write to disk scheduled\n"); | |
| } else { | |
| assert(!additional_action->scheduled()); | |
| dmaPort.dmaAction(MemCmd::ReadReq, start, size, | |
| additional_action, destination, 0); | |
| DPRINTF(UFSHostDevice, "Write scheduled\n"); | |
| } | |
| } | |
| /** | |
| * Manage write transfer. Manages correct transfer flow and makes sure that | |
| * the queues are filled on time | |
| */ | |
| void | |
| UFSHostDevice::manageWriteTransfer(uint8_t LUN, uint64_t offset, uint32_t | |
| sg_table_length, struct UFSHCDSGEntry* | |
| sglist) | |
| { | |
| struct writeToDiskBurst next_packet; | |
| next_packet.SCSIDiskOffset = offset; | |
| UFSDevice[LUN]->setTotalWrite(sg_table_length); | |
| /** | |
| * Break-up the transactions into actions defined by the scatter gather | |
| * list. | |
| */ | |
| for (uint32_t count = 0; count < sg_table_length; count++) { | |
| next_packet.start = sglist[count].upperAddr; | |
| next_packet.start = (next_packet.start << 32) | | |
| (sglist[count].baseAddr & 0xFFFFFFFF); | |
| next_packet.LUN = LUN; | |
| DPRINTF(UFSHostDevice, "Write data DMA start: 0x%8x\n", | |
| next_packet.start); | |
| DPRINTF(UFSHostDevice, "Write data DMA size: 0x%8x\n", | |
| (sglist[count].size + 1)); | |
| assert(sglist[count].size > 0); | |
| if (count != 0) | |
| next_packet.SCSIDiskOffset = next_packet.SCSIDiskOffset + | |
| (sglist[count - 1].size + 1); | |
| next_packet.size = sglist[count].size + 1; | |
| /**If the queue is empty, the transaction should be initiated*/ | |
| if (dmaWriteInfo.empty()) | |
| writeDevice(NULL, true, next_packet.start, next_packet.size, | |
| NULL, next_packet.SCSIDiskOffset, next_packet.LUN); | |
| else | |
| DPRINTF(UFSHostDevice, "Write not initiated queue: %d\n", | |
| dmaWriteInfo.size()); | |
| dmaWriteInfo.push_back(next_packet); | |
| DPRINTF(UFSHostDevice, "Write Location: 0x%8x\n", | |
| next_packet.SCSIDiskOffset); | |
| DPRINTF(UFSHostDevice, "Write transfer #: 0x%8x\n", count + 1); | |
| /** stats **/ | |
| stats.totalWrittenSSD += (sglist[count].size + 1); | |
| } | |
| /**stats**/ | |
| ++stats.totalWriteUFSTransactions; | |
| } | |
| /** | |
| * Write done handling function. Is only initiated when the flash is directly | |
| * approached | |
| */ | |
| void | |
| UFSHostDevice::writeDone() | |
| { | |
| /**DMA is done, information no longer needed*/ | |
| assert(dmaWriteInfo.size() > 0); | |
| dmaWriteInfo.pop_front(); | |
| assert(SSDWriteinfo.size() > 0); | |
| uint32_t lun = SSDWriteinfo.front().lunID; | |
| /**If there is nothing on the way, we need to start the events*/ | |
| DPRINTF(UFSHostDevice, "Write done entered, queue: %d\n", | |
| UFSDevice[lun]->SSDWriteDoneInfo.size()); | |
| /**Write the disk*/ | |
| UFSDevice[lun]->writeFlash(&SSDWriteinfo.front().buffer[0], | |
| SSDWriteinfo.front().offset, | |
| SSDWriteinfo.front().size); | |
| /** | |
| * Move to the second queue, enter the logic unit | |
| * This is where the disk is approached and the flash transaction is | |
| * handled SSDWriteDone will take care of the timing | |
| */ | |
| UFSDevice[lun]->SSDWriteDoneInfo.push_back(SSDWriteinfo.front()); | |
| SSDWriteinfo.pop_front(); | |
| --writePendingNum; | |
| /**so far, only the DMA part has been handled, lets do the disk delay*/ | |
| UFSDevice[lun]->SSDWriteStart(); | |
| /** stats **/ | |
| stats.currentWriteSSDQueue = UFSDevice[lun]->SSDWriteDoneInfo.size(); | |
| stats.averageWriteSSDQueue = UFSDevice[lun]->SSDWriteDoneInfo.size(); | |
| ++stats.totalWriteDiskTransactions; | |
| /**initiate the next dma action (if any)*/ | |
| if (!dmaWriteInfo.empty()) | |
| writeDevice(NULL, true, dmaWriteInfo.front().start, | |
| dmaWriteInfo.front().size, NULL, | |
| dmaWriteInfo.front().SCSIDiskOffset, | |
| dmaWriteInfo.front().LUN); | |
| DPRINTF(UFSHostDevice, "Write done end\n"); | |
| } | |
| /** | |
| * SSD write start. Starts the write action in the timing model | |
| */ | |
| void | |
| UFSHostDevice::UFSSCSIDevice::SSDWriteStart() | |
| { | |
| assert(SSDWriteDoneInfo.size() > 0); | |
| flashDevice->writeMemory( | |
| SSDWriteDoneInfo.front().offset, | |
| SSDWriteDoneInfo.front().size, memWriteCallback); | |
| SSDWriteDoneInfo.pop_front(); | |
| DPRINTF(UFSHostDevice, "Write is started; left in queue: %d\n", | |
| SSDWriteDoneInfo.size()); | |
| } | |
| /** | |
| * SSDisk write done | |
| */ | |
| void | |
| UFSHostDevice::UFSSCSIDevice::SSDWriteDone() | |
| { | |
| DPRINTF(UFSHostDevice, "Write disk, aiming for %d messages, %d so far\n", | |
| totalWrite, amountOfWriteTransfers); | |
| //we have done one extra transfer | |
| ++amountOfWriteTransfers; | |
| /**test whether call was correct*/ | |
| assert(totalWrite >= amountOfWriteTransfers && totalWrite != 0); | |
| /**are we there yet? (did we do everything)*/ | |
| if (totalWrite == amountOfWriteTransfers) { | |
| DPRINTF(UFSHostDevice, "Write transactions finished\n"); | |
| totalWrite = 0; | |
| amountOfWriteTransfers = 0; | |
| //Callback UFS Host | |
| setSignal(); | |
| signalDone->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( | |
| EventFunctionWrapper([this]{ readDone(); }, | |
| name())); | |
| assert(!readDoneEvent.back().scheduled()); | |
| dmaPort.dmaAction(MemCmd::WriteReq, start, size, | |
| &readDoneEvent.back(), destination, 0); | |
| //yes, a writereq at a read device function is correct. | |
| } else { | |
| if (additional_action != NULL) | |
| assert(!additional_action->scheduled()); | |
| dmaPort.dmaAction(MemCmd::WriteReq, start, size, | |
| additional_action, destination, 0); | |
| } | |
| } | |
| /** | |
| * Manage read transfer. Manages correct transfer flow and makes sure that | |
| * the queues are filled on time | |
| */ | |
| void | |
| UFSHostDevice::manageReadTransfer(uint32_t size, uint32_t LUN, uint64_t | |
| offset, uint32_t sg_table_length, | |
| struct UFSHCDSGEntry* sglist) | |
| { | |
| uint32_t size_accum = 0; | |
| DPRINTF(UFSHostDevice, "Data READ size: %d\n", size); | |
| /** | |
| * Break-up the transactions into actions defined by the scatter gather | |
| * list. | |
| */ | |
| for (uint32_t count = 0; count < sg_table_length; count++) { | |
| struct transferInfo new_transfer; | |
| new_transfer.offset = sglist[count].upperAddr; | |
| new_transfer.offset = (new_transfer.offset << 32) | | |
| (sglist[count].baseAddr & 0xFFFFFFFF); | |
| new_transfer.filePointer = offset + size_accum; | |
| new_transfer.size = (sglist[count].size + 1); | |
| new_transfer.lunID = LUN; | |
| DPRINTF(UFSHostDevice, "Data READ start: 0x%8x; size: %d\n", | |
| new_transfer.offset, new_transfer.size); | |
| UFSDevice[LUN]->SSDReadInfo.push_back(new_transfer); | |
| UFSDevice[LUN]->SSDReadInfo.back().buffer.resize(sglist[count].size | |
| + 1); | |
| /** | |
| * The disk image is read here; but the action is simultated later | |
| * You can see this as the preparation stage, whereas later is the | |
| * simulation phase. | |
| */ | |
| UFSDevice[LUN]->readFlash(&UFSDevice[LUN]-> | |
| SSDReadInfo.back().buffer[0], | |
| offset + size_accum, | |
| sglist[count].size + 1); | |
| size_accum += (sglist[count].size + 1); | |
| DPRINTF(UFSHostDevice, "Transfer %d; Remaining: 0x%8x, Accumulated:" | |
| " 0x%8x\n", (count + 1), (size-size_accum), size_accum); | |
| /** stats **/ | |
| stats.totalReadSSD += (sglist[count].size + 1); | |
| stats.currentReadSSDQueue = UFSDevice[LUN]->SSDReadInfo.size(); | |
| stats.averageReadSSDQueue = UFSDevice[LUN]->SSDReadInfo.size(); | |
| } | |
| UFSDevice[LUN]->SSDReadStart(sg_table_length); | |
| /** stats **/ | |
| ++stats.totalReadUFSTransactions; | |
| } | |
| /** | |
| * SSDisk start read; this function was created to keep the interfaces | |
| * between the layers simpler. Without this function UFSHost would need to | |
| * know about the flashdevice. | |
| */ | |
| void | |
| UFSHostDevice::UFSSCSIDevice::SSDReadStart(uint32_t total_read) | |
| { | |
| totalRead = total_read; | |
| for (uint32_t number_handled = 0; number_handled < SSDReadInfo.size(); | |
| number_handled++) { | |
| /** | |
| * Load all the read request to the Memory device. | |
| * It will call back when done. | |
| */ | |
| flashDevice->readMemory(SSDReadInfo.front().filePointer, | |
| SSDReadInfo.front().size, memReadCallback); | |
| } | |
| } | |
| /** | |
| * SSDisk read done | |
| */ | |
| void | |
| UFSHostDevice::UFSSCSIDevice::SSDReadDone() | |
| { | |
| DPRINTF(UFSHostDevice, "SSD read done at lun %d, Aiming for %d messages," | |
| " %d so far\n", lunID, totalRead, amountOfReadTransfers); | |
| if (totalRead == amountOfReadTransfers) { | |
| totalRead = 0; | |
| amountOfReadTransfers = 0; | |
| /**Callback: transferdone*/ | |
| setSignal(); | |
| signalDone->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( | |
| EventFunctionWrapper([this]{ readGarbage(); }, name())); | |
| //make sure the queue is popped a the end of the dma transaction | |
| readDevice(false, SSDReadPending.front().offset, | |
| SSDReadPending.front().size, | |
| &SSDReadPending.front().buffer[0], false, | |
| &readGarbageEventQueue.back()); | |
| /**stats*/ | |
| ++stats.totalReadDiskTransactions; | |
| } | |
| else | |
| panic("no read finished in tick %d\n", curTick()); | |
| } | |
| /** | |
| * After a disk read DMA transfer, the structure needs to be freed. This is | |
| * done in this function. | |
| */ | |
| void | |
| UFSHostDevice::readGarbage() | |
| { | |
| DPRINTF(UFSHostDevice, "Clean read data, %d\n", SSDReadPending.size()); | |
| SSDReadPending.pop_front(); | |
| readGarbageEventQueue.pop_front(); | |
| } | |
| /** | |
| * Serialize; needed to make checkpoints | |
| */ | |
| void | |
| UFSHostDevice::serialize(CheckpointOut &cp) const | |
| { | |
| DmaDevice::serialize(cp); | |
| const uint8_t* temp_HCI_mem = reinterpret_cast<const uint8_t*>(&UFSHCIMem); | |
| SERIALIZE_ARRAY(temp_HCI_mem, sizeof(HCIMem)); | |
| uint32_t lun_avail = lunAvail; | |
| SERIALIZE_SCALAR(lun_avail); | |
| } | |
| /** | |
| * Unserialize; needed to restore from checkpoints | |
| */ | |
| void | |
| UFSHostDevice::unserialize(CheckpointIn &cp) | |
| { | |
| DmaDevice::unserialize(cp); | |
| uint8_t* temp_HCI_mem = reinterpret_cast<uint8_t*>(&UFSHCIMem); | |
| UNSERIALIZE_ARRAY(temp_HCI_mem, sizeof(HCIMem)); | |
| uint32_t lun_avail; | |
| UNSERIALIZE_SCALAR(lun_avail); | |
| assert(lunAvail == lun_avail); | |
| } | |
| /** | |
| * Drain; needed to enable checkpoints | |
| */ | |
| DrainState | |
| UFSHostDevice::drain() | |
| { | |
| if (UFSHCIMem.TRUTRLDBR) { | |
| DPRINTF(UFSHostDevice, "UFSDevice is draining...\n"); | |
| return DrainState::Draining; | |
| } else { | |
| DPRINTF(UFSHostDevice, "UFSDevice drained\n"); | |
| return DrainState::Drained; | |
| } | |
| } | |
| /** | |
| * Checkdrain; needed to enable checkpoints | |
| */ | |
| void | |
| UFSHostDevice::checkDrain() | |
| { | |
| if (drainState() != DrainState::Draining) | |
| return; | |
| if (UFSHCIMem.TRUTRLDBR) { | |
| DPRINTF(UFSHostDevice, "UFSDevice is still draining; with %d active" | |
| " doorbells\n", activeDoorbells); | |
| } else { | |
| DPRINTF(UFSHostDevice, "UFSDevice is done draining\n"); | |
| signalDrainDone(); | |
| } | |
| } |