diff options
Diffstat (limited to 'host/lib/usrp')
-rw-r--r-- | host/lib/usrp/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/usrp/b200/CMakeLists.txt | 34 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_iface.cpp | 561 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_iface.hpp | 81 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_impl.cpp | 885 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_impl.hpp | 196 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_io_impl.cpp | 377 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_regs.hpp | 119 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_uart.cpp | 117 | ||||
-rw-r--r-- | host/lib/usrp/b200/b200_uart.hpp | 36 |
10 files changed, 2407 insertions, 0 deletions
diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index d251fbaeb..f8c817df5 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -37,3 +37,4 @@ INCLUDE_SUBDIRECTORY(usrp1) INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) INCLUDE_SUBDIRECTORY(e100) +INCLUDE_SUBDIRECTORY(b200) diff --git a/host/lib/usrp/b200/CMakeLists.txt b/host/lib/usrp/b200/CMakeLists.txt new file mode 100644 index 000000000..3d8aad052 --- /dev/null +++ b/host/lib/usrp/b200/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright 2012-2013 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 <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the B100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF) + +IF(ENABLE_B200) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/b200_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/b200_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/b200_io_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/b200_uart.cpp + ) +ENDIF(ENABLE_B200) diff --git a/host/lib/usrp/b200/b200_iface.cpp b/host/lib/usrp/b200/b200_iface.cpp new file mode 100644 index 000000000..5173beacb --- /dev/null +++ b/host/lib/usrp/b200/b200_iface.cpp @@ -0,0 +1,561 @@ +// +// Copyright 2012-2013 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 <http://www.gnu.org/licenses/>. +// + +#include "b200_iface.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/functional/hash.hpp> +#include <boost/thread/thread.hpp> +#include <boost/cstdint.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <fstream> +#include <string> +#include <vector> +#include <cstring> +#include <iomanip> +#include <libusb.h> + +using namespace uhd; +using namespace uhd::transport; + +static const bool load_img_msg = true; + +const static boost::uint8_t FX3_FIRMWARE_LOAD = 0xA0; +const static boost::uint8_t VRT_VENDOR_OUT = (LIBUSB_REQUEST_TYPE_VENDOR + | LIBUSB_ENDPOINT_OUT); +const static boost::uint8_t VRT_VENDOR_IN = (LIBUSB_REQUEST_TYPE_VENDOR + | LIBUSB_ENDPOINT_IN); +const static boost::uint8_t B200_VREQ_FPGA_START = 0x02; +const static boost::uint8_t B200_VREQ_FPGA_DATA = 0x12; +const static boost::uint8_t B200_VREQ_GET_COMPAT = 0x15; +const static boost::uint8_t B200_VREQ_SET_FPGA_HASH = 0x1C; +const static boost::uint8_t B200_VREQ_GET_FPGA_HASH = 0x1D; +const static boost::uint8_t B200_VREQ_SET_FW_HASH = 0x1E; +const static boost::uint8_t B200_VREQ_GET_FW_HASH = 0x1F; +const static boost::uint8_t B200_VREQ_LOOP = 0x22; +const static boost::uint8_t B200_VREQ_SPI_WRITE = 0x32; +const static boost::uint8_t B200_VREQ_SPI_READ = 0x42; +const static boost::uint8_t B200_VREQ_FPGA_RESET = 0x62; +const static boost::uint8_t B200_VREQ_GPIF_RESET = 0x72; +const static boost::uint8_t B200_VREQ_GET_USB = 0x80; +const static boost::uint8_t B200_VREQ_GET_STATUS = 0x83; +const static boost::uint8_t B200_VREQ_AD9361_CTRL_WRITE = 0x90; +const static boost::uint8_t B200_VREQ_AD9361_CTRL_READ = 0x91; +const static boost::uint8_t B200_VREQ_FX3_RESET = 0x99; +const static boost::uint8_t B200_VREQ_EEPROM_WRITE = 0xBA; +const static boost::uint8_t B200_VREQ_EEPROM_READ = 0xBB; + +const static boost::uint8_t FX3_STATE_FPGA_READY = 0x00; +const static boost::uint8_t FX3_STATE_CONFIGURING_FPGA = 0x01; +const static boost::uint8_t FX3_STATE_BUSY = 0x02; +const static boost::uint8_t FX3_STATE_RUNNING = 0x03; + +typedef boost::uint32_t hash_type; + + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +/*! + * Create a file hash + * The hash will be used to identify the loaded firmware and fpga image + * \param filename file used to generate hash value + * \return hash value in a size_t type + */ +static hash_type generate_hash(const char *filename) +{ + std::ifstream file(filename); + if (not file){ + throw uhd::io_error(std::string("cannot open input file ") + filename); + } + + size_t hash = 0; + + char ch; + long long count = 0; + while (file.get(ch)) { + count++; + boost::hash_combine(hash, ch); + } + + if (count == 0){ + throw uhd::io_error(std::string("empty input file ") + filename); + } + + if (not file.eof()){ + throw uhd::io_error(std::string("file error ") + filename); + } + + file.close(); + return hash_type(hash); +} + + +/*! + * Verify checksum of a Intel HEX record + * \param record a line from an Intel HEX file + * \return true if record is valid, false otherwise + */ +bool checksum(std::string *record) { + + size_t len = record->length(); + unsigned int i; + unsigned char sum = 0; + unsigned int val; + + for (i = 1; i < len; i += 2) { + std::istringstream(record->substr(i, 2)) >> std::hex >> val; + sum += val; + } + + if (sum == 0) + return true; + else + return false; +} + + +/*! + * Parse Intel HEX record + * + * \param record a line from an Intel HEX file + * \param len output length of record + * \param addr output address + * \param type output type + * \param data output data + * \return true if record is sucessfully read, false on error + */ +bool parse_record(std::string *record, boost::uint16_t &len, \ + boost::uint16_t &addr, boost::uint16_t &type, unsigned char* data) { + + unsigned int i; + std::string _data; + unsigned int val; + + if (record->substr(0, 1) != ":") + return false; + + std::istringstream(record->substr(1, 2)) >> std::hex >> len; + std::istringstream(record->substr(3, 4)) >> std::hex >> addr; + std::istringstream(record->substr(7, 2)) >> std::hex >> type; + + for (i = 0; i < len; i++) { + std::istringstream(record->substr(9 + 2 * i, 2)) >> std::hex >> val; + data[i] = (unsigned char) val; + } + + return true; +} + + +/*********************************************************************** + * The implementation class + **********************************************************************/ +class b200_iface_impl : public b200_iface{ +public: + + b200_iface_impl(usb_control::sptr usb_ctrl): + _usb_ctrl(usb_ctrl) + { + //NOP + } + + + int fx3_control_write(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length, + boost::int32_t timeout = 0) + { + return _usb_ctrl->submit(VRT_VENDOR_OUT, // bmReqeustType + request, // bRequest + value, // wValue + index, // wIndex + buff, // data + length, // wLength + timeout); // timeout + } + + + int fx3_control_read(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length, + boost::int32_t timeout = 0) + { + return _usb_ctrl->submit(VRT_VENDOR_IN, // bmReqeustType + request, // bRequest + value, // wValue + index, // wIndex + buff, // data + length, // wLength + timeout); // timeout + } + + + void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes) + { + throw uhd::not_implemented_error("b200 write i2c"); + } + + + byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes) + { + throw uhd::not_implemented_error("b200 read i2c"); + } + + void write_eeprom(boost::uint8_t addr, boost::uint8_t offset, + const byte_vector_t &bytes) + { + fx3_control_write(B200_VREQ_EEPROM_WRITE, + 0, offset | (boost::uint16_t(addr) << 8), + (unsigned char *) &bytes[0], + bytes.size()); + } + + byte_vector_t read_eeprom( + boost::uint8_t addr, + boost::uint8_t offset, + size_t num_bytes + ){ + byte_vector_t recv_bytes(num_bytes); + fx3_control_read(B200_VREQ_EEPROM_READ, + 0, offset | (boost::uint16_t(addr) << 8), + (unsigned char*) &recv_bytes[0], + num_bytes); + return recv_bytes; + } + + void transact_spi( + unsigned char *tx_data, + size_t num_tx_bits, + unsigned char *rx_data, + size_t num_rx_bits + ){ + int ret = 0; + boost::uint16_t tx_length = num_tx_bits / 8; + + if(tx_data[0] & 0x80) { + ret = fx3_control_write(B200_VREQ_SPI_WRITE, 0x00, \ + 0x00, tx_data, tx_length); + } else { + ret = fx3_control_write(B200_VREQ_SPI_READ, 0x00, \ + 0x00, tx_data, tx_length); + } + + if(ret < 0) { + throw uhd::io_error("transact_spi: fx3_control_write failed!"); + } + + + if(num_rx_bits) { + boost::uint16_t total_length = num_rx_bits / 8; + + ret = fx3_control_read(B200_VREQ_LOOP, 0x00, \ + 0x00, rx_data, total_length); + + if(ret < 0) { + throw uhd::io_error("transact_spi: readback failed!"); + } + } + } + + void ad9361_transact(const unsigned char in_buff[64], unsigned char out_buff[64]) { + fx3_control_write(B200_VREQ_AD9361_CTRL_WRITE, 0x00, 0x00, (unsigned char *)in_buff, 64); + int ret = 0; + for (size_t i = 0; i < 10; i++) + { + ret = fx3_control_read(B200_VREQ_AD9361_CTRL_READ, 0x00, 0x00, out_buff, 64, 1000); + if (ret == 64) return; + } + throw uhd::io_error(str(boost::format("ad9361_transact failed with usb error: %d") % ret)); + } + + + void load_firmware(const std::string filestring, bool force = false) + { + const char *filename = filestring.c_str(); + + /* Fields used in each USB control transfer. */ + boost::uint16_t len = 0; + boost::uint16_t type = 0; + boost::uint16_t lower_address_bits = 0x0000; + unsigned char data[512]; + + /* Can be set by the Intel HEX record 0x04, used for all 0x00 records + * thereafter. Note this field takes the place of the 'index' parameter in + * libusb calls, and is necessary for FX3's 32-bit addressing. */ + boost::uint16_t upper_address_bits = 0x0000; + + std::ifstream file; + file.open(filename, std::ifstream::in); + + if(!file.good()) { + throw uhd::io_error("fx3_load_firmware: cannot open firmware input file"); + } + + if (load_img_msg) UHD_MSG(status) << "Loading firmware image: " \ + << filestring << "..." << std::flush; + + while (!file.eof()) { + boost::int32_t ret = 0; + std::string record; + file >> record; + + /* Check for valid Intel HEX record. */ + if (!checksum(&record) || !parse_record(&record, len, \ + lower_address_bits, type, data)) { + throw uhd::io_error("fx3_load_firmware: bad intel hex record checksum"); + } + + /* Type 0x00: Data. */ + if (type == 0x00) { + ret = fx3_control_write(FX3_FIRMWARE_LOAD, \ + lower_address_bits, upper_address_bits, data, len); + + if (ret < 0) { + throw uhd::io_error("usrp_load_firmware: usrp_control_write failed"); + } + } + + /* Type 0x01: EOF. */ + else if (type == 0x01) { + if (lower_address_bits != 0x0000 || len != 0 ) { + throw uhd::io_error("fx3_load_firmware: For EOF record, address must be 0, length must be 0."); + } + + //TODO + //usrp_set_firmware_hash(hash); //set hash before reset + + /* Successful termination! */ + file.close(); + + /* Let the system settle. */ + boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); + return; + } + + /* Type 0x04: Extended Linear Address Record. */ + else if (type == 0x04) { + if (lower_address_bits != 0x0000 || len != 2 ) { + throw uhd::io_error("fx3_load_firmware: For ELA record, address must be 0, length must be 2."); + } + + upper_address_bits = ((boost::uint16_t)((data[0] & 0x00FF) << 8))\ + + ((boost::uint16_t)(data[1] & 0x00FF)); + } + + /* Type 0x05: Start Linear Address Record. */ + else if (type == 0x05) { + if (lower_address_bits != 0x0000 || len != 4 ) { + throw uhd::io_error("fx3_load_firmware: For SLA record, address must be 0, length must be 4."); + } + + /* The firmware load is complete. We now need to tell the CPU + * to jump to an execution address start point, now contained within + * the data field. Parse these address bits out, and then push the + * instruction. */ + upper_address_bits = ((boost::uint16_t)((data[0] & 0x00FF) << 8))\ + + ((boost::uint16_t)(data[1] & 0x00FF)); + lower_address_bits = ((boost::uint16_t)((data[2] & 0x00FF) << 8))\ + + ((boost::uint16_t)(data[3] & 0x00FF)); + + fx3_control_write(FX3_FIRMWARE_LOAD, lower_address_bits, \ + upper_address_bits, 0, 0); + + if (load_img_msg) UHD_MSG(status) << " done" << std::endl; + } + + /* If we receive an unknown record type, error out. */ + else { + throw uhd::io_error("fx3_load_firmware: unsupported record type."); + } + } + + /* There was no valid EOF. */ + throw uhd::io_error("fx3_load_firmware: No EOF record found."); + } + + + void reset_fx3(void) { + unsigned char data[4]; + memset(data, 0x00, sizeof(data)); + + fx3_control_write(B200_VREQ_FX3_RESET, 0x00, 0x00, data, 4); + } + + void reset_gpif(void) { + unsigned char data[4]; + memset(data, 0x00, sizeof(data)); + + fx3_control_write(B200_VREQ_GPIF_RESET, 0x00, 0x00, data, 4); + } + + void set_fpga_reset_pin(const bool reset) + { + unsigned char data[4]; + memset(data, (reset)? 0xFF : 0x00, sizeof(data)); + + UHD_THROW_INVALID_CODE_PATH(); + + fx3_control_write(B200_VREQ_FPGA_RESET, 0x00, 0x00, data, 4); + } + + boost::uint8_t get_usb_speed(void) { + + unsigned char rx_data[1]; + + fx3_control_read(B200_VREQ_GET_USB, 0x00, 0x00, rx_data, 1); + + return boost::lexical_cast<boost::uint8_t>(rx_data[0]); + } + + + boost::uint8_t get_fx3_status(void) { + + unsigned char rx_data[1]; + + fx3_control_read(B200_VREQ_GET_STATUS, 0x00, 0x00, rx_data, 1); + + return boost::lexical_cast<boost::uint8_t>(rx_data[0]); + } + + boost::uint16_t get_compat_num(void) { + + unsigned char rx_data[2]; + + fx3_control_read(B200_VREQ_GET_COMPAT , 0x00, 0x00, rx_data, 2); + + boost::uint16_t compat = 0x0000; + compat |= (((uint16_t) rx_data[0]) << 8); + compat |= (rx_data[1] & 0x00FF); + + return compat; + } + + void usrp_get_firmware_hash(hash_type &hash) { + fx3_control_read(B200_VREQ_GET_FW_HASH, 0x00, 0x00, + (unsigned char*) &hash, 4, 500); + } + + void usrp_set_firmware_hash(hash_type hash) { + fx3_control_write(B200_VREQ_SET_FW_HASH, 0x00, 0x00, + (unsigned char*) &hash, 4); + } + + void usrp_get_fpga_hash(hash_type &hash) { + fx3_control_read(B200_VREQ_GET_FPGA_HASH, 0x00, 0x00, + (unsigned char*) &hash, 4, 500); + } + + void usrp_set_fpga_hash(hash_type hash) { + fx3_control_write(B200_VREQ_SET_FPGA_HASH, 0x00, 0x00, + (unsigned char*) &hash, 4); + } + + void load_fpga(const std::string filestring) { + + boost::uint8_t fx3_state = 0; + + const char *filename = filestring.c_str(); + + hash_type hash = generate_hash(filename); + hash_type loaded_hash; usrp_get_fpga_hash(loaded_hash); + if (hash == loaded_hash) return; + + size_t file_size = 0; + { + std::ifstream file(filename, std::ios::in | std::ios::binary | std::ios::ate); + file_size = file.tellg(); + } + + std::ifstream file; + file.open(filename, std::ios::in | std::ios::binary); + + if(!file.good()) { + throw uhd::io_error("load_fpga: cannot open FPGA input file."); + } + + do { + fx3_state = get_fx3_status(); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + } while(fx3_state != FX3_STATE_FPGA_READY); + + if (load_img_msg) UHD_MSG(status) << "Loading FPGA image: " \ + << filestring << "..." << std::flush; + + unsigned char out_buff[64]; + memset(out_buff, 0x00, sizeof(out_buff)); + fx3_control_write(B200_VREQ_FPGA_START, 0, 0, out_buff, 1, 1000); + + do { + fx3_state = get_fx3_status(); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + } while(fx3_state != FX3_STATE_CONFIGURING_FPGA); + + + size_t bytes_sent = 0; + while(!file.eof()) { + file.read((char *) out_buff, sizeof(out_buff)); + const std::streamsize n = file.gcount(); + if(n == 0) continue; + + boost::uint16_t transfer_count = boost::uint16_t(n); + + /* Send the data to the device. */ + fx3_control_write(B200_VREQ_FPGA_DATA, 0, 0, out_buff, transfer_count, 5000); + + if (load_img_msg) + { + if (bytes_sent == 0) UHD_MSG(status) << " 0%" << std::flush; + const size_t percent_before = size_t((bytes_sent*100)/file_size); + bytes_sent += transfer_count; + const size_t percent_after = size_t((bytes_sent*100)/file_size); + if (percent_before/10 != percent_after/10) + { + UHD_MSG(status) << "\b\b\b\b" << std::setw(3) << percent_after << "%" << std::flush; + } + } + } + + file.close(); + + do { + fx3_state = get_fx3_status(); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + } while(fx3_state != FX3_STATE_RUNNING); + + usrp_set_fpga_hash(hash); + + if (load_img_msg) UHD_MSG(status) << "\b\b\b\b done" << std::endl; + } + +private: + usb_control::sptr _usb_ctrl; +}; + +/*********************************************************************** + * Make an instance of the implementation + **********************************************************************/ +b200_iface::sptr b200_iface::make(usb_control::sptr usb_ctrl) +{ + return sptr(new b200_iface_impl(usb_ctrl)); +} diff --git a/host/lib/usrp/b200/b200_iface.hpp b/host/lib/usrp/b200/b200_iface.hpp new file mode 100644 index 000000000..bf2e006a9 --- /dev/null +++ b/host/lib/usrp/b200/b200_iface.hpp @@ -0,0 +1,81 @@ +// +// Copyright 2012-2013 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 <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B200_IFACE_HPP +#define INCLUDED_B200_IFACE_HPP + +#include <stdint.h> +#include <uhd/transport/usb_control.hpp> +#include <uhd/types/serial.hpp> //i2c iface +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include "ad9361_ctrl.hpp" + +class b200_iface: boost::noncopyable, public uhd::i2c_iface, + public ad9361_ctrl_iface_type { +public: + typedef boost::shared_ptr<b200_iface> sptr; + + /*! + * Make a b200 interface object from a control transport + * \param usb_ctrl a USB control transport + * \return a new b200 interface object + */ + static sptr make(uhd::transport::usb_control::sptr usb_ctrl); + + //! query the device USB speed (2, 3) + virtual boost::uint8_t get_usb_speed(void) = 0; + + //! get the current status of the FX3 + virtual boost::uint8_t get_fx3_status(void) = 0; + + //! get the current status of the FX3 + virtual boost::uint16_t get_compat_num(void) = 0; + + //! load a firmware image + virtual void load_firmware(const std::string filestring, bool force=false) = 0; + + //! reset the FX3 + virtual void reset_fx3(void) = 0; + + //! reset the GPIF state machine + virtual void reset_gpif(void) = 0; + + //! set the FPGA_RESET line + virtual void set_fpga_reset_pin(const bool reset) = 0; + + //! load an FPGA image + virtual void load_fpga(const std::string filestring) = 0; + + //! Read and Write to/from the EEPROM on the motherboard + virtual void write_i2c(boost::uint8_t addr, const uhd::byte_vector_t &bytes) = 0; + virtual uhd::byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes) = 0; + + //! Overload the virtual read_eeprom function from i2c_iface, which won't + // work on the B200 since the i2c controller is actually the FX3. + virtual void write_eeprom(boost::uint8_t addr, boost::uint8_t offset, + const uhd::byte_vector_t &bytes) = 0; + virtual uhd::byte_vector_t read_eeprom(boost::uint8_t addr, boost::uint8_t offset, + size_t num_bytes) = 0; + + //! send SPI through the FX3 + virtual void transact_spi( unsigned char *tx_data, size_t num_tx_bits, \ + unsigned char *rx_data, size_t num_rx_bits) = 0; +}; + + +#endif /* INCLUDED_B200_IFACE_HPP */ diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp new file mode 100644 index 000000000..01db182e0 --- /dev/null +++ b/host/lib/usrp/b200/b200_impl.cpp @@ -0,0 +1,885 @@ +// +// Copyright 2012-2013 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 <http://www.gnu.org/licenses/>. +// + +#include "b200_impl.hpp" +#include "b200_regs.hpp" +#include <uhd/transport/usb_control.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/functional/hash.hpp> +#include <cstdio> +#include <ctime> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +const boost::uint16_t B200_VENDOR_ID = 0x2500; +const boost::uint16_t B200_PRODUCT_ID = 0x0020; +const boost::uint16_t INIT_PRODUCT_ID = 0x00f0; + +static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000); + +//! mapping of frontend to radio perif index +static const size_t FE1 = 1; +static const size_t FE2 = 0; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t b200_find(const device_addr_t &hint) +{ + device_addrs_t b200_addrs; + + //return an empty list of addresses when type is set to non-b200 + if (hint.has_key("type") and hint["type"] != "b200") return b200_addrs; + + //Return an empty list of addresses when an address is specified, + //since an address is intended for a different, non-USB, device. + if (hint.has_key("addr")) return b200_addrs; + + unsigned int vid, pid; + + if(hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type") && hint["type"] == "b200") { + sscanf(hint.get("vid").c_str(), "%x", &vid); + sscanf(hint.get("pid").c_str(), "%x", &pid); + } else { + vid = B200_VENDOR_ID; + pid = B200_PRODUCT_ID; + } + + // Important note: + // The get device list calls are nested inside the for loop. + // This allows the usb guts to decontruct when not in use, + // so that re-enumeration after fw load can occur successfully. + // This requirement is a courtesy of libusb1.0 on windows. + + //find the usrps and load firmware + size_t found = 0; + BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { + //extract the firmware path for the b200 + std::string b200_fw_image; + try{ + b200_fw_image = find_image_path(hint.get("fw", B200_FW_FILE_NAME)); + } + catch(...){ + UHD_MSG(warning) << boost::format( + "Could not locate B200 firmware.\n" + "Please install the images package.\n" + ); + return b200_addrs; + } + UHD_LOG << "the firmware image: " << b200_fw_image << std::endl; + + usb_control::sptr control; + try{control = usb_control::make(handle, 0);} + catch(const uhd::exception &){continue;} //ignore claimed + + //check if fw was already loaded + if (handle->get_manufacturer() != "Ettus Research LLC") + { + b200_iface::make(control)->load_firmware(b200_fw_image); + } + + found++; + } + + const boost::system_time timeout_time = boost::get_system_time() + REENUMERATION_TIMEOUT_MS; + + //search for the device until found or timeout + while (boost::get_system_time() < timeout_time and b200_addrs.empty() and found != 0) + { + BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) + { + usb_control::sptr control; + try{control = usb_control::make(handle, 0);} + catch(const uhd::exception &){continue;} //ignore claimed + + b200_iface::sptr iface = b200_iface::make(control); + const mboard_eeprom_t mb_eeprom = mboard_eeprom_t(*iface, "B200"); + + device_addr_t new_addr; + new_addr["type"] = "b200"; + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = handle->get_serial(); + //this is a found b200 when the hint serial and name match or blank + if ( + (not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + ){ + b200_addrs.push_back(new_addr); + } + } + } + + return b200_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr b200_make(const device_addr_t &device_addr) +{ + return device::sptr(new b200_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_b200_device) +{ + device::register_device(&b200_find, &b200_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +b200_impl::b200_impl(const device_addr_t &device_addr) +{ + _tree = property_tree::make(); + const fs_path mb_path = "/mboards/0"; + + //try to match the given device address with something on the USB bus + std::vector<usb_device_handle::sptr> device_list = + usb_device_handle::get_device_list(B200_VENDOR_ID, B200_PRODUCT_ID); + + //locate the matching handle in the device list + usb_device_handle::sptr handle; + BOOST_FOREACH(usb_device_handle::sptr dev_handle, device_list) { + if (dev_handle->get_serial() == device_addr["serial"]){ + handle = dev_handle; + break; + } + } + UHD_ASSERT_THROW(handle.get() != NULL); //better be found + + //create control objects + usb_control::sptr control = usb_control::make(handle, 0); + _iface = b200_iface::make(control); + this->check_fw_compat(); //check after making + + //////////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////////// + const mboard_eeprom_t mb_eeprom(*_iface, "B200"); + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(mb_eeprom) + .subscribe(boost::bind(&b200_impl::set_mb_eeprom, this, _1)); + + //////////////////////////////////////////////////////////////////// + // Load the FPGA image, then reset GPIF + //////////////////////////////////////////////////////////////////// + std::string default_file_name; + if (not mb_eeprom["product"].empty()) + { + switch (boost::lexical_cast<boost::uint16_t>(mb_eeprom["product"])) + { + case 0x0001: default_file_name = B200_FPGA_FILE_NAME; break; + case 0x0002: default_file_name = B210_FPGA_FILE_NAME; break; + default: throw uhd::runtime_error("b200 unknown product code: " + mb_eeprom["product"]); + } + } + if (default_file_name.empty()) + { + UHD_ASSERT_THROW(device_addr.has_key("fpga")); + } + + //extract the FPGA path for the B200 + std::string b200_fpga_image = find_image_path( + device_addr.has_key("fpga")? device_addr["fpga"] : default_file_name + ); + + _iface->load_fpga(b200_fpga_image); + _iface->reset_gpif(); + + //////////////////////////////////////////////////////////////////// + // Create control transport + //////////////////////////////////////////////////////////////////// + boost::uint8_t usb_speed = _iface->get_usb_speed(); + UHD_MSG(status) << "Operating over USB " << (int) usb_speed << "." << std::endl; + const std::string min_frame_size = (usb_speed == 3) ? "1024" : "512"; + + device_addr_t ctrl_xport_args; + ctrl_xport_args["recv_frame_size"] = min_frame_size; + ctrl_xport_args["num_recv_frames"] = "16"; + ctrl_xport_args["send_frame_size"] = min_frame_size; + ctrl_xport_args["num_send_frames"] = "16"; + + _ctrl_transport = usb_zero_copy::make( + handle, + 4, 8, //interface, endpoint + 3, 4, //interface, endpoint + ctrl_xport_args + ); + while (_ctrl_transport->get_recv_buff(0.0)){} //flush ctrl xport + + //////////////////////////////////////////////////////////////////// + // Async task structure + //////////////////////////////////////////////////////////////////// + _async_task_data.reset(new AsyncTaskData()); + _async_task_data->async_md.reset(new async_md_type(1000/*messages deep*/)); + _async_task = uhd::task::make(boost::bind(&b200_impl::handle_async_task, this, _ctrl_transport, _async_task_data)); + + //////////////////////////////////////////////////////////////////// + // Local control endpoint + //////////////////////////////////////////////////////////////////// + _local_ctrl = radio_ctrl_core_3000::make(vrt::if_packet_info_t::LINK_TYPE_CHDR, _ctrl_transport, zero_copy_if::sptr()/*null*/, B200_LOCAL_CTRL_SID); + _local_ctrl->hold_task(_async_task); + _async_task_data->local_ctrl = _local_ctrl; //weak + this->check_fpga_compat(); + + /* Initialize the GPIOs, set the default bandsels to the lower range. Note + * that calling update_bandsel calls update_gpio_state(). */ + _gpio_state = gpio_state(); + update_bandsel("RX", 800e6); + update_bandsel("TX", 850e6); + + //////////////////////////////////////////////////////////////////// + // Create the GPSDO control + //////////////////////////////////////////////////////////////////// + _async_task_data->gpsdo_uart = b200_uart::make(_ctrl_transport, B200_TX_GPS_UART_SID); + _async_task_data->gpsdo_uart->set_baud_divider(B200_BUS_CLOCK_RATE/115200); + _async_task_data->gpsdo_uart->write_uart("\n"); //cause the baud and response to be setup + boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for a little propagation + + if ((_local_ctrl->peek32(RB32_CORE_STATUS) & 0xff) != B200_GPSDO_ST_NONE) + { + UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush; + try + { + _gps = gps_ctrl::make(_async_task_data->gpsdo_uart); + } + catch(std::exception &e) + { + UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; + } + if (_gps and _gps->gps_detected()) + { + //UHD_MSG(status) << "found" << std::endl; + BOOST_FOREACH(const std::string &name, _gps->get_sensors()) + { + _tree->create<sensor_value_t>(mb_path / "sensors" / name) + .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name)); + } + } + else + { + UHD_MSG(status) << "not found" << std::endl; + _local_ctrl->poke32(TOREG(SR_CORE_GPSDO_ST), B200_GPSDO_ST_NONE); + } + } + + //////////////////////////////////////////////////////////////////// + // Initialize the properties tree + //////////////////////////////////////////////////////////////////// + _tree->create<std::string>("/name").set("B-Series Device"); + _tree->create<std::string>(mb_path / "name").set("B200"); + _tree->create<std::string>(mb_path / "codename").set("Sasquatch"); + + //////////////////////////////////////////////////////////////////// + // Create data transport + // This happens after FPGA ctrl instantiated so any junk that might + // be in the FPGAs buffers doesn't get pulled into the transport + // before being cleared. + //////////////////////////////////////////////////////////////////// + device_addr_t data_xport_args; + data_xport_args["recv_frame_size"] = device_addr.get("recv_frame_size", "8192"); + data_xport_args["num_recv_frames"] = device_addr.get("num_recv_frames", "16"); + data_xport_args["send_frame_size"] = device_addr.get("send_frame_size", "8192"); + data_xport_args["num_send_frames"] = device_addr.get("num_send_frames", "16"); + + _data_transport = usb_zero_copy::make( + handle, // identifier + 2, 6, // IN interface, endpoint + 1, 2, // OUT interface, endpoint + data_xport_args // param hints + ); + while (_data_transport->get_recv_buff(0.0)){} //flush ctrl xport + _demux.reset(new recv_packet_demuxer_3000(_data_transport)); + + //////////////////////////////////////////////////////////////////// + // Init codec - turns on clocks + //////////////////////////////////////////////////////////////////// + UHD_MSG(status) << "Initialize CODEC control..." << std::endl; + _codec_ctrl = ad9361_ctrl::make(_iface); + this->reset_codec_dcm(); + + //////////////////////////////////////////////////////////////////// + // create codec control objects + //////////////////////////////////////////////////////////////////// + { + const fs_path codec_path = mb_path / ("rx_codecs") / "A"; + _tree->create<std::string>(codec_path / "name").set("B200 RX dual ADC"); + _tree->create<int>(codec_path / "gains"); //empty cuz gains are in frontend + } + { + const fs_path codec_path = mb_path / ("tx_codecs") / "A"; + _tree->create<std::string>(codec_path / "name").set("B200 TX dual DAC"); + _tree->create<int>(codec_path / "gains"); //empty cuz gains are in frontend + } + + //////////////////////////////////////////////////////////////////// + // create clock control objects + //////////////////////////////////////////////////////////////////// + _tree->create<double>(mb_path / "tick_rate") + .coerce(boost::bind(&b200_impl::set_tick_rate, this, _1)) + .publish(boost::bind(&b200_impl::get_tick_rate, this)) + .subscribe(boost::bind(&b200_impl::update_tick_rate, this, _1)); + _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + + //////////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////////// + _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") + .publish(boost::bind(&b200_impl::get_ref_locked, this)); + + //////////////////////////////////////////////////////////////////// + // create frontend mapping + //////////////////////////////////////////////////////////////////// + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .set(subdev_spec_t()) + .subscribe(boost::bind(&b200_impl::update_rx_subdev_spec, this, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .set(subdev_spec_t()) + .subscribe(boost::bind(&b200_impl::update_tx_subdev_spec, this, _1)); + + //////////////////////////////////////////////////////////////////// + // setup radio control + //////////////////////////////////////////////////////////////////// + UHD_MSG(status) << "Initialize Radio control..." << std::endl; + const size_t num_radio_chains = ((_local_ctrl->peek32(RB32_CORE_STATUS) >> 8) & 0xff); + UHD_ASSERT_THROW(num_radio_chains > 0); + UHD_ASSERT_THROW(num_radio_chains <= 2); + _radio_perifs.resize(num_radio_chains); + for (size_t i = 0; i < _radio_perifs.size(); i++) this->setup_radio(i); + + //now test each radio module's connection to the codec interface + _codec_ctrl->data_port_loopback(true); + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + this->codec_loopback_self_test(perif.ctrl); + } + _codec_ctrl->data_port_loopback(false); + + //////////////////////////////////////////////////////////////////// + // create time and clock control objects + //////////////////////////////////////////////////////////////////// + _spi_iface = spi_core_3000::make(_local_ctrl, TOREG(SR_CORE_SPI), RB32_CORE_SPI); + _spi_iface->set_divider(B200_BUS_CLOCK_RATE/ADF4001_SPI_RATE); + _adf4001_iface = boost::shared_ptr<adf4001_ctrl>(new adf4001_ctrl(_spi_iface, ADF4001_SLAVENO)); + + //register time now and pps onto available radio cores + _tree->create<time_spec_t>(mb_path / "time" / "now") + .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)); + _tree->create<time_spec_t>(mb_path / "time" / "pps") + .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); + for (size_t i = 0; i < _radio_perifs.size(); i++) + { + _tree->access<time_spec_t>(mb_path / "time" / "now") + .subscribe(boost::bind(&time_core_3000::set_time_now, _radio_perifs[i].time64, _1)); + _tree->access<time_spec_t>(mb_path / "time" / "pps") + .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[i].time64, _1)); + } + + //setup time source props + _tree->create<std::string>(mb_path / "time_source" / "value") + .subscribe(boost::bind(&b200_impl::update_time_source, this, _1)); + static const std::vector<std::string> time_sources = boost::assign::list_of("none")("external")("gpsdo"); + _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); + //setup reference source props + _tree->create<std::string>(mb_path / "clock_source" / "value") + .subscribe(boost::bind(&b200_impl::update_clock_source, this, _1)); + static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("gpsdo"); + _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources); + + //////////////////////////////////////////////////////////////////// + // dboard eeproms but not really + //////////////////////////////////////////////////////////////////// + dboard_eeprom_t db_eeprom; + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom").set(db_eeprom); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom").set(db_eeprom); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom").set(db_eeprom); + + //////////////////////////////////////////////////////////////////// + // do some post-init tasks + //////////////////////////////////////////////////////////////////// + + //init the clock rate to something reasonable + _tree->access<double>(mb_path / "tick_rate").set( + device_addr.cast<double>("master_clock_rate", B200_DEFAULT_TICK_RATE)); + + //subdev spec contains full width of selections + subdev_spec_t rx_spec, tx_spec; + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) + { + rx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) + { + tx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec); + _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); + + //init to internal clock and time source + _tree->access<std::string>(mb_path / "clock_source/value").set("internal"); + _tree->access<std::string>(mb_path / "time_source/value").set("none"); + + //GPS installed: use external ref, time, and init time spec + if (_gps and _gps->gps_detected()) + { + UHD_MSG(status) << "Setting references to the internal GPSDO" << std::endl; + _tree->access<std::string>(mb_path / "time_source" / "value").set("gpsdo"); + _tree->access<std::string>(mb_path / "clock_source" / "value").set("gpsdo"); + UHD_MSG(status) << "Initializing time to the internal GPSDO" << std::endl; + const time_t tp = time_t(_gps->get_sensor("gps_time").to_int()+1); + _tree->access<time_spec_t>(mb_path / "time" / "pps").set(time_spec_t(tp)); + } + +} + +b200_impl::~b200_impl(void) +{ + UHD_SAFE_CALL + ( + _async_task.reset(); + ) +} + +/*********************************************************************** + * setup radio control objects + **********************************************************************/ + +void b200_impl::setup_radio(const size_t dspno) +{ + radio_perifs_t &perif = _radio_perifs[dspno]; + const fs_path mb_path = "/mboards/0"; + + //////////////////////////////////////////////////////////////////// + // radio control + //////////////////////////////////////////////////////////////////// + const boost::uint32_t sid = (dspno == 0)? B200_CTRL0_MSG_SID : B200_CTRL1_MSG_SID; + perif.ctrl = radio_ctrl_core_3000::make(vrt::if_packet_info_t::LINK_TYPE_CHDR, _ctrl_transport, zero_copy_if::sptr()/*null*/, sid); + perif.ctrl->hold_task(_async_task); + _async_task_data->radio_ctrl[dspno] = perif.ctrl; //weak + _tree->access<time_spec_t>(mb_path / "time" / "cmd") + .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); + this->register_loopback_self_test(perif.ctrl); + perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_ATR)); + + //////////////////////////////////////////////////////////////////// + // create rx dsp control objects + //////////////////////////////////////////////////////////////////// + perif.framer = rx_vita_core_3000::make(perif.ctrl, TOREG(SR_RX_CTRL)); + perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP)); + perif.ddc->set_link_rate(10e9/8); //whatever + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .subscribe(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); + const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % dspno); + _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); + _tree->create<double>(rx_dsp_path / "rate" / "value") + .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) + .subscribe(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1)) + .set(1e6); + _tree->create<double>(rx_dsp_path / "freq" / "value") + .coerce(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) + .set(0.0); + _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + + //////////////////////////////////////////////////////////////////// + // create tx dsp control objects + //////////////////////////////////////////////////////////////////// + perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL)); + perif.duc = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP)); + perif.duc->set_link_rate(10e9/8); //whatever + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1)) + .subscribe(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); + const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % dspno); + _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") + .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); + _tree->create<double>(tx_dsp_path / "rate" / "value") + .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) + .subscribe(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1)) + .set(1e6); + _tree->create<double>(tx_dsp_path / "freq" / "value") + .coerce(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) + .set(0.0); + _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") + .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + + //////////////////////////////////////////////////////////////////// + // create time control objects + //////////////////////////////////////////////////////////////////// + time_core_3000::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_now = RB64_TIME_NOW; + time64_rb_bases.rb_pps = RB64_TIME_PPS; + perif.time64 = time_core_3000::make(perif.ctrl, TOREG(SR_TIME), time64_rb_bases); + + //////////////////////////////////////////////////////////////////// + // create RF frontend interfacing + //////////////////////////////////////////////////////////////////// + for(size_t direction = 0; direction < 2; direction++) + { + const std::string x = direction? "rx" : "tx"; + const std::string key = std::string((direction? "RX" : "TX")) + std::string(((dspno == FE1)? "1" : "2")); + const fs_path rf_fe_path = mb_path / "dboards" / "A" / (x+"_frontends") / (dspno? "B" : "A"); + + _tree->create<std::string>(rf_fe_path / "name").set("FE-"+key); + _tree->create<int>(rf_fe_path / "sensors"); //empty TODO + BOOST_FOREACH(const std::string &name, ad9361_ctrl::get_gain_names(key)) + { + _tree->create<meta_range_t>(rf_fe_path / "gains" / name / "range") + .set(ad9361_ctrl::get_gain_range(key)); + + _tree->create<double>(rf_fe_path / "gains" / name / "value") + .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) + .set(0.0); + } + _tree->create<std::string>(rf_fe_path / "connection").set("IQ"); + _tree->create<bool>(rf_fe_path / "enabled").set(true); + _tree->create<bool>(rf_fe_path / "use_lo_offset").set(false); + _tree->create<double>(rf_fe_path / "bandwidth" / "value") + .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) + .set(40e6); + _tree->create<meta_range_t>(rf_fe_path / "bandwidth" / "range") + .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)); + _tree->create<double>(rf_fe_path / "freq" / "value") + .set(0.0) + .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) + .subscribe(boost::bind(&b200_impl::update_bandsel, this, key, _1)); + _tree->create<meta_range_t>(rf_fe_path / "freq" / "range") + .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)); + + //setup antenna stuff + if (key[0] == 'R') + { + static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2"); + _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); + _tree->create<std::string>(rf_fe_path / "antenna" / "value") + .subscribe(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1)) + .set("RX2"); + } + if (key[0] == 'T') + { + static const std::vector<std::string> ants(1, "TX/RX"); + _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); + _tree->create<std::string>(rf_fe_path / "antenna" / "value").set("TX/RX"); + } + + } +} + +/*********************************************************************** + * loopback tests + **********************************************************************/ + +void b200_impl::register_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + UHD_MSG(status) << "Performing register loopback test... " << std::flush; + size_t hash = time(NULL); + for (size_t i = 0; i < 100; i++) + { + boost::hash_combine(hash, i); + iface->poke32(TOREG(SR_TEST), boost::uint32_t(hash)); + test_fail = iface->peek32(RB32_TEST) != boost::uint32_t(hash); + if (test_fail) break; //exit loop on any failure + } + UHD_MSG(status) << ((test_fail)? "fail" : "pass") << std::endl; +} + +void b200_impl::codec_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush; + size_t hash = size_t(time(NULL)); + for (size_t i = 0; i < 100; i++) + { + boost::hash_combine(hash, i); + const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0; + iface->poke32(TOREG(SR_CODEC_IDLE), word32); + iface->peek64(RB64_CODEC_READBACK); //enough idleness for loopback to propagate + const boost::uint64_t rb_word64 = iface->peek64(RB64_CODEC_READBACK); + const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); + const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); + test_fail = word32 != rb_tx or word32 != rb_rx; + if (test_fail) break; //exit loop on any failure + } + UHD_MSG(status) << ((test_fail)? "fail" : "pass") << std::endl; + + /* Zero out the idle data. */ + iface->poke32(TOREG(SR_CODEC_IDLE), 0); +} + +/*********************************************************************** + * Sample and tick rate comprehension below + **********************************************************************/ +double b200_impl::set_tick_rate(const double raw_rate) +{ + //clip rate (which can be doubled by factor) to possible bounds + const double rate = ad9361_ctrl::get_samp_rate_range().clip(raw_rate); + + UHD_MSG(status) << "Asking for clock rate " << rate/1e6 << " MHz\n"; + _tick_rate = _codec_ctrl->set_clock_rate(rate); + UHD_MSG(status) << "Actually got clock rate " << _tick_rate/1e6 << " MHz\n"; + + //reset after clock rate change + this->reset_codec_dcm(); + + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + perif.time64->set_tick_rate(_tick_rate); + perif.time64->self_test(); + } + return _tick_rate; +} + +/*********************************************************************** + * compat checks + **********************************************************************/ + +void b200_impl::check_fw_compat(void) +{ + boost::uint16_t compat_num = _iface->get_compat_num(); + boost::uint32_t compat_major = (boost::uint32_t) (compat_num >> 8); + boost::uint32_t compat_minor = (boost::uint32_t) (compat_num & 0xFF); + + if (compat_major != B200_FW_COMPAT_NUM_MAJOR){ + throw uhd::runtime_error(str(boost::format( + "Expected firmware compatibility number 0x%x, but got 0x%x.%x:\n" + "The firmware build is not compatible with the host code build.\n" + "%s" + ) % int(B200_FW_COMPAT_NUM_MAJOR) % compat_major % compat_minor + % print_images_error())); + } + _tree->create<std::string>("/mboards/0/fw_version").set(str(boost::format("%u.%u") + % compat_major % compat_minor)); +} + +void b200_impl::check_fpga_compat(void) +{ + const boost::uint64_t compat = _local_ctrl->peek64(0); + const boost::uint32_t signature = boost::uint32_t(compat >> 32); + const boost::uint16_t compat_major = boost::uint16_t(compat >> 16); + const boost::uint16_t compat_minor = boost::uint16_t(compat & 0xffff); + if (signature != 0xACE0BA5E) throw uhd::runtime_error( + "b200::check_fpga_compat signature register readback failed"); + + if (compat_major != B200_FPGA_COMPAT_NUM){ + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number 0x%x, but got 0x%x.%x:\n" + "The FPGA build is not compatible with the host code build.\n" + "%s" + ) % int(B200_FPGA_COMPAT_NUM) % compat_major % compat_minor + % print_images_error())); + } + _tree->create<std::string>("/mboards/0/fpga_version").set(str(boost::format("%u.%u") + % compat_major % compat_minor)); +} + +void b200_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom) +{ + mb_eeprom.commit(*_iface, "B200"); +} + + +/*********************************************************************** + * Reference time and clock + **********************************************************************/ + +void b200_impl::update_clock_source(const std::string &source) +{ + if (source == "internal"){ + _adf4001_iface->set_lock_to_ext_ref(false); + } + else if ((source == "external") + or (source == "gpsdo")){ + + _adf4001_iface->set_lock_to_ext_ref(true); + } else { + throw uhd::key_error("update_clock_source: unknown source: " + source); + } + + _gpio_state.ref_sel = (source == "gpsdo")? 1 : 0; + this->update_gpio_state(); +} + +void b200_impl::update_time_source(const std::string &source) +{ + if (source == "none"){} + else if (source == "external"){} + else if (source == "gpsdo"){} + else throw uhd::key_error("update_time_source: unknown source: " + source); + for (size_t i = 0; i < _radio_perifs.size(); i++) + { + _radio_perifs[i].time64->set_time_source((source == "external")? "external" : "internal"); + } + this->update_gpio_state(); +} + +/*********************************************************************** + * GPIO setup + **********************************************************************/ + +void b200_impl::update_bandsel(const std::string& which, double freq) +{ + if(which[0] == 'R') { + if(freq < 2.2e9) { + _gpio_state.rx_bandsel_a = 0; + _gpio_state.rx_bandsel_b = 0; + _gpio_state.rx_bandsel_c = 1; + } else if((freq >= 2.2e9) && (freq < 4e9)) { + _gpio_state.rx_bandsel_a = 0; + _gpio_state.rx_bandsel_b = 1; + _gpio_state.rx_bandsel_c = 0; + } else if((freq >= 4e9) && (freq <= 6e9)) { + _gpio_state.rx_bandsel_a = 1; + _gpio_state.rx_bandsel_b = 0; + _gpio_state.rx_bandsel_c = 0; + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else if(which[0] == 'T') { + if(freq < 2.5e9) { + _gpio_state.tx_bandsel_a = 0; + _gpio_state.tx_bandsel_b = 1; + } else if((freq >= 2.5e9) && (freq <= 6e9)) { + _gpio_state.tx_bandsel_a = 1; + _gpio_state.tx_bandsel_b = 0; + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + + update_gpio_state(); +} + +void b200_impl::update_gpio_state(void) +{ + const boost::uint32_t misc_word = 0 + | (_gpio_state.tx_bandsel_a << 7) + | (_gpio_state.tx_bandsel_b << 6) + | (_gpio_state.rx_bandsel_a << 5) + | (_gpio_state.rx_bandsel_b << 4) + | (_gpio_state.rx_bandsel_c << 3) + | (_gpio_state.codec_arst << 2) + | (_gpio_state.mimo << 1) + | (_gpio_state.ref_sel << 0) + ; + + _local_ctrl->poke32(TOREG(RB32_CORE_MISC), misc_word); +} + +void b200_impl::reset_codec_dcm(void) +{ + _gpio_state.codec_arst = 1; + this->update_gpio_state(); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + _gpio_state.codec_arst = 0; + this->update_gpio_state(); +} + +void b200_impl::update_atrs(void) +{ + if (_radio_perifs.size() > FE1 and _radio_perifs[FE1].atr) + { + radio_perifs_t &perif = _radio_perifs[FE1]; + const bool enb_rx = bool(perif.rx_streamer.lock()); + const bool enb_tx = bool(perif.tx_streamer.lock()); + const bool is_rx2 = perif.ant_rx2; + const size_t rxonly = (enb_rx)? ((is_rx2)? STATE_RX1_RX2 : STATE_RX1_TXRX) : STATE_OFF; + const size_t txonly = (enb_tx)? (STATE_TX1_TXRX) : STATE_OFF; + size_t fd = STATE_OFF; + if (enb_rx and enb_tx) fd = STATE_FDX1_TXRX; + if (enb_rx and not enb_tx) fd = rxonly; + if (not enb_rx and enb_tx) fd = txonly; + gpio_core_200_32wo::sptr atr = perif.atr; + atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF); + atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly); + atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly); + atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd); + } + if (_radio_perifs.size() > FE2 and _radio_perifs[FE2].atr) + { + radio_perifs_t &perif = _radio_perifs[FE2]; + const bool enb_rx = bool(perif.rx_streamer.lock()); + const bool enb_tx = bool(perif.tx_streamer.lock()); + const bool is_rx2 = perif.ant_rx2; + const size_t rxonly = (enb_rx)? ((is_rx2)? STATE_RX2_RX2 : STATE_RX2_TXRX) : STATE_OFF; + const size_t txonly = (enb_tx)? (STATE_TX2_TXRX) : STATE_OFF; + size_t fd = STATE_OFF; + if (enb_rx and enb_tx) fd = STATE_FDX2_TXRX; + if (enb_rx and not enb_tx) fd = rxonly; + if (not enb_rx and enb_tx) fd = txonly; + gpio_core_200_32wo::sptr atr = perif.atr; + atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF); + atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly); + atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly); + atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd); + } +} + +void b200_impl::update_antenna_sel(const size_t which, const std::string &ant) +{ + if (ant != "TX/RX" and ant != "RX2") throw uhd::value_error("b200: unknown RX antenna option: " + ant); + _radio_perifs[which].ant_rx2 = (ant == "RX2"); + this->update_atrs(); +} + +void b200_impl::update_enables(void) +{ + //extract settings from state variables + const bool enb_tx1 = (_radio_perifs.size() > FE1) and bool(_radio_perifs[FE1].tx_streamer.lock()); + const bool enb_rx1 = (_radio_perifs.size() > FE1) and bool(_radio_perifs[FE1].rx_streamer.lock()); + const bool enb_tx2 = (_radio_perifs.size() > FE2) and bool(_radio_perifs[FE2].tx_streamer.lock()); + const bool enb_rx2 = (_radio_perifs.size() > FE2) and bool(_radio_perifs[FE2].rx_streamer.lock()); + const size_t num_rx = (enb_rx1?1:0) + (enb_rx2?1:0); + const size_t num_tx = (enb_tx1?1:0) + (enb_tx2?1:0); + const bool mimo = num_rx == 2 or num_tx == 2; + + //setup the active chains in the codec + _codec_ctrl->set_active_chains(enb_tx1, enb_tx2, enb_rx1, enb_rx2); + if ((num_rx + num_tx) == 0) _codec_ctrl->set_active_chains(true, false, true, false); //enable something + this->reset_codec_dcm(); //set_active_chains could cause a clock rate change - reset dcm + + //figure out if mimo is enabled based on new state + _gpio_state.mimo = (mimo)? 1 : 0; + update_gpio_state(); + + //atrs change based on enables + this->update_atrs(); +} + +sensor_value_t b200_impl::get_ref_locked(void) +{ + const bool lock = (_local_ctrl->peek32(RB32_CORE_MISC) & 0x1) == 0x1; + return sensor_value_t("Ref", lock, "locked", "unlocked"); +} diff --git a/host/lib/usrp/b200/b200_impl.hpp b/host/lib/usrp/b200/b200_impl.hpp new file mode 100644 index 000000000..d8a2232e0 --- /dev/null +++ b/host/lib/usrp/b200/b200_impl.hpp @@ -0,0 +1,196 @@ +// +// Copyright 2012-2013 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 <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B200_IMPL_HPP +#define INCLUDED_B200_IMPL_HPP + +#include "b200_iface.hpp" +#include "b200_uart.hpp" +#include "ad9361_ctrl.hpp" +#include "adf4001_ctrl.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "gpio_core_200.hpp" +#include "radio_ctrl_core_3000.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/weak_ptr.hpp> +#include "recv_packet_demuxer_3000.hpp" + +static const std::string B200_FW_FILE_NAME = "usrp_b200_fw.hex"; +static const std::string B200_FPGA_FILE_NAME = "usrp_b200_fpga.bin"; +static const std::string B210_FPGA_FILE_NAME = "usrp_b210_fpga.bin"; +static const boost::uint8_t B200_FW_COMPAT_NUM_MAJOR = 0x02; +static const boost::uint8_t B200_FW_COMPAT_NUM_MINOR = 0x00; +static const boost::uint16_t B200_FPGA_COMPAT_NUM = 0x01; +static const double B200_LINK_RATE_BPS = (5e9)/8; //practical link rate (5 Gbps) +static const double B200_BUS_CLOCK_RATE = 100e6; +static const double B200_DEFAULT_TICK_RATE = 32e6; +static const boost::uint32_t B200_GPSDO_ST_NONE = 0x83; + +#define FLIP_SID(sid) (((sid)<<16)|((sid)>>16)) + +static const boost::uint32_t B200_CTRL0_MSG_SID = 0x00000010; +static const boost::uint32_t B200_RESP0_MSG_SID = FLIP_SID(B200_CTRL0_MSG_SID); + +static const boost::uint32_t B200_CTRL1_MSG_SID = 0x00000020; +static const boost::uint32_t B200_RESP1_MSG_SID = FLIP_SID(B200_CTRL1_MSG_SID); + +static const boost::uint32_t B200_TX_DATA0_SID = 0x00000050; +static const boost::uint32_t B200_TX_MSG0_SID = FLIP_SID(B200_TX_DATA0_SID); + +static const boost::uint32_t B200_TX_DATA1_SID = 0x00000060; +static const boost::uint32_t B200_TX_MSG1_SID = FLIP_SID(B200_TX_DATA1_SID); + +static const boost::uint32_t B200_RX_DATA0_SID = 0x000000A0; +static const boost::uint32_t B200_RX_DATA1_SID = 0x000000B0; + +static const boost::uint32_t B200_TX_GPS_UART_SID = 0x00000030; +static const boost::uint32_t B200_RX_GPS_UART_SID = FLIP_SID(B200_TX_GPS_UART_SID); + +static const boost::uint32_t B200_LOCAL_CTRL_SID = 0x00000040; +static const boost::uint32_t B200_LOCAL_RESP_SID = FLIP_SID(B200_LOCAL_CTRL_SID); + +/*********************************************************************** + * The B200 Capability Constants + **********************************************************************/ + +//! Implementation guts +struct b200_impl : public uhd::device +{ + //structors + b200_impl(const uhd::device_addr_t &); + ~b200_impl(void); + + //the io interface + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + bool recv_async_msg(uhd::async_metadata_t &, double); + + uhd::property_tree::sptr _tree; + + //controllers + b200_iface::sptr _iface; + radio_ctrl_core_3000::sptr _local_ctrl; + ad9361_ctrl::sptr _codec_ctrl; + spi_core_3000::sptr _spi_iface; + boost::shared_ptr<uhd::usrp::adf4001_ctrl> _adf4001_iface; + uhd::gps_ctrl::sptr _gps; + + //transports + uhd::transport::zero_copy_if::sptr _data_transport; + uhd::transport::zero_copy_if::sptr _ctrl_transport; + boost::shared_ptr<uhd::usrp::recv_packet_demuxer_3000> _demux; + + //device properties interface + uhd::property_tree::sptr get_tree(void) const + { + return _tree; + } + + boost::weak_ptr<uhd::rx_streamer> _rx_streamer; + boost::weak_ptr<uhd::tx_streamer> _tx_streamer; + + //async ctrl + msgs + uhd::task::sptr _async_task; + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + struct AsyncTaskData + { + boost::shared_ptr<async_md_type> async_md; + boost::weak_ptr<radio_ctrl_core_3000> local_ctrl; + boost::weak_ptr<radio_ctrl_core_3000> radio_ctrl[2]; + b200_uart::sptr gpsdo_uart; + }; + boost::shared_ptr<AsyncTaskData> _async_task_data; + void handle_async_task(uhd::transport::zero_copy_if::sptr, boost::shared_ptr<AsyncTaskData>); + + void register_loopback_self_test(wb_iface::sptr iface); + void codec_loopback_self_test(wb_iface::sptr iface); + void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); + void check_fw_compat(void); + void check_fpga_compat(void); + void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_time_source(const std::string &); + void update_clock_source(const std::string &); + void update_bandsel(const std::string& which, double freq); + void update_antenna_sel(const size_t which, const std::string &ant); + uhd::sensor_value_t get_ref_locked(void); + + //perifs in the radio core + struct radio_perifs_t + { + radio_ctrl_core_3000::sptr ctrl; + gpio_core_200_32wo::sptr atr; + time_core_3000::sptr time64; + rx_vita_core_3000::sptr framer; + rx_dsp_core_3000::sptr ddc; + tx_vita_core_3000::sptr deframer; + tx_dsp_core_3000::sptr duc; + boost::weak_ptr<uhd::rx_streamer> rx_streamer; + boost::weak_ptr<uhd::tx_streamer> tx_streamer; + bool ant_rx2; + }; + std::vector<radio_perifs_t> _radio_perifs; + void setup_radio(const size_t which_radio); + void handle_overflow(const size_t index); + + struct gpio_state { + boost::uint32_t tx_bandsel_a, tx_bandsel_b, rx_bandsel_a, rx_bandsel_b, rx_bandsel_c, codec_arst, mimo, ref_sel; + + gpio_state() { + tx_bandsel_a = 0; + tx_bandsel_b = 0; + rx_bandsel_a = 0; + rx_bandsel_b = 0; + rx_bandsel_c = 0; + codec_arst = 0; + mimo = 0; + ref_sel = 0; + } + } _gpio_state; + + void update_gpio_state(void); + void reset_codec_dcm(void); + + void update_enables(void); + void update_atrs(void); + + void update_tick_rate(const double); + void update_rx_samp_rate(const size_t, const double); + void update_tx_samp_rate(const size_t, const double); + + double _tick_rate; + double get_tick_rate(void){return _tick_rate;} + double set_tick_rate(const double rate); +}; + +#endif /* INCLUDED_B200_IMPL_HPP */ diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp new file mode 100644 index 000000000..bb71fa5b6 --- /dev/null +++ b/host/lib/usrp/b200/b200_io_impl.cpp @@ -0,0 +1,377 @@ +// +// Copyright 2012-2013 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 <http://www.gnu.org/licenses/>. +// + +#include "b200_regs.hpp" +#include "b200_impl.hpp" +#include "validate_subdev_spec.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "async_packet_handler.hpp" +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * update streamer rates + **********************************************************************/ +void b200_impl::update_tick_rate(const double rate) +{ + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(perif.rx_streamer.lock()); + if (my_streamer) my_streamer->set_tick_rate(rate); + perif.framer->set_tick_rate(_tick_rate); + } + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock()); + if (my_streamer) my_streamer->set_tick_rate(rate); + perif.deframer->set_tick_rate(_tick_rate); + } +} + +void b200_impl::update_rx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_radio_perifs[dspno].rx_streamer.lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _radio_perifs[dspno].ddc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void b200_impl::update_tx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_radio_perifs[dspno].tx_streamer.lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _radio_perifs[dspno].duc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +/*********************************************************************** + * frontend selection + **********************************************************************/ +void b200_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ + //sanity checking + if (spec.size()) validate_subdev_spec(_tree, spec, "rx"); + UHD_ASSERT_THROW(spec.size() <= _radio_perifs.size()); + + if (spec.size() > 0) + { + UHD_ASSERT_THROW(spec[0].db_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A"); + } + if (spec.size() > 1) + { + //TODO we can support swapping at a later date, only this combo is supported + UHD_ASSERT_THROW(spec[1].db_name == "A"); + UHD_ASSERT_THROW(spec[1].sd_name == "B"); + } + + this->update_enables(); +} + +void b200_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ + //sanity checking + if (spec.size()) validate_subdev_spec(_tree, spec, "tx"); + UHD_ASSERT_THROW(spec.size() <= _radio_perifs.size()); + + if (spec.size() > 0) + { + UHD_ASSERT_THROW(spec[0].db_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A"); + } + if (spec.size() > 1) + { + //TODO we can support swapping at a later date, only this combo is supported + UHD_ASSERT_THROW(spec[1].db_name == "A"); + UHD_ASSERT_THROW(spec[1].sd_name == "B"); + } + + this->update_enables(); +} + +static void b200_if_hdr_unpack_le( + const boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info +){ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_unpack_le(packet_buff, if_packet_info); +} + +static void b200_if_hdr_pack_le( + boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info +){ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_pack_le(packet_buff, if_packet_info); +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool b200_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +){ + return _async_task_data->async_md->pop_with_timed_wait(async_metadata, timeout); +} + +void b200_impl::handle_async_task( + uhd::transport::zero_copy_if::sptr xport, + boost::shared_ptr<AsyncTaskData> data +) +{ + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff or buff->size() < 8) return; + const boost::uint32_t sid = uhd::wtohx(buff->cast<const boost::uint32_t *>()[1]); + switch (sid) + { + + //if the packet is a control response + case B200_RESP0_MSG_SID: + case B200_RESP1_MSG_SID: + case B200_LOCAL_RESP_SID: + { + radio_ctrl_core_3000::sptr ctrl; + if (sid == B200_RESP0_MSG_SID) ctrl = data->radio_ctrl[0].lock(); + if (sid == B200_RESP1_MSG_SID) ctrl = data->radio_ctrl[1].lock(); + if (sid == B200_LOCAL_RESP_SID) ctrl = data->local_ctrl.lock(); + if (ctrl) ctrl->push_response(buff->cast<const boost::uint32_t *>()); + break; + } + + //if the packet is a uart message + case B200_RX_GPS_UART_SID: + { + data->gpsdo_uart->handle_uart_packet(buff); + break; + } + + //or maybe the packet is a TX async message + case B200_TX_MSG0_SID: + case B200_TX_MSG1_SID: + { + const size_t i = (sid == B200_TX_MSG0_SID)? 0 : 1; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + try + { + b200_if_hdr_unpack_le(packet_buff, if_packet_info); + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "Error parsing ctrl packet: " << ex.what() << std::endl; + break; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff(uhd::wtohx<boost::uint32_t>, metadata, if_packet_info, packet_buff, _tick_rate, i); + data->async_md->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + break; + } + + //doh! + default: + UHD_MSG(error) << "Got a ctrl packet with unknown SID " << sid << std::endl; + } +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr b200_impl::get_rx_stream(const uhd::stream_args_t &args_) +{ + stream_args_t args = args_; + + //setup defaults for unspecified values + if (not args.otw_format.empty() and args.otw_format != "sc16") + { + throw uhd::value_error("b200_impl::get_rx_stream only supports otw_format sc16"); + } + args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + const size_t chan = args.channels[stream_i]; + radio_perifs_t &perif = _radio_perifs[chan]; + const boost::uint32_t sid = chan?B200_RX_DATA1_SID:B200_RX_DATA0_SID; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = _data_transport->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + + //init some streamer stuff + my_streamer->set_vrt_unpacker(&b200_if_hdr_unpack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_le"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.framer->clear(); + perif.framer->set_nsamps_per_packet(spp); + perif.framer->set_sid(sid); + perif.framer->setup(args); + perif.ddc->setup(args); + _demux->realloc_sid(sid); + my_streamer->set_xport_chan_get_buff(stream_i, boost::bind( + &recv_packet_demuxer_3000::get_recv_buff, _demux, sid, _1 + ), true /*flush*/); + my_streamer->set_overflow_handler(stream_i, boost::bind( + &b200_impl::handle_overflow, this, chan + )); + my_streamer->set_issue_stream_cmd(stream_i, boost::bind( + &rx_vita_core_3000::issue_stream_command, perif.framer, _1 + )); + perif.rx_streamer = my_streamer; //store weak pointer + + //sets all tick and samp rates on this streamer + this->update_tick_rate(this->get_tick_rate()); + _tree->access<double>(str(boost::format("/mboards/0/rx_dsps/%u/rate/value") % chan)).update(); + } + this->update_enables(); + + return my_streamer; +} + +void b200_impl::handle_overflow(const size_t i) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_radio_perifs[i].rx_streamer.lock()); + if (my_streamer->get_num_channels() == 2) //MIMO time + { + //find out if we were in continuous mode before stopping + const bool in_continuous_streaming_mode = _radio_perifs[i].framer->in_continuous_streaming_mode(); + //stop streaming + my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + //flush demux + _demux->realloc_sid(B200_RX_DATA0_SID); + _demux->realloc_sid(B200_RX_DATA1_SID); + //flush actual transport + while (_data_transport->get_recv_buff(0.001)){} + //restart streaming + if (in_continuous_streaming_mode) + { + stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = false; + stream_cmd.time_spec = _radio_perifs[i].time64->get_time_now() + time_spec_t(0.01); + my_streamer->issue_stream_cmd(stream_cmd); + } + } + else _radio_perifs[i].framer->handle_overflow(); +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr b200_impl::get_tx_stream(const uhd::stream_args_t &args_) +{ + stream_args_t args = args_; + + //setup defaults for unspecified values + if (not args.otw_format.empty() and args.otw_format != "sc16") + { + throw uhd::value_error("b200_impl::get_rx_stream only supports otw_format sc16"); + } + args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + boost::shared_ptr<sph::send_packet_streamer> my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + const size_t chan = args.channels[stream_i]; + radio_perifs_t &perif = _radio_perifs[chan]; + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + //+ sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + static const size_t bpp = _data_transport->get_send_frame_size() - hdr_size; + const size_t spp = bpp/convert::get_bytes_per_item(args.otw_format); + + //make the new streamer given the samples per packet + if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + + //init some streamer stuff + my_streamer->set_vrt_packer(&b200_if_hdr_pack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_le"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.deframer->clear(); + perif.deframer->setup(args); + perif.duc->setup(args); + + my_streamer->set_xport_chan_get_buff(stream_i, boost::bind( + &zero_copy_if::get_send_buff, _data_transport, _1 + )); + my_streamer->set_async_receiver(boost::bind( + &async_md_type::pop_with_timed_wait, _async_task_data->async_md, _1, _2 + )); + my_streamer->set_xport_chan_sid(stream_i, true, chan?B200_TX_DATA1_SID:B200_TX_DATA0_SID); + my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet + perif.tx_streamer = my_streamer; //store weak pointer + + //sets all tick and samp rates on this streamer + this->update_tick_rate(this->get_tick_rate()); + _tree->access<double>(str(boost::format("/mboards/0/tx_dsps/%u/rate/value") % chan)).update(); + } + this->update_enables(); + + return my_streamer; +} diff --git a/host/lib/usrp/b200/b200_regs.hpp b/host/lib/usrp/b200/b200_regs.hpp new file mode 100644 index 000000000..0e4cc82cd --- /dev/null +++ b/host/lib/usrp/b200/b200_regs.hpp @@ -0,0 +1,119 @@ +// +// Copyright 2012-2013 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 <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B200_REGS_HPP +#define INCLUDED_B200_REGS_HPP + +#include <boost/cstdint.hpp> + +#define TOREG(x) ((x)*4) + +#define localparam static const int + +localparam SR_CORE_SPI = 8; +localparam SR_CORE_MISC = 16; +localparam SR_CORE_COMPAT = 24; +localparam SR_CORE_GPSDO_ST = 40; +localparam RB32_CORE_SPI = 8; +localparam RB32_CORE_MISC = 16; +localparam RB32_CORE_STATUS = 20; + +localparam SR_SPI = 8; +localparam SR_ATR = 12; +localparam SR_TEST = 21; +localparam SR_CODEC_IDLE = 22; +localparam SR_READBACK = 32; +localparam SR_TX_CTRL = 64; +localparam SR_RX_CTRL = 96; +localparam SR_RX_DSP = 144; +localparam SR_TX_DSP = 184; +localparam SR_TIME = 128; + +localparam RB32_TEST = 0; +localparam RB64_TIME_NOW = 8; +localparam RB64_TIME_PPS = 16; +localparam RB64_CODEC_READBACK = 24; + +//pll constants +static const int ADF4001_SLAVENO = (1 << 1); +static const double ADF4001_SPI_RATE = 10e3; //slow for large time constant on spi lines + +/* ATR Control Bits */ +static const boost::uint32_t TX_ENABLE1 = (1 << 7); +static const boost::uint32_t SFDX1_RX = (1 << 6); +static const boost::uint32_t SFDX1_TX = (1 << 5); +static const boost::uint32_t SRX1_RX = (1 << 4); +static const boost::uint32_t SRX1_TX = (1 << 3); +static const boost::uint32_t LED_RX1 = (1 << 2); +static const boost::uint32_t LED_TXRX_RX1 = (1 << 1); +static const boost::uint32_t LED_TXRX_TX1 = (1 << 0); + +static const boost::uint32_t TX_ENABLE2 = (1 << 7); +static const boost::uint32_t SFDX2_RX = (1 << 6); +static const boost::uint32_t SFDX2_TX = (1 << 5); +static const boost::uint32_t SRX2_RX = (1 << 4); +static const boost::uint32_t SRX2_TX = (1 << 3); +static const boost::uint32_t LED_RX2 = (1 << 2); +static const boost::uint32_t LED_TXRX_RX2 = (1 << 1); +static const boost::uint32_t LED_TXRX_TX2 = (1 << 0); + + +/* ATR State Definitions. */ +static const boost::uint32_t STATE_OFF = 0x00; + +///////////////////////// side 1 /////////////////////////////////// +static const boost::uint32_t STATE_RX1_RX2 = (SFDX1_RX + | SFDX1_TX + | LED_RX1); + +static const boost::uint32_t STATE_RX1_TXRX = (SRX1_RX + | SRX1_TX + | LED_TXRX_RX1); + +static const boost::uint32_t STATE_FDX1_TXRX = (TX_ENABLE1 + | SFDX1_RX + | SFDX1_TX + | LED_TXRX_TX1 + | LED_RX1); + +static const boost::uint32_t STATE_TX1_TXRX = (TX_ENABLE1 + | SFDX1_RX + | SFDX1_TX + | LED_TXRX_TX1); + +///////////////////////// side 2 /////////////////////////////////// +static const boost::uint32_t STATE_RX2_RX2 = (SFDX2_RX + | SRX2_TX + | LED_RX2); + +static const boost::uint32_t STATE_RX2_TXRX = (SRX2_TX + | SRX2_RX + | LED_TXRX_RX2); + +static const boost::uint32_t STATE_FDX2_TXRX = (TX_ENABLE2 + | SFDX2_RX + | SFDX2_TX + | LED_TXRX_TX2 + | LED_RX2); + +static const boost::uint32_t STATE_TX2_TXRX = (TX_ENABLE2 + | SFDX2_RX + | SFDX2_TX + | LED_TXRX_TX2); + + +#endif /* INCLUDED_B200_REGS_HPP */ diff --git a/host/lib/usrp/b200/b200_uart.cpp b/host/lib/usrp/b200/b200_uart.cpp new file mode 100644 index 000000000..4682a79b9 --- /dev/null +++ b/host/lib/usrp/b200/b200_uart.cpp @@ -0,0 +1,117 @@ +// +// Copyright 2013 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 <http://www.gnu.org/licenses/>. +// + +#include "b200_uart.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/exception.hpp> + +using namespace uhd; +using namespace uhd::transport; + +struct b200_uart_impl : b200_uart +{ + b200_uart_impl(zero_copy_if::sptr xport, const boost::uint32_t sid): + _xport(xport), + _sid(sid), + _count(0), + _char_queue(4096) + { + //this default baud divider is over 9000 + this->set_baud_divider(9001); + } + + void send_char(const char ch) + { + managed_send_buffer::sptr buff = _xport->get_send_buff(); + UHD_ASSERT_THROW(bool(buff)); + + vrt::if_packet_info_t packet_info; + packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; + packet_info.num_payload_words32 = 2; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = _count++; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = _sid; + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + boost::uint32_t *packet_buff = buff->cast<boost::uint32_t *>(); + vrt::if_hdr_pack_le(packet_buff, packet_info); + packet_buff[packet_info.num_header_words32+0] = uhd::htowx(boost::uint32_t(_baud_div)); + packet_buff[packet_info.num_header_words32+1] = uhd::htowx(boost::uint32_t(ch)); + buff->commit(packet_info.num_packet_words32*sizeof(boost::uint32_t)); + } + + void write_uart(const std::string &buff) + { + for (size_t i = 0; i < buff.size(); i++) + { + if (buff[i] == '\n') this->send_char('\r'); + this->send_char(buff[i]); + } + } + + std::string read_uart(double timeout) + { + std::string line; + char ch = '\0'; + while (_char_queue.pop_with_timed_wait(ch, timeout)) + { + if (ch == '\r') continue; + line += std::string(&ch, 1); + if (ch == '\n') return line; + } + return line; + } + + void handle_uart_packet(managed_recv_buffer::sptr buff) + { + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + vrt::if_packet_info_t packet_info; + packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + vrt::if_hdr_unpack_le(packet_buff, packet_info); + const char ch = char(uhd::wtohx(packet_buff[packet_info.num_header_words32+1])); + _char_queue.push_with_pop_on_full(ch); + } + + void set_baud_divider(const double baud_div) + { + _baud_div = size_t(baud_div + 0.5); + } + + const zero_copy_if::sptr _xport; + const boost::uint32_t _sid; + size_t _count; + size_t _baud_div; + bounded_buffer<char> _char_queue; +}; + + +b200_uart::sptr b200_uart::make(zero_copy_if::sptr xport, const boost::uint32_t sid) +{ + return b200_uart::sptr(new b200_uart_impl(xport, sid)); +} diff --git a/host/lib/usrp/b200/b200_uart.hpp b/host/lib/usrp/b200/b200_uart.hpp new file mode 100644 index 000000000..1c8e44ddc --- /dev/null +++ b/host/lib/usrp/b200/b200_uart.hpp @@ -0,0 +1,36 @@ +// +// Copyright 2013 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 <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B200_UART_HPP +#define INCLUDED_B200_UART_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/serial.hpp> //uart iface +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class b200_uart: boost::noncopyable, public uhd::uart_iface +{ +public: + typedef boost::shared_ptr<b200_uart> sptr; + static sptr make(uhd::transport::zero_copy_if::sptr, const boost::uint32_t sid); + virtual void handle_uart_packet(uhd::transport::managed_recv_buffer::sptr buff) = 0; + virtual void set_baud_divider(const double baud_div) = 0; +}; + + +#endif /* INCLUDED_B200_UART_HPP */ |