2020-03-05 19:35:14 +00:00
|
|
|
//
|
|
|
|
|
// Copyright 2020 Ettus Research, a National Instruments Brand
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include <uhd/cal/database.hpp>
|
|
|
|
|
#include <uhd/exception.hpp>
|
|
|
|
|
#include <uhd/utils/log.hpp>
|
|
|
|
|
#include <uhd/utils/paths.hpp>
|
2020-05-19 18:12:34 +00:00
|
|
|
#include <uhd/utils/static.hpp>
|
2020-03-05 19:35:14 +00:00
|
|
|
#include <cmrc/cmrc.hpp>
|
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
|
#include <ctime>
|
|
|
|
|
#include <fstream>
|
2020-05-19 17:28:30 +00:00
|
|
|
#include <array>
|
|
|
|
|
#include <tuple>
|
|
|
|
|
#include <vector>
|
2020-03-05 19:35:14 +00:00
|
|
|
|
|
|
|
|
CMRC_DECLARE(rc);
|
|
|
|
|
|
|
|
|
|
using namespace uhd::usrp::cal;
|
|
|
|
|
namespace rc = cmrc::rc;
|
|
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
constexpr char LOG_ID[] = "CAL::DATABASE";
|
|
|
|
|
constexpr char CAL_EXT[] = ".cal";
|
|
|
|
|
//! This value is just for sanity checking. We pick a value (in bytes) that we
|
|
|
|
|
// are guaranteed to never exceed. Its only purpose is to avoid loading files
|
|
|
|
|
// that can't possibly be valid cal data based on the filesize. This can avoid
|
|
|
|
|
// someone bringing down a UHD session by trying to import a huge file, because
|
|
|
|
|
// we first load it entirely into heap space, and then deserialize it from there.
|
|
|
|
|
constexpr size_t CALDATA_MAX_SIZE = 10 * 1024 * 1024; // 10 MiB
|
|
|
|
|
|
2020-05-19 17:28:30 +00:00
|
|
|
/******************************************************************************
|
|
|
|
|
* RC implementation
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
//! Helper: Map a cal resource key into a source::RC path name
|
2020-03-05 19:35:14 +00:00
|
|
|
std::string get_cal_path_rc(const std::string& key)
|
|
|
|
|
{
|
|
|
|
|
return std::string("cal/") + key + CAL_EXT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//! Return true if a cal data resource with given key exists
|
2020-05-19 18:12:34 +00:00
|
|
|
//
|
|
|
|
|
// The serial parameter is ignored, as serial numbers should, by definition, not
|
|
|
|
|
// matter for RC data
|
2020-05-19 17:28:30 +00:00
|
|
|
bool has_cal_data_rc(const std::string& key, const std::string&)
|
2020-03-05 19:35:14 +00:00
|
|
|
{
|
|
|
|
|
auto fs = rc::get_filesystem();
|
2020-09-18 06:26:07 +00:00
|
|
|
const std::string cal_path(get_cal_path_rc(key));
|
|
|
|
|
UHD_LOG_TRACE(LOG_ID, "Checking for resource " << cal_path);
|
|
|
|
|
return fs.is_file(cal_path);
|
2020-03-05 19:35:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//! Return a byte array for a given cal resource
|
2020-05-19 17:28:30 +00:00
|
|
|
std::vector<uint8_t> get_cal_data_rc(const std::string& key, const std::string&)
|
2020-03-05 19:35:14 +00:00
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
auto fs = rc::get_filesystem();
|
|
|
|
|
auto file = fs.open(get_cal_path_rc(key));
|
|
|
|
|
return std::vector<uint8_t>(file.cbegin(), file.cend());
|
|
|
|
|
} catch (const std::system_error&) {
|
|
|
|
|
throw uhd::key_error(std::string("Unable to open resource with key: ") + key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 17:28:30 +00:00
|
|
|
/******************************************************************************
|
|
|
|
|
* Filesystem implementation
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
//! Helper: Check a path exists, or create it if not. Does not create recursively,
|
|
|
|
|
// think mkdir, not mkdir -p.
|
2020-03-05 19:35:14 +00:00
|
|
|
void check_or_create_dir(fs::path dir)
|
|
|
|
|
{
|
|
|
|
|
if (fs::exists(dir)) {
|
|
|
|
|
if (fs::is_directory(dir)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
UHD_LOG_ERROR(LOG_ID, "Path exists, but is not a directory: " << dir);
|
|
|
|
|
throw uhd::runtime_error("Path exists, but is not a directory!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fs::create_directory(dir)) {
|
|
|
|
|
UHD_LOG_ERROR(LOG_ID, "Cannot create cal data directory: " << dir);
|
|
|
|
|
throw uhd::runtime_error("Cannot create cal data directory!");
|
|
|
|
|
}
|
|
|
|
|
UHD_LOG_DEBUG(LOG_ID, "Created directory: " << dir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//! Make sure the calibration storage directory exists.
|
|
|
|
|
//
|
|
|
|
|
// The path returned by uhd::get_cal_data_path() might not exist (e.g., when run
|
|
|
|
|
// for the first time). This directory must be created before we try writing to
|
|
|
|
|
// it, or we won't be able to open the file.
|
|
|
|
|
//
|
|
|
|
|
// C++ doesn't have a mkdir -p equivalent, so we check the parent directory and
|
|
|
|
|
// the directory itself, in that order. Most of the time, the cal data path is
|
|
|
|
|
// in $XDG_DATA_HOME/uhd/cal_data. We assume that $XDG_DATA_HOME exists, and
|
|
|
|
|
// then first check $XDG_DATA_HOME/uhd, then $XDG_DATA_HOME/uhd/cal_data.
|
|
|
|
|
//
|
|
|
|
|
// This will not work if the user sets $UHD_CAL_DATA_PATH to an arbitrary path
|
|
|
|
|
// that requires multiple levels of directories to be created, but they will get
|
|
|
|
|
// a clear error message in that case.
|
|
|
|
|
void assert_cal_dir_exists()
|
|
|
|
|
{
|
|
|
|
|
const auto cal_path = fs::path(uhd::get_cal_data_path());
|
|
|
|
|
if (!cal_path.parent_path().empty()) {
|
|
|
|
|
check_or_create_dir(cal_path.parent_path());
|
|
|
|
|
}
|
|
|
|
|
check_or_create_dir(cal_path);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 17:28:30 +00:00
|
|
|
//! Helper: Map a cal resource key into a filesystem path name
|
|
|
|
|
// (relative to get_cal_data_path())
|
2020-03-05 19:35:14 +00:00
|
|
|
std::string get_cal_path_fs(const std::string& key, const std::string& serial)
|
|
|
|
|
{
|
|
|
|
|
return key + "_" + serial + CAL_EXT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//! Return true if a cal data resource with given key exists
|
|
|
|
|
bool has_cal_data_fs(const std::string& key, const std::string& serial)
|
|
|
|
|
{
|
|
|
|
|
auto const cal_file_path =
|
|
|
|
|
fs::path(uhd::get_cal_data_path()) / get_cal_path_fs(key, serial);
|
|
|
|
|
UHD_LOG_TRACE(LOG_ID, "Checking for file at " << cal_file_path.string());
|
|
|
|
|
// We might want to check readability also
|
|
|
|
|
return fs::exists(cal_file_path) && fs::is_regular_file(cal_file_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//! Return a byte array for a given filesystem resource
|
|
|
|
|
std::vector<uint8_t> get_cal_data_fs(const std::string& key, const std::string& serial)
|
|
|
|
|
{
|
|
|
|
|
if (!has_cal_data_fs(key, serial)) {
|
|
|
|
|
throw uhd::key_error(
|
|
|
|
|
std::string("Cannot find cal file for key=") + key + ", serial=" + serial);
|
|
|
|
|
}
|
|
|
|
|
const auto cal_file_path =
|
|
|
|
|
fs::path(uhd::get_cal_data_path()) / get_cal_path_fs(key, serial);
|
|
|
|
|
// We read the filesize first to do a sanity check (is this file small
|
|
|
|
|
// enough to reasonably be cal data?) and also to pre-allocate heap space in
|
|
|
|
|
// which we'll load the full data for future deserialization.
|
|
|
|
|
const size_t filesize = fs::file_size(cal_file_path);
|
|
|
|
|
if (filesize > CALDATA_MAX_SIZE) {
|
|
|
|
|
throw uhd::key_error(
|
|
|
|
|
std::string("The following cal data file exceeds maximum size limitations: ")
|
|
|
|
|
+ cal_file_path.string());
|
|
|
|
|
}
|
|
|
|
|
std::vector<uint8_t> result(filesize, 0);
|
|
|
|
|
std::ifstream file(cal_file_path.string(), std::ios::binary);
|
|
|
|
|
UHD_LOG_TRACE(LOG_ID, "Reading " << filesize << " bytes from " << cal_file_path);
|
|
|
|
|
file.read(reinterpret_cast<char*>(&result[0]), filesize);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2020-05-19 18:12:34 +00:00
|
|
|
/******************************************************************************
|
|
|
|
|
* Flash/EEPROM implementation
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
// Access to non-volatile memory is device-specific. Instead of implementing
|
|
|
|
|
// anything here, we allow devices to register callbacks to look up cal data
|
|
|
|
|
// from their EEPROMs / flash memories.
|
|
|
|
|
using lookup_registry_type =
|
|
|
|
|
std::vector<std::pair<database::has_data_fn_type, database::get_data_fn_type>>;
|
|
|
|
|
UHD_SINGLETON_FCN(lookup_registry_type, get_flash_lookup_registry);
|
|
|
|
|
|
|
|
|
|
bool has_cal_data_flash(const std::string& key, const std::string& serial)
|
|
|
|
|
{
|
|
|
|
|
for (auto& data_fn_pair : get_flash_lookup_registry()) {
|
|
|
|
|
if (data_fn_pair.first(key, serial)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> get_cal_data_flash(const std::string& key, const std::string& serial)
|
|
|
|
|
{
|
|
|
|
|
for (auto& data_fn_pair : get_flash_lookup_registry()) {
|
|
|
|
|
if (data_fn_pair.first(key, serial)) {
|
|
|
|
|
return data_fn_pair.second(key, serial);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// No data? Then throw:
|
|
|
|
|
throw uhd::key_error(
|
|
|
|
|
std::string("Cannot find flash cal data for key=") + key + ", serial=" + serial);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 17:28:30 +00:00
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
* Function lookup
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
typedef bool (*has_cal_data_fn)(const std::string&, const std::string&);
|
|
|
|
|
typedef std::vector<uint8_t> (*get_cal_data_fn)(const std::string&, const std::string&);
|
|
|
|
|
// These are in order of priority!
|
2020-05-19 18:12:34 +00:00
|
|
|
// clang-format off
|
|
|
|
|
constexpr std::array<std::tuple<source, has_cal_data_fn, get_cal_data_fn>, 3> data_fns{{
|
|
|
|
|
{source::FILESYSTEM, &has_cal_data_fs, &get_cal_data_fs },
|
|
|
|
|
{source::FLASH, &has_cal_data_flash, &get_cal_data_flash},
|
|
|
|
|
{source::RC, &has_cal_data_rc, &get_cal_data_rc }
|
|
|
|
|
}};
|
|
|
|
|
// clang-format on
|
2020-05-19 17:28:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
|
* cal::database implementation
|
|
|
|
|
*****************************************************************************/
|
2020-03-05 19:35:14 +00:00
|
|
|
std::vector<uint8_t> database::read_cal_data(
|
|
|
|
|
const std::string& key, const std::string& serial, const source source_type)
|
|
|
|
|
{
|
2020-05-19 17:28:30 +00:00
|
|
|
for (auto& data_fn : data_fns) {
|
|
|
|
|
if (source_type == source::ANY || source_type == std::get<0>(data_fn)) {
|
|
|
|
|
if (std::get<1>(data_fn)(key, serial)) {
|
|
|
|
|
return std::get<2>(data_fn)(key, serial);
|
|
|
|
|
}
|
2020-03-05 19:35:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string err_msg =
|
|
|
|
|
std::string("Calibration Data not found for: key=") + key + ", serial=" + serial;
|
|
|
|
|
UHD_LOG_ERROR(LOG_ID, err_msg);
|
|
|
|
|
throw uhd::key_error(err_msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool database::has_cal_data(
|
|
|
|
|
const std::string& key, const std::string& serial, const source source_type)
|
|
|
|
|
{
|
2020-05-19 17:28:30 +00:00
|
|
|
for (auto& data_fn : data_fns) {
|
|
|
|
|
if (source_type == source::ANY || source_type == std::get<0>(data_fn)) {
|
|
|
|
|
if (std::get<1>(data_fn)(key, serial)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-03-05 19:35:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void database::write_cal_data(const std::string& key,
|
|
|
|
|
const std::string& serial,
|
|
|
|
|
const std::vector<uint8_t>& cal_data,
|
|
|
|
|
const std::string& backup_ext)
|
|
|
|
|
{
|
|
|
|
|
assert_cal_dir_exists();
|
|
|
|
|
|
|
|
|
|
const auto cal_file_path =
|
|
|
|
|
(fs::path(uhd::get_cal_data_path()) / get_cal_path_fs(key, serial)).string();
|
|
|
|
|
|
|
|
|
|
if (fs::exists(cal_file_path)) {
|
|
|
|
|
const auto ext = backup_ext.empty() ? std::to_string(time(NULL)) : backup_ext;
|
|
|
|
|
const auto cal_file_path_backup = fs::path(uhd::get_cal_data_path())
|
|
|
|
|
/ (get_cal_path_fs(key, serial) + "." + ext);
|
|
|
|
|
UHD_LOG_WARNING(LOG_ID,
|
|
|
|
|
"Calibration data already exists for key: `"
|
|
|
|
|
<< key << "' serial: `" << serial
|
|
|
|
|
<< "'. Backing up to: " << cal_file_path_backup);
|
|
|
|
|
fs::rename(fs::path(cal_file_path), cal_file_path_backup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::ofstream file(cal_file_path, std::ios::binary);
|
|
|
|
|
UHD_LOG_DEBUG(LOG_ID, "Writing to " << cal_file_path);
|
|
|
|
|
file.write(reinterpret_cast<const char*>(cal_data.data()), cal_data.size());
|
|
|
|
|
}
|
2020-05-19 18:12:34 +00:00
|
|
|
|
|
|
|
|
void database::register_lookup(has_data_fn_type has_cal_data,
|
|
|
|
|
get_data_fn_type get_cal_data,
|
|
|
|
|
const source source_type)
|
|
|
|
|
{
|
|
|
|
|
UHD_ASSERT_THROW(source_type == source::FLASH);
|
|
|
|
|
get_flash_lookup_registry().push_back({has_cal_data, get_cal_data});
|
|
|
|
|
}
|