| /* |
| * 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(); |
| } |
| } |