| /* |
| * 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. |
| */ |
| |
| /** @file |
| * Device model implementation for an IDE disk |
| */ |
| |
| #include <cerrno> |
| #include <cstring> |
| #include <deque> |
| #include <string> |
| |
| #include "base/cprintf.hh" // csprintf |
| #include "base/trace.hh" |
| #include "dev/disk_image.hh" |
| #include "dev/ide_disk.hh" |
| #include "dev/ide_ctrl.hh" |
| #include "dev/tsunami.hh" |
| #include "dev/tsunami_pchip.hh" |
| #include "mem/functional/physical.hh" |
| #include "mem/bus/bus.hh" |
| #include "mem/bus/dma_interface.hh" |
| #include "mem/bus/pio_interface.hh" |
| #include "mem/bus/pio_interface_impl.hh" |
| #include "sim/builder.hh" |
| #include "sim/sim_object.hh" |
| #include "sim/root.hh" |
| #include "targetarch/isa_traits.hh" |
| |
| using namespace std; |
| |
| IdeDisk::IdeDisk(const string &name, DiskImage *img, PhysicalMemory *phys, |
| int id, Tick delay) |
| : SimObject(name), ctrl(NULL), image(img), physmem(phys), diskDelay(delay), |
| dmaTransferEvent(this), dmaReadWaitEvent(this), |
| dmaWriteWaitEvent(this), dmaPrdReadEvent(this), |
| dmaReadEvent(this), dmaWriteEvent(this) |
| { |
| // Reset the device state |
| reset(id); |
| |
| // 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 = 0x400; |
| // 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)); |
| |
| dmaInterfaceBytes = 0; |
| 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->plat->pciToDma(pciAddr); |
| else |
| panic("Access to unset controller!\n"); |
| } |
| |
| uint32_t |
| IdeDisk::bytesInDmaPage(Addr curAddr, uint32_t bytesLeft) |
| { |
| uint32_t bytesInPage = 0; |
| |
| // First calculate how many bytes could be in the page |
| if (bytesLeft > TheISA::PageBytes) |
| bytesInPage = TheISA::PageBytes; |
| else |
| bytesInPage = bytesLeft; |
| |
| // Next, see if we have crossed a page boundary, and adjust |
| Addr upperBound = curAddr + bytesInPage; |
| Addr pageBound = TheISA::TruncPage(curAddr) + TheISA::PageBytes; |
| |
| assert(upperBound >= curAddr && "DMA read wraps around address space!\n"); |
| |
| if (upperBound >= pageBound) |
| bytesInPage = pageBound - curAddr; |
| |
| return bytesInPage; |
| } |
| |
| //// |
| // Device registers read/write |
| //// |
| |
| void |
| IdeDisk::read(const Addr &offset, IdeRegType reg_type, uint8_t *data) |
| { |
| DevAction_t action = ACT_NONE; |
| |
| switch (reg_type) { |
| case COMMAND_BLOCK: |
| switch (offset) { |
| // Data transfers occur two bytes at a time |
| case DATA_OFFSET: |
| *(uint16_t*)data = cmdReg.data; |
| action = ACT_DATA_READ_SHORT; |
| break; |
| 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; |
| action = ACT_STAT_READ; |
| break; |
| default: |
| panic("Invalid IDE command register offset: %#x\n", offset); |
| } |
| break; |
| case CONTROL_BLOCK: |
| if (offset == ALTSTAT_OFFSET) |
| *data = status; |
| else |
| panic("Invalid IDE control register offset: %#x\n", offset); |
| break; |
| default: |
| panic("Unknown register block!\n"); |
| } |
| |
| if (action != ACT_NONE) |
| updateState(action); |
| } |
| |
| void |
| IdeDisk::write(const Addr &offset, IdeRegType reg_type, const uint8_t *data) |
| { |
| DevAction_t action = ACT_NONE; |
| |
| switch (reg_type) { |
| case COMMAND_BLOCK: |
| switch (offset) { |
| case DATA_OFFSET: |
| cmdReg.data = *(uint16_t*)data; |
| action = ACT_DATA_WRITE_SHORT; |
| break; |
| 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; |
| action = ACT_SELECT_WRITE; |
| break; |
| case COMMAND_OFFSET: |
| cmdReg.command = *data; |
| action = ACT_CMD_WRITE; |
| break; |
| default: |
| panic("Invalid IDE command register offset: %#x\n", offset); |
| } |
| break; |
| case CONTROL_BLOCK: |
| if (offset == CONTROL_OFFSET) { |
| if (*data & CONTROL_RST_BIT) { |
| // force the device into the reset state |
| devState = Device_Srst; |
| action = ACT_SRST_SET; |
| } else if (devState == Device_Srst && !(*data & CONTROL_RST_BIT)) |
| action = ACT_SRST_CLEAR; |
| |
| nIENBit = (*data & CONTROL_IEN_BIT) ? true : false; |
| } |
| else |
| panic("Invalid IDE control register offset: %#x\n", offset); |
| break; |
| default: |
| panic("Unknown register block!\n"); |
| } |
| |
| if (action != ACT_NONE) |
| updateState(action); |
| } |
| |
| //// |
| // 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); |
| |
| // first read the current PRD |
| if (dmaInterface) { |
| if (dmaInterface->busy()) { |
| // reschedule after waiting period |
| dmaTransferEvent.schedule(curTick + DMA_BACKOFF_PERIOD); |
| return; |
| } |
| |
| dmaInterface->doDMA(Read, curPrdAddr, sizeof(PrdEntry_t), curTick, |
| &dmaPrdReadEvent); |
| } else { |
| dmaPrdReadDone(); |
| } |
| } |
| |
| void |
| IdeDisk::dmaPrdReadDone() |
| { |
| // actually copy the PRD from physical memory |
| memcpy((void *)&curPrd.entry, |
| physmem->dma_addr(curPrdAddr, sizeof(PrdEntry_t)), |
| sizeof(PrdEntry_t)); |
| |
| 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) |
| doDmaRead(); |
| else |
| doDmaWrite(); |
| } |
| |
| void |
| IdeDisk::doDmaRead() |
| { |
| /** @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); |
| if (dmaInterface) { |
| if (dmaInterface->busy()) { |
| // reschedule after waiting period |
| dmaReadWaitEvent.schedule(curTick + DMA_BACKOFF_PERIOD); |
| return; |
| } |
| |
| Addr dmaAddr = pciToDma(curPrd.getBaseAddr()); |
| |
| uint32_t bytesInPage = bytesInDmaPage(curPrd.getBaseAddr(), |
| (uint32_t)curPrd.getByteCount()); |
| |
| dmaInterfaceBytes = bytesInPage; |
| |
| dmaInterface->doDMA(Read, dmaAddr, bytesInPage, |
| curTick + totalDiskDelay, &dmaReadEvent); |
| } else { |
| // schedule dmaReadEvent with sectorDelay (dmaReadDone) |
| dmaReadEvent.schedule(curTick + totalDiskDelay); |
| } |
| } |
| |
| void |
| IdeDisk::dmaReadDone() |
| { |
| |
| Addr curAddr = 0, dmaAddr = 0; |
| uint32_t bytesWritten = 0, bytesInPage = 0, bytesLeft = 0; |
| |
| // continue to use the DMA interface until all pages are read |
| if (dmaInterface && (dmaInterfaceBytes < curPrd.getByteCount())) { |
| // see if the interface is busy |
| if (dmaInterface->busy()) { |
| // reschedule after waiting period |
| dmaReadEvent.schedule(curTick + DMA_BACKOFF_PERIOD); |
| return; |
| } |
| |
| uint32_t bytesLeft = curPrd.getByteCount() - dmaInterfaceBytes; |
| curAddr = curPrd.getBaseAddr() + dmaInterfaceBytes; |
| dmaAddr = pciToDma(curAddr); |
| |
| bytesInPage = bytesInDmaPage(curAddr, bytesLeft); |
| dmaInterfaceBytes += bytesInPage; |
| |
| dmaInterface->doDMA(Read, dmaAddr, bytesInPage, |
| curTick, &dmaReadEvent); |
| |
| return; |
| } |
| |
| // set initial address |
| curAddr = curPrd.getBaseAddr(); |
| |
| // clear out the data buffer |
| memset(dataBuffer, 0, MAX_DMA_SIZE); |
| |
| // read the data from memory via DMA into a data buffer |
| while (bytesWritten < curPrd.getByteCount()) { |
| if (cmdBytesLeft <= 0) |
| panic("DMA data is larger than # of sectors specified\n"); |
| |
| dmaAddr = pciToDma(curAddr); |
| |
| // calculate how many bytes are in the current page |
| bytesLeft = curPrd.getByteCount() - bytesWritten; |
| bytesInPage = bytesInDmaPage(curAddr, bytesLeft); |
| |
| // copy the data from memory into the data buffer |
| memcpy((void *)(dataBuffer + bytesWritten), |
| physmem->dma_addr(dmaAddr, bytesInPage), |
| bytesInPage); |
| |
| curAddr += bytesInPage; |
| bytesWritten += bytesInPage; |
| cmdBytesLeft -= bytesInPage; |
| } |
| |
| // write the data to the disk image |
| for (bytesWritten = 0; |
| bytesWritten < curPrd.getByteCount(); |
| bytesWritten += 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::doDmaWrite() |
| { |
| /** @todo we need to figure out what the delay actually will be */ |
| Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize); |
| |
| DPRINTF(IdeDisk, "doDmaWrite, diskDelay: %d totalDiskDelay: %d\n", |
| diskDelay, totalDiskDelay); |
| |
| if (dmaInterface) { |
| if (dmaInterface->busy()) { |
| // reschedule after waiting period |
| dmaWriteWaitEvent.schedule(curTick + DMA_BACKOFF_PERIOD); |
| return; |
| } |
| |
| Addr dmaAddr = pciToDma(curPrd.getBaseAddr()); |
| |
| uint32_t bytesInPage = bytesInDmaPage(curPrd.getBaseAddr(), |
| (uint32_t)curPrd.getByteCount()); |
| |
| dmaInterfaceBytes = bytesInPage; |
| |
| dmaInterface->doDMA(WriteInvalidate, dmaAddr, |
| bytesInPage, curTick + totalDiskDelay, |
| &dmaWriteEvent); |
| } else { |
| // schedule event with disk delay (dmaWriteDone) |
| dmaWriteEvent.schedule(curTick + totalDiskDelay); |
| } |
| } |
| |
| void |
| IdeDisk::dmaWriteDone() |
| { |
| Addr curAddr = 0, pageAddr = 0, dmaAddr = 0; |
| uint32_t bytesRead = 0, bytesInPage = 0; |
| |
| // continue to use the DMA interface until all pages are read |
| if (dmaInterface && (dmaInterfaceBytes < curPrd.getByteCount())) { |
| // see if the interface is busy |
| if (dmaInterface->busy()) { |
| // reschedule after waiting period |
| dmaWriteEvent.schedule(curTick + DMA_BACKOFF_PERIOD); |
| return; |
| } |
| |
| uint32_t bytesLeft = curPrd.getByteCount() - dmaInterfaceBytes; |
| curAddr = curPrd.getBaseAddr() + dmaInterfaceBytes; |
| dmaAddr = pciToDma(curAddr); |
| |
| bytesInPage = bytesInDmaPage(curAddr, bytesLeft); |
| dmaInterfaceBytes += bytesInPage; |
| |
| dmaInterface->doDMA(WriteInvalidate, dmaAddr, |
| bytesInPage, curTick, |
| &dmaWriteEvent); |
| |
| return; |
| } |
| |
| // setup the initial page and DMA address |
| curAddr = curPrd.getBaseAddr(); |
| pageAddr = TheISA::TruncPage(curAddr); |
| dmaAddr = pciToDma(curAddr); |
| |
| // clear out the data buffer |
| memset(dataBuffer, 0, MAX_DMA_SIZE); |
| |
| while (bytesRead < curPrd.getByteCount()) { |
| // see if we have crossed into a new page |
| if (pageAddr != TheISA::TruncPage(curAddr)) { |
| // write the data to memory |
| memcpy(physmem->dma_addr(dmaAddr, bytesInPage), |
| (void *)(dataBuffer + (bytesRead - bytesInPage)), |
| bytesInPage); |
| |
| // update the DMA address and page address |
| pageAddr = TheISA::TruncPage(curAddr); |
| dmaAddr = pciToDma(curAddr); |
| |
| bytesInPage = 0; |
| } |
| |
| if (cmdBytesLeft <= 0) |
| panic("DMA requested data is larger than # sectors specified\n"); |
| |
| readDisk(curSector++, (uint8_t *)(dataBuffer + bytesRead)); |
| |
| curAddr += SectorSize; |
| bytesRead += SectorSize; |
| bytesInPage += SectorSize; |
| cmdBytesLeft -= SectorSize; |
| } |
| |
| // write the last page worth read to memory |
| if (bytesInPage != 0) { |
| memcpy(physmem->dma_addr(dmaAddr, bytesInPage), |
| (void *)(dataBuffer + (bytesRead - bytesInPage)), |
| bytesInPage); |
| } |
| |
| // 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) |
| dmaTransferEvent.schedule(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->bmi_regs.bmis0 |= IDEINTS; |
| 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); |
| |
| // Serialize current transfer related information |
| SERIALIZE_SCALAR(cmdBytesLeft); |
| SERIALIZE_SCALAR(cmdBytes); |
| SERIALIZE_SCALAR(drqBytesLeft); |
| SERIALIZE_SCALAR(curSector); |
| SERIALIZE_SCALAR(dmaRead); |
| SERIALIZE_SCALAR(dmaInterfaceBytes); |
| 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 : dmaTransferEvent.schedule(reschedule); break; |
| case ReadWait : dmaReadWaitEvent.schedule(reschedule); break; |
| case WriteWait : dmaWriteWaitEvent.schedule(reschedule); break; |
| case PrdRead : dmaPrdReadEvent.schedule(reschedule); break; |
| case DmaRead : dmaReadEvent.schedule(reschedule); break; |
| case DmaWrite : dmaWriteEvent.schedule(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); |
| |
| // Unserialize current transfer related information |
| UNSERIALIZE_SCALAR(cmdBytes); |
| UNSERIALIZE_SCALAR(cmdBytesLeft); |
| UNSERIALIZE_SCALAR(drqBytesLeft); |
| UNSERIALIZE_SCALAR(curSector); |
| UNSERIALIZE_SCALAR(dmaRead); |
| UNSERIALIZE_SCALAR(dmaInterfaceBytes); |
| UNSERIALIZE_SCALAR(intrPending); |
| UNSERIALIZE_ENUM(devState); |
| UNSERIALIZE_ENUM(dmaState); |
| UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE); |
| } |
| |
| #ifndef DOXYGEN_SHOULD_SKIP_THIS |
| |
| enum DriveID { master, slave }; |
| static const char *DriveID_strings[] = { "master", "slave" }; |
| BEGIN_DECLARE_SIM_OBJECT_PARAMS(IdeDisk) |
| |
| SimObjectParam<DiskImage *> image; |
| SimObjectParam<PhysicalMemory *> physmem; |
| SimpleEnumParam<DriveID> driveID; |
| Param<int> delay; |
| |
| END_DECLARE_SIM_OBJECT_PARAMS(IdeDisk) |
| |
| BEGIN_INIT_SIM_OBJECT_PARAMS(IdeDisk) |
| |
| INIT_PARAM(image, "Disk image"), |
| INIT_PARAM(physmem, "Physical memory"), |
| INIT_ENUM_PARAM(driveID, "Drive ID (0=master 1=slave)", DriveID_strings), |
| INIT_PARAM_DFLT(delay, "Fixed disk delay in microseconds", 1) |
| |
| END_INIT_SIM_OBJECT_PARAMS(IdeDisk) |
| |
| |
| CREATE_SIM_OBJECT(IdeDisk) |
| { |
| return new IdeDisk(getInstanceName(), image, physmem, driveID, delay); |
| } |
| |
| REGISTER_SIM_OBJECT("IdeDisk", IdeDisk) |
| |
| #endif //DOXYGEN_SHOULD_SKIP_THIS |