/* FPGA IP component. * * Author: Steffen Vogel * SPDX-FileCopyrightText: 2017 Institute for Automation of Complex Power Systems, RWTH Aachen University * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace villas::fpga; using namespace villas::fpga::ip; // Special IPs that have to be initialized first. Will be initialized in the // same order as they appear in this list, i.e. first here will be initialized // first. static std::list vlnvInitializationOrder = { Vlnv("xilinx.com:ip:axi_pcie:"), Vlnv("xilinx.com:ip:xdma:"), Vlnv("xilinx.com:module_ref:axi_pcie_intc:"), Vlnv("xilinx.com:ip:axis_switch:"), Vlnv("xilinx.com:ip:axi_iic:"), }; std::list> CoreFactory::make(Card *card, json_t *json_ips) { // We only have this logger until we know the factory to build an IP with auto loggerStatic = getStaticLogger(); std::list allIps; // All IPs available in config std::list orderedIps; // IPs ordered in initialization order std::list> configuredIps; // Successfully configured IPs if (!card->ips.empty()) { loggerStatic->error("IP list of card {} already contains IPs.", card->name); throw RuntimeError("IP list of card {} already contains IPs.", card->name); } // Parse all IP instance names and their VLNV into list `allIps` const char *ipName; json_t *json_ip; json_object_foreach(json_ips, ipName, json_ip) { const char *vlnv; json_error_t err; int ret = json_unpack_ex(json_ip, &err, 0, "{ s: s }", "vlnv", &vlnv); if (ret != 0) throw ConfigError(json_ip, err, "", "IP {} has no VLNV", ipName); allIps.push_back({vlnv, ipName}); } // Pick out IPs to be initialized first. // // Reverse walktrough, because we push to the // front of the output list, so that the first element will also be the // first to be initialized. for (auto viIt = vlnvInitializationOrder.rbegin(); viIt != vlnvInitializationOrder.rend(); ++viIt) { // Iterate over IPs, if VLNV matches, push to front and remove from list for (auto it = allIps.begin(); it != allIps.end(); ++it) { if (*viIt == it->getVlnv()) { orderedIps.push_front(*it); it = allIps.erase(it); } } } // Insert all other IPs at the end orderedIps.splice(orderedIps.end(), allIps); loggerStatic->debug("IP initialization order:"); for (auto &id : orderedIps) { loggerStatic->debug(" " CLR_BLD("{}"), id.getName()); } // Configure all IPs for (auto &id : orderedIps) { loggerStatic->info("Configuring {}", id); // Find the appropriate factory that can create the specified VLNV // Note: // This is the magic part! Factories automatically register as a // plugin as soon as they are instantiated. If there are multiple // candidates, the first suitable factory will be used. auto *f = lookup(id.getVlnv()); if (f == nullptr) { loggerStatic->warn("No plugin found to handle {}", id.getVlnv()); continue; } else loggerStatic->debug("Using {} for IP {}", f->getName(), id.getVlnv()); auto logger = f->getLogger(); // Create new IP instance. Since this function is virtual, it will // construct the right, specialized type without knowing it here // because we have already picked the right factory. // If something goes wrong with initialization, the shared_ptr will // take care to desctruct the Core again as it is not pushed to // the list and will run out of scope. auto ip = std::unique_ptr(f->make()); if (ip == nullptr) { logger->warn("Cannot create an instance of {}", f->getName()); continue; } // Setup generic IP type properties ip->card = card; ip->id = id; ip->logger = villas::logging.get(id.getName()); json_t *json_ip = json_object_get(json_ips, id.getName().c_str()); json_t *json_irqs = json_object_get(json_ip, "irqs"); if (json_is_object(json_irqs)) { logger->debug("Parse IRQs of {}", *ip); const char *irqName; json_t *json_irq; json_object_foreach(json_irqs, irqName, json_irq) { const char *irqEntry = json_string_value(json_irq); auto tokens = utils::tokenize(irqEntry, ":"); if (tokens.size() != 2) { logger->warn("Cannot parse IRQ '{}' of " CLR_BLD("{}"), irqEntry, id.getName()); continue; } const std::string &irqControllerName = tokens[0]; InterruptController *intc = nullptr; for (auto &configuredIp : configuredIps) { if (*configuredIp == irqControllerName) { intc = dynamic_cast(configuredIp.get()); break; } } if (intc == nullptr) { logger->error("Interrupt Controller {} for IRQ {} not found", irqControllerName, irqName); continue; } int num; try { num = std::stoi(tokens[1]); } catch (const std::invalid_argument &) { logger->warn("IRQ number is not an integer: '{}'", irqEntry); continue; } logger->debug("IRQ: {} -> {}:{}", irqName, irqControllerName, num); ip->irqs[irqName] = {num, intc, ""}; } } json_t *json_memory_view = json_object_get(json_ip, "memory-view"); if (json_is_object(json_memory_view)) { logger->debug("Parse memory view of {}", *ip); // Now find all slave address spaces this master can access const char *bus_name; json_t *json_bus; json_object_foreach(json_memory_view, bus_name, json_bus) { // This IP has a memory view => it is a bus master somewhere // Assemble name for master address space const std::string myAddrSpaceName = MemoryManager::getMasterAddrSpaceName(ip->getInstanceName(), bus_name); // Create a master address space const MemoryManager::AddressSpaceId myAddrSpaceId = MemoryManager::get().getOrCreateAddressSpace(myAddrSpaceName); ip->busMasterInterfaces[bus_name] = myAddrSpaceId; const char *instance_name; json_t *json_instance; json_object_foreach(json_bus, instance_name, json_instance) { const char *block_name; json_t *json_block; json_object_foreach(json_instance, block_name, json_block) { json_int_t base, high, size; json_error_t err; int ret = json_unpack_ex(json_block, &err, 0, "{ s: I, s: I, s: I }", "baseaddr", &base, "highaddr", &high, "size", &size); if (ret != 0) throw ConfigError( json_block, err, "", "Cannot parse address block {}/{}/{}/{}", ip->getInstanceName(), bus_name, instance_name, block_name); // Get or create the slave address space const std::string slaveAddrSpace = MemoryManager::getSlaveAddrSpaceName(instance_name, block_name); const MemoryManager::AddressSpaceId slaveAddrSpaceId = MemoryManager::get().getOrCreateAddressSpace(slaveAddrSpace); // Create a new mapping to the slave address space MemoryManager::get().createMapping( static_cast(base), 0, static_cast(size), bus_name, myAddrSpaceId, slaveAddrSpaceId); } } } } // IP-specific setup via JSON config f->parse(*ip, json_ip); // Set polling mode f->configurePollingMode( *ip, (card->polling ? PollingMode::POLL : PollingMode::IRQ)); // IP has been configured now configuredIps.push_back(std::move(ip)); } // Start and check IPs now for (auto &ip : configuredIps) { loggerStatic->info("Initializing {}", *ip); // Translate all memory blocks that the IP needs to be accessible from // the process and cache in the instance, so this has not to be done at // runtime. for (auto &memoryBlock : ip->getMemoryBlocks()) { // Construct the global name of this address block const auto addrSpaceName = MemoryManager::getSlaveAddrSpaceName( ip->getInstanceName(), memoryBlock); // Retrieve its address space identifier const auto addrSpaceId = MemoryManager::get().findAddressSpace(addrSpaceName); // ... and save it in IP ip->slaveAddressSpaces.emplace(memoryBlock, addrSpaceId); // Get the translation to the address space const auto &translation = MemoryManager::get().getTranslationFromProcess(addrSpaceId); // Cache it in the IP instance only with local name ip->addressTranslations.emplace(memoryBlock, translation); } if (not ip->init()) { throw RuntimeError("Failed to initialize IP {}", *ip); } if (not ip->check()) { throw RuntimeError("Failed to check IP {}", *ip); } // Will only be reached if the IP successfully was initialized card->ips.push_back(std::move(ip)); } loggerStatic->debug("Initialized IPs:"); for (auto &ip : card->ips) { loggerStatic->debug(" {}", *ip); } return card->ips; } void Core::dump() { logger->info("IP: {}", *this); for (auto &[num, irq] : irqs) { logger->info(" IRQ {}: {}:{}", num, irq.irqController->getInstanceName(), irq.num); } for (auto &[block, translation] : addressTranslations) { logger->info(" Memory {}: {}", block, translation); } } CoreFactory *CoreFactory::lookup(const Vlnv &vlnv) { for (auto &ip : plugin::registry->lookup()) { if (ip->getCompatibleVlnv() == vlnv) return ip; } return nullptr; } uintptr_t Core::getLocalAddr(const MemoryBlockName &block, uintptr_t address) const { // Throws exception if block not present auto &translation = addressTranslations.at(block); return translation.getLocalAddr(address); } InterruptController * Core::getInterruptController(const std::string &interruptName) const { try { const IrqPort irq = irqs.at(interruptName); return irq.irqController; } catch (const std::out_of_range &) { return nullptr; } }