//
// Copyright 2014-2015 Ettus Research LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#include
#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 = boost::assign::list_of
// 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() ;
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_INFO("RFNOC") << "Reading XML file: " << filename.string().c_str() ;
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"] = boost::lexical_cast(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") << boost::format("Found invalid argument: %s") % 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 fs::path &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 fs::path &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: