8f8d09538f
after merge from head. Checkpointing may need some work now. Endian-happiness still not complete. SConscript: add all devices back into make file base/inet.hh: dev/etherbus.cc: dev/etherbus.hh: dev/etherdump.cc: dev/etherdump.hh: dev/etherint.hh: dev/etherlink.cc: dev/etherlink.hh: dev/etherpkt.cc: dev/etherpkt.hh: dev/ethertap.cc: dev/ethertap.hh: dev/pktfifo.cc: dev/pktfifo.hh: rename PacketPtr EthPacketPtr so it doesn't conflict with the PacketPtr type in the memory system configs/test/fs.py: add nics to fs.py cpu/cpu_exec_context.cc: remove this check, as it's not valid. We may want to add something else back in to make sure that no one can delete the static virtual ports in the exec context cpu/simple/cpu.cc: cpu/simple/cpu.hh: dev/alpha_console.cc: dev/ide_ctrl.cc: use new methods for accessing packet data dev/ide_disk.cc: add some more dprintfs dev/io_device.cc: delete packets when we are done with them. Update for new packet methods to access data dev/isa_fake.cc: dev/pciconfigall.cc: dev/tsunami_cchip.cc: dev/tsunami_io.cc: dev/tsunami_pchip.cc: dev/uart8250.cc: dev/uart8250.hh: mem/physical.cc: mem/port.cc: dUpdate for new packet methods to access data dev/ns_gige.cc: Update for new memory system dev/ns_gige.hh: python/m5/objects/Ethernet.py: update for new memory system dev/sinic.cc: dev/sinic.hh: Update for new memory system. Untested as need to merge in head because of kernel driver differences between versions mem/packet.hh: Add methods to access data instead of accessing it directly. --HG-- extra : convert_revision : 223f43876afd404e68337270cd9a5e44d0bf553e
1108 lines
31 KiB
C++
1108 lines
31 KiB
C++
/*
|
|
* 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/chunk_generator.hh"
|
|
#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 "sim/builder.hh"
|
|
#include "sim/sim_object.hh"
|
|
#include "sim/root.hh"
|
|
#include "arch/isa_traits.hh"
|
|
|
|
using namespace std;
|
|
using namespace TheISA;
|
|
|
|
IdeDisk::IdeDisk(const string &name, DiskImage *img,
|
|
int id, Tick delay)
|
|
: SimObject(name), ctrl(NULL), image(img), diskDelay(delay),
|
|
dmaTransferEvent(this), dmaReadCG(NULL), dmaReadWaitEvent(this),
|
|
dmaWriteCG(NULL), 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));
|
|
|
|
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");
|
|
}
|
|
|
|
////
|
|
// 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");
|
|
}
|
|
DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset,
|
|
(uint32_t)*data);
|
|
|
|
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");
|
|
}
|
|
|
|
DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset,
|
|
(uint32_t)*data);
|
|
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);
|
|
|
|
if (ctrl->dmaPending()) {
|
|
dmaTransferEvent.schedule(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);
|
|
|
|
dmaReadWaitEvent.schedule(curTick + totalDiskDelay);
|
|
}
|
|
|
|
|
|
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()) {
|
|
panic("shouldn't be reentant??");
|
|
dmaReadWaitEvent.schedule(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());
|
|
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;
|
|
}
|
|
|
|
dmaWriteWaitEvent.schedule(curTick + totalDiskDelay);
|
|
}
|
|
|
|
void
|
|
IdeDisk::doDmaWrite()
|
|
{
|
|
|
|
if (!dmaWriteCG) {
|
|
// clear out the data buffer
|
|
dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(),
|
|
curPrd.getByteCount(), TheISA::PageBytes);
|
|
}
|
|
if (ctrl->dmaPending()) {
|
|
panic("shouldn't be reentant??");
|
|
dmaWriteWaitEvent.schedule(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());
|
|
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)
|
|
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);
|
|
|
|
/** @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 : 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);
|
|
|
|
/** @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);
|
|
}
|
|
|
|
#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;
|
|
SimpleEnumParam<DriveID> driveID;
|
|
Param<int> delay;
|
|
|
|
END_DECLARE_SIM_OBJECT_PARAMS(IdeDisk)
|
|
|
|
BEGIN_INIT_SIM_OBJECT_PARAMS(IdeDisk)
|
|
|
|
INIT_PARAM(image, "Disk image"),
|
|
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, driveID, delay);
|
|
}
|
|
|
|
REGISTER_SIM_OBJECT("IdeDisk", IdeDisk)
|
|
|
|
#endif //DOXYGEN_SHOULD_SKIP_THIS
|