| /* |
| * Copyright 2022 Google, Inc. |
| * |
| * 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/qemu/fw_cfg.hh" |
| |
| #include <cstring> |
| #include <sstream> |
| #include <string> |
| |
| #include "base/compiler.hh" |
| #include "base/cprintf.hh" |
| #include "base/logging.hh" |
| #include "base/trace.hh" |
| #include "debug/QemuFwCfg.hh" |
| #include "debug/QemuFwCfgVerbose.hh" |
| #include "mem/packet_access.hh" |
| #include "sim/byteswap.hh" |
| |
| namespace gem5 |
| { |
| |
| namespace qemu |
| { |
| |
| void |
| FwCfgItemFixed::read(void *buf, uint64_t offset, uint32_t to_read) |
| { |
| // Get access to the data we need to fill this buffer. |
| const void *data = bytes(); |
| const uint64_t total_length = length(); |
| |
| if (offset > total_length) { |
| // We're completely off the end, so return only zeroes. |
| std::memset(buf, 0, to_read); |
| return; |
| } |
| |
| if (offset + to_read > total_length) { |
| // We're partially off the end, truncate this read and zero fill. |
| |
| // Figure out how far past the end we're attempting to read. |
| uint64_t overflow = offset + to_read - total_length; |
| |
| // Reduce the requested read size to what we can actually fill. |
| to_read -= overflow; |
| |
| // Zero out the part we won't read data into. |
| std::memset((uint8_t *)buf + to_read, 0, overflow); |
| } |
| |
| // Do the read. |
| std::memcpy(buf, (uint8_t *)data + offset, to_read); |
| } |
| |
| FwCfg::FwCfg(const Params &p, const AddrRangeList &addr_ranges) : |
| PioDevice(p), |
| signature(".[FW_CFG_SIGNATURE]", false, "QEMU CFG", 0), |
| // The ID says we support the traditional interface but not DMA. To enable |
| // DMA, this should be equal to 3. |
| id(".[FW_CFG_ID]", false, "\x1", 1), |
| addrRanges(addr_ranges) |
| { |
| // Add the unnamed, fixed items. |
| addItem(&signature); |
| addItem(&id); |
| |
| for (auto factory: p.items) { |
| // Process named items and add them to the index. |
| auto &item = factory->item(); |
| |
| uint32_t &next_index = |
| item.archSpecific() ? nextArchIndex : nextGenericIndex; |
| const uint32_t &max_index = |
| item.archSpecific() ? MaxArchIndex : MaxGenericIndex; |
| |
| // Automatically assign an ID if a fixed one wasn't specified. |
| if (!item.index()) |
| item.index(next_index++); |
| |
| panic_if(item.index() >= max_index, |
| "Firmware config device out of %s indexes.", |
| item.archSpecific() ? "arch" : "generic"); |
| |
| addItem(&item); |
| } |
| |
| directory.update(names, numbers); |
| addItem(&directory); |
| }; |
| |
| void |
| FwCfg::addItem(FwCfgItem *item) |
| { |
| const auto [kit, ksuccess] = |
| numbers.insert(std::make_pair(item->index(), item)); |
| |
| panic_if(!ksuccess, "Duplicate firmware config item key %#x, " |
| "paths %s and %s.", |
| item->index(), item->path(), kit->second->path()); |
| |
| const std::string &path = item->path(); |
| if (path.empty() || path[0] != '.') { |
| const auto res = |
| names.insert(std::make_pair(item->path(), item->index())); |
| |
| panic_if(!res.second, "Duplicate firmware config item path %s.", |
| item->path()); |
| } |
| } |
| |
| void |
| FwCfg::select(uint16_t key) |
| { |
| DPRINTF(QemuFwCfg, "Selecting item with key %#x.\n", key); |
| |
| // Clear any previous selection. |
| offset = 0; |
| current = nullptr; |
| |
| auto iter = numbers.find(key); |
| if (iter == numbers.end()) { |
| warn("Firmware config failed to select item with key %#x.", key); |
| return; |
| } |
| |
| auto item = iter->second; |
| |
| current = item; |
| if (current) |
| DPRINTF(QemuFwCfg, "Selected item with path %s.\n", item->path()); |
| else |
| DPRINTF(QemuFwCfg, "No item is currently selected.\n"); |
| } |
| |
| void |
| FwCfg::readItem(void *buf, uint32_t length) |
| { |
| if (!current) { |
| DPRINTF(QemuFwCfgVerbose, |
| "Tried to read while nothing was selected.\n"); |
| std::memset(buf, 0, length); |
| return; |
| } |
| |
| current->read(buf, offset, length); |
| |
| if (gem5::debug::QemuFwCfgVerbose) { |
| std::stringstream data_str; |
| for (int idx = 0; idx < length; idx++) |
| ccprintf(data_str, " %02x", ((uint8_t *)buf)[idx]); |
| |
| DPRINTF(QemuFwCfgVerbose, "Read [%#x-%#x) =>%s.\n", |
| offset, offset + length, data_str.str()); |
| } |
| |
| offset += length; |
| } |
| |
| FwCfg::Directory::Directory() : |
| FwCfgItemFixed(".[FW_CFG_FILE_DIR]", false, 0x19) |
| {} |
| |
| void |
| FwCfg::Directory::update( |
| const std::map<std::string, uint16_t> &names, |
| const std::map<uint16_t, FwCfgItem *> &numbers) |
| { |
| uint32_t count = names.size(); |
| |
| struct GEM5_PACKED File |
| { |
| uint32_t size; |
| uint16_t select; |
| uint16_t reserved; |
| char name[56]; |
| }; |
| |
| uint64_t bytes = sizeof(count) + sizeof(File) * count; |
| data.resize(bytes); |
| |
| uint8_t *ptr = data.data(); |
| |
| uint32_t be_count = htobe(count); |
| std::memcpy(ptr, &be_count, sizeof(be_count)); |
| ptr += sizeof(be_count); |
| |
| for (auto &[name, index]: names) { |
| // Fill in the entry. |
| File file{(uint32_t)numbers.at(index)->length(), index, 0, {}}; |
| std::memset(file.name, 0, sizeof(file.name)); |
| std::strncpy(file.name, name.c_str(), sizeof(file.name) - 1); |
| |
| // Fix endianness. |
| file.size = htobe(file.size); |
| file.select = htobe(file.select); |
| |
| // Copy it to the buffer and update ptr. |
| std::memcpy(ptr, &file, sizeof(file)); |
| ptr += sizeof(file); |
| } |
| } |
| |
| FwCfgIo::FwCfgIo(const Params &p) : FwCfg(p, { |
| // This covers both the 16 bit selector, and the 8 bit data reg which |
| // overlaps it. |
| {p.selector_addr, p.selector_addr + 2}}), |
| selectorAddr(p.selector_addr), dataAddr(p.selector_addr + 1) |
| {} |
| |
| Tick |
| FwCfgIo::read(PacketPtr pkt) |
| { |
| const Addr addr = pkt->getAddr(); |
| const auto size = pkt->getSize(); |
| |
| pkt->makeResponse(); |
| // The default response is all zeroes. |
| std::memset(pkt->getPtr<uint8_t>(), 0, size); |
| |
| if (addr == selectorAddr) { |
| warn("Read from firmware config selector register not supported."); |
| } else if (addr == dataAddr) { |
| if (size == 1) { |
| readItem(pkt->getPtr<void>(), size); |
| } else { |
| warn("Read from firmware config data register with width %d not " |
| "supported.", size); |
| } |
| } else { |
| panic("Unregognized firmware config read [%#x-%#x).", |
| addr, addr + size); |
| } |
| |
| return 0; |
| } |
| |
| Tick |
| FwCfgIo::write(PacketPtr pkt) |
| { |
| const Addr addr = pkt->getAddr(); |
| const auto size = pkt->getSize(); |
| |
| pkt->makeResponse(); |
| |
| if (addr == selectorAddr) { |
| if (size != 2) { |
| warn("Write to firmware config selector register with width %d " |
| "not supported.", size); |
| } else { |
| auto key = pkt->getLE<uint16_t>(); |
| select(key); |
| } |
| } else if (addr == dataAddr) { |
| // Writes to the firmware config data can only be done through the |
| // DMA interface. |
| warn("Write to firmware config data register not supported."); |
| } else { |
| panic("Unrecognized firmware config write [%#x-%#x).", |
| addr, addr + size); |
| } |
| |
| return 0; |
| } |
| |
| FwCfgMmio::FwCfgMmio(const Params &p) : FwCfg(p, { |
| {p.selector_addr, p.selector_addr + 2}, |
| {p.data_addr_range}}), |
| selectorAddr(p.selector_addr), |
| dataAddr(p.data_addr_range.start()), dataSize(p.data_addr_range.size()) |
| {} |
| |
| Tick |
| FwCfgMmio::read(PacketPtr pkt) |
| { |
| const Addr addr = pkt->getAddr(); |
| const auto size = pkt->getSize(); |
| |
| pkt->makeResponse(); |
| // The default response is all zeroes. |
| std::memset(pkt->getPtr<uint8_t>(), 0, size); |
| |
| if (addr == selectorAddr) { |
| warn("Read from firmware config selector register not supported."); |
| } else if (addr == dataAddr) { |
| if (size == dataSize) { |
| readItem(pkt->getPtr<void>(), size); |
| } else { |
| warn("Read from firmware config data register with width %d not " |
| "supported.", size); |
| } |
| } else { |
| panic("Unregognized firmware config read [%#x-%#x).", |
| addr, addr + size); |
| } |
| |
| return 0; |
| } |
| |
| Tick |
| FwCfgMmio::write(PacketPtr pkt) |
| { |
| const Addr addr = pkt->getAddr(); |
| const auto size = pkt->getSize(); |
| |
| pkt->makeResponse(); |
| |
| if (addr == selectorAddr) { |
| if (size != 2) { |
| warn("Write to firmware config selector register with width %d " |
| "not supported.", size); |
| } else { |
| auto key = pkt->getBE<uint16_t>(); |
| select(key); |
| } |
| } else if (addr == dataAddr) { |
| // Writes to the firmware config data can only be done through the |
| // DMA interface. |
| warn("Write to firmware config data register not supported."); |
| } else { |
| panic("Unrecognized firmware config write [%#x-%#x).", |
| addr, addr + size); |
| } |
| |
| return 0; |
| } |
| |
| } // namespace qemu |
| } // namespace gem5 |