Files
gem5/src/dev/storage/ide_disk.cc
Gabe Black adb13e4fc7 dev: Stop using the OS page size in the IDE controller.
This size was used to break up DMA transactions so that a single
transaction would not cross a page boundary. This was because on Alpha,
there was an actual page table which translated between PCI and DMA
address spaces. On all currently implemented systems, the mapping is
simply to add a scalar offset, so it's not possible for a legal region
of memory to be contiguous in one space but not in the other.

Additionally, if it *was* possible for there to be a mismatch, it was
only coincidence that Alpha used a page table which had the same sized
pages as it normally used. There is no requirement that there even would
be fixed sized pages in the first place.

To avoid this artificial dependency between the IDE controller and the
ISA, this change simply changes the chunk size for DMA accesses to 4K.
That's the page size at least on x86 and probably other architectures,
and will be a pretty close approximation of the previous behavior.

It's possible that even having this chunking in the first place is
unnecessary and functionally useless, but there are some checks which
happen between chunks, and changing how big they are would change the
frequency of those checks. For instance, the controller/disk may not
notice in the same amount of time if a DMA was cancelled somehow.

Change-Id: I1ec840d1f158c3faa31ba0184458b69bf654c252
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/34178
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
Maintainer: Gabe Black <gabeblack@google.com>
Tested-by: kokoro <noreply+kokoro@google.com>
2020-09-20 07:26:04 +00:00

1206 lines
34 KiB
C++

