| /* | |
| * Copyright (c) 2013 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. | |
| * | |
| * Copyright (c) 2004-2005 The Regents of The University of Michigan | |
| * All rights reserved. | |
| * | |
| * 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: Andrew Schultz | |
| * Ali Saidi | |
| */ | |
| /** @file | |
| * Device model implementation for an IDE disk | |
| */ | |
| #include "dev/storage/ide_disk.hh" | |
| #include <cerrno> | |
| #include <cstring> | |
| #include <deque> | |
| #include <string> | |
| #include "arch/isa_traits.hh" | |
| #include "base/chunk_generator.hh" | |
| #include "base/cprintf.hh" // csprintf | |
| #include "base/trace.hh" | |
| #include "config/the_isa.hh" | |
| #include "debug/IdeDisk.hh" | |
| #include "dev/storage/disk_image.hh" | |
| #include "dev/storage/ide_ctrl.hh" | |
| #include "sim/core.hh" | |
| #include "sim/sim_object.hh" | |
| using namespace std; | |
| using namespace TheISA; | |
| IdeDisk::IdeDisk(const Params *p) | |
| : SimObject(p), ctrl(NULL), image(p->image), diskDelay(p->delay), | |
| dmaTransferEvent([this]{ doDmaTransfer(); }, name()), | |
| dmaReadCG(NULL), | |
| dmaReadWaitEvent([this]{ doDmaRead(); }, name()), | |
| dmaWriteCG(NULL), | |
| dmaWriteWaitEvent([this]{ doDmaWrite(); }, name()), | |
| dmaPrdReadEvent([this]{ dmaPrdReadDone(); }, name()), | |
| dmaReadEvent([this]{ dmaReadDone(); }, name()), | |
| dmaWriteEvent([this]{ dmaWriteDone(); }, name()) | |
| { | |
| // Reset the device state | |
| reset(p->driveID); | |
| // fill out the drive ID structure | |
| memset(&driveID, 0, sizeof(struct ataparams)); | |
| // Calculate LBA and C/H/S values | |
| uint16_t cylinders; | |
| uint8_t heads; | |
| uint8_t sectors; | |
| uint32_t lba_size = image->size(); | |
| if (lba_size >= 16383*16*63) { | |
| cylinders = 16383; | |
| heads = 16; | |
| sectors = 63; | |
| } else { | |
| if (lba_size >= 63) | |
| sectors = 63; | |
| else if (lba_size == 0) | |
| panic("Bad IDE image size: 0\n"); | |
| else | |
| sectors = lba_size; | |
| if ((lba_size / sectors) >= 16) | |
| heads = 16; | |
| else | |
| heads = (lba_size / sectors); | |
| cylinders = lba_size / (heads * sectors); | |
| } | |
| // Setup the model name | |
| strncpy((char *)driveID.atap_model, "5MI EDD si k", | |
| sizeof(driveID.atap_model)); | |
| // Set the maximum multisector transfer size | |
| driveID.atap_multi = MAX_MULTSECT; | |
| // IORDY supported, IORDY disabled, LBA enabled, DMA enabled | |
| driveID.atap_capabilities1 = 0x7; | |
| // UDMA support, EIDE support | |
| driveID.atap_extensions = 0x6; | |
| // Setup default C/H/S settings | |
| driveID.atap_cylinders = cylinders; | |
| driveID.atap_sectors = sectors; | |
| driveID.atap_heads = heads; | |
| // Setup the current multisector transfer size | |
| driveID.atap_curmulti = MAX_MULTSECT; | |
| driveID.atap_curmulti_valid = 0x1; | |
| // Number of sectors on disk | |
| driveID.atap_capacity = lba_size; | |
| // Multiword DMA mode 2 and below supported | |
| driveID.atap_dmamode_supp = 0x4; | |
| // Set PIO mode 4 and 3 supported | |
| driveID.atap_piomode_supp = 0x3; | |
| // Set DMA mode 4 and below supported | |
| driveID.atap_udmamode_supp = 0x1f; | |
| // Statically set hardware config word | |
| driveID.atap_hwreset_res = 0x4001; | |
| //arbitrary for now... | |
| driveID.atap_ata_major = WDC_VER_ATA7; | |
| } | |
| IdeDisk::~IdeDisk() | |
| { | |
| // destroy the data buffer | |
| delete [] dataBuffer; | |
| } | |
| void | |
| IdeDisk::reset(int id) | |
| { | |
| // initialize the data buffer and shadow registers | |
| dataBuffer = new uint8_t[MAX_DMA_SIZE]; | |
| memset(dataBuffer, 0, MAX_DMA_SIZE); | |
| memset(&cmdReg, 0, sizeof(CommandReg_t)); | |
| memset(&curPrd.entry, 0, sizeof(PrdEntry_t)); | |
| curPrdAddr = 0; | |
| curSector = 0; | |
| cmdBytes = 0; | |
| cmdBytesLeft = 0; | |
| drqBytesLeft = 0; | |
| dmaRead = false; | |
| intrPending = false; | |
| dmaAborted = false; | |
| // set the device state to idle | |
| dmaState = Dma_Idle; | |
| if (id == DEV0) { | |
| devState = Device_Idle_S; | |
| devID = DEV0; | |
| } else if (id == DEV1) { | |
| devState = Device_Idle_NS; | |
| devID = DEV1; | |
| } else { | |
| panic("Invalid device ID: %#x\n", id); | |
| } | |
| // set the device ready bit | |
| status = STATUS_DRDY_BIT; | |
| /* The error register must be set to 0x1 on start-up to | |
| indicate that no diagnostic error was detected */ | |
| cmdReg.error = 0x1; | |
| } | |
| //// | |
| // Utility functions | |
| //// | |
| bool | |
| IdeDisk::isDEVSelect() | |
| { | |
| return ctrl->isDiskSelected(this); | |
| } | |
| Addr | |
| IdeDisk::pciToDma(Addr pciAddr) | |
| { | |
| if (ctrl) | |
| return ctrl->pciToDma(pciAddr); | |
| else | |
| panic("Access to unset controller!\n"); | |
| } | |
| //// | |
| // Device registers read/write | |
| //// | |
| void | |
| IdeDisk::readCommand(const Addr offset, int size, uint8_t *data) | |
| { | |
| if (offset == DATA_OFFSET) { | |
| if (size == sizeof(uint16_t)) { | |
| *(uint16_t *)data = cmdReg.data; | |
| } else if (size == sizeof(uint32_t)) { | |
| *(uint16_t *)data = cmdReg.data; | |
| updateState(ACT_DATA_READ_SHORT); | |
| *((uint16_t *)data + 1) = cmdReg.data; | |
| } else { | |
| panic("Data read of unsupported size %d.\n", size); | |
| } | |
| updateState(ACT_DATA_READ_SHORT); | |
| return; | |
| } | |
| assert(size == sizeof(uint8_t)); | |
| switch (offset) { | |
| case ERROR_OFFSET: | |
| *data = cmdReg.error; | |
| break; | |
| case NSECTOR_OFFSET: | |
| *data = cmdReg.sec_count; | |
| break; | |
| case SECTOR_OFFSET: | |
| *data = cmdReg.sec_num; | |
| break; | |
| case LCYL_OFFSET: | |
| *data = cmdReg.cyl_low; | |
| break; | |
| case HCYL_OFFSET: | |
| *data = cmdReg.cyl_high; | |
| break; | |
| case DRIVE_OFFSET: | |
| *data = cmdReg.drive; | |
| break; | |
| case STATUS_OFFSET: | |
| *data = status; | |
| updateState(ACT_STAT_READ); | |
| break; | |
| default: | |
| panic("Invalid IDE command register offset: %#x\n", offset); | |
| } | |
| DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data); | |
| } | |
| void | |
| IdeDisk::readControl(const Addr offset, int size, uint8_t *data) | |
| { | |
| assert(size == sizeof(uint8_t)); | |
| *data = status; | |
| if (offset != ALTSTAT_OFFSET) | |
| panic("Invalid IDE control register offset: %#x\n", offset); | |
| DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data); | |
| } | |
| void | |
| IdeDisk::writeCommand(const Addr offset, int size, const uint8_t *data) | |
| { | |
| if (offset == DATA_OFFSET) { | |
| if (size == sizeof(uint16_t)) { | |
| cmdReg.data = *(const uint16_t *)data; | |
| } else if (size == sizeof(uint32_t)) { | |
| cmdReg.data = *(const uint16_t *)data; | |
| updateState(ACT_DATA_WRITE_SHORT); | |
| cmdReg.data = *((const uint16_t *)data + 1); | |
| } else { | |
| panic("Data write of unsupported size %d.\n", size); | |
| } | |
| updateState(ACT_DATA_WRITE_SHORT); | |
| return; | |
| } | |
| assert(size == sizeof(uint8_t)); | |
| switch (offset) { | |
| case FEATURES_OFFSET: | |
| break; | |
| case NSECTOR_OFFSET: | |
| cmdReg.sec_count = *data; | |
| break; | |
| case SECTOR_OFFSET: | |
| cmdReg.sec_num = *data; | |
| break; | |
| case LCYL_OFFSET: | |
| cmdReg.cyl_low = *data; | |
| break; | |
| case HCYL_OFFSET: | |
| cmdReg.cyl_high = *data; | |
| break; | |
| case DRIVE_OFFSET: | |
| cmdReg.drive = *data; | |
| updateState(ACT_SELECT_WRITE); | |
| break; | |
| case COMMAND_OFFSET: | |
| cmdReg.command = *data; | |
| updateState(ACT_CMD_WRITE); | |
| break; | |
| default: | |
| panic("Invalid IDE command register offset: %#x\n", offset); | |
| } | |
| DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset, | |
| (uint32_t)*data); | |
| } | |
| void | |
| IdeDisk::writeControl(const Addr offset, int size, const uint8_t *data) | |
| { | |
| if (offset != CONTROL_OFFSET) | |
| panic("Invalid IDE control register offset: %#x\n", offset); | |
| if (*data & CONTROL_RST_BIT) { | |
| // force the device into the reset state | |
| devState = Device_Srst; | |
| updateState(ACT_SRST_SET); | |
| } else if (devState == Device_Srst && !(*data & CONTROL_RST_BIT)) { | |
| updateState(ACT_SRST_CLEAR); | |
| } | |
| nIENBit = *data & CONTROL_IEN_BIT; | |
| DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset, | |
| (uint32_t)*data); | |
| } | |
| //// | |
| // Perform DMA transactions | |
| //// | |
| void | |
| IdeDisk::doDmaTransfer() | |
| { | |
| if (dmaAborted) { | |
| DPRINTF(IdeDisk, "DMA Aborted before reading PRD entry\n"); | |
| updateState(ACT_CMD_ERROR); | |
| return; | |
| } | |
| if (dmaState != Dma_Transfer || devState != Transfer_Data_Dma) | |
| panic("Inconsistent DMA transfer state: dmaState = %d devState = %d\n", | |
| dmaState, devState); | |
| if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) { | |
| schedule(dmaTransferEvent, curTick() + DMA_BACKOFF_PERIOD); | |
| return; | |
| } else | |
| ctrl->dmaRead(curPrdAddr, sizeof(PrdEntry_t), &dmaPrdReadEvent, | |
| (uint8_t*)&curPrd.entry); | |
| } | |
| void | |
| IdeDisk::dmaPrdReadDone() | |
| { | |
| if (dmaAborted) { | |
| DPRINTF(IdeDisk, "DMA Aborted while reading PRD entry\n"); | |
| updateState(ACT_CMD_ERROR); | |
| return; | |
| } | |
| DPRINTF(IdeDisk, | |
| "PRD: baseAddr:%#x (%#x) byteCount:%d (%d) eot:%#x sector:%d\n", | |
| curPrd.getBaseAddr(), pciToDma(curPrd.getBaseAddr()), | |
| curPrd.getByteCount(), (cmdBytesLeft/SectorSize), | |
| curPrd.getEOT(), curSector); | |
| // the prd pointer has already been translated, so just do an increment | |
| curPrdAddr = curPrdAddr + sizeof(PrdEntry_t); | |
| if (dmaRead) | |
| doDmaDataRead(); | |
| else | |
| doDmaDataWrite(); | |
| } | |
| void | |
| IdeDisk::doDmaDataRead() | |
| { | |
| /** @todo we need to figure out what the delay actually will be */ | |
| Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize); | |
| DPRINTF(IdeDisk, "doDmaRead, diskDelay: %d totalDiskDelay: %d\n", | |
| diskDelay, totalDiskDelay); | |
| schedule(dmaReadWaitEvent, curTick() + totalDiskDelay); | |
| } | |
| void | |
| IdeDisk::regStats() | |
| { | |
| SimObject::regStats(); | |
| using namespace Stats; | |
| dmaReadFullPages | |
| .name(name() + ".dma_read_full_pages") | |
| .desc("Number of full page size DMA reads (not PRD).") | |
| ; | |
| dmaReadBytes | |
| .name(name() + ".dma_read_bytes") | |
| .desc("Number of bytes transfered via DMA reads (not PRD).") | |
| ; | |
| dmaReadTxs | |
| .name(name() + ".dma_read_txs") | |
| .desc("Number of DMA read transactions (not PRD).") | |
| ; | |
| dmaWriteFullPages | |
| .name(name() + ".dma_write_full_pages") | |
| .desc("Number of full page size DMA writes.") | |
| ; | |
| dmaWriteBytes | |
| .name(name() + ".dma_write_bytes") | |
| .desc("Number of bytes transfered via DMA writes.") | |
| ; | |
| dmaWriteTxs | |
| .name(name() + ".dma_write_txs") | |
| .desc("Number of DMA write transactions.") | |
| ; | |
| } | |
| void | |
| IdeDisk::doDmaRead() | |
| { | |
| if (dmaAborted) { | |
| DPRINTF(IdeDisk, "DMA Aborted in middle of Dma Read\n"); | |
| if (dmaReadCG) | |
| delete dmaReadCG; | |
| dmaReadCG = NULL; | |
| updateState(ACT_CMD_ERROR); | |
| return; | |
| } | |
| if (!dmaReadCG) { | |
| // clear out the data buffer | |
| memset(dataBuffer, 0, MAX_DMA_SIZE); | |
| dmaReadCG = new ChunkGenerator(curPrd.getBaseAddr(), | |
| curPrd.getByteCount(), TheISA::PageBytes); | |
| } | |
| if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) { | |
| schedule(dmaReadWaitEvent, curTick() + DMA_BACKOFF_PERIOD); | |
| return; | |
| } else if (!dmaReadCG->done()) { | |
| assert(dmaReadCG->complete() < MAX_DMA_SIZE); | |
| ctrl->dmaRead(pciToDma(dmaReadCG->addr()), dmaReadCG->size(), | |
| &dmaReadWaitEvent, dataBuffer + dmaReadCG->complete()); | |
| dmaReadBytes += dmaReadCG->size(); | |
| dmaReadTxs++; | |
| if (dmaReadCG->size() == TheISA::PageBytes) | |
| dmaReadFullPages++; | |
| dmaReadCG->next(); | |
| } else { | |
| assert(dmaReadCG->done()); | |
| delete dmaReadCG; | |
| dmaReadCG = NULL; | |
| dmaReadDone(); | |
| } | |
| } | |
| void | |
| IdeDisk::dmaReadDone() | |
| { | |
| uint32_t bytesWritten = 0; | |
| // write the data to the disk image | |
| for (bytesWritten = 0; bytesWritten < curPrd.getByteCount(); | |
| bytesWritten += SectorSize) { | |
| cmdBytesLeft -= SectorSize; | |
| writeDisk(curSector++, (uint8_t *)(dataBuffer + bytesWritten)); | |
| } | |
| // check for the EOT | |
| if (curPrd.getEOT()) { | |
| assert(cmdBytesLeft == 0); | |
| dmaState = Dma_Idle; | |
| updateState(ACT_DMA_DONE); | |
| } else { | |
| doDmaTransfer(); | |
| } | |
| } | |
| void | |
| IdeDisk::doDmaDataWrite() | |
| { | |
| /** @todo we need to figure out what the delay actually will be */ | |
| Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize); | |
| uint32_t bytesRead = 0; | |
| DPRINTF(IdeDisk, "doDmaWrite, diskDelay: %d totalDiskDelay: %d\n", | |
| diskDelay, totalDiskDelay); | |
| memset(dataBuffer, 0, MAX_DMA_SIZE); | |
| assert(cmdBytesLeft <= MAX_DMA_SIZE); | |
| while (bytesRead < curPrd.getByteCount()) { | |
| readDisk(curSector++, (uint8_t *)(dataBuffer + bytesRead)); | |
| bytesRead += SectorSize; | |
| cmdBytesLeft -= SectorSize; | |
| } | |
| DPRINTF(IdeDisk, "doDmaWrite, bytesRead: %d cmdBytesLeft: %d\n", | |
| bytesRead, cmdBytesLeft); | |
| schedule(dmaWriteWaitEvent, curTick() + totalDiskDelay); | |
| } | |
| void | |
| IdeDisk::doDmaWrite() | |
| { | |
| if (dmaAborted) { | |
| DPRINTF(IdeDisk, "DMA Aborted while doing DMA Write\n"); | |
| if (dmaWriteCG) | |
| delete dmaWriteCG; | |
| dmaWriteCG = NULL; | |
| updateState(ACT_CMD_ERROR); | |
| return; | |
| } | |
| if (!dmaWriteCG) { | |
| // clear out the data buffer | |
| dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(), | |
| curPrd.getByteCount(), TheISA::PageBytes); | |
| } | |
| if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) { | |
| schedule(dmaWriteWaitEvent, curTick() + DMA_BACKOFF_PERIOD); | |
| DPRINTF(IdeDisk, "doDmaWrite: rescheduling\n"); | |
| return; | |
| } else if (!dmaWriteCG->done()) { | |
| assert(dmaWriteCG->complete() < MAX_DMA_SIZE); | |
| ctrl->dmaWrite(pciToDma(dmaWriteCG->addr()), dmaWriteCG->size(), | |
| &dmaWriteWaitEvent, dataBuffer + dmaWriteCG->complete()); | |
| DPRINTF(IdeDisk, "doDmaWrite: not done curPrd byte count %d, eot %#x\n", | |
| curPrd.getByteCount(), curPrd.getEOT()); | |
| dmaWriteBytes += dmaWriteCG->size(); | |
| dmaWriteTxs++; | |
| if (dmaWriteCG->size() == TheISA::PageBytes) | |
| dmaWriteFullPages++; | |
| dmaWriteCG->next(); | |
| } else { | |
| DPRINTF(IdeDisk, "doDmaWrite: done curPrd byte count %d, eot %#x\n", | |
| curPrd.getByteCount(), curPrd.getEOT()); | |
| assert(dmaWriteCG->done()); | |
| delete dmaWriteCG; | |
| dmaWriteCG = NULL; | |
| dmaWriteDone(); | |
| } | |
| } | |
| void | |
| IdeDisk::dmaWriteDone() | |
| { | |
| DPRINTF(IdeDisk, "doWriteDone: curPrd byte count %d, eot %#x cmd bytes left:%d\n", | |
| curPrd.getByteCount(), curPrd.getEOT(), cmdBytesLeft); | |
| // check for the EOT | |
| if (curPrd.getEOT()) { | |
| assert(cmdBytesLeft == 0); | |
| dmaState = Dma_Idle; | |
| updateState(ACT_DMA_DONE); | |
| } else { | |
| doDmaTransfer(); | |
| } | |
| } | |
| //// | |
| // Disk utility routines | |
| /// | |
| void | |
| IdeDisk::readDisk(uint32_t sector, uint8_t *data) | |
| { | |
| uint32_t bytesRead = image->read(data, sector); | |
| if (bytesRead != SectorSize) | |
| panic("Can't read from %s. Only %d of %d read. errno=%d\n", | |
| name(), bytesRead, SectorSize, errno); | |
| } | |
| void | |
| IdeDisk::writeDisk(uint32_t sector, uint8_t *data) | |
| { | |
| uint32_t bytesWritten = image->write(data, sector); | |
| if (bytesWritten != SectorSize) | |
| panic("Can't write to %s. Only %d of %d written. errno=%d\n", | |
| name(), bytesWritten, SectorSize, errno); | |
| } | |
| //// | |
| // Setup and handle commands | |
| //// | |
| void | |
| IdeDisk::startDma(const uint32_t &prdTableBase) | |
| { | |
| if (dmaState != Dma_Start) | |
| panic("Inconsistent DMA state, should be in Dma_Start!\n"); | |
| if (devState != Transfer_Data_Dma) | |
| panic("Inconsistent device state for DMA start!\n"); | |
| // PRD base address is given by bits 31:2 | |
| curPrdAddr = pciToDma((Addr)(prdTableBase & ~ULL(0x3))); | |
| dmaState = Dma_Transfer; | |
| // schedule dma transfer (doDmaTransfer) | |
| schedule(dmaTransferEvent, curTick() + 1); | |
| } | |
| void | |
| IdeDisk::abortDma() | |
| { | |
| if (dmaState == Dma_Idle) | |
| panic("Inconsistent DMA state, should be Start or Transfer!"); | |
| if (devState != Transfer_Data_Dma && devState != Prepare_Data_Dma) | |
| panic("Inconsistent device state, should be Transfer or Prepare!\n"); | |
| updateState(ACT_CMD_ERROR); | |
| } | |
| void | |
| IdeDisk::startCommand() | |
| { | |
| DevAction_t action = ACT_NONE; | |
| uint32_t size = 0; | |
| dmaRead = false; | |
| // Decode commands | |
| switch (cmdReg.command) { | |
| // Supported non-data commands | |
| case WDSF_READ_NATIVE_MAX: | |
| size = (uint32_t)image->size() - 1; | |
| cmdReg.sec_num = (size & 0xff); | |
| cmdReg.cyl_low = ((size & 0xff00) >> 8); | |
| cmdReg.cyl_high = ((size & 0xff0000) >> 16); | |
| cmdReg.head = ((size & 0xf000000) >> 24); | |
| devState = Command_Execution; | |
| action = ACT_CMD_COMPLETE; | |
| break; | |
| case WDCC_RECAL: | |
| case WDCC_IDP: | |
| case WDCC_STANDBY_IMMED: | |
| case WDCC_FLUSHCACHE: | |
| case WDSF_VERIFY: | |
| case WDSF_SEEK: | |
| case SET_FEATURES: | |
| case WDCC_SETMULTI: | |
| case WDCC_IDLE: | |
| devState = Command_Execution; | |
| action = ACT_CMD_COMPLETE; | |
| break; | |
| // Supported PIO data-in commands | |
| case WDCC_IDENTIFY: | |
| case ATAPI_IDENTIFY_DEVICE: | |
| cmdBytes = cmdBytesLeft = sizeof(struct ataparams); | |
| devState = Prepare_Data_In; | |
| action = ACT_DATA_READY; | |
| break; | |
| case WDCC_READMULTI: | |
| case WDCC_READ: | |
| if (!(cmdReg.drive & DRIVE_LBA_BIT)) | |
| panic("Attempt to perform CHS access, only supports LBA\n"); | |
| if (cmdReg.sec_count == 0) | |
| cmdBytes = cmdBytesLeft = (256 * SectorSize); | |
| else | |
| cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize); | |
| curSector = getLBABase(); | |
| /** @todo make this a scheduled event to simulate disk delay */ | |
| devState = Prepare_Data_In; | |
| action = ACT_DATA_READY; | |
| break; | |
| // Supported PIO data-out commands | |
| case WDCC_WRITEMULTI: | |
| case WDCC_WRITE: | |
| if (!(cmdReg.drive & DRIVE_LBA_BIT)) | |
| panic("Attempt to perform CHS access, only supports LBA\n"); | |
| if (cmdReg.sec_count == 0) | |
| cmdBytes = cmdBytesLeft = (256 * SectorSize); | |
| else | |
| cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize); | |
| DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d\n", cmdBytesLeft); | |
| curSector = getLBABase(); | |
| devState = Prepare_Data_Out; | |
| action = ACT_DATA_READY; | |
| break; | |
| // Supported DMA commands | |
| case WDCC_WRITEDMA: | |
| dmaRead = true; // a write to the disk is a DMA read from memory | |
| M5_FALLTHROUGH; | |
| case WDCC_READDMA: | |
| if (!(cmdReg.drive & DRIVE_LBA_BIT)) | |
| panic("Attempt to perform CHS access, only supports LBA\n"); | |
| if (cmdReg.sec_count == 0) | |
| cmdBytes = cmdBytesLeft = (256 * SectorSize); | |
| else | |
| cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize); | |
| DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d in readdma\n", cmdBytesLeft); | |
| curSector = getLBABase(); | |
| devState = Prepare_Data_Dma; | |
| action = ACT_DMA_READY; | |
| break; | |
| default: | |
| panic("Unsupported ATA command: %#x\n", cmdReg.command); | |
| } | |
| if (action != ACT_NONE) { | |
| // set the BSY bit | |
| status |= STATUS_BSY_BIT; | |
| // clear the DRQ bit | |
| status &= ~STATUS_DRQ_BIT; | |
| // clear the DF bit | |
| status &= ~STATUS_DF_BIT; | |
| updateState(action); | |
| } | |
| } | |
| //// | |
| // Handle setting and clearing interrupts | |
| //// | |
| void | |
| IdeDisk::intrPost() | |
| { | |
| DPRINTF(IdeDisk, "Posting Interrupt\n"); | |
| if (intrPending) | |
| panic("Attempt to post an interrupt with one pending\n"); | |
| intrPending = true; | |
| // talk to controller to set interrupt | |
| if (ctrl) { | |
| ctrl->intrPost(); | |
| } | |
| } | |
| void | |
| IdeDisk::intrClear() | |
| { | |
| DPRINTF(IdeDisk, "Clearing Interrupt\n"); | |
| if (!intrPending) | |
| panic("Attempt to clear a non-pending interrupt\n"); | |
| intrPending = false; | |
| // talk to controller to clear interrupt | |
| if (ctrl) | |
| ctrl->intrClear(); | |
| } | |
| //// | |
| // Manage the device internal state machine | |
| //// | |
| void | |
| IdeDisk::updateState(DevAction_t action) | |
| { | |
| switch (devState) { | |
| case Device_Srst: | |
| if (action == ACT_SRST_SET) { | |
| // set the BSY bit | |
| status |= STATUS_BSY_BIT; | |
| } else if (action == ACT_SRST_CLEAR) { | |
| // clear the BSY bit | |
| status &= ~STATUS_BSY_BIT; | |
| // reset the device state | |
| reset(devID); | |
| } | |
| break; | |
| case Device_Idle_S: | |
| if (action == ACT_SELECT_WRITE && !isDEVSelect()) { | |
| devState = Device_Idle_NS; | |
| } else if (action == ACT_CMD_WRITE) { | |
| startCommand(); | |
| } | |
| break; | |
| case Device_Idle_SI: | |
| if (action == ACT_SELECT_WRITE && !isDEVSelect()) { | |
| devState = Device_Idle_NS; | |
| intrClear(); | |
| } else if (action == ACT_STAT_READ || isIENSet()) { | |
| devState = Device_Idle_S; | |
| intrClear(); | |
| } else if (action == ACT_CMD_WRITE) { | |
| intrClear(); | |
| startCommand(); | |
| } | |
| break; | |
| case Device_Idle_NS: | |
| if (action == ACT_SELECT_WRITE && isDEVSelect()) { | |
| if (!isIENSet() && intrPending) { | |
| devState = Device_Idle_SI; | |
| intrPost(); | |
| } | |
| if (isIENSet() || !intrPending) { | |
| devState = Device_Idle_S; | |
| } | |
| } | |
| break; | |
| case Command_Execution: | |
| if (action == ACT_CMD_COMPLETE) { | |
| // clear the BSY bit | |
| setComplete(); | |
| if (!isIENSet()) { | |
| devState = Device_Idle_SI; | |
| intrPost(); | |
| } else { | |
| devState = Device_Idle_S; | |
| } | |
| } | |
| break; | |
| case Prepare_Data_In: | |
| if (action == ACT_CMD_ERROR) { | |
| // clear the BSY bit | |
| setComplete(); | |
| if (!isIENSet()) { | |
| devState = Device_Idle_SI; | |
| intrPost(); | |
| } else { | |
| devState = Device_Idle_S; | |
| } | |
| } else if (action == ACT_DATA_READY) { | |
| // clear the BSY bit | |
| status &= ~STATUS_BSY_BIT; | |
| // set the DRQ bit | |
| status |= STATUS_DRQ_BIT; | |
| // copy the data into the data buffer | |
| if (cmdReg.command == WDCC_IDENTIFY || | |
| cmdReg.command == ATAPI_IDENTIFY_DEVICE) { | |
| // Reset the drqBytes for this block | |
| drqBytesLeft = sizeof(struct ataparams); | |
| memcpy((void *)dataBuffer, (void *)&driveID, | |
| sizeof(struct ataparams)); | |
| } else { | |
| // Reset the drqBytes for this block | |
| drqBytesLeft = SectorSize; | |
| readDisk(curSector++, dataBuffer); | |
| } | |
| // put the first two bytes into the data register | |
| memcpy((void *)&cmdReg.data, (void *)dataBuffer, | |
| sizeof(uint16_t)); | |
| if (!isIENSet()) { | |
| devState = Data_Ready_INTRQ_In; | |
| intrPost(); | |
| } else { | |
| devState = Transfer_Data_In; | |
| } | |
| } | |
| break; | |
| case Data_Ready_INTRQ_In: | |
| if (action == ACT_STAT_READ) { | |
| devState = Transfer_Data_In; | |
| intrClear(); | |
| } | |
| break; | |
| case Transfer_Data_In: | |
| if (action == ACT_DATA_READ_BYTE || action == ACT_DATA_READ_SHORT) { | |
| if (action == ACT_DATA_READ_BYTE) { | |
| panic("DEBUG: READING DATA ONE BYTE AT A TIME!\n"); | |
| } else { | |
| drqBytesLeft -= 2; | |
| cmdBytesLeft -= 2; | |
| // copy next short into data registers | |
| if (drqBytesLeft) | |
| memcpy((void *)&cmdReg.data, | |
| (void *)&dataBuffer[SectorSize - drqBytesLeft], | |
| sizeof(uint16_t)); | |
| } | |
| if (drqBytesLeft == 0) { | |
| if (cmdBytesLeft == 0) { | |
| // Clear the BSY bit | |
| setComplete(); | |
| devState = Device_Idle_S; | |
| } else { | |
| devState = Prepare_Data_In; | |
| // set the BSY_BIT | |
| status |= STATUS_BSY_BIT; | |
| // clear the DRQ_BIT | |
| status &= ~STATUS_DRQ_BIT; | |
| /** @todo change this to a scheduled event to simulate | |
| disk delay */ | |
| updateState(ACT_DATA_READY); | |
| } | |
| } | |
| } | |
| break; | |
| case Prepare_Data_Out: | |
| if (action == ACT_CMD_ERROR || cmdBytesLeft == 0) { | |
| // clear the BSY bit | |
| setComplete(); | |
| if (!isIENSet()) { | |
| devState = Device_Idle_SI; | |
| intrPost(); | |
| } else { | |
| devState = Device_Idle_S; | |
| } | |
| } else if (action == ACT_DATA_READY && cmdBytesLeft != 0) { | |
| // clear the BSY bit | |
| status &= ~STATUS_BSY_BIT; | |
| // set the DRQ bit | |
| status |= STATUS_DRQ_BIT; | |
| // clear the data buffer to get it ready for writes | |
| memset(dataBuffer, 0, MAX_DMA_SIZE); | |
| // reset the drqBytes for this block | |
| drqBytesLeft = SectorSize; | |
| if (cmdBytesLeft == cmdBytes || isIENSet()) { | |
| devState = Transfer_Data_Out; | |
| } else { | |
| devState = Data_Ready_INTRQ_Out; | |
| intrPost(); | |
| } | |
| } | |
| break; | |
| case Data_Ready_INTRQ_Out: | |
| if (action == ACT_STAT_READ) { | |
| devState = Transfer_Data_Out; | |
| intrClear(); | |
| } | |
| break; | |
| case Transfer_Data_Out: | |
| if (action == ACT_DATA_WRITE_BYTE || | |
| action == ACT_DATA_WRITE_SHORT) { | |
| if (action == ACT_DATA_READ_BYTE) { | |
| panic("DEBUG: WRITING DATA ONE BYTE AT A TIME!\n"); | |
| } else { | |
| // copy the latest short into the data buffer | |
| memcpy((void *)&dataBuffer[SectorSize - drqBytesLeft], | |
| (void *)&cmdReg.data, | |
| sizeof(uint16_t)); | |
| drqBytesLeft -= 2; | |
| cmdBytesLeft -= 2; | |
| } | |
| if (drqBytesLeft == 0) { | |
| // copy the block to the disk | |
| writeDisk(curSector++, dataBuffer); | |
| // set the BSY bit | |
| status |= STATUS_BSY_BIT; | |
| // set the seek bit | |
| status |= STATUS_SEEK_BIT; | |
| // clear the DRQ bit | |
| status &= ~STATUS_DRQ_BIT; | |
| devState = Prepare_Data_Out; | |
| /** @todo change this to a scheduled event to simulate | |
| disk delay */ | |
| updateState(ACT_DATA_READY); | |
| } | |
| } | |
| break; | |
| case Prepare_Data_Dma: | |
| if (action == ACT_CMD_ERROR) { | |
| // clear the BSY bit | |
| setComplete(); | |
| if (!isIENSet()) { | |
| devState = Device_Idle_SI; | |
| intrPost(); | |
| } else { | |
| devState = Device_Idle_S; | |
| } | |
| } else if (action == ACT_DMA_READY) { | |
| // clear the BSY bit | |
| status &= ~STATUS_BSY_BIT; | |
| // set the DRQ bit | |
| status |= STATUS_DRQ_BIT; | |
| devState = Transfer_Data_Dma; | |
| if (dmaState != Dma_Idle) | |
| panic("Inconsistent DMA state, should be Dma_Idle\n"); | |
| dmaState = Dma_Start; | |
| // wait for the write to the DMA start bit | |
| } | |
| break; | |
| case Transfer_Data_Dma: | |
| if (action == ACT_CMD_ERROR) { | |
| dmaAborted = true; | |
| devState = Device_Dma_Abort; | |
| } else if (action == ACT_DMA_DONE) { | |
| // clear the BSY bit | |
| setComplete(); | |
| // set the seek bit | |
| status |= STATUS_SEEK_BIT; | |
| // clear the controller state for DMA transfer | |
| ctrl->setDmaComplete(this); | |
| if (!isIENSet()) { | |
| devState = Device_Idle_SI; | |
| intrPost(); | |
| } else { | |
| devState = Device_Idle_S; | |
| } | |
| } | |
| break; | |
| case Device_Dma_Abort: | |
| if (action == ACT_CMD_ERROR) { | |
| setComplete(); | |
| status |= STATUS_SEEK_BIT; | |
| ctrl->setDmaComplete(this); | |
| dmaAborted = false; | |
| dmaState = Dma_Idle; | |
| if (!isIENSet()) { | |
| devState = Device_Idle_SI; | |
| intrPost(); | |
| } else { | |
| devState = Device_Idle_S; | |
| } | |
| } else { | |
| DPRINTF(IdeDisk, "Disk still busy aborting previous DMA command\n"); | |
| } | |
| break; | |
| default: | |
| panic("Unknown IDE device state: %#x\n", devState); | |
| } | |
| } | |
| void | |
| IdeDisk::serialize(CheckpointOut &cp) const | |
| { | |
| // Check all outstanding events to see if they are scheduled | |
| // these are all mutually exclusive | |
| Tick reschedule = 0; | |
| Events_t event = None; | |
| int eventCount = 0; | |
| if (dmaTransferEvent.scheduled()) { | |
| reschedule = dmaTransferEvent.when(); | |
| event = Transfer; | |
| eventCount++; | |
| } | |
| if (dmaReadWaitEvent.scheduled()) { | |
| reschedule = dmaReadWaitEvent.when(); | |
| event = ReadWait; | |
| eventCount++; | |
| } | |
| if (dmaWriteWaitEvent.scheduled()) { | |
| reschedule = dmaWriteWaitEvent.when(); | |
| event = WriteWait; | |
| eventCount++; | |
| } | |
| if (dmaPrdReadEvent.scheduled()) { | |
| reschedule = dmaPrdReadEvent.when(); | |
| event = PrdRead; | |
| eventCount++; | |
| } | |
| if (dmaReadEvent.scheduled()) { | |
| reschedule = dmaReadEvent.when(); | |
| event = DmaRead; | |
| eventCount++; | |
| } | |
| if (dmaWriteEvent.scheduled()) { | |
| reschedule = dmaWriteEvent.when(); | |
| event = DmaWrite; | |
| eventCount++; | |
| } | |
| assert(eventCount <= 1); | |
| SERIALIZE_SCALAR(reschedule); | |
| SERIALIZE_ENUM(event); | |
| // Serialize device registers | |
| SERIALIZE_SCALAR(cmdReg.data); | |
| SERIALIZE_SCALAR(cmdReg.sec_count); | |
| SERIALIZE_SCALAR(cmdReg.sec_num); | |
| SERIALIZE_SCALAR(cmdReg.cyl_low); | |
| SERIALIZE_SCALAR(cmdReg.cyl_high); | |
| SERIALIZE_SCALAR(cmdReg.drive); | |
| SERIALIZE_SCALAR(cmdReg.command); | |
| SERIALIZE_SCALAR(status); | |
| SERIALIZE_SCALAR(nIENBit); | |
| SERIALIZE_SCALAR(devID); | |
| // Serialize the PRD related information | |
| SERIALIZE_SCALAR(curPrd.entry.baseAddr); | |
| SERIALIZE_SCALAR(curPrd.entry.byteCount); | |
| SERIALIZE_SCALAR(curPrd.entry.endOfTable); | |
| SERIALIZE_SCALAR(curPrdAddr); | |
| /** @todo need to serialized chunk generator stuff!! */ | |
| // Serialize current transfer related information | |
| SERIALIZE_SCALAR(cmdBytesLeft); | |
| SERIALIZE_SCALAR(cmdBytes); | |
| SERIALIZE_SCALAR(drqBytesLeft); | |
| SERIALIZE_SCALAR(curSector); | |
| SERIALIZE_SCALAR(dmaRead); | |
| SERIALIZE_SCALAR(intrPending); | |
| SERIALIZE_SCALAR(dmaAborted); | |
| SERIALIZE_ENUM(devState); | |
| SERIALIZE_ENUM(dmaState); | |
| SERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); | |
| } | |
| void | |
| IdeDisk::unserialize(CheckpointIn &cp) | |
| { | |
| // Reschedule events that were outstanding | |
| // these are all mutually exclusive | |
| Tick reschedule = 0; | |
| Events_t event = None; | |
| UNSERIALIZE_SCALAR(reschedule); | |
| UNSERIALIZE_ENUM(event); | |
| switch (event) { | |
| case None : break; | |
| case Transfer : schedule(dmaTransferEvent, reschedule); break; | |
| case ReadWait : schedule(dmaReadWaitEvent, reschedule); break; | |
| case WriteWait : schedule(dmaWriteWaitEvent, reschedule); break; | |
| case PrdRead : schedule(dmaPrdReadEvent, reschedule); break; | |
| case DmaRead : schedule(dmaReadEvent, reschedule); break; | |
| case DmaWrite : schedule(dmaWriteEvent, reschedule); break; | |
| } | |
| // Unserialize device registers | |
| UNSERIALIZE_SCALAR(cmdReg.data); | |
| UNSERIALIZE_SCALAR(cmdReg.sec_count); | |
| UNSERIALIZE_SCALAR(cmdReg.sec_num); | |
| UNSERIALIZE_SCALAR(cmdReg.cyl_low); | |
| UNSERIALIZE_SCALAR(cmdReg.cyl_high); | |
| UNSERIALIZE_SCALAR(cmdReg.drive); | |
| UNSERIALIZE_SCALAR(cmdReg.command); | |
| UNSERIALIZE_SCALAR(status); | |
| UNSERIALIZE_SCALAR(nIENBit); | |
| UNSERIALIZE_SCALAR(devID); | |
| // Unserialize the PRD related information | |
| UNSERIALIZE_SCALAR(curPrd.entry.baseAddr); | |
| UNSERIALIZE_SCALAR(curPrd.entry.byteCount); | |
| UNSERIALIZE_SCALAR(curPrd.entry.endOfTable); | |
| UNSERIALIZE_SCALAR(curPrdAddr); | |
| /** @todo need to serialized chunk generator stuff!! */ | |
| // Unserialize current transfer related information | |
| UNSERIALIZE_SCALAR(cmdBytes); | |
| UNSERIALIZE_SCALAR(cmdBytesLeft); | |
| UNSERIALIZE_SCALAR(drqBytesLeft); | |
| UNSERIALIZE_SCALAR(curSector); | |
| UNSERIALIZE_SCALAR(dmaRead); | |
| UNSERIALIZE_SCALAR(intrPending); | |
| UNSERIALIZE_SCALAR(dmaAborted); | |
| UNSERIALIZE_ENUM(devState); | |
| UNSERIALIZE_ENUM(dmaState); | |
| UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); | |
| } | |
| IdeDisk * | |
| IdeDiskParams::create() | |
| { | |
| return new IdeDisk(this); | |
| } |