diff options
Diffstat (limited to 'src/dev/storage/ide_ctrl.cc')
-rw-r--r-- | src/dev/storage/ide_ctrl.cc | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/src/dev/storage/ide_ctrl.cc b/src/dev/storage/ide_ctrl.cc new file mode 100644 index 000000000..feed9cfd2 --- /dev/null +++ b/src/dev/storage/ide_ctrl.cc @@ -0,0 +1,650 @@ +/* + * 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. + * + * Authors: Andrew Schultz + * Ali Saidi + * Miguel Serrano + */ + +#include "dev/storage/ide_ctrl.hh" + +#include <string> + +#include "cpu/intr_control.hh" +#include "debug/IdeCtrl.hh" +#include "dev/storage/ide_disk.hh" +#include "mem/packet.hh" +#include "mem/packet_access.hh" +#include "params/IdeController.hh" +#include "sim/byteswap.hh" + +// clang complains about std::set being overloaded with Packet::set if +// we open up the entire namespace std +using std::string; + +// Bus master IDE registers +enum BMIRegOffset { + BMICommand = 0x0, + BMIStatus = 0x2, + BMIDescTablePtr = 0x4 +}; + +// PCI config space registers +enum ConfRegOffset { + PrimaryTiming = 0x40, + SecondaryTiming = 0x42, + DeviceTiming = 0x44, + UDMAControl = 0x48, + UDMATiming = 0x4A, + IDEConfig = 0x54 +}; + +static const uint16_t timeRegWithDecodeEn = 0x8000; + +IdeController::Channel::Channel( + string newName, Addr _cmdSize, Addr _ctrlSize) : + _name(newName), + cmdAddr(0), cmdSize(_cmdSize), ctrlAddr(0), ctrlSize(_ctrlSize), + master(NULL), slave(NULL), selected(NULL) +{ + memset(&bmiRegs, 0, sizeof(bmiRegs)); + bmiRegs.status.dmaCap0 = 1; + bmiRegs.status.dmaCap1 = 1; +} + +IdeController::Channel::~Channel() +{ +} + +IdeController::IdeController(Params *p) + : PciDevice(p), primary(name() + ".primary", BARSize[0], BARSize[1]), + secondary(name() + ".secondary", BARSize[2], BARSize[3]), + bmiAddr(0), bmiSize(BARSize[4]), + primaryTiming(htole(timeRegWithDecodeEn)), + secondaryTiming(htole(timeRegWithDecodeEn)), + deviceTiming(0), udmaControl(0), udmaTiming(0), ideConfig(0), + ioEnabled(false), bmEnabled(false), + ioShift(p->io_shift), ctrlOffset(p->ctrl_offset) +{ + if (params()->disks.size() > 3) + panic("IDE controllers support a maximum of 4 devices attached!\n"); + + // Assign the disks to channels + int numDisks = params()->disks.size(); + if (numDisks > 0) + primary.master = params()->disks[0]; + if (numDisks > 1) + primary.slave = params()->disks[1]; + if (numDisks > 2) + secondary.master = params()->disks[2]; + if (numDisks > 3) + secondary.slave = params()->disks[3]; + + for (int i = 0; i < params()->disks.size(); i++) { + params()->disks[i]->setController(this); + } + primary.select(false); + secondary.select(false); + + if ((BARAddrs[0] & ~BAR_IO_MASK) && (!legacyIO[0] || ioShift)) { + primary.cmdAddr = BARAddrs[0]; primary.cmdSize = BARSize[0]; + primary.ctrlAddr = BARAddrs[1]; primary.ctrlSize = BARSize[1]; + } + if ((BARAddrs[2] & ~BAR_IO_MASK) && (!legacyIO[2] || ioShift)) { + secondary.cmdAddr = BARAddrs[2]; secondary.cmdSize = BARSize[2]; + secondary.ctrlAddr = BARAddrs[3]; secondary.ctrlSize = BARSize[3]; + } + + ioEnabled = (config.command & htole(PCI_CMD_IOSE)); + bmEnabled = (config.command & htole(PCI_CMD_BME)); +} + +bool +IdeController::isDiskSelected(IdeDisk *diskPtr) +{ + return (primary.selected == diskPtr || secondary.selected == diskPtr); +} + +void +IdeController::intrPost() +{ + primary.bmiRegs.status.intStatus = 1; + PciDevice::intrPost(); +} + +void +IdeController::setDmaComplete(IdeDisk *disk) +{ + Channel *channel; + if (disk == primary.master || disk == primary.slave) { + channel = &primary; + } else if (disk == secondary.master || disk == secondary.slave) { + channel = &secondary; + } else { + panic("Unable to find disk based on pointer %#x\n", disk); + } + + channel->bmiRegs.command.startStop = 0; + channel->bmiRegs.status.active = 0; + channel->bmiRegs.status.intStatus = 1; +} + +Tick +IdeController::readConfig(PacketPtr pkt) +{ + int offset = pkt->getAddr() & PCI_CONFIG_SIZE; + if (offset < PCI_DEVICE_SPECIFIC) { + return PciDevice::readConfig(pkt); + } + + switch (pkt->getSize()) { + case sizeof(uint8_t): + switch (offset) { + case DeviceTiming: + pkt->set<uint8_t>(deviceTiming); + break; + case UDMAControl: + pkt->set<uint8_t>(udmaControl); + break; + case PrimaryTiming + 1: + pkt->set<uint8_t>(bits(htole(primaryTiming), 15, 8)); + break; + case SecondaryTiming + 1: + pkt->set<uint8_t>(bits(htole(secondaryTiming), 15, 8)); + break; + case IDEConfig: + pkt->set<uint8_t>(bits(htole(ideConfig), 7, 0)); + break; + case IDEConfig + 1: + pkt->set<uint8_t>(bits(htole(ideConfig), 15, 8)); + break; + default: + panic("Invalid PCI configuration read for size 1 at offset: %#x!\n", + offset); + } + DPRINTF(IdeCtrl, "PCI read offset: %#x size: 1 data: %#x\n", offset, + (uint32_t)pkt->get<uint8_t>()); + break; + case sizeof(uint16_t): + switch (offset) { + case UDMAControl: + pkt->set<uint16_t>(udmaControl); + break; + case PrimaryTiming: + pkt->set<uint16_t>(primaryTiming); + break; + case SecondaryTiming: + pkt->set<uint16_t>(secondaryTiming); + break; + case UDMATiming: + pkt->set<uint16_t>(udmaTiming); + break; + case IDEConfig: + pkt->set<uint16_t>(ideConfig); + break; + default: + panic("Invalid PCI configuration read for size 2 offset: %#x!\n", + offset); + } + DPRINTF(IdeCtrl, "PCI read offset: %#x size: 2 data: %#x\n", offset, + (uint32_t)pkt->get<uint16_t>()); + break; + case sizeof(uint32_t): + switch (offset) { + case PrimaryTiming: + pkt->set<uint32_t>(primaryTiming); + break; + case IDEConfig: + pkt->set<uint32_t>(ideConfig); + break; + default: + panic("No 32bit reads implemented for this device."); + } + DPRINTF(IdeCtrl, "PCI read offset: %#x size: 4 data: %#x\n", offset, + (uint32_t)pkt->get<uint32_t>()); + break; + default: + panic("invalid access size(?) for PCI configspace!\n"); + } + pkt->makeAtomicResponse(); + return configDelay; +} + + +Tick +IdeController::writeConfig(PacketPtr pkt) +{ + int offset = pkt->getAddr() & PCI_CONFIG_SIZE; + if (offset < PCI_DEVICE_SPECIFIC) { + PciDevice::writeConfig(pkt); + } else { + switch (pkt->getSize()) { + case sizeof(uint8_t): + switch (offset) { + case DeviceTiming: + deviceTiming = pkt->get<uint8_t>(); + break; + case UDMAControl: + udmaControl = pkt->get<uint8_t>(); + break; + case IDEConfig: + replaceBits(ideConfig, 7, 0, pkt->get<uint8_t>()); + break; + case IDEConfig + 1: + replaceBits(ideConfig, 15, 8, pkt->get<uint8_t>()); + break; + default: + panic("Invalid PCI configuration write " + "for size 1 offset: %#x!\n", offset); + } + DPRINTF(IdeCtrl, "PCI write offset: %#x size: 1 data: %#x\n", + offset, (uint32_t)pkt->get<uint8_t>()); + break; + case sizeof(uint16_t): + switch (offset) { + case UDMAControl: + udmaControl = pkt->get<uint16_t>(); + break; + case PrimaryTiming: + primaryTiming = pkt->get<uint16_t>(); + break; + case SecondaryTiming: + secondaryTiming = pkt->get<uint16_t>(); + break; + case UDMATiming: + udmaTiming = pkt->get<uint16_t>(); + break; + case IDEConfig: + ideConfig = pkt->get<uint16_t>(); + break; + default: + panic("Invalid PCI configuration write " + "for size 2 offset: %#x!\n", + offset); + } + DPRINTF(IdeCtrl, "PCI write offset: %#x size: 2 data: %#x\n", + offset, (uint32_t)pkt->get<uint16_t>()); + break; + case sizeof(uint32_t): + switch (offset) { + case PrimaryTiming: + primaryTiming = pkt->get<uint32_t>(); + break; + case IDEConfig: + ideConfig = pkt->get<uint32_t>(); + break; + default: + panic("Write of unimplemented PCI config. register: %x\n", offset); + } + break; + default: + panic("invalid access size(?) for PCI configspace!\n"); + } + pkt->makeAtomicResponse(); + } + + /* Trap command register writes and enable IO/BM as appropriate as well as + * BARs. */ + switch(offset) { + case PCI0_BASE_ADDR0: + if (BARAddrs[0] != 0) + primary.cmdAddr = BARAddrs[0]; + break; + + case PCI0_BASE_ADDR1: + if (BARAddrs[1] != 0) + primary.ctrlAddr = BARAddrs[1]; + break; + + case PCI0_BASE_ADDR2: + if (BARAddrs[2] != 0) + secondary.cmdAddr = BARAddrs[2]; + break; + + case PCI0_BASE_ADDR3: + if (BARAddrs[3] != 0) + secondary.ctrlAddr = BARAddrs[3]; + break; + + case PCI0_BASE_ADDR4: + if (BARAddrs[4] != 0) + bmiAddr = BARAddrs[4]; + break; + + case PCI_COMMAND: + DPRINTF(IdeCtrl, "Writing to PCI Command val: %#x\n", config.command); + ioEnabled = (config.command & htole(PCI_CMD_IOSE)); + bmEnabled = (config.command & htole(PCI_CMD_BME)); + break; + } + return configDelay; +} + +void +IdeController::Channel::accessCommand(Addr offset, + int size, uint8_t *data, bool read) +{ + const Addr SelectOffset = 6; + const uint8_t SelectDevBit = 0x10; + + if (!read && offset == SelectOffset) + select(*data & SelectDevBit); + + if (selected == NULL) { + assert(size == sizeof(uint8_t)); + *data = 0; + } else if (read) { + selected->readCommand(offset, size, data); + } else { + selected->writeCommand(offset, size, data); + } +} + +void +IdeController::Channel::accessControl(Addr offset, + int size, uint8_t *data, bool read) +{ + if (selected == NULL) { + assert(size == sizeof(uint8_t)); + *data = 0; + } else if (read) { + selected->readControl(offset, size, data); + } else { + selected->writeControl(offset, size, data); + } +} + +void +IdeController::Channel::accessBMI(Addr offset, + int size, uint8_t *data, bool read) +{ + assert(offset + size <= sizeof(BMIRegs)); + if (read) { + memcpy(data, (uint8_t *)&bmiRegs + offset, size); + } else { + switch (offset) { + case BMICommand: + { + if (size != sizeof(uint8_t)) + panic("Invalid BMIC write size: %x\n", size); + + BMICommandReg oldVal = bmiRegs.command; + BMICommandReg newVal = *data; + + // if a DMA transfer is in progress, R/W control cannot change + if (oldVal.startStop && oldVal.rw != newVal.rw) + oldVal.rw = newVal.rw; + + if (oldVal.startStop != newVal.startStop) { + if (selected == NULL) + panic("DMA start for disk which does not exist\n"); + + if (oldVal.startStop) { + DPRINTF(IdeCtrl, "Stopping DMA transfer\n"); + bmiRegs.status.active = 0; + + selected->abortDma(); + } else { + DPRINTF(IdeCtrl, "Starting DMA transfer\n"); + bmiRegs.status.active = 1; + + selected->startDma(letoh(bmiRegs.bmidtp)); + } + } + + bmiRegs.command = newVal; + } + break; + case BMIStatus: + { + if (size != sizeof(uint8_t)) + panic("Invalid BMIS write size: %x\n", size); + + BMIStatusReg oldVal = bmiRegs.status; + BMIStatusReg newVal = *data; + + // the BMIDEA bit is read only + newVal.active = oldVal.active; + + // to reset (set 0) IDEINTS and IDEDMAE, write 1 to each + if ((oldVal.intStatus == 1) && (newVal.intStatus == 1)) { + newVal.intStatus = 0; // clear the interrupt? + } else { + // Assigning two bitunion fields to each other does not + // work as intended, so we need to use this temporary variable + // to get around the bug. + uint8_t tmp = oldVal.intStatus; + newVal.intStatus = tmp; + } + if ((oldVal.dmaError == 1) && (newVal.dmaError == 1)) { + newVal.dmaError = 0; + } else { + uint8_t tmp = oldVal.dmaError; + newVal.dmaError = tmp; + } + + bmiRegs.status = newVal; + } + break; + case BMIDescTablePtr: + if (size != sizeof(uint32_t)) + panic("Invalid BMIDTP write size: %x\n", size); + bmiRegs.bmidtp = htole(*(uint32_t *)data & ~0x3); + break; + default: + if (size != sizeof(uint8_t) && size != sizeof(uint16_t) && + size != sizeof(uint32_t)) + panic("IDE controller write of invalid write size: %x\n", size); + memcpy((uint8_t *)&bmiRegs + offset, data, size); + } + } +} + +void +IdeController::dispatchAccess(PacketPtr pkt, bool read) +{ + if (pkt->getSize() != 1 && pkt->getSize() != 2 && pkt->getSize() !=4) + panic("Bad IDE read size: %d\n", pkt->getSize()); + + if (!ioEnabled) { + pkt->makeAtomicResponse(); + DPRINTF(IdeCtrl, "io not enabled\n"); + return; + } + + Addr addr = pkt->getAddr(); + int size = pkt->getSize(); + uint8_t *dataPtr = pkt->getPtr<uint8_t>(); + + if (addr >= primary.cmdAddr && + addr < (primary.cmdAddr + primary.cmdSize)) { + addr -= primary.cmdAddr; + // linux may have shifted the address by ioShift, + // here we shift it back, similarly for ctrlOffset. + addr >>= ioShift; + primary.accessCommand(addr, size, dataPtr, read); + } else if (addr >= primary.ctrlAddr && + addr < (primary.ctrlAddr + primary.ctrlSize)) { + addr -= primary.ctrlAddr; + addr += ctrlOffset; + primary.accessControl(addr, size, dataPtr, read); + } else if (addr >= secondary.cmdAddr && + addr < (secondary.cmdAddr + secondary.cmdSize)) { + addr -= secondary.cmdAddr; + secondary.accessCommand(addr, size, dataPtr, read); + } else if (addr >= secondary.ctrlAddr && + addr < (secondary.ctrlAddr + secondary.ctrlSize)) { + addr -= secondary.ctrlAddr; + secondary.accessControl(addr, size, dataPtr, read); + } else if (addr >= bmiAddr && addr < (bmiAddr + bmiSize)) { + if (!read && !bmEnabled) + return; + addr -= bmiAddr; + if (addr < sizeof(Channel::BMIRegs)) { + primary.accessBMI(addr, size, dataPtr, read); + } else { + addr -= sizeof(Channel::BMIRegs); + secondary.accessBMI(addr, size, dataPtr, read); + } + } else { + panic("IDE controller access to invalid address: %#x\n", addr); + } + +#ifndef NDEBUG + uint32_t data; + if (pkt->getSize() == 1) + data = pkt->get<uint8_t>(); + else if (pkt->getSize() == 2) + data = pkt->get<uint16_t>(); + else + data = pkt->get<uint32_t>(); + DPRINTF(IdeCtrl, "%s from offset: %#x size: %#x data: %#x\n", + read ? "Read" : "Write", pkt->getAddr(), pkt->getSize(), data); +#endif + + pkt->makeAtomicResponse(); +} + +Tick +IdeController::read(PacketPtr pkt) +{ + dispatchAccess(pkt, true); + return pioDelay; +} + +Tick +IdeController::write(PacketPtr pkt) +{ + dispatchAccess(pkt, false); + return pioDelay; +} + +void +IdeController::serialize(CheckpointOut &cp) const +{ + // Serialize the PciDevice base class + PciDevice::serialize(cp); + + // Serialize channels + primary.serialize("primary", cp); + secondary.serialize("secondary", cp); + + // Serialize config registers + SERIALIZE_SCALAR(primaryTiming); + SERIALIZE_SCALAR(secondaryTiming); + SERIALIZE_SCALAR(deviceTiming); + SERIALIZE_SCALAR(udmaControl); + SERIALIZE_SCALAR(udmaTiming); + SERIALIZE_SCALAR(ideConfig); + + // Serialize internal state + SERIALIZE_SCALAR(ioEnabled); + SERIALIZE_SCALAR(bmEnabled); + SERIALIZE_SCALAR(bmiAddr); + SERIALIZE_SCALAR(bmiSize); +} + +void +IdeController::Channel::serialize(const std::string &base, + CheckpointOut &cp) const +{ + paramOut(cp, base + ".cmdAddr", cmdAddr); + paramOut(cp, base + ".cmdSize", cmdSize); + paramOut(cp, base + ".ctrlAddr", ctrlAddr); + paramOut(cp, base + ".ctrlSize", ctrlSize); + uint8_t command = bmiRegs.command; + paramOut(cp, base + ".bmiRegs.command", command); + paramOut(cp, base + ".bmiRegs.reserved0", bmiRegs.reserved0); + uint8_t status = bmiRegs.status; + paramOut(cp, base + ".bmiRegs.status", status); + paramOut(cp, base + ".bmiRegs.reserved1", bmiRegs.reserved1); + paramOut(cp, base + ".bmiRegs.bmidtp", bmiRegs.bmidtp); + paramOut(cp, base + ".selectBit", selectBit); +} + +void +IdeController::unserialize(CheckpointIn &cp) +{ + // Unserialize the PciDevice base class + PciDevice::unserialize(cp); + + // Unserialize channels + primary.unserialize("primary", cp); + secondary.unserialize("secondary", cp); + + // Unserialize config registers + UNSERIALIZE_SCALAR(primaryTiming); + UNSERIALIZE_SCALAR(secondaryTiming); + UNSERIALIZE_SCALAR(deviceTiming); + UNSERIALIZE_SCALAR(udmaControl); + UNSERIALIZE_SCALAR(udmaTiming); + UNSERIALIZE_SCALAR(ideConfig); + + // Unserialize internal state + UNSERIALIZE_SCALAR(ioEnabled); + UNSERIALIZE_SCALAR(bmEnabled); + UNSERIALIZE_SCALAR(bmiAddr); + UNSERIALIZE_SCALAR(bmiSize); +} + +void +IdeController::Channel::unserialize(const std::string &base, CheckpointIn &cp) +{ + paramIn(cp, base + ".cmdAddr", cmdAddr); + paramIn(cp, base + ".cmdSize", cmdSize); + paramIn(cp, base + ".ctrlAddr", ctrlAddr); + paramIn(cp, base + ".ctrlSize", ctrlSize); + uint8_t command; + paramIn(cp, base +".bmiRegs.command", command); + bmiRegs.command = command; + paramIn(cp, base + ".bmiRegs.reserved0", bmiRegs.reserved0); + uint8_t status; + paramIn(cp, base + ".bmiRegs.status", status); + bmiRegs.status = status; + paramIn(cp, base + ".bmiRegs.reserved1", bmiRegs.reserved1); + paramIn(cp, base + ".bmiRegs.bmidtp", bmiRegs.bmidtp); + paramIn(cp, base + ".selectBit", selectBit); + select(selectBit); +} + +IdeController * +IdeControllerParams::create() +{ + return new IdeController(this); +} |