/*
* Copyright (c) 2013 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Copyright (c) 2004-2005 The Regents of The University of Michigan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @file
* Device model implementation for an IDE disk
*/
#include "dev/storage/ide_disk.hh"
#include <cerrno>
#include <cstring>
#include <deque>
#include <string>
#include "base/chunk_generator.hh"
#include "base/cprintf.hh" // csprintf
#include "base/trace.hh"
#include "debug/IdeDisk.hh"
#include "dev/storage/disk_image.hh"
#include "dev/storage/ide_ctrl.hh"
#include "sim/core.hh"
#include "sim/sim_object.hh"
IdeDisk::IdeDisk(const Params *p)
: SimObject(p), ctrl(NULL), image(p->image), diskDelay(p->delay),
dmaTransferEvent([this]{ doDmaTransfer(); }, name()),
dmaReadCG(NULL),
dmaReadWaitEvent([this]{ doDmaRead(); }, name()),
dmaWriteCG(NULL),
dmaWriteWaitEvent([this]{ doDmaWrite(); }, name()),
dmaPrdReadEvent([this]{ dmaPrdReadDone(); }, name()),
dmaReadEvent([this]{ dmaReadDone(); }, name()),
dmaWriteEvent([this]{ dmaWriteDone(); }, name())
{
// Reset the device state
reset(p->driveID);
// fill out the drive ID structure
memset(&driveID, 0, sizeof(struct ataparams));
// Calculate LBA and C/H/S values
uint16_t cylinders;
uint8_t heads;
uint8_t sectors;
uint32_t lba_size = image->size();
if (lba_size >= 16383*16*63) {
cylinders = 16383;
heads = 16;
sectors = 63;
} else {
if (lba_size >= 63)
sectors = 63;
else if (lba_size == 0)
panic("Bad IDE image size: 0\n");
else
sectors = lba_size;
if ((lba_size / sectors) >= 16)
heads = 16;
else
heads = (lba_size / sectors);
cylinders = lba_size / (heads * sectors);
}
// Setup the model name
strncpy((char *)driveID.atap_model, "5MI EDD si k",
sizeof(driveID.atap_model));
// Set the maximum multisector transfer size
driveID.atap_multi = MAX_MULTSECT;
// IORDY supported, IORDY disabled, LBA enabled, DMA enabled
driveID.atap_capabilities1 = 0x7;
// UDMA support, EIDE support
driveID.atap_extensions = 0x6;
// Setup default C/H/S settings
driveID.atap_cylinders = cylinders;
driveID.atap_sectors = sectors;
driveID.atap_heads = heads;
// Setup the current multisector transfer size
driveID.atap_curmulti = MAX_MULTSECT;
driveID.atap_curmulti_valid = 0x1;
// Number of sectors on disk
driveID.atap_capacity = lba_size;
// Multiword DMA mode 2 and below supported
driveID.atap_dmamode_supp = 0x4;
// Set PIO mode 4 and 3 supported
driveID.atap_piomode_supp = 0x3;
// Set DMA mode 4 and below supported
driveID.atap_udmamode_supp = 0x1f;
// Statically set hardware config word
driveID.atap_hwreset_res = 0x4001;
//arbitrary for now...
driveID.atap_ata_major = WDC_VER_ATA7;
}
IdeDisk::~IdeDisk()
{
// destroy the data buffer
delete [] dataBuffer;
}
void
IdeDisk::reset(int id)
{
// initialize the data buffer and shadow registers
dataBuffer = new uint8_t[MAX_DMA_SIZE];
memset(dataBuffer, 0, MAX_DMA_SIZE);
memset(&cmdReg, 0, sizeof(CommandReg_t));
memset(&curPrd.entry, 0, sizeof(PrdEntry_t));
curPrdAddr = 0;
curSector = 0;
cmdBytes = 0;
cmdBytesLeft = 0;
drqBytesLeft = 0;
dmaRead = false;
intrPending = false;
dmaAborted = false;
// set the device state to idle
dmaState = Dma_Idle;
if (id == DEV0) {
devState = Device_Idle_S;
devID = DEV0;
} else if (id == DEV1) {
devState = Device_Idle_NS;
devID = DEV1;
} else {
panic("Invalid device ID: %#x\n", id);
}
// set the device ready bit
status = STATUS_DRDY_BIT;
/* The error register must be set to 0x1 on start-up to
indicate that no diagnostic error was detected */
cmdReg.error = 0x1;
}
////
// Utility functions
////
bool
IdeDisk::isDEVSelect()
{
return ctrl->isDiskSelected(this);
}
Addr
IdeDisk::pciToDma(Addr pciAddr)
{
if (ctrl)
return ctrl->pciToDma(pciAddr);
else
panic("Access to unset controller!\n");
}
////
// Device registers read/write
////
void
IdeDisk::readCommand(const Addr offset, int size, uint8_t *data)
{
if (offset == DATA_OFFSET) {
if (size == sizeof(uint16_t)) {
*(uint16_t *)data = cmdReg.data;
} else if (size == sizeof(uint32_t)) {
*(uint16_t *)data = cmdReg.data;
updateState(ACT_DATA_READ_SHORT);
*((uint16_t *)data + 1) = cmdReg.data;
} else {
panic("Data read of unsupported size %d.\n", size);
}
updateState(ACT_DATA_READ_SHORT);
return;
}
assert(size == sizeof(uint8_t));
switch (offset) {
case ERROR_OFFSET:
*data = cmdReg.error;
break;
case NSECTOR_OFFSET:
*data = cmdReg.sec_count;
break;
case SECTOR_OFFSET:
*data = cmdReg.sec_num;
break;
case LCYL_OFFSET:
*data = cmdReg.cyl_low;
break;
case HCYL_OFFSET:
*data = cmdReg.cyl_high;
break;
case DRIVE_OFFSET:
*data = cmdReg.drive;
break;
case STATUS_OFFSET:
*data = status;
updateState(ACT_STAT_READ);
break;
default:
panic("Invalid IDE command register offset: %#x\n", offset);
}
DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data);
}
void
IdeDisk::readControl(const Addr offset, int size, uint8_t *data)
{
assert(size == sizeof(uint8_t));
*data = status;
if (offset != ALTSTAT_OFFSET)
panic("Invalid IDE control register offset: %#x\n", offset);
DPRINTF(IdeDisk, "Read to disk at offset: %#x data %#x\n", offset, *data);
}
void
IdeDisk::writeCommand(const Addr offset, int size, const uint8_t *data)
{
if (offset == DATA_OFFSET) {
if (size == sizeof(uint16_t)) {
cmdReg.data = *(const uint16_t *)data;
} else if (size == sizeof(uint32_t)) {
cmdReg.data = *(const uint16_t *)data;
updateState(ACT_DATA_WRITE_SHORT);
cmdReg.data = *((const uint16_t *)data + 1);
} else {
panic("Data write of unsupported size %d.\n", size);
}
updateState(ACT_DATA_WRITE_SHORT);
return;
}
assert(size == sizeof(uint8_t));
switch (offset) {
case FEATURES_OFFSET:
break;
case NSECTOR_OFFSET:
cmdReg.sec_count = *data;
break;
case SECTOR_OFFSET:
cmdReg.sec_num = *data;
break;
case LCYL_OFFSET:
cmdReg.cyl_low = *data;
break;
case HCYL_OFFSET:
cmdReg.cyl_high = *data;
break;
case DRIVE_OFFSET:
cmdReg.drive = *data;
updateState(ACT_SELECT_WRITE);
break;
case COMMAND_OFFSET:
cmdReg.command = *data;
updateState(ACT_CMD_WRITE);
break;
default:
panic("Invalid IDE command register offset: %#x\n", offset);
}
DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset,
(uint32_t)*data);
}
void
IdeDisk::writeControl(const Addr offset, int size, const uint8_t *data)
{
if (offset != CONTROL_OFFSET)
panic("Invalid IDE control register offset: %#x\n", offset);
if (*data & CONTROL_RST_BIT) {
// force the device into the reset state
devState = Device_Srst;
updateState(ACT_SRST_SET);
} else if (devState == Device_Srst && !(*data & CONTROL_RST_BIT)) {
updateState(ACT_SRST_CLEAR);
}
nIENBit = *data & CONTROL_IEN_BIT;
DPRINTF(IdeDisk, "Write to disk at offset: %#x data %#x\n", offset,
(uint32_t)*data);
}
////
// Perform DMA transactions
////
void
IdeDisk::doDmaTransfer()
{
if (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted before reading PRD entry\n");
updateState(ACT_CMD_ERROR);
return;
}
if (dmaState != Dma_Transfer || devState != Transfer_Data_Dma)
panic("Inconsistent DMA transfer state: dmaState = %d devState = %d\n",
dmaState, devState);
if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) {
schedule(dmaTransferEvent, curTick() + DMA_BACKOFF_PERIOD);
return;
} else
ctrl->dmaRead(curPrdAddr, sizeof(PrdEntry_t), &dmaPrdReadEvent,
(uint8_t*)&curPrd.entry);
}
void
IdeDisk::dmaPrdReadDone()
{
if (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted while reading PRD entry\n");
updateState(ACT_CMD_ERROR);
return;
}
DPRINTF(IdeDisk,
"PRD: baseAddr:%#x (%#x) byteCount:%d (%d) eot:%#x sector:%d\n",
curPrd.getBaseAddr(), pciToDma(curPrd.getBaseAddr()),
curPrd.getByteCount(), (cmdBytesLeft/SectorSize),
curPrd.getEOT(), curSector);
// the prd pointer has already been translated, so just do an increment
curPrdAddr = curPrdAddr + sizeof(PrdEntry_t);
if (dmaRead)
doDmaDataRead();
else
doDmaDataWrite();
}
void
IdeDisk::doDmaDataRead()
{
/** @todo we need to figure out what the delay actually will be */
Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize);
DPRINTF(IdeDisk, "doDmaRead, diskDelay: %d totalDiskDelay: %d\n",
diskDelay, totalDiskDelay);
schedule(dmaReadWaitEvent, curTick() + totalDiskDelay);
}
void
IdeDisk::regStats()
{
SimObject::regStats();
using namespace Stats;
dmaReadFullPages
.name(name() + ".dma_read_full_pages")
.desc("Number of full page size DMA reads (not PRD).")
;
dmaReadBytes
.name(name() + ".dma_read_bytes")
.desc("Number of bytes transfered via DMA reads (not PRD).")
;
dmaReadTxs
.name(name() + ".dma_read_txs")
.desc("Number of DMA read transactions (not PRD).")
;
dmaWriteFullPages
.name(name() + ".dma_write_full_pages")
.desc("Number of full page size DMA writes.")
;
dmaWriteBytes
.name(name() + ".dma_write_bytes")
.desc("Number of bytes transfered via DMA writes.")
;
dmaWriteTxs
.name(name() + ".dma_write_txs")
.desc("Number of DMA write transactions.")
;
}
void
IdeDisk::doDmaRead()
{
if (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted in middle of Dma Read\n");
if (dmaReadCG)
delete dmaReadCG;
dmaReadCG = NULL;
updateState(ACT_CMD_ERROR);
return;
}
if (!dmaReadCG) {
// clear out the data buffer
memset(dataBuffer, 0, MAX_DMA_SIZE);
dmaReadCG = new ChunkGenerator(curPrd.getBaseAddr(),
curPrd.getByteCount(), chunkBytes);
}
if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) {
schedule(dmaReadWaitEvent, curTick() + DMA_BACKOFF_PERIOD);
return;
} else if (!dmaReadCG->done()) {
assert(dmaReadCG->complete() < MAX_DMA_SIZE);
ctrl->dmaRead(pciToDma(dmaReadCG->addr()), dmaReadCG->size(),
&dmaReadWaitEvent, dataBuffer + dmaReadCG->complete());
dmaReadBytes += dmaReadCG->size();
dmaReadTxs++;
if (dmaReadCG->size() == chunkBytes)
dmaReadFullPages++;
dmaReadCG->next();
} else {
assert(dmaReadCG->done());
delete dmaReadCG;
dmaReadCG = NULL;
dmaReadDone();
}
}
void
IdeDisk::dmaReadDone()
{
uint32_t bytesWritten = 0;
// write the data to the disk image
for (bytesWritten = 0; bytesWritten < curPrd.getByteCount();
bytesWritten += SectorSize) {
cmdBytesLeft -= SectorSize;
writeDisk(curSector++, (uint8_t *)(dataBuffer + bytesWritten));
}
// check for the EOT
if (curPrd.getEOT()) {
assert(cmdBytesLeft == 0);
dmaState = Dma_Idle;
updateState(ACT_DMA_DONE);
} else {
doDmaTransfer();
}
}
void
IdeDisk::doDmaDataWrite()
{
/** @todo we need to figure out what the delay actually will be */
Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize);
uint32_t bytesRead = 0;
DPRINTF(IdeDisk, "doDmaWrite, diskDelay: %d totalDiskDelay: %d\n",
diskDelay, totalDiskDelay);
memset(dataBuffer, 0, MAX_DMA_SIZE);
assert(cmdBytesLeft <= MAX_DMA_SIZE);
while (bytesRead < curPrd.getByteCount()) {
readDisk(curSector++, (uint8_t *)(dataBuffer + bytesRead));
bytesRead += SectorSize;
cmdBytesLeft -= SectorSize;
}
DPRINTF(IdeDisk, "doDmaWrite, bytesRead: %d cmdBytesLeft: %d\n",
bytesRead, cmdBytesLeft);
schedule(dmaWriteWaitEvent, curTick() + totalDiskDelay);
}
void
IdeDisk::doDmaWrite()
{
if (dmaAborted) {
DPRINTF(IdeDisk, "DMA Aborted while doing DMA Write\n");
if (dmaWriteCG)
delete dmaWriteCG;
dmaWriteCG = NULL;
updateState(ACT_CMD_ERROR);
return;
}
if (!dmaWriteCG) {
// clear out the data buffer
dmaWriteCG = new ChunkGenerator(curPrd.getBaseAddr(),
curPrd.getByteCount(), chunkBytes);
}
if (ctrl->dmaPending() || ctrl->drainState() != DrainState::Running) {
schedule(dmaWriteWaitEvent, curTick() + DMA_BACKOFF_PERIOD);
DPRINTF(IdeDisk, "doDmaWrite: rescheduling\n");
return;
} else if (!dmaWriteCG->done()) {
assert(dmaWriteCG->complete() < MAX_DMA_SIZE);
ctrl->dmaWrite(pciToDma(dmaWriteCG->addr()), dmaWriteCG->size(),
&dmaWriteWaitEvent, dataBuffer + dmaWriteCG->complete());
DPRINTF(IdeDisk, "doDmaWrite: not done curPrd byte count %d, eot %#x\n",
curPrd.getByteCount(), curPrd.getEOT());
dmaWriteBytes += dmaWriteCG->size();
dmaWriteTxs++;
if (dmaWriteCG->size() == chunkBytes)
dmaWriteFullPages++;
dmaWriteCG->next();
} else {
DPRINTF(IdeDisk, "doDmaWrite: done curPrd byte count %d, eot %#x\n",
curPrd.getByteCount(), curPrd.getEOT());
assert(dmaWriteCG->done());
delete dmaWriteCG;
dmaWriteCG = NULL;
dmaWriteDone();
}
}
void
IdeDisk::dmaWriteDone()
{
DPRINTF(IdeDisk, "doWriteDone: curPrd byte count %d, eot %#x cmd bytes left:%d\n",
curPrd.getByteCount(), curPrd.getEOT(), cmdBytesLeft);
// check for the EOT
if (curPrd.getEOT()) {
assert(cmdBytesLeft == 0);
dmaState = Dma_Idle;
updateState(ACT_DMA_DONE);
} else {
doDmaTransfer();
}
}
////
// Disk utility routines
///
void
IdeDisk::readDisk(uint32_t sector, uint8_t *data)
{
uint32_t bytesRead = image->read(data, sector);
if (bytesRead != SectorSize)
panic("Can't read from %s. Only %d of %d read. errno=%d\n",
name(), bytesRead, SectorSize, errno);
}
void
IdeDisk::writeDisk(uint32_t sector, uint8_t *data)
{
uint32_t bytesWritten = image->write(data, sector);
if (bytesWritten != SectorSize)
panic("Can't write to %s. Only %d of %d written. errno=%d\n",
name(), bytesWritten, SectorSize, errno);
}
////
// Setup and handle commands
////
void
IdeDisk::startDma(const uint32_t &prdTableBase)
{
if (dmaState != Dma_Start)
panic("Inconsistent DMA state, should be in Dma_Start!\n");
if (devState != Transfer_Data_Dma)
panic("Inconsistent device state for DMA start!\n");
// PRD base address is given by bits 31:2
curPrdAddr = pciToDma((Addr)(prdTableBase & ~ULL(0x3)));
dmaState = Dma_Transfer;
// schedule dma transfer (doDmaTransfer)
schedule(dmaTransferEvent, curTick() + 1);
}
void
IdeDisk::abortDma()
{
if (dmaState == Dma_Idle)
panic("Inconsistent DMA state, should be Start or Transfer!");
if (devState != Transfer_Data_Dma && devState != Prepare_Data_Dma)
panic("Inconsistent device state, should be Transfer or Prepare!\n");
updateState(ACT_CMD_ERROR);
}
void
IdeDisk::startCommand()
{
DevAction_t action = ACT_NONE;
uint32_t size = 0;
dmaRead = false;
// Decode commands
switch (cmdReg.command) {
// Supported non-data commands
case WDSF_READ_NATIVE_MAX:
size = (uint32_t)image->size() - 1;
cmdReg.sec_num = (size & 0xff);
cmdReg.cyl_low = ((size & 0xff00) >> 8);
cmdReg.cyl_high = ((size & 0xff0000) >> 16);
cmdReg.head = ((size & 0xf000000) >> 24);
devState = Command_Execution;
action = ACT_CMD_COMPLETE;
break;
case WDCC_RECAL:
case WDCC_IDP:
case WDCC_STANDBY_IMMED:
case WDCC_FLUSHCACHE:
case WDSF_VERIFY:
case WDSF_SEEK:
case SET_FEATURES:
case WDCC_SETMULTI:
case WDCC_IDLE:
devState = Command_Execution;
action = ACT_CMD_COMPLETE;
break;
// Supported PIO data-in commands
case WDCC_IDENTIFY:
case ATAPI_IDENTIFY_DEVICE:
cmdBytes = cmdBytesLeft = sizeof(struct ataparams);
devState = Prepare_Data_In;
action = ACT_DATA_READY;
break;
case WDCC_READMULTI:
case WDCC_READ:
if (!(cmdReg.drive & DRIVE_LBA_BIT))
panic("Attempt to perform CHS access, only supports LBA\n");
if (cmdReg.sec_count == 0)
cmdBytes = cmdBytesLeft = (256 * SectorSize);
else
cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize);
curSector = getLBABase();
/** @todo make this a scheduled event to simulate disk delay */
devState = Prepare_Data_In;
action = ACT_DATA_READY;
break;
// Supported PIO data-out commands
case WDCC_WRITEMULTI:
case WDCC_WRITE:
if (!(cmdReg.drive & DRIVE_LBA_BIT))
panic("Attempt to perform CHS access, only supports LBA\n");
if (cmdReg.sec_count == 0)
cmdBytes = cmdBytesLeft = (256 * SectorSize);
else
cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize);
DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d\n", cmdBytesLeft);
curSector = getLBABase();
devState = Prepare_Data_Out;
action = ACT_DATA_READY;
break;
// Supported DMA commands
case WDCC_WRITEDMA:
dmaRead = true; // a write to the disk is a DMA read from memory
M5_FALLTHROUGH;
case WDCC_READDMA:
if (!(cmdReg.drive & DRIVE_LBA_BIT))
panic("Attempt to perform CHS access, only supports LBA\n");
if (cmdReg.sec_count == 0)
cmdBytes = cmdBytesLeft = (256 * SectorSize);
else
cmdBytes = cmdBytesLeft = (cmdReg.sec_count * SectorSize);
DPRINTF(IdeDisk, "Setting cmdBytesLeft to %d in readdma\n", cmdBytesLeft);
curSector = getLBABase();
devState = Prepare_Data_Dma;
action = ACT_DMA_READY;
break;
default:
panic("Unsupported ATA command: %#x\n", cmdReg.command);
}
if (action != ACT_NONE) {
// set the BSY bit
status |= STATUS_BSY_BIT;
// clear the DRQ bit
status &= ~STATUS_DRQ_BIT;
// clear the DF bit
status &= ~STATUS_DF_BIT;
updateState(action);
}
}
////
// Handle setting and clearing interrupts
////
void
IdeDisk::intrPost()
{
DPRINTF(IdeDisk, "Posting Interrupt\n");
if (intrPending)
panic("Attempt to post an interrupt with one pending\n");
intrPending = true;
// talk to controller to set interrupt
if (ctrl) {
ctrl->intrPost();
}
}
void
IdeDisk::intrClear()
{
DPRINTF(IdeDisk, "Clearing Interrupt\n");
if (!intrPending)
panic("Attempt to clear a non-pending interrupt\n");
intrPending = false;
// talk to controller to clear interrupt
if (ctrl)
ctrl->intrClear();
}
////
// Manage the device internal state machine
////
void
IdeDisk::updateState(DevAction_t action)
{
switch (devState) {
case Device_Srst:
if (action == ACT_SRST_SET) {
// set the BSY bit
status |= STATUS_BSY_BIT;
} else if (action == ACT_SRST_CLEAR) {
// clear the BSY bit
status &= ~STATUS_BSY_BIT;
// reset the device state
reset(devID);
}
break;
case Device_Idle_S:
if (action == ACT_SELECT_WRITE && !isDEVSelect()) {
devState = Device_Idle_NS;
} else if (action == ACT_CMD_WRITE) {
startCommand();
}
break;
case Device_Idle_SI:
if (action == ACT_SELECT_WRITE && !isDEVSelect()) {
devState = Device_Idle_NS;
intrClear();
} else if (action == ACT_STAT_READ || isIENSet()) {
devState = Device_Idle_S;
intrClear();
} else if (action == ACT_CMD_WRITE) {
intrClear();
startCommand();
}
break;
case Device_Idle_NS:
if (action == ACT_SELECT_WRITE && isDEVSelect()) {
if (!isIENSet() && intrPending) {
devState = Device_Idle_SI;
intrPost();
}
if (isIENSet() || !intrPending) {
devState = Device_Idle_S;
}
}
break;
case Command_Execution:
if (action == ACT_CMD_COMPLETE) {
// clear the BSY bit
setComplete();
if (!isIENSet()) {
devState = Device_Idle_SI;
intrPost();
} else {
devState = Device_Idle_S;
}
}
break;
case Prepare_Data_In:
if (action == ACT_CMD_ERROR) {
// clear the BSY bit
setComplete();
if (!isIENSet()) {
devState = Device_Idle_SI;
intrPost();
} else {
devState = Device_Idle_S;
}
} else if (action == ACT_DATA_READY) {
// clear the BSY bit
status &= ~STATUS_BSY_BIT;
// set the DRQ bit
status |= STATUS_DRQ_BIT;
// copy the data into the data buffer
if (cmdReg.command == WDCC_IDENTIFY ||
cmdReg.command == ATAPI_IDENTIFY_DEVICE) {
// Reset the drqBytes for this block
drqBytesLeft = sizeof(struct ataparams);
memcpy((void *)dataBuffer, (void *)&driveID,
sizeof(struct ataparams));
} else {
// Reset the drqBytes for this block
drqBytesLeft = SectorSize;
readDisk(curSector++, dataBuffer);
}
// put the first two bytes into the data register
memcpy((void *)&cmdReg.data, (void *)dataBuffer,
sizeof(uint16_t));
if (!isIENSet()) {
devState = Data_Ready_INTRQ_In;
intrPost();
} else {
devState = Transfer_Data_In;
}
}
break;
case Data_Ready_INTRQ_In:
if (action == ACT_STAT_READ) {
devState = Transfer_Data_In;
intrClear();
}
break;
case Transfer_Data_In:
if (action == ACT_DATA_READ_BYTE || action == ACT_DATA_READ_SHORT) {
if (action == ACT_DATA_READ_BYTE) {
panic("DEBUG: READING DATA ONE BYTE AT A TIME!\n");
} else {
drqBytesLeft -= 2;
cmdBytesLeft -= 2;
// copy next short into data registers
if (drqBytesLeft)
memcpy((void *)&cmdReg.data,
(void *)&dataBuffer[SectorSize - drqBytesLeft],
sizeof(uint16_t));
}
if (drqBytesLeft == 0) {
if (cmdBytesLeft == 0) {
// Clear the BSY bit
setComplete();
devState = Device_Idle_S;
} else {
devState = Prepare_Data_In;
// set the BSY_BIT
status |= STATUS_BSY_BIT;
// clear the DRQ_BIT
status &= ~STATUS_DRQ_BIT;
/** @todo change this to a scheduled event to simulate
disk delay */
updateState(ACT_DATA_READY);
}
}
}
break;
case Prepare_Data_Out:
if (action == ACT_CMD_ERROR || cmdBytesLeft == 0) {
// clear the BSY bit
setComplete();
if (!isIENSet()) {
devState = Device_Idle_SI;
intrPost();
} else {
devState = Device_Idle_S;
}
} else if (action == ACT_DATA_READY && cmdBytesLeft != 0) {
// clear the BSY bit
status &= ~STATUS_BSY_BIT;
// set the DRQ bit
status |= STATUS_DRQ_BIT;
// clear the data buffer to get it ready for writes
memset(dataBuffer, 0, MAX_DMA_SIZE);
// reset the drqBytes for this block
drqBytesLeft = SectorSize;
if (cmdBytesLeft == cmdBytes || isIENSet()) {
devState = Transfer_Data_Out;
} else {
devState = Data_Ready_INTRQ_Out;
intrPost();
}
}
break;
case Data_Ready_INTRQ_Out:
if (action == ACT_STAT_READ) {
devState = Transfer_Data_Out;
intrClear();
}
break;
case Transfer_Data_Out:
if (action == ACT_DATA_WRITE_BYTE ||
action == ACT_DATA_WRITE_SHORT) {
if (action == ACT_DATA_READ_BYTE) {
panic("DEBUG: WRITING DATA ONE BYTE AT A TIME!\n");
} else {
// copy the latest short into the data buffer
memcpy((void *)&dataBuffer[SectorSize - drqBytesLeft],
(void *)&cmdReg.data,
sizeof(uint16_t));
drqBytesLeft -= 2;
cmdBytesLeft -= 2;
}
if (drqBytesLeft == 0) {
// copy the block to the disk
writeDisk(curSector++, dataBuffer);
// set the BSY bit
status |= STATUS_BSY_BIT;
// set the seek bit
status |= STATUS_SEEK_BIT;
// clear the DRQ bit
status &= ~STATUS_DRQ_BIT;
devState = Prepare_Data_Out;
/** @todo change this to a scheduled event to simulate
disk delay */
updateState(ACT_DATA_READY);
}
}
break;
case Prepare_Data_Dma:
if (action == ACT_CMD_ERROR) {
// clear the BSY bit
setComplete();
if (!isIENSet()) {
devState = Device_Idle_SI;
intrPost();
} else {
devState = Device_Idle_S;
}
} else if (action == ACT_DMA_READY) {
// clear the BSY bit
status &= ~STATUS_BSY_BIT;
// set the DRQ bit
status |= STATUS_DRQ_BIT;
devState = Transfer_Data_Dma;
if (dmaState != Dma_Idle)
panic("Inconsistent DMA state, should be Dma_Idle\n");
dmaState = Dma_Start;
// wait for the write to the DMA start bit
}
break;
case Transfer_Data_Dma:
if (action == ACT_CMD_ERROR) {
dmaAborted = true;
devState = Device_Dma_Abort;
} else if (action == ACT_DMA_DONE) {
// clear the BSY bit
setComplete();
// set the seek bit
status |= STATUS_SEEK_BIT;
// clear the controller state for DMA transfer
ctrl->setDmaComplete(this);
if (!isIENSet()) {
devState = Device_Idle_SI;
intrPost();
} else {
devState = Device_Idle_S;
}
}
break;
case Device_Dma_Abort:
if (action == ACT_CMD_ERROR) {
setComplete();
status |= STATUS_SEEK_BIT;
ctrl->setDmaComplete(this);
dmaAborted = false;
dmaState = Dma_Idle;
if (!isIENSet()) {
devState = Device_Idle_SI;
intrPost();
} else {
devState = Device_Idle_S;
}
} else {
DPRINTF(IdeDisk, "Disk still busy aborting previous DMA command\n");
}
break;
default:
panic("Unknown IDE device state: %#x\n", devState);
}
}
void
IdeDisk::serialize(CheckpointOut &cp) const
{
// Check all outstanding events to see if they are scheduled
// these are all mutually exclusive
Tick reschedule = 0;
Events_t event = None;
int eventCount = 0;
if (dmaTransferEvent.scheduled()) {
reschedule = dmaTransferEvent.when();
event = Transfer;
eventCount++;
}
if (dmaReadWaitEvent.scheduled()) {
reschedule = dmaReadWaitEvent.when();
event = ReadWait;
eventCount++;
}
if (dmaWriteWaitEvent.scheduled()) {
reschedule = dmaWriteWaitEvent.when();
event = WriteWait;
eventCount++;
}
if (dmaPrdReadEvent.scheduled()) {
reschedule = dmaPrdReadEvent.when();
event = PrdRead;
eventCount++;
}
if (dmaReadEvent.scheduled()) {
reschedule = dmaReadEvent.when();
event = DmaRead;
eventCount++;
}
if (dmaWriteEvent.scheduled()) {
reschedule = dmaWriteEvent.when();
event = DmaWrite;
eventCount++;
}
assert(eventCount <= 1);
SERIALIZE_SCALAR(reschedule);
SERIALIZE_ENUM(event);
// Serialize device registers
SERIALIZE_SCALAR(cmdReg.data);
SERIALIZE_SCALAR(cmdReg.sec_count);
SERIALIZE_SCALAR(cmdReg.sec_num);
SERIALIZE_SCALAR(cmdReg.cyl_low);
SERIALIZE_SCALAR(cmdReg.cyl_high);
SERIALIZE_SCALAR(cmdReg.drive);
SERIALIZE_SCALAR(cmdReg.command);
SERIALIZE_SCALAR(status);
SERIALIZE_SCALAR(nIENBit);
SERIALIZE_SCALAR(devID);
// Serialize the PRD related information
SERIALIZE_SCALAR(curPrd.entry.baseAddr);
SERIALIZE_SCALAR(curPrd.entry.byteCount);
SERIALIZE_SCALAR(curPrd.entry.endOfTable);
SERIALIZE_SCALAR(curPrdAddr);
/** @todo need to serialized chunk generator stuff!! */
// Serialize current transfer related information
SERIALIZE_SCALAR(cmdBytesLeft);
SERIALIZE_SCALAR(cmdBytes);
SERIALIZE_SCALAR(drqBytesLeft);
SERIALIZE_SCALAR(curSector);
SERIALIZE_SCALAR(dmaRead);
SERIALIZE_SCALAR(intrPending);
SERIALIZE_SCALAR(dmaAborted);
SERIALIZE_ENUM(devState);
SERIALIZE_ENUM(dmaState);
SERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE);
}
void
IdeDisk::unserialize(CheckpointIn &cp)
{
// Reschedule events that were outstanding
// these are all mutually exclusive
Tick reschedule = 0;
Events_t event = None;
UNSERIALIZE_SCALAR(reschedule);
UNSERIALIZE_ENUM(event);
switch (event) {
case None : break;
case Transfer : schedule(dmaTransferEvent, reschedule); break;
case ReadWait : schedule(dmaReadWaitEvent, reschedule); break;
case WriteWait : schedule(dmaWriteWaitEvent, reschedule); break;
case PrdRead : schedule(dmaPrdReadEvent, reschedule); break;
case DmaRead : schedule(dmaReadEvent, reschedule); break;
case DmaWrite : schedule(dmaWriteEvent, reschedule); break;
}
// Unserialize device registers
UNSERIALIZE_SCALAR(cmdReg.data);
UNSERIALIZE_SCALAR(cmdReg.sec_count);
UNSERIALIZE_SCALAR(cmdReg.sec_num);
UNSERIALIZE_SCALAR(cmdReg.cyl_low);
UNSERIALIZE_SCALAR(cmdReg.cyl_high);
UNSERIALIZE_SCALAR(cmdReg.drive);
UNSERIALIZE_SCALAR(cmdReg.command);
UNSERIALIZE_SCALAR(status);
UNSERIALIZE_SCALAR(nIENBit);
UNSERIALIZE_SCALAR(devID);
// Unserialize the PRD related information
UNSERIALIZE_SCALAR(curPrd.entry.baseAddr);
UNSERIALIZE_SCALAR(curPrd.entry.byteCount);
UNSERIALIZE_SCALAR(curPrd.entry.endOfTable);
UNSERIALIZE_SCALAR(curPrdAddr);
/** @todo need to serialized chunk generator stuff!! */
// Unserialize current transfer related information
UNSERIALIZE_SCALAR(cmdBytes);
UNSERIALIZE_SCALAR(cmdBytesLeft);
UNSERIALIZE_SCALAR(drqBytesLeft);
UNSERIALIZE_SCALAR(curSector);
UNSERIALIZE_SCALAR(dmaRead);
UNSERIALIZE_SCALAR(intrPending);
UNSERIALIZE_SCALAR(dmaAborted);
UNSERIALIZE_ENUM(devState);
UNSERIALIZE_ENUM(dmaState);
UNSERIALIZE_ARRAY(dataBuffer, MAX_DMA_SIZE);
}
IdeDisk *
IdeDiskParams::create()
{
return new IdeDisk(this);
}