|  | /* | 
|  | * 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 <cerrno> | 
|  | #include <cstring> | 
|  | #include <deque> | 
|  | #include <string> | 
|  |  | 
|  | #include "arch/isa_traits.hh" | 
|  | #include "config/the_isa.hh" | 
|  | #include "base/chunk_generator.hh" | 
|  | #include "base/cprintf.hh" // csprintf | 
|  | #include "base/trace.hh" | 
|  | #include "dev/disk_image.hh" | 
|  | #include "dev/ide_ctrl.hh" | 
|  | #include "dev/ide_disk.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), dmaReadCG(NULL), dmaReadWaitEvent(this), | 
|  | dmaWriteCG(NULL), dmaWriteWaitEvent(this), dmaPrdReadEvent(this), | 
|  | dmaReadEvent(this), dmaWriteEvent(this) | 
|  | { | 
|  | // 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 | 
|  | 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; | 
|  |  | 
|  | // 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 (dmaState != Dma_Transfer || devState != Transfer_Data_Dma) | 
|  | panic("Inconsistent DMA transfer state: dmaState = %d devState = %d\n", | 
|  | dmaState, devState); | 
|  |  | 
|  | if (ctrl->dmaPending() || ctrl->getState() != SimObject::Running) { | 
|  | schedule(dmaTransferEvent, curTick + DMA_BACKOFF_PERIOD); | 
|  | return; | 
|  | } else | 
|  | ctrl->dmaRead(curPrdAddr, sizeof(PrdEntry_t), &dmaPrdReadEvent, | 
|  | (uint8_t*)&curPrd.entry); | 
|  | } | 
|  |  | 
|  | void | 
|  | IdeDisk::dmaPrdReadDone() | 
|  | { | 
|  | 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() | 
|  | { | 
|  | 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 (!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->getState() != SimObject::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; | 
|  | } | 
|  |  | 
|  | schedule(dmaWriteWaitEvent, curTick + totalDiskDelay); | 
|  | } | 
|  |  | 
|  | void | 
|  | IdeDisk::doDmaWrite() | 
|  | { | 
|  |  | 
|  | if (!dmaWriteCG) { | 
|  | // clear out the data buffer | 
|  | dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(), | 
|  | curPrd.getByteCount(), TheISA::PageBytes); | 
|  | } | 
|  | if (ctrl->dmaPending() || ctrl->getState() != SimObject::Running) { | 
|  | schedule(dmaWriteWaitEvent, curTick + DMA_BACKOFF_PERIOD); | 
|  | return; | 
|  | } else if (!dmaWriteCG->done()) { | 
|  | assert(dmaWriteCG->complete() < MAX_DMA_SIZE); | 
|  | ctrl->dmaWrite(pciToDma(dmaWriteCG->addr()), dmaWriteCG->size(), | 
|  | &dmaWriteWaitEvent, dataBuffer + dmaWriteCG->complete()); | 
|  | dmaWriteBytes += dmaWriteCG->size(); | 
|  | dmaWriteTxs++; | 
|  | if (dmaWriteCG->size() == TheISA::PageBytes) | 
|  | dmaWriteFullPages++; | 
|  | dmaWriteCG->next(); | 
|  | } else { | 
|  | assert(dmaWriteCG->done()); | 
|  | delete dmaWriteCG; | 
|  | dmaWriteCG = NULL; | 
|  | dmaWriteDone(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | IdeDisk::dmaWriteDone() | 
|  | { | 
|  | // 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 = 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: | 
|  | devState = Command_Execution; | 
|  | action = ACT_CMD_COMPLETE; | 
|  | break; | 
|  |  | 
|  | // Supported PIO data-in commands | 
|  | case WDCC_IDENTIFY: | 
|  | 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); | 
|  |  | 
|  | 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 | 
|  | 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); | 
|  |  | 
|  | 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) { | 
|  | // 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 || 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; | 
|  |  | 
|  | default: | 
|  | panic("Unknown IDE device state: %#x\n", devState); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | IdeDisk::serialize(ostream &os) | 
|  | { | 
|  | // 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_ENUM(devState); | 
|  | SERIALIZE_ENUM(dmaState); | 
|  | SERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); | 
|  | } | 
|  |  | 
|  | void | 
|  | IdeDisk::unserialize(Checkpoint *cp, const string §ion) | 
|  | { | 
|  | // 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_ENUM(devState); | 
|  | UNSERIALIZE_ENUM(dmaState); | 
|  | UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); | 
|  | } | 
|  |  | 
|  | IdeDisk * | 
|  | IdeDiskParams::create() | 
|  | { | 
|  | return new IdeDisk(this); | 
|  | } |