X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=dev%2Fide_disk.cc;h=99724f07788c1c5f7b5cde237ccf465274da6319;hb=239183cfe675684aeca1a2b24362ca5e0a82809d;hp=edf01552f304a5bb9d01f8091340d64c8de48077;hpb=f4168a708c15c219a968d9cd665b571705467875;p=gem5.git diff --git a/dev/ide_disk.cc b/dev/ide_disk.cc index edf01552f..99724f077 100644 --- a/dev/ide_disk.cc +++ b/dev/ide_disk.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003 The Regents of The University of Michigan + * Copyright (c) 2004 The Regents of The University of Michigan * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,34 +35,1136 @@ #include #include +#include "arch/alpha/pmap.h" #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_mem/physical_memory.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/universe.hh" using namespace std; -IdeDisk::IdeDisk(const string &name, DiskImage *img, int delay) - : SimObject(name), ctrl(NULL), image(img) +IdeDisk::IdeDisk(const string &name, DiskImage *img, PhysicalMemory *phys, + int id, int delay) + : SimObject(name), ctrl(NULL), image(img), physmem(phys), + dmaTransferEvent(this), dmaReadWaitEvent(this), + dmaWriteWaitEvent(this), dmaPrdReadEvent(this), + dmaReadEvent(this), dmaWriteEvent(this) { - diskDelay = delay * ticksPerSecond / 1000; + // Reset the device state + reset(id); + + // calculate disk delay in microseconds + diskDelay = (delay * ticksPerSecond / 100000); + + // fill out the drive ID structure + memset(&driveID, 0, sizeof(struct hd_driveid)); + + // 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 + sprintf((char *)driveID.model, "5MI EDD si k"); + // Set the maximum multisector transfer size + driveID.max_multsect = MAX_MULTSECT; + // IORDY supported, IORDY disabled, LBA enabled, DMA enabled + driveID.capability = 0x7; + // UDMA support, EIDE support + driveID.field_valid = 0x6; + // Setup default C/H/S settings + driveID.cyls = cylinders; + driveID.sectors = sectors; + driveID.heads = heads; + // Setup the current multisector transfer size + driveID.multsect = MAX_MULTSECT; + driveID.multsect_valid = 0x1; + // Number of sectors on disk + driveID.lba_capacity = lba_size; + // Multiword DMA mode 2 and below supported + driveID.dma_mword = 0x400; + // Set PIO mode 4 and 3 supported + driveID.eide_pio_modes = 0x3; + // Set DMA mode 4 and below supported + driveID.dma_ultra = 0x10; + // Statically set hardware config word + driveID.hw_config = 0x4001; } 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; +} + +//// +// Utility functions +//// + +bool +IdeDisk::isDEVSelect() +{ + return ctrl->isDiskSelected(this); +} + +Addr +IdeDisk::pciToDma(Addr pciAddr) +{ + if (ctrl) + return ctrl->tsunami->pchip->translatePciToDma(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 > ALPHA_PGBYTES) + bytesInPage = ALPHA_PGBYTES; + else + bytesInPage = bytesLeft; + + // Next, see if we have crossed a page boundary, and adjust + Addr upperBound = curAddr + bytesInPage; + Addr pageBound = alpha_trunc_page(curAddr) + ALPHA_PGBYTES; + + 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, bool byte, bool cmdBlk, uint8_t *data) +{ + DevAction_t action = ACT_NONE; + + if (cmdBlk) { + if (offset < 0 || offset > sizeof(CommandReg_t)) + panic("Invalid disk command register offset: %#x\n", offset); + + if (!byte && offset != DATA_OFFSET) + panic("Invalid 16-bit read, only allowed on data reg\n"); + + if (!byte) + *(uint16_t *)data = *(uint16_t *)&cmdReg.data0; + else + *data = ((uint8_t *)&cmdReg)[offset]; + + // determine if an action needs to be taken on the state machine + if (offset == STATUS_OFFSET) { + action = ACT_STAT_READ; + *data = status; // status is in a shadow, explicity copy + } else if (offset == DATA_OFFSET) { + if (byte) + action = ACT_DATA_READ_BYTE; + else + action = ACT_DATA_READ_SHORT; + } + + } else { + if (offset != ALTSTAT_OFFSET) + panic("Invalid disk control register offset: %#x\n", offset); + + if (!byte) + panic("Invalid 16-bit read from control block\n"); + + *data = status; + } + + if (action != ACT_NONE) + updateState(action); +} + +void +IdeDisk::write(const Addr &offset, bool byte, bool cmdBlk, const uint8_t *data) +{ + DevAction_t action = ACT_NONE; + + if (cmdBlk) { + if (offset < 0 || offset > sizeof(CommandReg_t)) + panic("Invalid disk command register offset: %#x\n", offset); + + if (!byte && offset != DATA_OFFSET) + panic("Invalid 16-bit write, only allowed on data reg\n"); + + if (!byte) + *((uint16_t *)&cmdReg.data0) = *(uint16_t *)data; + else + ((uint8_t *)&cmdReg)[offset] = *data; + + // determine if an action needs to be taken on the state machine + if (offset == COMMAND_OFFSET) { + action = ACT_CMD_WRITE; + } else if (offset == DATA_OFFSET) { + if (byte) + action = ACT_DATA_WRITE_BYTE; + else + action = ACT_DATA_WRITE_SHORT; + } else if (offset == SELECT_OFFSET) { + action = ACT_SELECT_WRITE; + } + + } else { + if (offset != CONTROL_OFFSET) + panic("Invalid disk control register offset: %#x\n", offset); + + if (!byte) + panic("Invalid 16-bit write to control block\n"); + + 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; + } + + 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() +{ + Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize); + + 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() +{ + Tick totalDiskDelay = diskDelay + (curPrd.getByteCount() / SectorSize); + + 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 = alpha_trunc_page(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 != alpha_trunc_page(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 = alpha_trunc_page(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 in Dma_Start or Dma_Transfer!\n"); + + if (devState != Transfer_Data_Dma && devState != Prepare_Data_Dma) + panic("Inconsistent device state, should be in 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 WIN_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 WIN_RECAL: + case WIN_SPECIFY: + case WIN_STANDBYNOW1: + case WIN_FLUSH_CACHE: + case WIN_VERIFY: + case WIN_SEEK: + case WIN_SETFEATURES: + case WIN_SETMULT: + devState = Command_Execution; + action = ACT_CMD_COMPLETE; + break; + + // Supported PIO data-in commands + case WIN_IDENTIFY: + cmdBytes = cmdBytesLeft = sizeof(struct hd_driveid); + devState = Prepare_Data_In; + action = ACT_DATA_READY; + break; + + case WIN_MULTREAD: + case WIN_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 WIN_MULTWRITE: + case WIN_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 WIN_WRITEDMA: + dmaRead = true; // a write to the disk is a DMA read from memory + case WIN_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() +{ + 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() +{ + 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 == WIN_IDENTIFY) { + // Reset the drqBytes for this block + drqBytesLeft = sizeof(struct hd_driveid); + + memcpy((void *)dataBuffer, (void *)&driveID, + sizeof(struct hd_driveid)); + } else { + // Reset the drqBytes for this block + drqBytesLeft = SectorSize; + + readDisk(curSector++, dataBuffer); + } + + // put the first two bytes into the data register + memcpy((void *)&cmdReg.data0, (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.data0, + (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.data0, + 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.data0); + SERIALIZE_SCALAR(cmdReg.data1); + 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.data0); + UNSERIALIZE_SCALAR(cmdReg.data1); + 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 @@ -70,6 +1172,8 @@ IdeDisk::unserialize(Checkpoint *cp, const string §ion) BEGIN_DECLARE_SIM_OBJECT_PARAMS(IdeDisk) SimObjectParam image; + SimObjectParam physmem; + Param driveID; Param disk_delay; END_DECLARE_SIM_OBJECT_PARAMS(IdeDisk) @@ -77,14 +1181,17 @@ END_DECLARE_SIM_OBJECT_PARAMS(IdeDisk) BEGIN_INIT_SIM_OBJECT_PARAMS(IdeDisk) INIT_PARAM(image, "Disk image"), - INIT_PARAM_DFLT(disk_delay, "Fixed disk delay in milliseconds", 0) + INIT_PARAM(physmem, "Physical memory"), + INIT_PARAM(driveID, "Drive ID (0=master 1=slave)"), + INIT_PARAM_DFLT(disk_delay, "Fixed disk delay in microseconds", 1) END_INIT_SIM_OBJECT_PARAMS(IdeDisk) CREATE_SIM_OBJECT(IdeDisk) { - return new IdeDisk(getInstanceName(), image, disk_delay); + return new IdeDisk(getInstanceName(), image, physmem, driveID, + disk_delay); } REGISTER_SIM_OBJECT("IdeDisk", IdeDisk)