// // Copyright 2014-2015 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: GPL-3.0-or-later // #include #include #include #include #include #include #include #include #include #include #include #include using namespace uhd; using namespace uhd::rfnoc; namespace fs = boost::filesystem; namespace pt = boost::property_tree; static const fs::path XML_BLOCKS_SUBDIR("blocks"); static const fs::path XML_COMPONENTS_SUBDIR("components"); static const fs::path XML_EXTENSION(".xml"); /**************************************************************************** * port_t stuff ****************************************************************************/ const device_addr_t blockdef::port_t::PORT_ARGS( "name," "type," "vlen=0," "pkt_size=0," "optional=0," "bursty=0," "port," ); blockdef::port_t::port_t() { // This guarantees that we can access these keys // even if they were never initialized: for(const std::string &key: PORT_ARGS.keys()) { set(key, PORT_ARGS[key]); } } bool blockdef::port_t::is_variable(const std::string &key) const { const std::string &val = get(key); return (val[0] == '$'); } bool blockdef::port_t::is_keyword(const std::string &key) const { const std::string &val = get(key); return (val[0] == '%'); } bool blockdef::port_t::is_valid() const { // Check we have all the keys: for(const std::string &key: PORT_ARGS.keys()) { if (not has_key(key)) { return false; } } // Twelve of the clock, all seems well return true; } std::string blockdef::port_t::to_string() const { std::string result; for(const std::string &key: PORT_ARGS.keys()) { if (has_key(key)) { result += str(boost::format("%s=%s,") % key % get(key)); } } return result; } /**************************************************************************** * arg_t stuff ****************************************************************************/ const device_addr_t blockdef::arg_t::ARG_ARGS( // List all tags/args an can have here: "name," "type," "value," "check," "check_message," "action," "port=0," ); const std::set blockdef::arg_t::VALID_TYPES = { // List all tags/args a can have here: "string", "int", "int_vector", "double" }; blockdef::arg_t::arg_t() { // This guarantees that we can access these keys // even if they were never initialized: for(const std::string &key: ARG_ARGS.keys()) { set(key, ARG_ARGS[key]); } } bool blockdef::arg_t::is_valid() const { // 1. Check we have all the keys: for(const std::string &key: ARG_ARGS.keys()) { if (not has_key(key)) { return false; } } // 2. Check arg type is valid if (not get("type").empty() and not VALID_TYPES.count(get("type"))) { return false; } // Twelve of the clock, all seems well return true; } std::string blockdef::arg_t::to_string() const { std::string result; for(const std::string &key: ARG_ARGS.keys()) { if (has_key(key)) { result += str(boost::format("%s=%s,") % key % get(key)); } } return result; } /**************************************************************************** * blockdef_impl stuff ****************************************************************************/ class blockdef_xml_impl : public blockdef { public: enum xml_repr_t { DESCRIBES_BLOCK, DESCRIBES_COMPONENT }; //! Returns a list of base paths for the XML files. // It is assumed that block definitions are in a subdir with name // XML_BLOCKS_SUBDIR and component definitions in a subdir with name // XML_COMPONENTS_SUBDIR static std::vector get_xml_paths() { std::vector paths; // Path from environment variable if (std::getenv(XML_PATH_ENV.c_str()) != NULL) { paths.push_back(boost::filesystem::path(std::getenv(XML_PATH_ENV.c_str()))); } // Finally, the default path const boost::filesystem::path pkg_path = uhd::get_pkg_path(); paths.push_back(pkg_path / XML_DEFAULT_PATH); return paths; } //! Matches a NoC ID through substring matching static bool match_noc_id(const std::string &lhs_, uint64_t rhs_) { // Sanitize input: Make both values strings with all uppercase // characters and no leading 0x. Check inputs are valid. std::string lhs = boost::to_upper_copy(lhs_); std::string rhs = str(boost::format("%016X") % rhs_); if (lhs.size() > 2 and lhs[0] == '0' and lhs[1] == 'X') { lhs = lhs.substr(2); } UHD_ASSERT_THROW(rhs.size() == 16); if (lhs.size() < 4 or lhs.size() > 16) { throw uhd::value_error(str(boost::format( "%s is not a valid NoC ID (must be hexadecimal, min 4 and max 16 characters)" ) % lhs_)); } // OK, all good now. Next, we try and match the substring lhs in rhs: return (rhs.find(lhs) == 0); } //! Open the file at filename and see if it's a block definition for the given NoC ID static bool has_noc_id(uint64_t noc_id, const fs::path &filename) { pt::ptree propt; try { read_xml(filename.string(), propt); for(pt::ptree::value_type &v: propt.get_child("nocblock.ids")) { if (v.first == "id" and match_noc_id(v.second.data(), noc_id)) { return true; } } } catch (std::exception &e) { UHD_LOGGER_WARNING("RFNOC") << "has_noc_id(): caught exception " << e.what() << " while parsing file: " << filename.string(); return false; } return false; } blockdef_xml_impl(const fs::path &filename, uint64_t noc_id, xml_repr_t type=DESCRIBES_BLOCK) : _type(type), _noc_id(noc_id) { UHD_LOGGER_DEBUG("RFNOC") << boost::format("Reading XML file %s for NOC ID 0x%08X") % filename.string().c_str() % noc_id ; read_xml(filename.string(), _pt); try { // Check key is valid get_key(); // Check name is valid get_name(); // Check there's at least one port ports_t in = get_input_ports(); ports_t out = get_output_ports(); if (in.empty() and out.empty()) { throw uhd::runtime_error("Block does not define inputs or outputs."); } // Check args are valid get_args(); // TODO any more checks? } catch (const std::exception &e) { throw uhd::runtime_error(str( boost::format("Invalid block definition in %s: %s") % filename.string() % e.what() )); } } bool is_block() const { return _type == DESCRIBES_BLOCK; } bool is_component() const { return _type == DESCRIBES_COMPONENT; } std::string get_key() const { try { return _pt.get("nocblock.key"); } catch (const pt::ptree_bad_path &) { return _pt.get("nocblock.blockname"); } } std::string get_name() const { return _pt.get("nocblock.blockname"); } uint64_t noc_id() const { return _noc_id; } ports_t get_input_ports() { return _get_ports("sink"); } ports_t get_output_ports() { return _get_ports("source"); } ports_t _get_ports(const std::string &port_type) { std::set port_numbers; size_t n_ports = 0; ports_t ports; for(pt::ptree::value_type &v: _pt.get_child("nocblock.ports")) { if (v.first != port_type) continue; // Now we have the correct sink or source node: port_t port; for(const std::string &key: port_t::PORT_ARGS.keys()) { port[key] = v.second.get(key, port_t::PORT_ARGS[key]); } // We have to be extra-careful with the port numbers: if (port["port"].empty()) { port["port"] = std::to_string(n_ports); } size_t new_port_number; try { new_port_number = boost::lexical_cast(port["port"]); } catch (const boost::bad_lexical_cast &e) { throw uhd::value_error(str( boost::format("Invalid port number '%s' on port '%s'") % port["port"] % port["name"] )); } if (port_numbers.count(new_port_number) or new_port_number > MAX_NUM_PORTS) { throw uhd::value_error(str( boost::format("Port '%s' has invalid port number %d!") % port["name"] % new_port_number )); } port_numbers.insert(new_port_number); n_ports++; ports.push_back(port); } return ports; } std::vector get_all_port_numbers() { std::set set_ports; for(const port_t &port: get_input_ports()) { set_ports.insert(boost::lexical_cast(port["port"])); } for(const port_t &port: get_output_ports()) { set_ports.insert(boost::lexical_cast(port["port"])); } return std::vector(set_ports.begin(), set_ports.end()); } blockdef::args_t get_args() { args_t args; bool is_valid = true; pt::ptree def; for(pt::ptree::value_type &v: _pt.get_child("nocblock.args", def)) { arg_t arg; if (v.first != "arg") continue; for(const std::string &key: arg_t::ARG_ARGS.keys()) { arg[key] = v.second.get(key, arg_t::ARG_ARGS[key]); } if (arg["type"].empty()) { arg["type"] = "string"; } if (not arg.is_valid()) { UHD_LOGGER_WARNING("RFNOC") << "Found invalid argument: " << arg.to_string(); is_valid = false; } args.push_back(arg); } if (not is_valid) { throw uhd::runtime_error(str( boost::format("Found invalid arguments for block %s.") % get_name() )); } return args; } registers_t get_settings_registers() { return _get_regs("setreg"); } registers_t get_readback_registers() { return _get_regs("readback"); } registers_t _get_regs(const std::string ®_type) { registers_t registers; pt::ptree def; for(pt::ptree::value_type &v: _pt.get_child("nocblock.registers", def)) { if (v.first != reg_type) continue; registers[v.second.get("name")] = boost::lexical_cast(v.second.get("address")); } return registers; } private: //! Tells us if is this for a NoC block, or a component. const xml_repr_t _type; //! The NoC-ID as reported (there may be several valid NoC IDs, this is the one used) const uint64_t _noc_id; //! This is a boost property tree, not the same as // our property tree. pt::ptree _pt; }; blockdef::sptr blockdef::make_from_noc_id(uint64_t noc_id) { std::vector paths = blockdef_xml_impl::get_xml_paths(); std::vector valid; // Check if any of the paths exist for (const auto& base_path : paths) { fs::path this_path = base_path / XML_BLOCKS_SUBDIR; if (fs::exists(this_path) and fs::is_directory(this_path)) { valid.push_back(this_path); } } if (valid.empty()) { throw uhd::assertion_error( "Failed to find a valid XML path for RFNoC blocks.\n" "Try setting the enviroment variable UHD_RFNOC_DIR " "to the correct location" ); } // Iterate over all paths for (const auto& path : valid) { // Iterate over all .xml files fs::directory_iterator end_itr; for (fs::directory_iterator i(path); i != end_itr; ++i) { if (not fs::exists(*i) or fs::is_directory(*i) or fs::is_empty(*i)) { continue; } if (i->path().filename().extension() != XML_EXTENSION) { continue; } if (blockdef_xml_impl::has_noc_id(noc_id, i->path())) { return blockdef::sptr(new blockdef_xml_impl(i->path(), noc_id)); } } } return blockdef::sptr(); } // vim: sw=4 et: