/* * 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. */ #include "dev/storage/ide_ctrl.hh" #include #include "base/cprintf.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" namespace gem5 { // Bus master IDE registers enum BMIRegOffset { BMICommand = 0x0, BMIStatus = 0x2, BMIDescTablePtr = 0x4 }; IdeController::Channel::Channel(std::string new_name, IdeController *new_ctrl, bool new_primary) : Named(new_name), ctrl(new_ctrl), primary(new_primary) { bmiRegs.reset(); bmiRegs.status.dmaCap0 = 1; bmiRegs.status.dmaCap1 = 1; } IdeController::IdeController(const Params &p) : PciDevice(p), configSpaceRegs(name() + ".config_space_regs"), primary(name() + ".primary", this, true), secondary(name() + ".secondary", this, false), ioShift(p.io_shift), ctrlOffset(p.ctrl_offset) { panic_if(params().disks.size() > 4, "IDE controllers support a maximum of 4 devices attached!"); // Assign the disks to channels for (int i = 0; i < params().disks.size(); i++) { auto *disk = params().disks[i]; auto &channel = (i < 2) ? primary : secondary; if (!disk) continue; if (i % 2 == 0) channel.setDevice0(disk); else channel.setDevice1(disk); // Arbitrarily set the chunk size to 4K. disk->setChannel(&channel, 4 * 1024); } primary.select(false); secondary.select(false); } void IdeController::ConfigSpaceRegs::serialize(CheckpointOut &cp) const { SERIALIZE_SCALAR(primaryTiming); SERIALIZE_SCALAR(secondaryTiming); SERIALIZE_SCALAR(deviceTiming); SERIALIZE_SCALAR(udmaControl); SERIALIZE_SCALAR(udmaTiming); } void IdeController::ConfigSpaceRegs::unserialize(CheckpointIn &cp) { UNSERIALIZE_SCALAR(primaryTiming); UNSERIALIZE_SCALAR(secondaryTiming); UNSERIALIZE_SCALAR(deviceTiming); UNSERIALIZE_SCALAR(udmaControl); UNSERIALIZE_SCALAR(udmaTiming); } void IdeController::Channel::postInterrupt() { bmiRegs.status.intStatus = 1; _pendingInterrupt = true; ctrl->postInterrupt(isPrimary()); } void IdeController::Channel::clearInterrupt() { bmiRegs.status.intStatus = 0; _pendingInterrupt = false; ctrl->clearInterrupt(isPrimary()); } void IdeController::postInterrupt(bool is_primary) { auto &other = is_primary ? secondary : primary; // If an interrupt isn't already posted for the other channel... if (!other.pendingInterrupt()) PciDevice::intrPost(); } void IdeController::clearInterrupt(bool is_primary) { auto &other = is_primary ? secondary : primary; // If the interrupt isn't still needed by the other channel... if (!other.pendingInterrupt()) PciDevice::intrClear(); } Tick IdeController::readConfig(PacketPtr pkt) { int offset = pkt->getAddr() & PCI_CONFIG_SIZE; if (offset < PCI_DEVICE_SPECIFIC) return PciDevice::readConfig(pkt); size_t size = pkt->getSize(); configSpaceRegs.read(offset, pkt->getPtr(), size); DPRINTF(IdeCtrl, "PCI read offset: %#x size: %d data: %#x\n", offset, size, pkt->getUintX(ByteOrder::little)); pkt->makeAtomicResponse(); return configDelay; } Tick IdeController::writeConfig(PacketPtr pkt) { int offset = pkt->getAddr() & PCI_CONFIG_SIZE; if (offset < PCI_DEVICE_SPECIFIC) return PciDevice::writeConfig(pkt); size_t size = pkt->getSize(); DPRINTF(IdeCtrl, "PCI write offset: %#x size: %d data: %#x\n", offset, size, pkt->getUintX(ByteOrder::little)); configSpaceRegs.write(offset, pkt->getConstPtr(), size); pkt->makeAtomicResponse(); 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 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()); Addr addr = pkt->getAddr(); int size = pkt->getSize(); uint8_t *dataPtr = pkt->getPtr(); int bar_num; Addr offset; panic_if(!getBAR(addr, bar_num, offset), "IDE controller access to invalid address: %#x.", addr); switch (bar_num) { case 0: // linux may have shifted the address by ioShift, // here we shift it back, similarly for ctrlOffset. offset >>= ioShift; primary.accessCommand(offset, size, dataPtr, read); break; case 1: offset += ctrlOffset; primary.accessControl(offset, size, dataPtr, read); break; case 2: secondary.accessCommand(offset, size, dataPtr, read); break; case 3: secondary.accessControl(offset, size, dataPtr, read); break; case 4: { PciCommandRegister command = letoh(config.command); if (!read && !command.busMaster) return; if (offset < sizeof(Channel::BMIRegs)) { primary.accessBMI(offset, size, dataPtr, read); } else { offset -= sizeof(Channel::BMIRegs); secondary.accessBMI(offset, size, dataPtr, read); } } } #ifndef NDEBUG uint32_t data; if (pkt->getSize() == 1) data = pkt->getLE(); else if (pkt->getSize() == 2) data = pkt->getLE(); else data = pkt->getLE(); DPRINTF(IdeCtrl, "%s from offset: %#x size: %#x data: %#x\n", read ? "Read" : "Write", pkt->getAddr(), pkt->getSize(), data); #endif pkt->makeAtomicResponse(); } void IdeController::Channel::setDmaComplete() { bmiRegs.command.startStop = 0; bmiRegs.status.active = 0; bmiRegs.status.intStatus = 1; } 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 configSpaceRegs.serialize(cp); } void IdeController::Channel::serialize(const std::string &base, CheckpointOut &cp) const { 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 configSpaceRegs.unserialize(cp); } void IdeController::Channel::unserialize(const std::string &base, CheckpointIn &cp) { 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); } } // namespace gem5