diff options
237 files changed, 16669 insertions, 3069 deletions
diff --git a/.gitmodules b/.gitmodules index 473a21289..c85c089b3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "fpga-src"] path = fpga-src url = https://github.com/EttusResearch/fpga.git - branch = maint + branch = master diff --git a/firmware/usrp3/CMakeLists.txt b/firmware/usrp3/CMakeLists.txt index f71b79b0c..66a43b6bd 100644 --- a/firmware/usrp3/CMakeLists.txt +++ b/firmware/usrp3/CMakeLists.txt @@ -25,6 +25,10 @@ SET(CMAKE_SYSTEM_NAME Generic) CMAKE_FORCE_C_COMPILER(zpu-elf-gcc GNU) PROJECT(USRP3_FW C) +SET(UHD_VERSION_HASH 0 CACHE INTEGER "UHD Version Hash") +EXECUTE_PROCESS(COMMAND ${CMAKE_SOURCE_DIR}/utils/git-hash.sh OUTPUT_VARIABLE UHD_VERSION_HASH) +ADD_DEFINITIONS(-DUHD_VERSION_HASH=0x${UHD_VERSION_HASH}) + INCLUDE_DIRECTORIES(include) find_package(PythonInterp) @@ -130,3 +134,4 @@ ENDMACRO(GEN_OUTPUTS) ######################################################################## ADD_SUBDIRECTORY(lib) ADD_SUBDIRECTORY(x300) +ADD_SUBDIRECTORY(n230) diff --git a/firmware/usrp3/include/cron.h b/firmware/usrp3/include/cron.h new file mode 100644 index 000000000..2d43d97f5 --- /dev/null +++ b/firmware/usrp3/include/cron.h @@ -0,0 +1,75 @@ +// +// Copyright 2014 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_CRON_H +#define INCLUDED_CRON_H + +#include <stdint.h> +#include <stdbool.h> + +#define CRON_MAX_JOBS 4 + +typedef enum { + SEC = 1, MILLISEC = 1000, MICROSEC = 1000000 +} cron_time_unit_t; + +typedef uint32_t (*cron_counter_fetcher_t)(); + +/*! + * \brief Initialize cron subsystem with a mechanism to fetch a counter and its frequency + */ +void cron_init(const cron_counter_fetcher_t fetch_counter, uint32_t counter_freq); + +/*! + * \brief Get the hardware tick count + */ +uint32_t cron_get_ticks(); + +/*! + * \brief Get the time elapsed between start and stop in the specified units + */ +uint32_t get_elapsed_time(uint32_t start_ticks, uint32_t stop_ticks, cron_time_unit_t unit); + +/*! + * \brief Sleep (spinloop) for about 'ticks' counter ticks + * Use only if simulating, _very_ short delay + */ +void sleep_ticks(uint32_t ticks); + +/*! + * \brief Sleep (spinloop) for about 'duration' microseconds + * Use only if simulating, _very_ short delay + */ +void sleep_us(uint32_t duration); + +/*! + * \brief Sleep (spinloop) for about 'duration' milliseconds + * Use only if simulating, _very_ short delay + */ +void sleep_ms(uint32_t duration); + +/*! + * \brief Initialize a unique cron job with 'job_id' and interval 'interval_ms' + */ +void cron_job_init(uint32_t job_id, uint32_t interval_ms); + +/*! + * \brief Check if cron job with 'job_id' is due for execution + */ +bool cron_job_run_due(uint32_t job_id); + +#endif /* INCLUDED_CRON_H */ diff --git a/firmware/usrp3/include/flash/spi_flash.h b/firmware/usrp3/include/flash/spi_flash.h new file mode 100644 index 000000000..8ed73f648 --- /dev/null +++ b/firmware/usrp3/include/flash/spi_flash.h @@ -0,0 +1,92 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * 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_SPI_FLASH +#define INCLUDED_SPI_FLASH + +#include <wb_spi.h> + +//Device class that encapsulates the geometry and control +//interface for the flash chip +typedef struct { + uint32_t page_size; //in bytes + uint32_t sector_size; //in bytes + uint32_t num_sectors; + const wb_spi_slave_t* bus; +} spi_flash_dev_t; + +//Low level device specific operations +typedef uint16_t (*spif_read_id_fn_t)(const spi_flash_dev_t* flash); +typedef void (*spif_read_fn_t)(const spi_flash_dev_t* flash, uint32_t offset, void *buf, uint32_t num_bytes); +typedef bool (*spif_erase_sector_dispatch_fn_t)(const spi_flash_dev_t* flash, uint32_t offset); +typedef bool (*spif_erase_sector_commit_fn_t)(const spi_flash_dev_t* flash, uint32_t offset); +typedef bool (*spif_erase_sector_busy_fn_t)(const spi_flash_dev_t* flash); +typedef bool (*spif_write_page_dispatch_fn_t)(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); +typedef bool (*spif_write_page_commit_fn_t)(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); +typedef bool (*spif_write_page_busy_fn_t)(const spi_flash_dev_t* flash); + +//Interface struct for all low level device operations +typedef struct { + spif_read_id_fn_t read_id; + spif_read_fn_t read; + spif_erase_sector_dispatch_fn_t erase_sector_dispatch; + spif_erase_sector_commit_fn_t erase_sector_commit; + spif_erase_sector_busy_fn_t erase_sector_busy; + spif_write_page_dispatch_fn_t write_page_dispatch; + spif_write_page_commit_fn_t write_page_commit; + spif_write_page_busy_fn_t write_page_busy; +} spi_flash_ops_t; + +typedef enum { + IDLE, WRITE_IN_PROGRESS, ERASE_IN_PROGRESS +} spi_flash_state_t; + +//A session struct that encapsulates everything about the flash +//in a device agnostic way +typedef struct { + const spi_flash_dev_t* device; + const spi_flash_ops_t* ops; + spi_flash_state_t state; + uint32_t last_offset; + uint16_t id; +} spi_flash_session_t; + +/*! + * Initialize the spi_flash_session_t object + */ +void spif_init(spi_flash_session_t* flash, const spi_flash_dev_t* device, const spi_flash_ops_t* ops); + +/*! + * Read "num_bytes" from "offset" in the flash into the buffer "buf". + * This call will block until all data is available. + */ +void spif_read_sync(const spi_flash_session_t* flash, uint32_t offset, void *buf, uint32_t num_bytes); + +/*! + * Erase sector at "offset" in the flash. + * This call will block until the erase is complete. + */ +bool spif_erase_sector_sync(const spi_flash_session_t* flash, uint32_t offset); + +/*! + * Write "num_bytes" from buffer "buf" at "offset" in the flash. + * This call will block until the write is complete. + */ +bool spif_write_page_sync(const spi_flash_session_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); + + +#endif /* INCLUDED_SPI_FLASH */ diff --git a/firmware/usrp3/include/flash/spif_spsn_s25flxx.h b/firmware/usrp3/include/flash/spif_spsn_s25flxx.h new file mode 100644 index 000000000..1e6eededf --- /dev/null +++ b/firmware/usrp3/include/flash/spif_spsn_s25flxx.h @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * 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_SPIF_SPSN_S25FLXX_H +#define INCLUDED_SPIF_SPSN_S25FLXX_H + +#include <flash/spi_flash.h> + +const spi_flash_ops_t* spif_spsn_s25flxx_operations(); + +uint16_t spif_spsn_s25flxx_read_id(const spi_flash_dev_t* flash); + +void spif_spsn_s25flxx_read(const spi_flash_dev_t* flash, uint32_t offset, void *buf, uint32_t num_bytes); + +bool spif_spsn_s25flxx_erase_sector_dispatch(const spi_flash_dev_t* flash, uint32_t offset); + +bool spif_spsn_s25flxx_erase_sector_commit(const spi_flash_dev_t* flash, uint32_t offset); + +bool spif_spsn_s25flxx_write_page_dispatch(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); + +bool spif_spsn_s25flxx_write_page_commit(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes); + +bool spif_spsn_s25flxx_device_busy(const spi_flash_dev_t* flash); + +#endif /* INCLUDED_SPIF_SPSN_S25FLXX_H */ diff --git a/firmware/usrp3/include/mdelay.h b/firmware/usrp3/include/mdelay.h deleted file mode 100644 index 226bbb3f7..000000000 --- a/firmware/usrp3/include/mdelay.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- c -*- */ -/* - * Copyright 2007 Free Software Foundation, Inc. - * - * 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_MDELAY_H -#define INCLUDED_MDELAY_H - -/*! - * \brief Delay about ms milliseconds - * - * If simulating, _very_ short delay - */ -void mdelay(int ms); - -#endif /* INCLUDED_MDELAY_H */ diff --git a/firmware/usrp3/include/trace.h b/firmware/usrp3/include/trace.h index 0daa231fe..015ae9049 100644 --- a/firmware/usrp3/include/trace.h +++ b/firmware/usrp3/include/trace.h @@ -31,6 +31,7 @@ * An alternate way of defining the level is the "TRACE_LEVEL" * variable in cmake. (eg. -DTRACE_LEVEL=13). */ + //#define UHD_FW_TRACE_LEVEL 13 typedef enum diff --git a/firmware/usrp3/include/wb_soft_reg.h b/firmware/usrp3/include/wb_soft_reg.h new file mode 100644 index 000000000..cbfc311bb --- /dev/null +++ b/firmware/usrp3/include/wb_soft_reg.h @@ -0,0 +1,135 @@ +// +// Copyright 2014 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_SOFT_REG_H +#define INCLUDED_SOFT_REG_H + +#include <stdint.h> +#include <stdbool.h> +#include <wb_utils.h> + +/* Keeps track of all metadata associated with a soft register. + * Use this struct when you need to manage a hardware register that needs + * to be accessed from different sections of software. If a register contains + * several unrelated bitfields, this object can be used to ensure coherency. + * It is recommended that the client hold this as a global/static object. + */ +typedef struct +{ + uint32_t wr_addr; + uint32_t rd_addr; + uint32_t soft_copy; +} soft_reg_t; + +/* A register field is defined as a tuple of the mask and the shift. + * It can be used to make read-modify-write operations more convenient + * For efficiency reasons, it is recommended to always use a constant + * of this type because it will get optimized out by the compiler and + * will result in zero memory overhead + */ +typedef struct +{ + uint8_t num_bits; + uint8_t shift; +} soft_reg_field_t; + + +/*! + * Initialize the soft_reg_t struct as a read-write register. + * Params: + * - reg: Pointer to the soft_reg struct + * - wr_addr: The address used to flush the register to HW + * - rd_addr: The address used to read the register from HW + */ +static inline void initialize_readwrite_soft_reg(soft_reg_t* reg, uint32_t wr_addr, uint32_t rd_addr) +{ + reg->wr_addr = wr_addr; + reg->rd_addr = rd_addr; + reg->soft_copy = 0; +} + +/*! + * Initialize the soft_reg_t struct as a write-only register. + * Params: + * - reg: Pointer to the soft_reg struct + * - addr: The address used to flush the register to HW + */ +static inline void initialize_writeonly_soft_reg(soft_reg_t* reg, uint32_t addr) +{ + reg->wr_addr = addr; + reg->rd_addr = 0; + reg->soft_copy = 0; +} + +/*! + * Update specified field in the soft-copy with the arg value. + * Performs a read-modify-write operation so all other field are preserved. + * NOTE: This does not write the value to hardware. + */ +static inline void soft_reg_set(soft_reg_t* reg, const soft_reg_field_t field, const uint32_t field_value) +{ + const uint32_t mask = ((1<<field.num_bits)-1)<<field.shift; + reg->soft_copy = (reg->soft_copy & ~mask) | ((field_value << field.shift) & mask); +} + +/*! + * Write the contents of the soft-copy to hardware. + */ +static inline void soft_reg_flush(const soft_reg_t* reg) +{ + wb_poke32(reg->wr_addr, reg->soft_copy); +} + +/*! + * Shortcut for a set and a flush. + */ +static inline void soft_reg_write(soft_reg_t* reg, const soft_reg_field_t field, const uint32_t field_value) +{ + soft_reg_set(reg, field, field_value); + soft_reg_flush(reg); +} + +/*! + * Get the value of the specified field from the soft-copy. + * NOTE: This does not read anything from hardware. + */ +static inline uint32_t soft_reg_get(const soft_reg_t* reg, const soft_reg_field_t field) +{ + const uint32_t mask = ((1<<field.num_bits)-1)<<field.shift; + return (reg->soft_copy & mask) >> field.shift; +} + +/*! + * Read the contents of the register from hardware and update the soft copy. + */ +static inline void soft_reg_refresh(soft_reg_t* reg) +{ + if (reg->rd_addr) { + reg->soft_copy = wb_peek32(reg->rd_addr); + } +} + +/*! + * Shortcut for refresh and get + */ +static inline uint32_t soft_reg_read(soft_reg_t* reg, const soft_reg_field_t field) +{ + soft_reg_refresh(reg); + return soft_reg_get(reg, field); +} + +#endif /* INCLUDED_SOFT_REG_H */ diff --git a/firmware/usrp3/include/wb_spi.h b/firmware/usrp3/include/wb_spi.h new file mode 100644 index 000000000..ebbb20b16 --- /dev/null +++ b/firmware/usrp3/include/wb_spi.h @@ -0,0 +1,55 @@ + +// Copyright 2012 Ettus Research LLC + +#ifndef INCLUDED_WB_SPI_H +#define INCLUDED_WB_SPI_H + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +typedef enum { + WRITE, WRITE_READ +} wb_spi_rw_mode_t; + +typedef enum { + RISING, FALLING +} wb_spi_edge_t; + +typedef struct { + void* base; + uint32_t slave_sel; + uint32_t clk_div; + wb_spi_edge_t mosi_edge; + wb_spi_edge_t miso_edge; + bool lsb_first; +} wb_spi_slave_t; + +/*! + * \brief Initialize SPI slave device + */ +void wb_spi_init(const wb_spi_slave_t* slave); + +/*! + * \brief Perform a SPI transaction in auto chip-select mode. + */ +inline void wb_spi_transact(const wb_spi_slave_t* slave, + wb_spi_rw_mode_t rw_mode, const void* mosi_buf, void* miso_buf, uint32_t length); + +/*! + * \brief Perform a SPI transaction in manual chip-select mode. + */ +inline void wb_spi_transact_man_ss(const wb_spi_slave_t* slave, + wb_spi_rw_mode_t rw_mode, const void* mosi_buf, void* miso_buf, uint32_t length); + +/*! + * \brief Select SPI slave + */ +void wb_spi_slave_select(const wb_spi_slave_t* slave); + +/*! + * \brief Deselect SPI slave + */ +void wb_spi_slave_deselect(const wb_spi_slave_t* slave); + +#endif /* INCLUDED_WB_SPI_H */ diff --git a/firmware/usrp3/lib/CMakeLists.txt b/firmware/usrp3/lib/CMakeLists.txt index 621b9b611..9d9ee3c6c 100644 --- a/firmware/usrp3/lib/CMakeLists.txt +++ b/firmware/usrp3/lib/CMakeLists.txt @@ -21,12 +21,16 @@ add_library(usrp3fw STATIC udp_uart.c wb_uart.c wb_i2c.c + wb_spi.c printf.c wb_pkt_iface64.c u3_net_stack.c ethernet.c - mdelay.c chinch.c print_addrs.c link_state_route_proto.c + cron.c + fw_comm_protocol.c + flash/spi_flash.c + flash/spif_spsn_s25flxx.c ) diff --git a/firmware/usrp3/lib/cron.c b/firmware/usrp3/lib/cron.c new file mode 100644 index 000000000..24b8feb4e --- /dev/null +++ b/firmware/usrp3/lib/cron.c @@ -0,0 +1,92 @@ +// +// Copyright 2014 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 "cron.h" + +//Counter specific +static cron_counter_fetcher_t cron_fetch_counter; +static uint32_t cron_counter_freq; + +//Cron job specific +typedef struct { + uint32_t tick_interval; + uint32_t last_tick_count; +} cron_job_t; + +static cron_job_t cron_job_table[CRON_MAX_JOBS]; + +void cron_init(const cron_counter_fetcher_t fetch_counter, uint32_t counter_freq) +{ + cron_fetch_counter = fetch_counter; + cron_counter_freq = counter_freq; + + for (int i = 0; i < CRON_MAX_JOBS; i++) { + cron_job_table[i].tick_interval = 0; + } +} + +uint32_t cron_get_ticks() +{ + return cron_fetch_counter(); +} + +uint32_t get_elapsed_time(uint32_t start_ticks, uint32_t stop_ticks, cron_time_unit_t unit) +{ + return ((stop_ticks - start_ticks) / cron_counter_freq) * ((uint32_t)unit); +} + +void sleep_ticks(uint32_t ticks) +{ + if (ticks == 0) return; //Handle the 0 delay case quickly + + const uint32_t ticks_begin = cron_fetch_counter(); + while(cron_fetch_counter() - ticks_begin < ticks) { + /*NOP: Spinloop*/ + } +} + +void sleep_us(uint32_t duration) +{ + sleep_ticks((duration * (cron_counter_freq/1000000))); +} + +void sleep_ms(uint32_t duration) +{ + sleep_ticks((duration * (cron_counter_freq/1000))); +} + +void cron_job_init(uint32_t job_id, uint32_t interval_ms) +{ + cron_job_table[job_id].tick_interval = (interval_ms * (cron_counter_freq/1000)); + cron_job_table[job_id].last_tick_count = 0; +} + +bool cron_job_run_due(uint32_t job_id) +{ + uint32_t new_tick_count = cron_fetch_counter(); + bool run_job = (new_tick_count - cron_job_table[job_id].last_tick_count) >= + cron_job_table[job_id].tick_interval; + + if (run_job) { + //If the job is due to run, update the tick count for the next run + //The assumption here is that the caller will actually run their job + //when the return value is true. If not, the caller just missed this + //iteration and will have to option to run the job in the next pass through. + cron_job_table[job_id].last_tick_count = new_tick_count; + } + return run_job; +} diff --git a/firmware/usrp3/lib/ethernet.c b/firmware/usrp3/lib/ethernet.c index 91efbfe1d..e9c18528d 100644 --- a/firmware/usrp3/lib/ethernet.c +++ b/firmware/usrp3/lib/ethernet.c @@ -21,7 +21,7 @@ #endif #include "../x300/x300_defs.h" #include "ethernet.h" -#include "mdelay.h" +#include "cron.h" #include <trace.h> #include "wb_i2c.h" #include "wb_utils.h" @@ -220,7 +220,7 @@ xge_read_sfpp_type(const uint32_t base, const uint32_t delay_ms) int x; // Delay read of SFPP if (delay_ms) - mdelay(delay_ms); + sleep_ms(delay_ms); // Read ID code from SFP x = xge_i2c_rd(base, MODULE_DEV_ADDR, 3); // I2C Error? @@ -312,10 +312,9 @@ static void xge_mac_init(const uint32_t base) } // base is pointer to XGE MAC on Wishbone. -static void xge_phy_init(const uint8_t eth, const uint32_t mdio_port_arg) +static void xge_phy_init(const uint8_t eth, const uint32_t mdio_port) { int x; - uint32_t mdio_port = eth==0 ? 1 : mdio_port_arg; // Read LASI Ctrl register to capture state. //y = xge_read_mdio(0x9002,XGE_MDIO_DEVICE_PMA,XGE_MDIO_ADDR_PHY_A); UHD_FW_TRACE(DEBUG, "Begining XGE PHY init sequence."); @@ -323,8 +322,10 @@ static void xge_phy_init(const uint8_t eth, const uint32_t mdio_port_arg) x = read_mdio(eth, 0x0, XGE_MDIO_DEVICE_PMA,mdio_port); x = x | (1 << 15); write_mdio(eth, 0x0,XGE_MDIO_DEVICE_PMA,mdio_port,x); + uint32_t loopCount = 0; while(x&(1<<15)) { x = read_mdio(eth, 0x0,XGE_MDIO_DEVICE_PMA,mdio_port); + if( loopCount++ > 200 ) break; // usually succeeds after 22 or 23 polls } } diff --git a/firmware/usrp3/lib/flash/spi_flash.c b/firmware/usrp3/lib/flash/spi_flash.c new file mode 100644 index 000000000..b4257c96f --- /dev/null +++ b/firmware/usrp3/lib/flash/spi_flash.c @@ -0,0 +1,50 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * 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 <flash/spi_flash.h> + +void spif_init(spi_flash_session_t* flash, const spi_flash_dev_t* device, const spi_flash_ops_t* ops) +{ + flash->device = device; + flash->ops = ops; + flash->state = IDLE; + flash->last_offset = 0; + flash->id = ops->read_id(device); +} + +void spif_read_sync(const spi_flash_session_t* flash, uint32_t offset, void *buf, uint32_t num_bytes) +{ + flash->ops->read(flash->device, offset, buf, num_bytes); +} + +bool spif_erase_sector_sync(const spi_flash_session_t* flash, uint32_t offset) +{ + if (flash->ops->erase_sector_dispatch(flash->device, offset)) { + return flash->ops->erase_sector_commit(flash->device, offset); + } else { + return false; + } +} + +bool spif_write_page_sync(const spi_flash_session_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes) +{ + if (flash->ops->write_page_dispatch(flash->device, offset, buf, num_bytes)) { + return flash->ops->write_page_commit(flash->device, offset, buf, num_bytes); + } else { + return false; + } +} diff --git a/firmware/usrp3/lib/flash/spif_spsn_s25flxx.c b/firmware/usrp3/lib/flash/spif_spsn_s25flxx.c new file mode 100644 index 000000000..244115b6f --- /dev/null +++ b/firmware/usrp3/lib/flash/spif_spsn_s25flxx.c @@ -0,0 +1,238 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * 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 <wb_spi.h> +#include <flash/spif_spsn_s25flxx.h> +#include <cron.h> +#include <trace.h> +#include <string.h> //for memset, memcpy + +#define S25FLXX_CMD_WIDTH 8 +#define S25FLXX_ADDR_WIDTH 24 + +/* S25FLxx-specific commands */ +#define S25FLXX_CMD_READID 0x90 /* Read Manufacturer and Device Identification */ +#define S25FLXX_CMD_READSIG 0xAB /* Read Electronic Signature (Will release from Deep PD) */ +#define S25FLXX_CMD_READ 0x03 /* Read Data Bytes */ +#define S25FLXX_CMD_FAST_READ 0x0B /* Read Data Bytes at Higher Speed */ + +#define S25FLXX_CMD_WREN 0x06 /* Write Enable */ +#define S25FLXX_CMD_WRDI 0x04 /* Write Disable */ + +#define S25FLXX_CMD_PP 0x02 /* Page Program */ +#define S25FLXX_CMD_SE 0xD8 /* Sector Erase */ +#define S25FLXX_CMD_BE 0xC7 /* Bulk Erase */ +#define S25FLXX_CMD_DP 0xB9 /* Deep Power-down */ + +#define S25FLXX_CMD_RDSR 0x05 /* Read Status Register */ +#define S25FLXX_CMD_WRSR 0x01 /* Write Status Register */ + +#define S25FLXX_STATUS_WIP 0x01 /* Write in Progress */ +#define S25FLXX_STATUS_E_ERR 0x20 /* Erase Error Occured */ +#define S25FLXX_STATUS_P_ERR 0x40 /* Programming Error Occured */ + +#define S25FLXX_SECTOR_ERASE_TIME_MS 750 //Spec: 650ms +#define S25FLXX_PAGE_WRITE_TIME_MS 1 //Spec: 750us + +#define S25FLXX_SMALL_SECTORS_PER_LOGICAL 16 //16 4-kB physical sectors per logical sector +#define S25FLXX_LARGE_SECTOR_BASE 0x20000 //Large physical sectors start at logical sector 2 + +inline static uint8_t _spif_read_status(const spi_flash_dev_t* flash) +{ + uint16_t cmd = S25FLXX_CMD_RDSR << 8, status = 0xFFFF; + wb_spi_transact(flash->bus, WRITE_READ, &cmd, &status, S25FLXX_CMD_WIDTH + 8 /* 8 bits of status */); + return status; +} + +inline static bool _spif_wait_ready(const spi_flash_dev_t* flash, uint32_t timeout_ms) +{ + uint32_t start_ticks = cron_get_ticks(); + do { + if ((_spif_read_status(flash) & S25FLXX_STATUS_WIP) == 0) { + return true; + } + } while (get_elapsed_time(start_ticks, cron_get_ticks(), MILLISEC) < timeout_ms); + + return false; // Timed out +} + +inline static void _spi_flash_set_write_enabled(const spi_flash_dev_t* flash, bool enabled) +{ + uint8_t cmd = enabled ? S25FLXX_CMD_WREN : S25FLXX_CMD_WRDI; + wb_spi_transact(flash->bus, WRITE, &cmd, NULL, S25FLXX_CMD_WIDTH); +} + +const spi_flash_ops_t spif_spsn_s25flxx_ops = +{ + .read_id = spif_spsn_s25flxx_read_id, + .read = spif_spsn_s25flxx_read, + .erase_sector_dispatch = spif_spsn_s25flxx_erase_sector_dispatch, + .erase_sector_commit = spif_spsn_s25flxx_erase_sector_commit, + .erase_sector_busy = spif_spsn_s25flxx_device_busy, + .write_page_dispatch = spif_spsn_s25flxx_write_page_dispatch, + .write_page_commit = spif_spsn_s25flxx_write_page_commit, + .write_page_busy = spif_spsn_s25flxx_device_busy +}; + +const spi_flash_ops_t* spif_spsn_s25flxx_operations() +{ + return &spif_spsn_s25flxx_ops; +} + +uint16_t spif_spsn_s25flxx_read_id(const spi_flash_dev_t* flash) +{ + wb_spi_slave_select(flash->bus); + uint32_t command = S25FLXX_CMD_READID << 24; + wb_spi_transact_man_ss(flash->bus, WRITE, &command, NULL, 32); + uint16_t id = 0; + wb_spi_transact_man_ss(flash->bus, WRITE_READ, NULL, &id, 16); + wb_spi_slave_deselect(flash->bus); + return id; +} + +void spif_spsn_s25flxx_read(const spi_flash_dev_t* flash, uint32_t offset, void *buf, uint32_t num_bytes) +{ + //We explicitly control the slave select here, so that we can + //do the entire read operation as a single transaction from + //device's point of view. (The most our SPI peripheral can transfer + //in a single shot is 16 bytes.) + + //Do the 5 byte instruction tranfer: + //FAST_READ_CMD, ADDR2, ADDR1, ADDR0, DUMMY (0) + uint8_t read_cmd[5]; + read_cmd[4] = S25FLXX_CMD_FAST_READ; + *((uint32_t*)(read_cmd + 3)) = (offset << 8); + + wb_spi_slave_select(flash->bus); + wb_spi_transact_man_ss(flash->bus, WRITE_READ, read_cmd, NULL, 5*8); + + //Read up to 4 bytes at a time until done + uint8_t data_sw[16], data[16]; + size_t xact_size = 16; + unsigned char *bytes = (unsigned char *) buf; + for (size_t i = 0; i < num_bytes; i += 16) { + if (xact_size > num_bytes - i) xact_size = num_bytes - i; + wb_spi_transact_man_ss(flash->bus, WRITE_READ, NULL, data_sw, xact_size*8); + for (size_t k = 0; k < 4; k++) { //Fix word level significance + ((uint32_t*)data)[k] = ((uint32_t*)data_sw)[3-k]; + } + for (size_t j = 0; j < xact_size; j++) { + *bytes = data[j]; + bytes++; + } + } + wb_spi_slave_deselect(flash->bus); +} + +bool spif_spsn_s25flxx_erase_sector_dispatch(const spi_flash_dev_t* flash, uint32_t offset) +{ + //Sanity check sector size + if (offset % flash->sector_size) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_erase_sector: Erase offset not a multiple of sector size."); + return false; + } + + if (!_spif_wait_ready(flash, S25FLXX_SECTOR_ERASE_TIME_MS)) { + UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_erase_sector: Timeout. Sector at 0x%X was not ready for erase.", offset); + return false; + } + _spi_flash_set_write_enabled(flash, true); + + //Send sector erase command + uint32_t command = (S25FLXX_CMD_SE << 24) | (offset & 0x00FFFFFF); + wb_spi_transact(flash->bus, WRITE_READ, &command, NULL, 32); + + return true; +} + +bool spif_spsn_s25flxx_erase_sector_commit(const spi_flash_dev_t* flash, uint32_t offset) +{ + //Poll status until write done + uint8_t phy_sector_count = (offset < S25FLXX_LARGE_SECTOR_BASE) ? S25FLXX_SMALL_SECTORS_PER_LOGICAL : 1; + bool status = false; + for (uint8_t i = 0; i < phy_sector_count && !status; i++) { + status = _spif_wait_ready(flash, S25FLXX_SECTOR_ERASE_TIME_MS); + } + if (!status) { + UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_erase_sector_commit: Timeout. Sector at 0x%X did not finish erasing in time.", offset); + } + _spi_flash_set_write_enabled(flash, false); + return status; +} + +bool spif_spsn_s25flxx_write_page_dispatch(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes) +{ + if (num_bytes == 0 || num_bytes > flash->page_size) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_write_page: Invalid size. Must be > 0 and <= Page Size."); + return false; + } + if (num_bytes > (flash->sector_size * flash->num_sectors)) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_write_page: Cannot write past flash boundary."); + return false; + } + + //Wait until ready and enable write enabled + if (!_spif_wait_ready(flash, S25FLXX_PAGE_WRITE_TIME_MS)) { + UHD_FW_TRACE_FSTR(ERROR, "spif_spsn_s25flxx_write_page: Timeout. Page at 0x%X was not ready for write.", offset); + return false; + } + _spi_flash_set_write_enabled(flash, true); + + //We explicitly control the slave select here, so that we can + //do the entire read operation as a single transaction from + //device's point of view. (The most our SPI peripheral can transfer + //in a single shot is 16 bytes.) + + //Do the 4 byte instruction tranfer: + //PP_CMD, ADDR2, ADDR1, ADDR0 + uint32_t write_cmd = (S25FLXX_CMD_PP << 24) | (offset & 0x00FFFFFF); + + wb_spi_slave_select(flash->bus); + wb_spi_transact_man_ss(flash->bus, WRITE, &write_cmd, NULL, 32); + + //Write the page 16 bytes at a time. + uint8_t bytes_sw[16]; + uint8_t* bytes = (uint8_t*) buf; + for (int32_t bytes_left = num_bytes; bytes_left > 0; bytes_left -= 16) { + const uint32_t xact_size = (bytes_left < 16) ? bytes_left : 16; + for (size_t k = 0; k < 4; k++) { //Fix word level significance + ((uint32_t*)bytes_sw)[k] = ((uint32_t*)bytes)[3-k]; + } + wb_spi_transact_man_ss(flash->bus, WRITE, bytes_sw, NULL, xact_size * 8); + bytes += xact_size; + } + wb_spi_slave_deselect(flash->bus); + + return true; +} + +bool spif_spsn_s25flxx_write_page_commit(const spi_flash_dev_t* flash, uint32_t offset, const void *buf, uint32_t num_bytes) +{ + //Wait until write done + if (!_spif_wait_ready(flash, S25FLXX_PAGE_WRITE_TIME_MS)) { + UHD_FW_TRACE(ERROR, "spif_spsn_s25flxx_commit_write: Timeout. Page did not finish writing in time."); + return false; + } + _spi_flash_set_write_enabled(flash, false); + return true; +} + +bool spif_spsn_s25flxx_device_busy(const spi_flash_dev_t* flash) +{ + return (_spif_read_status(flash) & S25FLXX_STATUS_WIP); +} + diff --git a/firmware/usrp3/lib/fw_comm_protocol.c b/firmware/usrp3/lib/fw_comm_protocol.c new file mode 100644 index 000000000..0cc931a76 --- /dev/null +++ b/firmware/usrp3/lib/fw_comm_protocol.c @@ -0,0 +1,105 @@ +// +// Copyright 2014 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 "../../../host/lib/usrp/common/fw_comm_protocol.h" + +#include <trace.h> +#include <string.h> //memcmp + +bool process_fw_comm_protocol_pkt( + const fw_comm_pkt_t* request, + fw_comm_pkt_t* response, + uint8_t product_id, + uint32_t iface_id, + poke32_func poke_callback, + peek32_func peek_callback) +{ + bool send_response = false; + + uint16_t signature = request->id; + uint8_t version = FW_COMM_GET_PROTOCOL_VER(request->id); + uint8_t product = FW_COMM_GET_PRODUCT_ID(request->id); + if (signature == FW_COMM_PROTOCOL_SIGNATURE && //Verify protocol + version <= FW_COMM_PROTOCOL_VERSION && //Verify protocol version (older versions supported) + product == product_id) //Verify device + { + //Request is valid. Copy it into the reply. + memcpy(response, request, sizeof(fw_comm_pkt_t)); + + //Start assuming no error + response->flags &= ~FW_COMM_FLAGS_ERROR_MASK; + + //Otherwise, run the command set by the flags + switch (request->flags & FW_COMM_FLAGS_CMD_MASK) { + case FW_COMM_CMD_ECHO: { + UHD_FW_TRACE(DEBUG, "fw_comm_protocol::echo()"); + response->data_words = 1; + response->data[0] = iface_id; + } break; + + case FW_COMM_CMD_POKE32: { + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::poke32(0x%x)=0x%x", + request->addr,*(request->data)); + poke_callback(request->addr, *(request->data)); + } break; + + case FW_COMM_CMD_PEEK32: { + *(response->data) = peek_callback(request->addr); + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::peek32(0x%x)=0x%x", + request->addr,*(response->data)); + } break; + + case FW_COMM_CMD_BLOCK_POKE32: { + if (request->data_words > FW_COMM_MAX_DATA_WORDS) { + response->flags |= FW_COMM_ERR_SIZE_ERROR; + response->data_words = FW_COMM_MAX_DATA_WORDS; + } else { + response->data_words = request->data_words; + } + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::block_poke32(0x%x,%d)",request->addr,response->data_words); + for (uint32_t i = 0; i < response->data_words; i++) { + poke_callback(request->addr + (i * sizeof(uint32_t)), request->data[i]); + } + } break; + + case FW_COMM_CMD_BLOCK_PEEK32: { + if (request->data_words > FW_COMM_MAX_DATA_WORDS) { + response->flags |= FW_COMM_ERR_SIZE_ERROR; + response->data_words = FW_COMM_MAX_DATA_WORDS; + } else { + response->data_words = request->data_words; + } + for (uint32_t i = 0; i < response->data_words; i++) { + response->data[i] = peek_callback(request->addr + (i * sizeof(uint32_t))); + } + UHD_FW_TRACE_FSTR(DEBUG, "fw_comm_protocol::block_peek32(0x%x,%d)",request->addr,response->data_words); + } break; + + default: { + UHD_FW_TRACE(ERROR, "fw_comm_protocol got an invalid command."); + response->flags |= FW_COMM_ERR_CMD_ERROR; + } + } + + //Send a reply if ack requested + send_response = (request->flags & FW_COMM_FLAGS_ACK); + } else { //Size, protocol, product check failed + UHD_FW_TRACE(WARN, "fw_comm_protocol ignored an unknown request."); + send_response = false; + } + return send_response; +} diff --git a/firmware/usrp3/lib/mdelay.c b/firmware/usrp3/lib/mdelay.c deleted file mode 100644 index 6d2742206..000000000 --- a/firmware/usrp3/lib/mdelay.c +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- c -*- */ -/* - * Copyright 2007 Free Software Foundation, Inc. - * Copyright 2009 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 "mdelay.h" -#include "wb_utils.h" -#include "printf.h" -#include <stdint.h> -//IJB FIXME. -#include "../x300/x300_defs.h" - -void mdelay(int ms){ - for(int i = 0; i < ms; i++){ - static const uint32_t num_ticks = CPU_CLOCK/1000; - const uint32_t ticks_begin = wb_peek32(SR_ADDR(RB0_BASE, RB_COUNTER)); - // printf("DEBUG: Counter is %d\n",ticks_begin); - while((wb_peek32(SR_ADDR(RB0_BASE, RB_COUNTER)) - ticks_begin) < num_ticks) { - /*NOP*/ - } - } -} diff --git a/firmware/usrp3/lib/wb_spi.c b/firmware/usrp3/lib/wb_spi.c new file mode 100644 index 000000000..04904feea --- /dev/null +++ b/firmware/usrp3/lib/wb_spi.c @@ -0,0 +1,206 @@ +/* + * Copyright 2014 Free Software Foundation, Inc. + * + * 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 <wb_spi.h> +#include <trace.h> + +typedef struct { + volatile uint32_t data0; + volatile uint32_t data1; + volatile uint32_t data2; + volatile uint32_t data3; + volatile uint32_t ctrl_status; + volatile uint32_t clkdiv; + volatile uint32_t slavesel; +} wb_spi_regs_t; + +#define WB_SPI_REGS(base) ((wb_spi_regs_t *) base) + +// Masks for different parts of CTRL reg +#define WB_SPI_CTRL_AUTO_SS (1 << 13) +#define WB_SPI_CTRL_IE (1 << 12) +#define WB_SPI_CTRL_LSB (1 << 11) +#define WB_SPI_CTRL_TXNEG (1 << 10) +#define WB_SPI_CTRL_RXNEG (1 << 9) +#define WB_SPI_CTRL_GO_BSY (1 << 8) +#define WB_SPI_CTRL_LENGTH(x) (x & 0x7F) + +static inline uint32_t _wb_spi_get_flags(const wb_spi_slave_t* slave) +{ + uint32_t flags = 0; + //If the SPI slave samples on the rising edge then shift + //data out on the falling edge. + if (slave->mosi_edge == RISING) flags |= WB_SPI_CTRL_TXNEG; + //If the SPI slave drives on the rising edge then shift + //data in on the falling edge. + if (slave->miso_edge == RISING) flags |= WB_SPI_CTRL_RXNEG; + if (slave->lsb_first) flags |= WB_SPI_CTRL_LSB; + return flags; +} + +static inline void _wait_for_xfer(const wb_spi_slave_t* slave) +{ + while (WB_SPI_REGS(slave->base)->ctrl_status & WB_SPI_CTRL_GO_BSY) { + /*NOP*/ + } +} + +void wb_spi_init(const wb_spi_slave_t* slave) +{ + WB_SPI_REGS(slave->base)->clkdiv = slave->clk_div; + WB_SPI_REGS(slave->base)->slavesel = 0; + + //Do a dummy transaction with no slave selected to prime the engine + uint32_t ctrl = WB_SPI_CTRL_LENGTH(8) | _wb_spi_get_flags(slave); + WB_SPI_REGS(slave->base)->ctrl_status = ctrl | WB_SPI_CTRL_GO_BSY; + _wait_for_xfer(slave); +} + +void _wb_spi_transact_buf( + const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode, + const void* mosi_buf, void* miso_buf, uint32_t length, + bool auto_slave_sel) +{ + if (length == 0) return; + + //Wait for previous transaction to finish + _wait_for_xfer(slave); + + //Write SPI data register(s) + if (mosi_buf) { + uint8_t* mosi_bytes = (uint8_t*) mosi_buf; + uint8_t bits_left = length; + for (uint32_t reg_index = 0; reg_index < 4; reg_index++) { + uint32_t word = 0; + if (bits_left < 32) { + if (bits_left <= 8) { + word = (uint32_t) mosi_bytes[0]; + } else if (bits_left <= 16) { + word = (((uint32_t) mosi_bytes[1]) << 0) | + (((uint32_t) mosi_bytes[0]) << 8); + } else if (bits_left <= 24) { + word = (((uint32_t) mosi_bytes[2]) << 0) | + (((uint32_t) mosi_bytes[1]) << 8) | + (((uint32_t) mosi_bytes[0]) << 16); + } else { + word = *((uint32_t*) mosi_bytes); + } + bits_left = 0; + } else { + word = *((uint32_t*) mosi_bytes); + mosi_bytes += 4; + bits_left -= 32; + } + + switch (reg_index) { + case 0: WB_SPI_REGS(slave->base)->data0 = word; break; + case 1: WB_SPI_REGS(slave->base)->data1 = word; break; + case 2: WB_SPI_REGS(slave->base)->data2 = word; break; + case 3: WB_SPI_REGS(slave->base)->data3 = word; break; + } + + if (bits_left == 0) break; + } + } + + //Compute flags for slave and write control register + uint32_t ctrl = WB_SPI_CTRL_LENGTH(length) | _wb_spi_get_flags(slave); + if (auto_slave_sel) ctrl |= WB_SPI_CTRL_AUTO_SS; + WB_SPI_REGS(slave->base)->ctrl_status = ctrl; + + // Tell it which SPI slave device to access + WB_SPI_REGS(slave->base)->slavesel = slave->slave_sel; + + //Go go go! + WB_SPI_REGS(slave->base)->ctrl_status = ctrl | WB_SPI_CTRL_GO_BSY; + + if (rw_mode == WRITE_READ) { + //Wait for SPI read operation to complete + _wait_for_xfer(slave); + + if (miso_buf) { + //Read SPI data registers + uint8_t* miso_bytes = (uint8_t*) miso_buf; + uint8_t bits_left = length; + for (uint32_t reg_index = 0; reg_index < 4; reg_index++) { + uint32_t word = 0; + switch (reg_index) { + case 0: word = WB_SPI_REGS(slave->base)->data0; break; + case 1: word = WB_SPI_REGS(slave->base)->data1; break; + case 2: word = WB_SPI_REGS(slave->base)->data2; break; + case 3: word = WB_SPI_REGS(slave->base)->data3; break; + } + + if (bits_left < 32) { + if (bits_left <= 8) { + miso_bytes[0] = word & 0xFF; + } else if (bits_left <= 16) { + miso_bytes[1] = word & 0xFF; + miso_bytes[0] = (word >> 8) & 0xFF; + } else if (bits_left <= 24) { + miso_bytes[2] = word & 0xFF; + miso_bytes[1] = (word >> 8) & 0xFF; + miso_bytes[0] = (word >> 16) & 0xFF; + } else { + *((uint32_t*) miso_bytes) = word; + } + bits_left = 0; + } else { + *((uint32_t*) miso_bytes) = word; + miso_bytes += 4; + bits_left -= 32; + } + + if (bits_left == 0) break; + } + } + } +} + +void wb_spi_transact( + const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode, + const void* mosi_buf, void* miso_buf, uint32_t length) +{ + return _wb_spi_transact_buf(slave, rw_mode, mosi_buf, miso_buf, length, true); +} + +void wb_spi_transact_man_ss( + const wb_spi_slave_t* slave, wb_spi_rw_mode_t rw_mode, + const void* mosi_buf, void* miso_buf, uint32_t length) +{ + return _wb_spi_transact_buf(slave, rw_mode, mosi_buf, miso_buf, length, false); +} + +void wb_spi_slave_select(const wb_spi_slave_t* slave) +{ + //Wait for previous transactions to finish + _wait_for_xfer(slave); + //Disable auto slave select + WB_SPI_REGS(slave->base)->ctrl_status = _wb_spi_get_flags(slave); + //Manually select slave + WB_SPI_REGS(slave->base)->slavesel = slave->slave_sel; +} + +void wb_spi_slave_deselect(const wb_spi_slave_t* slave) +{ + //Wait for previous transactions to finish + _wait_for_xfer(slave); + //Disable auto slave select + WB_SPI_REGS(slave->base)->ctrl_status = _wb_spi_get_flags(slave); + //Manually deselect slave + WB_SPI_REGS(slave->base)->slavesel = 0; +} diff --git a/firmware/usrp3/n230/CMakeLists.txt b/firmware/usrp3/n230/CMakeLists.txt new file mode 100644 index 000000000..6247477f0 --- /dev/null +++ b/firmware/usrp3/n230/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# Copyright 2010-2014,2016 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_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/../../host/lib/usrp/n230) + +list(APPEND n230_sources n230_eeprom.c n230_eth_handlers.c n230_init.c n230_main.c) + +######################################################################## +set(GEN_OUTPUTS_BIN_SIZE 0x7fff) + +add_executable(n230_main.elf ${n230_sources}) +target_link_libraries(n230_main.elf usrp3fw) +GEN_OUTPUTS(n230_main.elf n230) + +#INSTALL( +# FILES ${CMAKE_CURRENT_BINARY_DIR}/n230_main.bin +# DESTINATION share/uhd/images +# RENAME usrp_n230_fw.bin +#) diff --git a/firmware/usrp3/n230/n230_burner.py b/firmware/usrp3/n230/n230_burner.py new file mode 100755 index 000000000..7b9920de7 --- /dev/null +++ b/firmware/usrp3/n230/n230_burner.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python +# +# Copyright 2014 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/>. +# + +import optparse +import math +import socket +import struct +import os.path +import sys +from array import array + +######################################################################## +# constants +######################################################################## +N230_FLASH_COMM_UDP_PORT = 49154 +N230_FLASH_COMM_PAYLOAD_SIZE = 128 +N230_FLASH_COMM_SECTOR_SIZE = 65536 + +N230_FLASH_COMM_FLAGS_ACK = 0x00000001 +N230_FLASH_COMM_FLAGS_CMD_MASK = 0x00000FF0 +N230_FLASH_COMM_FLAGS_ERROR_MASK = 0xFF000000 + +N230_FLASH_COMM_CMD_READ_NV_DATA = 0x00000010 +N230_FLASH_COMM_CMD_WRITE_NV_DATA = 0x00000020 +N230_FLASH_COMM_CMD_READ_FPGA = 0x00000030 +N230_FLASH_COMM_CMD_WRITE_FPGA = 0x00000040 +N230_FLASH_COMM_CMD_ERASE_FPGA = 0x00000050 + +N230_FLASH_COMM_ERR_PKT_ERROR = 0x80000000 +N230_FLASH_COMM_ERR_CMD_ERROR = 0x40000000 +N230_FLASH_COMM_ERR_SIZE_ERROR = 0x20000000 + +N230_FLASH_COMM_SAFE_IMG_BASE = 0x000000 +N230_FLASH_COMM_PROD_IMG_BASE = 0x400000 +N230_FLASH_COMM_FPGA_IMG_MAX_SIZE = 0x400000 + +UDP_MAX_XFER_BYTES = 256 +UDP_TIMEOUT = 3 + +_seq = -1 +def next_seq(): + global _seq + _seq = _seq+1 + return _seq + +def seq(): + return _seq + +######################################################################## +# helper functions +######################################################################## + +short = struct.Struct('>H') +ulong = struct.Struct('>I') + +def unpack_flash_transaction(buf): + (flags, seqno, offset, size) = struct.unpack_from('!LLLL', buf) + check_error(flags) + if (seqno != seq()): + raise Exception("The flash transaction operation returned an incorrect sequence number") + data = bytes() + for i in xrange(16, len(buf), 1): + data += buf[i] + return (flags, offset, size, data) + +def pack_flash_transaction(flags, offset, size, data): + buf = bytes() + buf = struct.pack('!LLLL', flags, next_seq(), offset, size) + for i in range(N230_FLASH_COMM_PAYLOAD_SIZE): + if (i < size): + buf += struct.pack('!B', data[i]) + else: + buf += struct.pack('!B', 0) + return buf + +def check_error(flags): + if flags & N230_FLASH_COMM_ERR_PKT_ERROR == N230_FLASH_COMM_ERR_PKT_ERROR: + raise Exception("The flash transaction operation returned a packet error") + if flags & N230_FLASH_COMM_ERR_CMD_ERROR == N230_FLASH_COMM_ERR_CMD_ERROR: + raise Exception("The flash transaction operation returned a command error") + if flags & N230_FLASH_COMM_ERR_SIZE_ERROR == N230_FLASH_COMM_ERR_SIZE_ERROR: + raise Exception("The flash transaction operation returned a size error") + +def chunkify(stuff, n): + return [stuff[i:i+n] for i in range(0, len(stuff), n)] + +def draw_progress_bar(percent, bar_len = 32): + sys.stdout.write("\r") + progress = "" + for i in range(bar_len): + if i < int((bar_len * percent) / 100): + progress += "=" + else: + progress += "-" + sys.stdout.write("[%s] %d%%" % (progress, percent)) + sys.stdout.flush() + +######################################################################## +# Burner class, holds a socket and send/recv routines +######################################################################## +class ctrl_socket(object): + def __init__(self, addr): + self._safe_image = False + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.settimeout(UDP_TIMEOUT) + self._sock.connect((addr, N230_FLASH_COMM_UDP_PORT)) + self.set_callbacks(lambda *a: None, lambda *a: None) + + def set_safe_image(self, noprompt): + confirm_msg = ('----------------------------------------------------------------------\n' + 'WARNING!!! You are about to access the safe-image stored in the flash \n' + '----------------------------------------------------------------------\n' + 'Writing a non-functional image will brick the device.\n' + 'Are you sure you want to proceed?') + if not noprompt: + if raw_input("%s (y/N) " % confirm_msg).lower() == 'y': + self._safe_image = True + else: + print 'Aborted by user' + sys.exit(1) + else: + print '[WARNING] Operating on safe image without a prompt as requested' + self._safe_image = True + + def set_callbacks(self, progress_cb, status_cb): + self._progress_cb = progress_cb + self._status_cb = status_cb + + def send_and_recv(self, pkt): + self._sock.send(pkt) + return self._sock.recv(UDP_MAX_XFER_BYTES) + + def compute_offset(self, offset): + base = N230_FLASH_COMM_SAFE_IMG_BASE if (self._safe_image) else N230_FLASH_COMM_PROD_IMG_BASE + return base + offset + + def burn_fpga_to_flash(self, bitfile_path, noprompt): + print '[BURN] Reading ' + bitfile_path + '...' + with open(bitfile_path, 'rb') as bitfile: + header = file_bytes = bitfile.read() + if (self._safe_image != self.parse_bitfile_header(file_bytes)['safe-image']): + confirm_msg = ('----------------------------------------------------------------------\n' + 'WARNING!!! You are about to burn a safe image into a production slot \n' + ' or a production image into a safe slot. \n' + '----------------------------------------------------------------------\n' + 'This is dangerous and can cause the device to boot incorrectly.\n' + 'Are you sure you want to proceed?') + if not noprompt: + if raw_input("%s (y/N) " % confirm_msg).lower() != 'y': + print '[BURN] Aborted by user' + return + else: + print '[WARNING] Burning image to the wrong slot without a prompt as requested' + + print '[BURN] Writing to flash...' + pkt_chunks = chunkify(file_bytes, N230_FLASH_COMM_PAYLOAD_SIZE) + offset = 0 + for pkt_data in pkt_chunks: + pkt_data = array("B", pkt_data) + size = N230_FLASH_COMM_PAYLOAD_SIZE if (len(pkt_data) >= N230_FLASH_COMM_PAYLOAD_SIZE) else len(pkt_data) + #Erase sector + if (offset % N230_FLASH_COMM_SECTOR_SIZE == 0): + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, pkt_data) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + #Write data + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, pkt_data) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + #Increment + offset += N230_FLASH_COMM_PAYLOAD_SIZE + draw_progress_bar((((offset/N230_FLASH_COMM_PAYLOAD_SIZE)+1)*100)/len(pkt_chunks)) + print('\n[BURN] DONE') + + def parse_bitfile_header(self, header_bytes): + xil_header = dict() + n230_header = dict() + n230_header['valid'] = False + ptr = 0 + #Field 1 + if short.unpack(header_bytes[ptr:ptr+2])[0] == 9 and ulong.unpack(header_bytes[ptr+2:ptr+6])[0] == 0x0ff00ff0: + #Headers + ptr += short.unpack(header_bytes[ptr:ptr+2])[0] + 2 + ptr += short.unpack(header_bytes[ptr:ptr+2])[0] + 1 + #Fields a-d + for keynum in range(0, 4): + key = header_bytes[ptr] + ptr += 1 + val_len = short.unpack(header_bytes[ptr:ptr+2])[0] + ptr += 2 + val = header_bytes[ptr:ptr+val_len] + ptr += val_len + xil_header[key] = val + #Field e + ptr += 1 + length = ulong.unpack(header_bytes[ptr:ptr+4])[0] + xil_header['bl'] = length #Bitstream length + ptr += 4 + xil_header['hl'] = ptr #Header lengt + + #Map Xilinx header field to N230 specific ones + if xil_header and xil_header['a'].split(';')[0] == 'n230': + n230_header['valid'] = True + n230_header['user-id'] = int(xil_header['a'].split(';')[1].split('=')[1], 16) + n230_header['safe-image'] = (n230_header['user-id'] >> 16 == 0x5AFE) + n230_header['product'] = xil_header['b'] + n230_header['timestamp'] = xil_header['c'] + ' ' + xil_header['d'] + n230_header['filesize'] = xil_header['hl'] + xil_header['bl'] + return n230_header + + def read_bitfile_header_from_flash(self): + max_header_size = 1024 #Should be enough + header_bytes = bytes() + for offset in range(0, max_header_size, N230_FLASH_COMM_PAYLOAD_SIZE): + #Read data + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_READ_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), N230_FLASH_COMM_PAYLOAD_SIZE, [0]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + header_bytes += data + return self.parse_bitfile_header(header_bytes) + + def extract_fpga_from_flash(self, bitfile_path): + header = self.read_bitfile_header_from_flash(); + if not header['valid']: + raise Exception("Could not detect a vaild Xilinx .bit burned into the flash") + max_offset = header['filesize'] + print '[EXTRACT] Writing ' + bitfile_path + '...' + with open(bitfile_path, 'wb') as bitfile: + for i in range(0, int(math.ceil(float(max_offset)/N230_FLASH_COMM_PAYLOAD_SIZE))): + offset = i * N230_FLASH_COMM_PAYLOAD_SIZE + size = N230_FLASH_COMM_PAYLOAD_SIZE if (max_offset - offset >= N230_FLASH_COMM_PAYLOAD_SIZE) else (max_offset - offset) + #Read data + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_READ_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, [0]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + bitfile.write(data[:size]) + draw_progress_bar(((offset*100)/max_offset) + 1) + print('\n[EXTRACT] DONE') + + def erase_fpga_from_flash(self): + print '[ERASE] Erasing image from flash...' + for offset in range(0, N230_FLASH_COMM_FPGA_IMG_MAX_SIZE, N230_FLASH_COMM_SECTOR_SIZE): + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA + out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), N230_FLASH_COMM_PAYLOAD_SIZE, [0]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + draw_progress_bar(((offset+N230_FLASH_COMM_SECTOR_SIZE)*100)/N230_FLASH_COMM_FPGA_IMG_MAX_SIZE) + print('\n[ERASE] DONE') + + def wipe_user_data(self, noprompt): + confirm_msg = ('-------------------------------------------------------------------\n' + 'WARNING!!! You are about to erase all the user data from the flash \n' + '-------------------------------------------------------------------\n' + 'This will cause the device to lose the following:\n' + ' * IP Address (Will default to 192.168.10.2)\n' + ' * Subnet Mask (Will default to 255.255.255.2)\n' + ' * MAC Address\n' + ' * Serial Number\n' + ' * Hardware Revision\n' + ' * ...and other identification info\n' + 'Are you sure you want to proceed?') + if not noprompt: + if raw_input("%s (y/N) " % confirm_msg).lower() == 'y': + wipe_ok = True + else: + print '[WIPE] Aborted by user' + wipe_ok = False + else: + print '[WARNING] Wiping user data without prompt a as requested' + wipe_ok = True + + if wipe_ok: + print '[WIPE] Erasing all user data from flash...' + flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_NV_DATA + out_pkt = pack_flash_transaction(flags, 0, N230_FLASH_COMM_PAYLOAD_SIZE, [0xFF]*N230_FLASH_COMM_PAYLOAD_SIZE) + (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt)) + print('[WIPE] DONE. Please power-cycle the device.') + + def print_status(self): + header = self.read_bitfile_header_from_flash(); + if header['valid']: + print('[STATUS] Detected a valid .bit header in the flash (Product = %s, Datestamp = %s%s)' % \ + (header['product'], header['timestamp'], ', Safe-Image' if header['safe-image'] else '')) + else: + print('[STATUS] No .bit header detected. Either the flash is uninitialized or the image is corrupt.') + + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--addr", type="string", help="N230 device address", default='') + parser.add_option("--status", action="store_true", help="Print out the status of the burned image", default=False) + parser.add_option("--erase", action="store_true", help="Erase FPGA bitstream from flash", default=False) + parser.add_option("--burn", type="string", help="Path to FPGA bitstream (.bit) to burn to flash", default=None) + parser.add_option("--extract", type="string", help="Destination bitfile to dump contents of the extracted image", default=None) + parser.add_option("--safe_image", action="store_true", help="Operate on the safe image. WARNING: This could be dangerous", default=False) + parser.add_option("--wipe_user_data", action="store_true", help="Erase all user data like IP, MAC, S/N, etc from flash", default=False) + parser.add_option("--no_prompt", action="store_true", help="Suppress all warning prompts", default=False) + (options, args) = parser.parse_args() + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + + if not options.addr: raise Exception('No address specified') + + ctrl_sock = ctrl_socket(addr=options.addr) + + # Initialize safe image selector first + if options.safe_image: + ctrl_sock.set_safe_image(options.no_prompt) + + if options.status: + ctrl_sock.print_status() + + # Order of operations: + # 1. Extract (if specified) + # 2. Erase (if specified) + # 3. Burn (if specified) + + if options.extract is not None: + file_path = options.extract + ctrl_sock.print_status() + ctrl_sock.extract_fpga_from_flash(file_path) + + if options.erase: + ctrl_sock.erase_fpga_from_flash() + ctrl_sock.print_status() + + if options.burn is not None: + file_path = options.burn + extension = os.path.splitext(file_path)[1] + if (extension.lower() == '.bit'): + ctrl_sock.burn_fpga_to_flash(file_path, options.no_prompt) + ctrl_sock.print_status() + else: + raise Exception("Unsupported FPGA bitfile format. You must use a .bit file.") + + if options.wipe_user_data: + ctrl_sock.wipe_user_data(options.no_prompt) diff --git a/firmware/usrp3/n230/n230_debug.py b/firmware/usrp3/n230/n230_debug.py new file mode 100755 index 000000000..f9ff64ab7 --- /dev/null +++ b/firmware/usrp3/n230/n230_debug.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 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/>. +# + +import optparse +import math +import socket +import struct +import array +import os.path +import sys +import time +try: + import fcntl + N230_DEVICE_DISCOVERY_AVAILABLE = True +except: + N230_DEVICE_DISCOVERY_AVAILABLE = False + +######################################################################## +# constants +######################################################################## +N230_FW_COMMS_UDP_PORT = 49152 +N230_FW_COMMS_MAX_DATA_WORDS = 16 + +N230_FW_COMMS_FLAGS_ACK = 0x00000001 +N230_FW_COMMS_FLAGS_ERROR_MASK = 0xF0000000 +N230_FW_COMMS_FLAGS_CMD_MASK = 0x000000F0 + +N230_FW_COMMS_CMD_ECHO = 0x00000000 +N230_FW_COMMS_CMD_POKE32 = 0x00000010 +N230_FW_COMMS_CMD_PEEK32 = 0x00000020 +N230_FW_COMMS_CMD_BLOCK_POKE32 = 0x00000030 +N230_FW_COMMS_CMD_BLOCK_PEEK32 = 0x00000040 + +N230_FW_COMMS_ERR_PKT_ERROR = 0x80000000 +N230_FW_COMMS_ERR_CMD_ERROR = 0x40000000 +N230_FW_COMMS_ERR_SIZE_ERROR = 0x20000000 + +N230_FW_COMMS_ID = 0x0001ACE3 + +N230_FW_LOADER_ADDR = 0xfa00 +N230_FW_LOADER_DATA = 0xfa04 +N230_FW_LOADER_NUM_WORDS = 8192 +N230_FW_LOADER_BOOT_DONE_ADDR = 0xA004 +N230_FW_LOADER_BOOT_TIMEOUT = 5 + +N230_JESD204_TEST = 0xA014 +N230_FPGA_HASH_ADDR = 0xA010 +N230_FW_HASH_ADDR = 0x10004 +N230_ICAP_ADDR = 0xF800 +#ICAP_DUMMY_WORD = 0xFFFFFFFF +#ICAP_SYNC_WORD = 0xAA995566 +#ICAP_TYPE1_NOP = 0x20000000 +#ICAP_WRITE_WBSTAR = 0x30020001 +#ICAP_WBSTAR_ADDR = 0x00000000 +#ICAP_WRITE_CMD = 0x30008001 +#ICAP_IPROG_CMD = 0x0000000F +# Bit reversed values per Xilinx UG470 - Bits reversed within bytes. +ICAP_DUMMY_WORD = 0xFFFFFFFF +ICAP_SYNC_WORD = 0x5599AA66 +ICAP_TYPE1_NOP = 0x04000000 +ICAP_WRITE_WBSTAR = 0x0C400080 +ICAP_WBSTAR_ADDR = 0x00000000 +ICAP_WRITE_CMD = 0x0C000180 +ICAP_IPROG_CMD = 0x000000F0 + + +UDP_MAX_XFER_BYTES = 256 +UDP_TIMEOUT = 3 +FPGA_LOAD_TIMEOUT = 10 + +_seq = -1 +def seq(): + global _seq + _seq = _seq+1 + return _seq + +######################################################################## +# helper functions +######################################################################## + +def pack_fw_command(flags, seq, num_words, addr, data_arr): + if (num_words > N230_FW_COMMS_MAX_DATA_WORDS): + raise Exception("Data size too large. Firmware supports a max 16 words per block." % (addr)) + buf = bytes() + buf = struct.pack('!IIIII', N230_FW_COMMS_ID, flags, seq, num_words, addr) + for i in range(N230_FW_COMMS_MAX_DATA_WORDS): + if (i < num_words): + buf += struct.pack('!I', data_arr[i]) + else: + buf += struct.pack('!I', 0) + return buf + +def unpack_fw_command(buf, fmt=None): + (id, flags, seq, num_words, addr) = struct.unpack_from('!IIIII', buf) + fw_check_error(flags) + data = [] + if fmt is None: + fmt = 'I' + for i in xrange(20, len(buf), 4): + data.append(struct.unpack('!'+fmt, buf[i:i+4])[0]) + return (flags, seq, num_words, addr, data) + +def fw_check_error(flags): + if flags & N230_FW_COMMS_ERR_PKT_ERROR == N230_FW_COMMS_ERR_PKT_ERROR: + raise Exception("The fiwmware operation returned a packet error") + if flags & N230_FW_COMMS_ERR_CMD_ERROR == N230_FW_COMMS_ERR_CMD_ERROR: + raise Exception("The fiwmware operation returned a command error") + if flags & N230_FW_COMMS_ERR_SIZE_ERROR == N230_FW_COMMS_ERR_SIZE_ERROR: + raise Exception("The fiwmware operation returned a size error") + +def chunkify(stuff, n): + return [stuff[i:i+n] for i in range(0, len(stuff), n)] + +def draw_progress_bar(percent, bar_len = 32): + sys.stdout.write("\r") + progress = "" + for i in range(bar_len): + if i < int((bar_len * percent) / 100): + progress += "=" + else: + progress += "-" + sys.stdout.write("[%s] %d%%" % (progress, percent)) + sys.stdout.flush() + +######################################################################## +# Discovery class +######################################################################## +class discovery_socket(object): + def __init__(self): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self._sock.settimeout(0.250) + + def get_bcast_addrs(self): + max_possible = 128 # arbitrary. raise if needed. + num_bytes = max_possible * 32 + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array('B', '\0' * num_bytes) + outbytes = struct.unpack('iL', fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack('iL', num_bytes, names.buffer_info()[0]) + ))[0] + namestr = names.tostring() + lst = [] + for i in range(0, outbytes, 40): + name = namestr[i:i+16].split('\0', 1)[0] + ip = map(ord, namestr[i+20:i+24]) + mask = map(ord, fcntl.ioctl(s.fileno(), 0x891B, struct.pack('256s', name))[20:24]) + bcast = [] + for i in range(len(ip)): + bcast.append((ip[i] | (~mask[i])) & 0xFF) + if (name != 'lo'): + lst.append(str(bcast[0]) + '.' + str(bcast[1]) + '.' + str(bcast[2]) + '.' + str(bcast[3])) + return lst + + def discover(self): + addrs = [] + for bcast_addr in self.get_bcast_addrs(): + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_ECHO|N230_FW_COMMS_FLAGS_ACK, seq(), 0, 0, [0]) + self._sock.sendto(out_pkt, (bcast_addr, N230_FW_COMMS_UDP_PORT)) + while 1: + try: + (in_pkt, addr_pair) = self._sock.recvfrom(UDP_MAX_XFER_BYTES) + if len(in_pkt) < 20: + continue + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt) + addrs.append(addr_pair[0]) + except socket.error: + break + return addrs + + +######################################################################## +# Communications class, holds a socket and send/recv routine +######################################################################## +class ctrl_socket(object): + def __init__(self, addr, port): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.settimeout(UDP_TIMEOUT) + self._sock.connect((addr, port)) + self.set_callbacks(lambda *a: None, lambda *a: None) + self.peek(0) #Dummy read + + def set_callbacks(self, progress_cb, status_cb): + self._progress_cb = progress_cb + self._status_cb = status_cb + + def send(self, pkt): + self._sock.send(pkt) + + def recv(self): + return self._sock.recv(UDP_MAX_XFER_BYTES) + + def send_and_recv(self, pkt): + self.send(pkt) + return self.recv() + + def peek(self, peek_addr, fmt=None): + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 1, peek_addr, [0]) + in_pkt = self.send_and_recv(out_pkt) + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt, fmt) + return data[0] + + def peek64(self, peek_addr, fmt=None): + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_BLOCK_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 2, peek_addr, [0,0]) + in_pkt = self.send_and_recv(out_pkt) + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt, fmt) + return (data[0] | (data[1] << 32)) + + def poke(self, poke_addr, poke_data, ack=True): + ack_flag = N230_FW_COMMS_FLAGS_ACK if ack else 0 + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_POKE32|ack_flag, seq(), 1, poke_addr, [poke_data]) + self.send(out_pkt) + if ack: + in_pkt = self.recv() + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt) + + def live_load_firmware_bin(self, bin_path): + raise Exception("live_load_firmware_bin not implemented yet!") + + def live_load_firmware_coe(self, coe_path): + with open(coe_path, 'r') as coe_file: + print("Loading %s..." % coe_path) + coe_lines = [line.strip(',;\n ') for line in coe_file] + start_index = coe_lines.index("memory_initialization_vector=") + 1 + coe_words = coe_lines[start_index:] + if len(coe_words) != N230_FW_LOADER_NUM_WORDS: + raise Exception("invalid COE file. Must contain 8192 words!") + self.poke(N230_FW_LOADER_ADDR, 0) #Load start address + for i in range(0, len(coe_words)): + self.poke(N230_FW_LOADER_DATA, int(coe_words[i],16), (i%10==0) and (i<len(coe_words)-1)) + draw_progress_bar(((i+1)*100)/len(coe_words)) + print("\nRebooting...") + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_POKE32, seq(), 1, N230_FW_LOADER_BOOT_DONE_ADDR, [1]) + self._sock.send(out_pkt) + self._sock.settimeout(1) + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 1, 0, [0]) + for i in range(N230_FW_LOADER_BOOT_TIMEOUT): + try: + self._sock.send(out_pkt) + in_pkt = self._sock.recv(UDP_MAX_XFER_BYTES) + print("Firmware is alive!") + self._sock.settimeout(UDP_TIMEOUT) + return + except socket.error: + pass + print("Firmware boot FAILED!!!") + self._sock.settimeout(UDP_TIMEOUT) + + def read_hash(self): + fpga_hash = self.peek(N230_FPGA_HASH_ADDR) + fpga_status = "clean" if (fpga_hash & 0xf0000000 == 0x0) else "modified" + fw_hash = self.peek(N230_FW_HASH_ADDR) + fw_status = "clean" if (fw_hash & 0xf0000000 == 0x0) else "modified" + print("FPGA Version : %x (%s)" % (fpga_hash & 0xfffffff, fpga_status)) + print("Firmware Version : %x (%s)" % (fw_hash & 0xfffffff, fw_status)) + + def is_claimed(self): + claimed = self.peek(0x10008) + print("Claimed : %s") % ('YES' if claimed else 'NO') + + def reset_fpga(self): + print("Reseting USRP...") + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_DUMMY_WORD) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_SYNC_WORD) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WRITE_WBSTAR) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WBSTAR_ADDR) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WRITE_CMD) + ctrl_sock.poke(N230_ICAP_ADDR,ICAP_IPROG_CMD, False) + print("Waiting for FPGA to load...") + self._sock.settimeout(1) + out_pkt = pack_fw_command(N230_FW_COMMS_CMD_ECHO|N230_FW_COMMS_FLAGS_ACK, seq(), 1, 0, [0]) + for i in range(FPGA_LOAD_TIMEOUT): + try: + in_pkt = self.send_and_recv(out_pkt) + (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt) + print("FPGA loaded successfully.") + self._sock.settimeout(UDP_TIMEOUT) + return + except socket.error: + pass + print("FPGA load FAILED!!!") + self._sock.settimeout(UDP_TIMEOUT) + + def jesd204_test_connector(self): + print("Testing JESD204 connectors. Molex cable #79576-2102 must be connected") + ctrl_sock.poke(N230_JESD204_TEST,0x1) + while True: + jesd204_test_status = ctrl_sock.peek(N230_JESD204_TEST) + if (jesd204_test_status & 0x10000 == 0x10000): + break + ctrl_sock.poke(N230_JESD204_TEST,0x0) + if (jesd204_test_status & 0xFFFF != 0x0): + print("JESD204 loopback test Failed!: Returned status is %4x" % (jesd204_test_status & 0xFFFF)) + else: + print("JESD204 loopback test Passed.") + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--discover", action="store_true",help="Find all devices connected N230 devices", default=False) + parser.add_option("--addr", type="string", help="N230 device address", default='') + parser.add_option("--peek", type="int", help="Read from memory map", default=None) + parser.add_option("--poke", type="int", help="Write to memory map", default=None) + parser.add_option("--data", type="int", help="Data for poke", default=None) + parser.add_option("--fw", type="string", help="Path to FW image to load", default=None) + parser.add_option("--hash", action="store_true",help="Display FPGA git hash", default=False) + parser.add_option("--reset", action="store_true",help="Reset and Reload USRP FPGA.", default=False) + parser.add_option("--jesd204test", action="store_true",help="Test mini-SAS connectors with loopback cable..", default=False) + + (options, args) = parser.parse_args() + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + + if options.discover: + if N230_DEVICE_DISCOVERY_AVAILABLE: + disc_sock = discovery_socket() + for addr in disc_sock.discover(): + print '==== FOUND ' + addr + ' ====' + ctrl_sock = ctrl_socket(addr, N230_FW_COMMS_UDP_PORT) + ctrl_sock.read_hash() + ctrl_sock.is_claimed() + sys.exit() + else: + raise Exception('Discovery is only supported on Linux.') + + if not options.addr: + raise Exception('No address specified') + + ctrl_sock = ctrl_socket(options.addr, N230_FW_COMMS_UDP_PORT) + + if options.fw is not None: + file_path = options.fw + extension = os.path.splitext(file_path)[1] + if (extension.lower() == '.coe'): + ctrl_sock.live_load_firmware_coe(file_path) + elif (extension.lower() == '.bin'): + ctrl_sock.live_load_firmware_bin(file_path) + else: + raise Exception("Unsupported firmware file format") + + if options.hash: + ctrl_sock.read_hash() + + if options.peek is not None: + addr = options.peek + data = ctrl_sock.peek(addr) + print("PEEK[0x%x (%d)] => 0x%x (%d)" % (addr,addr,data,data)) + + if options.poke is not None and options.data is not None: + addr = options.poke + data = options.data + ctrl_sock.poke(addr,data) + print("POKE[0x%x (%d)] <= 0x%x (%d)" % (addr,addr,data,data)) + + if options.reset: + ctrl_sock.reset_fpga() + + if options.jesd204test: + ctrl_sock.jesd204_test_connector() diff --git a/firmware/usrp3/n230/n230_eeprom.c b/firmware/usrp3/n230/n230_eeprom.c new file mode 100644 index 000000000..8f756d41f --- /dev/null +++ b/firmware/usrp3/n230/n230_eeprom.c @@ -0,0 +1,196 @@ +// +// Copyright 2014 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 "../../../host/lib/usrp/n230/n230_eeprom.h" + +#include <trace.h> +#include <stddef.h> +#include <flash/spi_flash.h> +#include <flash/spif_spsn_s25flxx.h> +#include <string.h> //memcpy + +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" +#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h" + +static const wb_spi_slave_t flash_spi_slave = { + .base = (void*) 0xB000, + .slave_sel = 0x0001, + .clk_div = 4, //80MHz/4 = 20MHz + .mosi_edge = RISING, + .miso_edge = FALLING, + .lsb_first = false +}; + +static const spi_flash_dev_t spi_flash_device = { + .page_size = 256, + .sector_size = 65536, + .num_sectors = 254, + .bus = &flash_spi_slave +}; + +/*********************************************************************** + * Non-volatile device data + **********************************************************************/ +#define N230_FLASH_NV_DATA_OFFSET 0x800000 + +//Default values in case the EEPROM is not read, corrupt +const n230_eeprom_map_t default_eeprom = { + .data_version_major = N230_EEPROM_VER_MAJOR, + .data_version_minor = N230_EEPROM_VER_MINOR, + .hw_revision = 0, + .hw_product = 0x01, + .gateway = N230_DEFAULT_GATEWAY, + .eth_info = { + { //eth0 + .mac_addr = N230_DEFAULT_ETH0_MAC, + .subnet = N230_DEFAULT_ETH0_MASK, + .ip_addr = N230_DEFAULT_ETH0_IP + }, + { //eth1 + .mac_addr = N230_DEFAULT_ETH1_MAC, + .subnet = N230_DEFAULT_ETH1_MASK, + .ip_addr = N230_DEFAULT_ETH1_IP + } + } +}; + +//EEPROM cache +static spi_flash_session_t flash_session = {.device = NULL}; +static n230_eeprom_map_t eeprom_cache; +static bool cache_dirty = true; + +bool read_n230_eeprom() +{ + bool status = false; + if (flash_session.device == NULL) { //Initialize flash session structure for the first time + wb_spi_init(spi_flash_device.bus); + spif_init(&flash_session, &spi_flash_device, spif_spsn_s25flxx_operations()); + } + spif_read_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET, &eeprom_cache, sizeof(n230_eeprom_map_t)); + + //Verify data format + status = (eeprom_cache.data_version_major == default_eeprom.data_version_major); + //Sanity communication info + if (eeprom_cache.eth_info[0].ip_addr == 0xFFFFFFFF) + eeprom_cache.eth_info[0].ip_addr = default_eeprom.eth_info[0].ip_addr; + if (eeprom_cache.eth_info[1].ip_addr == 0xFFFFFFFF) + eeprom_cache.eth_info[1].ip_addr = default_eeprom.eth_info[1].ip_addr; + if (eeprom_cache.eth_info[0].subnet == 0xFFFFFFFF) + eeprom_cache.eth_info[0].subnet = default_eeprom.eth_info[0].subnet; + if (eeprom_cache.eth_info[1].subnet == 0xFFFFFFFF) + eeprom_cache.eth_info[1].subnet = default_eeprom.eth_info[1].subnet; + + if (!status) { + UHD_FW_TRACE(WARN, "read_n230_eeprom: Initialized cache to the default map."); + memcpy(&eeprom_cache, &default_eeprom, sizeof(n230_eeprom_map_t)); + } + cache_dirty = !status; + return status; +} + +bool write_n230_eeprom() +{ + //Assumption: sizeof(n230_eeprom_map_t) <= flash_page_size + //This function would need to be reimplemented if this assumption is no longer true + if (sizeof(n230_eeprom_map_t) > flash_session.device->page_size) { + UHD_FW_TRACE(ERROR, "write_n230_eeprom: sizeof(n230_eeprom_map_t) > flash_page_size"); + return false; + } + + bool status = true; + if (cache_dirty) { + n230_eeprom_map_t device_eeprom; + spif_read_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET, &device_eeprom, sizeof(n230_eeprom_map_t)); + if (memcmp(&eeprom_cache, &device_eeprom, sizeof(n230_eeprom_map_t)) != 0) { + //Cache does not match read state. Write. + UHD_FW_TRACE(DEBUG, "write_n230_eeprom: Writing data to flash..."); + status = spif_erase_sector_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET); + if (status) { + status = spif_write_page_sync( + &flash_session, N230_FLASH_NV_DATA_OFFSET, &eeprom_cache, sizeof(n230_eeprom_map_t)); + } + if (!status) { + UHD_FW_TRACE(ERROR, "write_n230_eeprom: Operation failed!"); + } + cache_dirty = !status; + } else { + UHD_FW_TRACE(DEBUG, "write_n230_eeprom: No new data. Write skipped."); + //Cache matches read state. So mark as clean + cache_dirty = false; + } + } + return status; +} + +bool is_n230_eeprom_cache_dirty() +{ + return cache_dirty; +} + +n230_eeprom_map_t* get_n230_eeprom_map() +{ + cache_dirty = true; + return &eeprom_cache; +} + +const n230_eeprom_map_t* get_n230_const_eeprom_map() +{ + return &eeprom_cache; +} + +const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface) { + if (iface >= N230_NUM_ETH_PORTS) { + UHD_FW_TRACE_FSTR(ERROR, + "get_n230_ethernet_info called with iface=%d when there are only %d ports!!!", + iface, N230_NUM_ETH_PORTS); + } + return &(get_n230_const_eeprom_map()->eth_info[iface]); +} + + +/*********************************************************************** + * Storage for bootstrap FPGA Image + **********************************************************************/ +#define N230_FLASH_FPGA_IMAGE_OFFSET 0x000000 +#define N230_FLASH_FPGA_IMAGE_SIZE 0x400000 +#define N230_FLASH_NUM_FPGA_IMAGES 2 + +void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes) +{ + if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) { + UHD_FW_TRACE_FSTR(ERROR, "read_n230_fpga_image_page: Offset 0x%x out of bounds", offset); + } + spif_read_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset, buf, num_bytes); +} + +bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes) +{ + if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) { + UHD_FW_TRACE_FSTR(ERROR, "write_n230_fpga_image_page: Offset 0x%x out of bounds", offset); + return false; + } + return spif_write_page_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset, buf, num_bytes); +} + +bool erase_n230_fpga_image_sector(uint32_t offset) +{ + if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) { + UHD_FW_TRACE_FSTR(ERROR, "erase_n230_fpga_image_sector: Offset 0x%x out of bounds", offset); + return false; + } + return spif_erase_sector_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset); +} diff --git a/firmware/usrp3/n230/n230_eth_handlers.c b/firmware/usrp3/n230/n230_eth_handlers.c new file mode 100644 index 000000000..b291bb39f --- /dev/null +++ b/firmware/usrp3/n230/n230_eth_handlers.c @@ -0,0 +1,340 @@ +// +// Copyright 2014 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 "n230_eth_handlers.h" + +#include <wb_utils.h> +#include <string.h> //memcmp +#include <u3_net_stack.h> +#include <print_addrs.h> +#include <trace.h> +#include "../../../host/lib/usrp/common/fw_comm_protocol.h" +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" +#include "../n230/n230_fw_host_iface.h" +#include "../../../host/lib/usrp/n230/n230_eeprom.h" + +static n230_host_shared_mem_t* host_shared_mem_ptr; + +static const soft_reg_field_t LED_REG_FIELD_ETH_LINK2 = {.num_bits=1, .shift=0}; +static const soft_reg_field_t LED_REG_FIELD_ETH_LINK1 = {.num_bits=1, .shift=1}; +static const soft_reg_field_t LED_REG_FIELD_ETH_ACT2 = {.num_bits=1, .shift=2}; +static const soft_reg_field_t LED_REG_FIELD_ETH_ACT1 = {.num_bits=1, .shift=3}; + +/*********************************************************************** + * Handler for host <-> firmware communication + **********************************************************************/ + +static inline void n230_poke32(const uint32_t addr, const uint32_t data) +{ + if (addr >= N230_FW_HOST_SHMEM_RW_BASE_ADDR && addr <= N230_FW_HOST_SHMEM_MAX_ADDR) { + host_shared_mem_ptr->buff[(addr - N230_FW_HOST_SHMEM_BASE_ADDR)/sizeof(uint32_t)] = data; + } else if (addr < N230_FW_HOST_SHMEM_BASE_ADDR) { + wb_poke32(addr, data); + } +} + +static inline uint32_t n230_peek32(const uint32_t addr) +{ + if (addr >= N230_FW_HOST_SHMEM_BASE_ADDR && addr <= N230_FW_HOST_SHMEM_MAX_ADDR) { + return host_shared_mem_ptr->buff[(addr - N230_FW_HOST_SHMEM_BASE_ADDR)/sizeof(uint32_t)]; + } else if (addr < N230_FW_HOST_SHMEM_BASE_ADDR) { + return wb_peek32(addr); + } else { + return 0; + } +} + +void n230_handle_udp_fw_comms( + const uint8_t ethno, + const struct ip_addr *src, const struct ip_addr *dst, + const uint16_t src_port, const uint16_t dst_port, + const void *buff, const size_t num_bytes) +{ + if (buff == NULL) { + UHD_FW_TRACE(WARN, "n230_handle_udp_fw_comms got an ICMP_DUR"); + /* We got here from ICMP_DUR undeliverable packet */ + /* Future space for hooks to tear down streaming radios etc */ + } else if (num_bytes != sizeof(fw_comm_pkt_t)) { + UHD_FW_TRACE(WARN, "n230_handle_udp_fw_comms got an unknown request (bad size)."); + } else { + const fw_comm_pkt_t *request = (const fw_comm_pkt_t *)buff; + fw_comm_pkt_t response; + bool send_response = process_fw_comm_protocol_pkt( + request, &response, + N230_FW_PRODUCT_ID, + (uint32_t)ethno, + n230_poke32, n230_peek32); + + if (send_response) { + u3_net_stack_send_udp_pkt(ethno, src, dst_port, src_port, &response, sizeof(response)); + } + } +} + +void n230_register_udp_fw_comms_handler(n230_host_shared_mem_t* shared_mem_ptr) +{ + host_shared_mem_ptr = shared_mem_ptr; + u3_net_stack_register_udp_handler(N230_FW_COMMS_UDP_PORT, &n230_handle_udp_fw_comms); +} + + +/*********************************************************************** + * Handler for UDP framer program packets + **********************************************************************/ +void program_udp_framer( + const uint8_t ethno, + const uint32_t sid, + const struct ip_addr *dst_ip, + const uint16_t dst_port, + const uint16_t src_port) +{ + const eth_mac_addr_t *dst_mac = u3_net_stack_arp_cache_lookup(dst_ip); + const size_t vdest = (sid >> 16) & 0xff; + + uint32_t framer_base = + ((ethno == 1) ? SR_ZPU_ETHINT1 : SR_ZPU_ETHINT0) + SR_ZPU_ETHINT_FRAMER_BASE; + + //setup source framer + const eth_mac_addr_t *src_mac = u3_net_stack_get_mac_addr(ethno); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_MAC_HI), + (((uint32_t)src_mac->addr[0]) << 8) | (((uint32_t)src_mac->addr[1]) << 0)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_MAC_LO), + (((uint32_t)src_mac->addr[2]) << 24) | (((uint32_t)src_mac->addr[3]) << 16) | + (((uint32_t)src_mac->addr[4]) << 8) | (((uint32_t)src_mac->addr[5]) << 0)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_IP_ADDR), u3_net_stack_get_ip_addr(ethno)->addr); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_UDP_PORT), src_port); + + //setup destination framer + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_RAM_ADDR), vdest); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_IP_ADDR), dst_ip->addr); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_UDP_MAC), + (((uint32_t)dst_port) << 16) | + (((uint32_t)dst_mac->addr[0]) << 8) | (((uint32_t)dst_mac->addr[1]) << 0)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_MAC_LO), + (((uint32_t)dst_mac->addr[2]) << 24) | (((uint32_t)dst_mac->addr[3]) << 16) | + (((uint32_t)dst_mac->addr[4]) << 8) | (((uint32_t)dst_mac->addr[5]) << 0)); +} + +void handle_udp_prog_framer( + const uint8_t ethno, + const struct ip_addr *src, const struct ip_addr *dst, + const uint16_t src_port, const uint16_t dst_port, + const void *buff, const size_t num_bytes) +{ + if (buff == NULL) { + /* We got here from ICMP_DUR undeliverable packet */ + /* Future space for hooks to tear down streaming radios etc */ + } else { + const uint32_t sid = ((const uint32_t *)buff)[1]; + program_udp_framer(ethno, sid, src, src_port, dst_port); + UHD_FW_TRACE_FSTR(INFO, "Reprogrammed eth%d framer. Src=%s:%d, Dest=%s:%d", + ethno,ip_addr_to_str(src),src_port,ip_addr_to_str(dst),dst_port); + } +} + +void n230_register_udp_prog_framer() +{ + u3_net_stack_register_udp_handler(N230_FW_COMMS_CVITA_PORT, &handle_udp_prog_framer); +} + + +/*********************************************************************** + * Handler for flash programming interface over UDP + **********************************************************************/ + +void n230_handle_flash_prog_comms( + const uint8_t ethno, + const struct ip_addr *src, const struct ip_addr *dst, + const uint16_t src_port, const uint16_t dst_port, + const void *buff, const size_t num_bytes) +{ + if (buff == NULL) { + UHD_FW_TRACE(WARN, "n230_handle_flash_prog_comms got an ICMP_DUR"); + /* We got here from ICMP_DUR undeliverable packet */ + /* Future space for hooks to tear down streaming radios etc */ + } else if (num_bytes != sizeof(n230_flash_prog_t)) { + UHD_FW_TRACE(WARN, "n230_handle_flash_prog_comms got an unknown request (bad size)."); + } else { + const n230_flash_prog_t *request = (const n230_flash_prog_t *)buff; + n230_flash_prog_t response; + bool ack_requested = request->flags & N230_FLASH_COMM_FLAGS_ACK; + + //Request is valid. Copy it into the reply. + memcpy(&response, request, sizeof(n230_flash_prog_t)); + + switch (request->flags & N230_FLASH_COMM_FLAGS_CMD_MASK) { + case N230_FLASH_COMM_CMD_READ_NV_DATA: { + UHD_FW_TRACE(DEBUG, "n230_handle_flash_prog_comms::read_nv_data()"); + //Offset ignored because all non-volatile data fits in a packet. + if (is_n230_eeprom_cache_dirty()) { + read_n230_eeprom(); + } + //EEPROM cache is up-to-date. Copy it into the packet. + //Assumption: Cache size < 256. If this is no longer true, the offset field + //will have to be used. + memcpy(response.data, get_n230_const_eeprom_map(), sizeof(n230_eeprom_map_t)); + ack_requested = true; + } break; + + case N230_FLASH_COMM_CMD_WRITE_NV_DATA: { + UHD_FW_TRACE(DEBUG, "n230_handle_flash_prog_comms::write_nv_data()"); + //Offset ignored because all non-volatile data fits in a packet. + memcpy(get_n230_eeprom_map(), request->data, sizeof(n230_eeprom_map_t)); + if (!write_n230_eeprom()) { + response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR; + } + } break; + + case N230_FLASH_COMM_CMD_READ_FPGA: { + UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::read_fpga_page(offset=0x%x, size=%d)", + request->offset, request->size); + read_n230_fpga_image_page(request->offset, response.data, request->size); + ack_requested = true; + } break; + + case N230_FLASH_COMM_CMD_WRITE_FPGA: { + UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::write_fpga_page(offset=0x%x, size=%d)", + request->offset, request->size); + if (!write_n230_fpga_image_page(request->offset, request->data, request->size)) { + response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR; + } + } break; + + case N230_FLASH_COMM_CMD_ERASE_FPGA: { + UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::erase_fpga_sector(offset=0x%x)", + request->offset); + if (!erase_n230_fpga_image_sector(request->offset)) { + response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR; + } + } break; + + default :{ + UHD_FW_TRACE(ERROR, "n230_handle_flash_prog_comms got an invalid command."); + response.flags |= FW_COMM_ERR_CMD_ERROR; + } + } + //Send a reply if ack requested + if (ack_requested) { + u3_net_stack_send_udp_pkt(ethno, src, dst_port, src_port, &response, sizeof(response)); + } + } +} + +void n230_register_flash_comms_handler() +{ + u3_net_stack_register_udp_handler(N230_FW_COMMS_FLASH_PROG_PORT, &n230_handle_flash_prog_comms); +} + +/*********************************************************************** + * Handler for SFP state changes + **********************************************************************/ +#define SFPP_STATUS_MODABS_CHG (1 << 5) // Has MODABS changed since last read? +#define SFPP_STATUS_TXFAULT_CHG (1 << 4) // Has TXFAULT changed since last read? +#define SFPP_STATUS_RXLOS_CHG (1 << 3) // Has RXLOS changed since last read? +#define SFPP_STATUS_MODABS (1 << 2) // MODABS state +#define SFPP_STATUS_TXFAULT (1 << 1) // TXFAULT state +#define SFPP_STATUS_RXLOS (1 << 0) // RXLOS state + +static bool links_up[N230_MAX_NUM_ETH_PORTS] = {}; +static uint32_t packet_count[N230_MAX_NUM_ETH_PORTS] = {}; + +void n230_poll_sfp_status(const uint32_t eth, bool force, bool* state_updated) +{ + // Has MODDET/MODAbS changed since we last looked? + uint32_t rb = wb_peek32(SR_ADDR(WB_SBRB_BASE, (eth==0) ? RB_ZPU_SFP_STATUS0 : RB_ZPU_SFP_STATUS1)); + + if (rb & SFPP_STATUS_RXLOS_CHG) + UHD_FW_TRACE_FSTR(DEBUG, "eth%1d RXLOS changed state: %d", eth, (rb & SFPP_STATUS_RXLOS)); + if (rb & SFPP_STATUS_TXFAULT_CHG) + UHD_FW_TRACE_FSTR(DEBUG, "eth%1d TXFAULT changed state: %d", eth, ((rb & SFPP_STATUS_TXFAULT) >> 1)); + if (rb & SFPP_STATUS_MODABS_CHG) + UHD_FW_TRACE_FSTR(DEBUG, "eth%1d MODABS changed state: %d", eth, ((rb & SFPP_STATUS_MODABS) >> 2)); + + //update the link up status + if ((rb & SFPP_STATUS_RXLOS_CHG) || (rb & SFPP_STATUS_TXFAULT_CHG) || (rb & SFPP_STATUS_MODABS_CHG) || force) + { + const bool old_link_up = links_up[eth]; + const uint32_t status_reg_addr = (eth==0) ? RB_ZPU_SFP_STATUS0 : RB_ZPU_SFP_STATUS1; + + uint32_t sfpp_status = wb_peek32(SR_ADDR(WB_SBRB_BASE, status_reg_addr)) & 0xFFFF; + if ((sfpp_status & (SFPP_STATUS_RXLOS|SFPP_STATUS_TXFAULT|SFPP_STATUS_MODABS)) == 0) { + int8_t timeout = 100; + bool link_up = false; + do { + link_up = ((wb_peek32(SR_ADDR(WB_SBRB_BASE, status_reg_addr)) >> 16) & 0x1) != 0; + } while (!link_up && timeout-- > 0); + + links_up[eth] = link_up; + } else { + links_up[eth] = false; + } + + if (!old_link_up && links_up[eth]) u3_net_stack_send_arp_request(eth, u3_net_stack_get_ip_addr(eth)); + UHD_FW_TRACE_FSTR(INFO, "The link on eth port %u is %s", eth, links_up[eth]?"up":"down"); + if (rb & SFPP_STATUS_MODABS_CHG) { + // MODDET has changed state since last checked + if (rb & SFPP_STATUS_MODABS) { + // MODDET is high, module currently removed. + UHD_FW_TRACE_FSTR(INFO, "An SFP+ module has been removed from eth port %d.", eth); + } else { + // MODDET is low, module currently inserted. + // Return status. + UHD_FW_TRACE_FSTR(INFO, "A new SFP+ module has been inserted into eth port %d.", eth); + } + } + *state_updated = true; + } else { + *state_updated = false; + } +} + +void n230_update_link_act_state(soft_reg_t* led_reg) +{ + static bool first_poll = 1; + static uint32_t poll_cnt; + + bool activity[N230_MAX_NUM_ETH_PORTS] = {}; + for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) { + if (first_poll) { + links_up[i] = 0; + packet_count[i] = 0; + poll_cnt = 0; + } + + //Check SFP status and update links_up + bool link_state_from_sfp = false; + n230_poll_sfp_status(i, first_poll, &link_state_from_sfp); + + //Check packet counters less frequently to keep the LED on for a visible duration + uint32_t cnt = wb_peek32(SR_ADDR(WB_SBRB_BASE, (i==0)?RB_ZPU_ETH0_PKT_CNT:RB_ZPU_ETH1_PKT_CNT)); + activity[i] = (cnt != packet_count[i]); + packet_count[i] = cnt; + + //Update links_up if there is activity only if the SFP + //handler has not updated it + if (activity[i] && !link_state_from_sfp) links_up[i] = true; + } + + //TODO: Swap this when Ethernet port swap issues is fixed + soft_reg_write(led_reg, LED_REG_FIELD_ETH_LINK2, links_up[0]?1:0); + soft_reg_write(led_reg, LED_REG_FIELD_ETH_LINK1, links_up[1]?1:0); + soft_reg_write(led_reg, LED_REG_FIELD_ETH_ACT2, activity[0]?1:0); + soft_reg_write(led_reg, LED_REG_FIELD_ETH_ACT1, activity[1]?1:0); + + first_poll = 0; +} + diff --git a/firmware/usrp3/n230/n230_eth_handlers.h b/firmware/usrp3/n230/n230_eth_handlers.h new file mode 100644 index 000000000..67afbb246 --- /dev/null +++ b/firmware/usrp3/n230/n230_eth_handlers.h @@ -0,0 +1,48 @@ +// +// Copyright 2014 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_N230_ETH_HANDLERS_H +#define INCLUDED_N230_ETH_HANDLERS_H + +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> +#include <lwip/ip_addr.h> +#include <wb_soft_reg.h> +#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h" + +/*! + * Registrar for host firmware communications handler. + */ +void n230_register_udp_fw_comms_handler(n230_host_shared_mem_t* shared_mem_ptr); + +/*! + * Registrar for framer programmer handler. + */ +void n230_register_udp_prog_framer(); + +/*! + * Registrar for host firmware communications handler. + */ +void n230_register_flash_comms_handler(); + +/*! + * Handle SFP updates. + */ +void n230_update_link_act_state(soft_reg_t* led_reg); + +#endif /* INCLUDED_N230_ETH_HANDLERS_H */ diff --git a/firmware/usrp3/n230/n230_init.c b/firmware/usrp3/n230/n230_init.c new file mode 100644 index 000000000..14f5ebd77 --- /dev/null +++ b/firmware/usrp3/n230/n230_init.c @@ -0,0 +1,125 @@ +// +// Copyright 2014 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 <cron.h> +#include <printf.h> +#include <wb_utils.h> +#include <wb_uart.h> +#include <wb_i2c.h> +#include <wb_pkt_iface64.h> +#include <u3_net_stack.h> +#include <print_addrs.h> +#include <trace.h> +#include "../../../host/lib/usrp/n230/n230_eeprom.h" +#include "n230_init.h" +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" + +static wb_pkt_iface64_config_t pkt_config; + +static void putc(void *p, char c) +{ +//If FW_TRACE_LEVEL is defined, then the trace level is set +//to a non-zero number. Turn on the debug UART to enable tracing +#ifdef UHD_FW_TRACE_LEVEL + wb_uart_putc(WB_DBG_UART_BASE, c); +#endif +} + +static uint32_t get_counter_val() +{ + return wb_peek32(SR_ADDR(WB_SBRB_BASE, RB_ZPU_COUNTER)); +} + +void n230_init(void) +{ + //TODO: We may need to remove the debug UART before we release. + //Initialize the debug UART first. + wb_uart_init(WB_DBG_UART_BASE, CPU_CLOCK_FREQ/DBG_UART_BAUD); + init_printf(NULL, putc); + + //Now we can init the rest with prints + UHD_FW_TRACE_FSTR(INFO, "[ZPU Init Begin -- CPU CLOCK is %d MHz]", (CPU_CLOCK_FREQ/1000000)); + + //Initialize cron and the per millisecond cron job + UHD_FW_TRACE(INFO, "Initializing cron..."); + cron_init(get_counter_val, CPU_CLOCK_FREQ); + cron_job_init(PER_MILLISEC_CRON_JOBID, 1); + cron_job_init(PER_SECOND_CRON_JOBID, 1000); + + //Initialize rate for I2C cores + UHD_FW_TRACE(INFO, "Initializing I2C..."); + for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) { + wb_i2c_init((i==1)?WB_ETH1_I2C_BASE:WB_ETH0_I2C_BASE, CPU_CLOCK_FREQ); + } + + //Initialize eeprom + read_n230_eeprom(); + + UHD_FW_TRACE(INFO, "Initializing network stack..."); + init_network_stack(); +} + +void init_network_stack(void) +{ + //Hold Ethernet PHYs in reset + wb_poke32(SR_ADDR(WB_SBRB_BASE, SR_ZPU_SW_RST), SR_ZPU_SW_RST_PHY); + + //Initialize ethernet packet interface + pkt_config = wb_pkt_iface64_init(WB_PKT_RAM_BASE, WB_PKT_RAM_CTRL_OFFSET); + u3_net_stack_init(&pkt_config); + + //Initialize MACs + for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) { + init_ethernet_mac(i); + } + + //Pull Ethernet PHYs out of reset + wb_poke32(SR_ADDR(WB_SBRB_BASE, SR_ZPU_SW_RST), SR_ZPU_SW_RST_NONE); +} + +void init_ethernet_mac(uint32_t iface_num) +{ + UHD_FW_TRACE_FSTR(INFO, "Initializing eth%d...", iface_num); + + //Get interface info from the EEPROM (or defaults otherwise) + const n230_eth_eeprom_map_t* eth_eeprom_map = get_n230_ethernet_info(iface_num); + const eth_mac_addr_t *my_mac = (const eth_mac_addr_t *) &(eth_eeprom_map->mac_addr); + const struct ip_addr *my_ip = (const struct ip_addr *) &(eth_eeprom_map->ip_addr); + const struct ip_addr *subnet = (const struct ip_addr *) &(eth_eeprom_map->subnet); + + //Init software fields related to ethernet + u3_net_stack_init_eth(iface_num, my_mac, my_ip, subnet); + + uint32_t dispatcher_base = + ((iface_num == 1) ? SR_ZPU_ETHINT1 : SR_ZPU_ETHINT0) + SR_ZPU_ETHINT_DISPATCHER_BASE; + + //Program dispatcher + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 0), + (my_mac->addr[5] << 0) | (my_mac->addr[4] << 8) | (my_mac->addr[3] << 16) | (my_mac->addr[2] << 24)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 1), (my_mac->addr[1] << 0) | (my_mac->addr[0] << 8)); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 2), my_ip->addr); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 4), 0/*nofwd*/); + wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 5), (ICMP_IRQ << 8) | 0); //no fwd: type, code + + //DEBUG: Print initialized info + UHD_FW_TRACE_FSTR(INFO, "-- MAC%u: %s", iface_num, mac_addr_to_str(u3_net_stack_get_mac_addr(iface_num))); + UHD_FW_TRACE_FSTR(INFO, "-- IP%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_ip_addr(iface_num))); + UHD_FW_TRACE_FSTR(INFO, "-- SUBNET%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_subnet(iface_num))); + UHD_FW_TRACE_FSTR(INFO, "-- BCAST%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_bcast(iface_num))); +} + + diff --git a/firmware/usrp3/n230/n230_init.h b/firmware/usrp3/n230/n230_init.h new file mode 100644 index 000000000..e2231909e --- /dev/null +++ b/firmware/usrp3/n230/n230_init.h @@ -0,0 +1,28 @@ +// +// Copyright 2014 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_N230_INIT_H +#define INCLUDED_N230_INIT_H + +#include <stdint.h> +#include <stdbool.h> + +void n230_init(void); +void init_network_stack(void); +void init_ethernet_mac(uint32_t iface_num); + +#endif /* INCLUDED_B250_INIT_H */ diff --git a/firmware/usrp3/n230/n230_main.c b/firmware/usrp3/n230/n230_main.c new file mode 100644 index 000000000..a6c12e56d --- /dev/null +++ b/firmware/usrp3/n230/n230_main.c @@ -0,0 +1,113 @@ +// +// Copyright 2014 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 <cron.h> +#include <wb_soft_reg.h> +#include <u3_net_stack.h> +#include <trace.h> +#include "../../../host/lib/usrp/n230/n230_fw_defs.h" +#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h" +#include "n230_eth_handlers.h" +#include "n230_init.h" + +//The version hash should come from a cmake build variable +//If it doesn't then the build system does not support the feature +//so just default to 0xFFFFFFFF +#ifndef UHD_VERSION_HASH +#define UHD_VERSION_HASH 0xFFFFFFFF +#endif + +//TODO: This is just for initial debugging. +static soft_reg_t g_led_register; + +//Shared memory +static n230_host_shared_mem_t g_host_shared_mem; + +//Functions +static void n230_handle_claim(); + +/*********************************************************************** + * Main loop runs all the handlers + **********************************************************************/ +int main(void) +{ + //Initialize host shared mem + g_host_shared_mem.data.fw_compat_num = N230_FW_COMPAT_NUM; + g_host_shared_mem.data.fw_version_hash = UHD_VERSION_HASH; + + //Main initialization function + n230_init(); + + //Initialize UDP Handlers + n230_register_udp_fw_comms_handler(&g_host_shared_mem); + n230_register_udp_prog_framer(); + n230_register_flash_comms_handler(); + + initialize_writeonly_soft_reg(&g_led_register, SR_ADDR(WB_SBRB_BASE, SR_ZPU_LEDS)); + + uint32_t heart_beat = 0; + while(true) + { + //TODO: This is just for initial debugging. Once the firmware + //is somewhat stable we should delete this cron job + if (cron_job_run_due(PER_SECOND_CRON_JOBID)) { + //Everything in this block runs approx once per second + if (heart_beat % 10 == 0) { + UHD_FW_TRACE_FSTR(INFO, "0.1Hz Heartbeat (%u)", heart_beat); + } + heart_beat++; + } + + if (cron_job_run_due(PER_MILLISEC_CRON_JOBID)) { + //Everything in this block runs approx once per millisecond + n230_handle_claim(); + n230_update_link_act_state(&g_led_register); + } + + //run the network stack - poll and handle + u3_net_stack_handle_one(); + } + return 0; +} + +// Watchdog timer for claimer +static void n230_handle_claim() +{ + static uint32_t last_time = 0; + static size_t timeout = 0; + + if (g_host_shared_mem.data.claim_time == 0) { + //If time is 0 if the claim was forfeit + g_host_shared_mem.data.claim_status = 0; + } else if (last_time != g_host_shared_mem.data.claim_time) { + //If the time changes, reset timeout + g_host_shared_mem.data.claim_status = 1; + timeout = 0; + } else { + //Otherwise increment for timeout + timeout++; + } + + //Always stash the last seen time + last_time = g_host_shared_mem.data.claim_time; + + //Timeout logic + if (timeout > N230_CLAIMER_TIMEOUT_IN_MS) { + g_host_shared_mem.data.claim_time = 0; + } +} + diff --git a/firmware/usrp3/utils/git-hash.sh b/firmware/usrp3/utils/git-hash.sh new file mode 100755 index 000000000..ff7ae5ecb --- /dev/null +++ b/firmware/usrp3/utils/git-hash.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +if [[ $(command -v git) = "" ]]; then + short_hash="FFFFFFFF" +else + if (git diff --quiet); then + #Clean + short_hash="0$(git rev-parse --verify HEAD --short)" + else + #Dirty + short_hash="F$(git rev-parse --verify HEAD --short)" + fi +fi +echo ${short_hash^^}
\ No newline at end of file diff --git a/firmware/usrp3/x300/x300_debug.py b/firmware/usrp3/x300/x300_debug.py index c9bcbb138..c518ba4e0 100755 --- a/firmware/usrp3/x300/x300_debug.py +++ b/firmware/usrp3/x300/x300_debug.py @@ -25,12 +25,18 @@ import struct ######################################################################## # constants ######################################################################## -B250_FW_COMMS_UDP_PORT = 49152 +X300_FW_COMMS_UDP_PORT = 49152 -B250_FW_COMMS_FLAGS_ACK = 1 -B250_FW_COMMS_FLAGS_ERROR = 2 -B250_FW_COMMS_FLAGS_POKE32 = 4 -B250_FW_COMMS_FLAGS_PEEK32 = 8 +X300_FW_COMMS_FLAGS_ACK = 0x00000001 +X300_FW_COMMS_FLAGS_POKE32 = 0x00000010 +X300_FW_COMMS_FLAGS_PEEK32 = 0x00000020 + +X300_FW_COMMS_ERR_PKT_ERROR = 0x80000000 +X300_FW_COMMS_ERR_CMD_ERROR = 0x40000000 +X300_FW_COMMS_ERR_SIZE_ERROR = 0x20000000 + +X300_FW_COMMS_ID = 0x0000ACE3 +X300_FW_COMMS_MAX_DATA_WORDS = 16 #UDP_CTRL_PORT = 49183 UDP_MAX_XFER_BYTES = 1024 @@ -39,7 +45,7 @@ UDP_TIMEOUT = 3 #REG_ARGS_FMT = '!LLLLLB15x' #REG_IP_FMT = '!LLLL20x' -REG_PEEK_POKE_FMT = '!LLLL' +REG_PEEK_POKE_FMT = '!LLLLL' _seq = -1 @@ -52,12 +58,33 @@ def seq(): ######################################################################## # helper functions ######################################################################## - -def unpack_reg_peek_poke_fmt(s): - return struct.unpack(REG_PEEK_POKE_FMT,s) #(flags, seq, addr, data) +def fw_check_error(flags): + if flags & X300_FW_COMMS_ERR_PKT_ERROR == X300_FW_COMMS_ERR_PKT_ERROR: + raise Exception("The fiwmware operation returned a packet error") + if flags & X300_FW_COMMS_ERR_CMD_ERROR == X300_FW_COMMS_ERR_CMD_ERROR: + raise Exception("The fiwmware operation returned a command error") + if flags & X300_FW_COMMS_ERR_SIZE_ERROR == X300_FW_COMMS_ERR_SIZE_ERROR: + raise Exception("The fiwmware operation returned a size error") def pack_reg_peek_poke_fmt(flags, seq, addr, data): - return struct.pack(REG_PEEK_POKE_FMT, flags, seq, addr, data); + num_words = 1 + data_arr = [data] + buf = bytes() + buf = struct.pack('!LLLLL', X300_FW_COMMS_ID, flags, seq, num_words, addr) + for i in range(X300_FW_COMMS_MAX_DATA_WORDS): + if (i < num_words): + buf += struct.pack('!L', data_arr[i]) + else: + buf += struct.pack('!L', 0) + return buf + +def unpack_reg_peek_poke_fmt(buf): + (id, flags, seq, num_words, addr) = struct.unpack_from('!LLLLL', buf) + fw_check_error(flags) + data = [] + for i in xrange(20, len(buf), 4): + data.append(struct.unpack('!L', buf[i:i+4])[0]) + return (flags, seq, addr, data[0]) ######################################################################## # Burner class, holds a socket and send/recv routines @@ -66,7 +93,7 @@ class ctrl_socket(object): def __init__(self, addr): self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._sock.settimeout(UDP_TIMEOUT) - self._sock.connect((addr, B250_FW_COMMS_UDP_PORT)) + self._sock.connect((addr, X300_FW_COMMS_UDP_PORT)) self.set_callbacks(lambda *a: None, lambda *a: None) #self.init_update() #check that the device is there @@ -92,11 +119,10 @@ class ctrl_socket(object): for in_prt in range (0, 8): print("%s |" % ports[in_prt]), for out_prt in range (0, 8): - out_pkt = pack_reg_peek_poke_fmt(B250_FW_COMMS_FLAGS_PEEK32|B250_FW_COMMS_FLAGS_ACK, seq(), 0xA000+256+((in_prt*8+out_prt)*4), 0) + out_pkt = pack_reg_peek_poke_fmt(X300_FW_COMMS_FLAGS_PEEK32|X300_FW_COMMS_FLAGS_ACK, seq(), 0xA000+256+((in_prt*8+out_prt)*4), 0) in_pkt = self.send_and_recv(out_pkt) (flags, rxseq, addr, data) = unpack_reg_peek_poke_fmt(in_pkt) - if flags & B250_FW_COMMS_FLAGS_ERROR == B250_FW_COMMS_FLAGS_ERROR: - raise Exception("B250 peek returns error code") + fw_check_error(flags) print("%10d " % (data)), print print @@ -105,19 +131,17 @@ class ctrl_socket(object): def peek(self,peek_addr): - out_pkt = pack_reg_peek_poke_fmt(B250_FW_COMMS_FLAGS_PEEK32|B250_FW_COMMS_FLAGS_ACK, seq(), peek_addr, 0) + out_pkt = pack_reg_peek_poke_fmt(X300_FW_COMMS_FLAGS_PEEK32|X300_FW_COMMS_FLAGS_ACK, seq(), peek_addr, 0) in_pkt = self.send_and_recv(out_pkt) (flags, rxseq, addr, data) = unpack_reg_peek_poke_fmt(in_pkt) - if flags & B250_FW_COMMS_FLAGS_ERROR == B250_FW_COMMS_FLAGS_ERROR: - raise Exception("B250 peek of address %d returns error code" % (addr)) + fw_check_error(flags) print("PEEK of address %d(0x%x) reads %d(0x%x)" % (addr,addr,data,data)) def poke(self,poke_addr,poke_data): - out_pkt = pack_reg_peek_poke_fmt(B250_FW_COMMS_FLAGS_POKE32|B250_FW_COMMS_FLAGS_ACK, seq(), poke_addr, poke_data) + out_pkt = pack_reg_peek_poke_fmt(X300_FW_COMMS_FLAGS_POKE32|X300_FW_COMMS_FLAGS_ACK, seq(), poke_addr, poke_data) in_pkt = self.send_and_recv(out_pkt) (flags, rxseq, addr, data) = unpack_reg_peek_poke_fmt(in_pkt) - if flags & B250_FW_COMMS_FLAGS_ERROR == B250_FW_COMMS_FLAGS_ERROR: - raise Exception("B250 peek of address %d returns error code" % (addr)) + fw_check_error(flags) print("POKE of address %d(0x%x) with %d(0x%x)" % (poke_addr,poke_addr,poke_data,poke_data) ) @@ -126,12 +150,12 @@ class ctrl_socket(object): ######################################################################## def get_options(): parser = optparse.OptionParser() - parser.add_option("--addr", type="string", help="USRP-N2XX device address", default='') - parser.add_option("--list", action="store_true", help="list possible network devices", default=False) + parser.add_option("--addr", type="string", help="USRP-X300 device address", default='') + parser.add_option("--list", action="store_true", help="list possible network devices", default=False) parser.add_option("--peek", type="int", help="Read from memory map", default=None) parser.add_option("--poke", type="int", help="Write to memory map", default=None) parser.add_option("--data", type="int", help="Data for poke", default=None) - parser.add_option("--stats", action="store_true", help="Display SuperMIMO Network Stats", default=False) + parser.add_option("--stats", action="store_true", help="Display SuperMIMO Network Stats", default=False) (options, args) = parser.parse_args() return options diff --git a/firmware/usrp3/x300/x300_init.c b/firmware/usrp3/x300/x300_init.c index ef97412a2..97b20032b 100644 --- a/firmware/usrp3/x300/x300_init.c +++ b/firmware/usrp3/x300/x300_init.c @@ -1,7 +1,7 @@ #include "x300_init.h" #include "x300_defs.h" #include "ethernet.h" -#include "mdelay.h" +#include "cron.h" #include <wb_utils.h> #include <wb_uart.h> #include <wb_i2c.h> @@ -121,6 +121,11 @@ static void putc(void *p, char c) #endif } +static uint32_t get_counter_val() +{ + return wb_peek32(SR_ADDR(RB0_BASE, RB_COUNTER)); +} + void x300_init(void) { //first - uart @@ -136,6 +141,9 @@ void x300_init(void) UHD_FW_TRACE_FSTR(INFO, "-- FPGA Compat Number: %u.%u", (fpga_compat>>16), (fpga_compat&0xFFFF)); UHD_FW_TRACE_FSTR(INFO, "-- Clock Frequency: %u MHz", (CPU_CLOCK/1000000)); + //Initialize cron + cron_init(get_counter_val, CPU_CLOCK); + //i2c rate init wb_i2c_init(I2C0_BASE, CPU_CLOCK); wb_i2c_init(I2C1_BASE, CPU_CLOCK); @@ -163,7 +171,7 @@ void x300_init(void) } // For eth interfaces, initialize the PHY's - mdelay(100); + sleep_ms(100); ethernet_init(0); ethernet_init(1); } diff --git a/firmware/usrp3/x300/x300_main.c b/firmware/usrp3/x300/x300_main.c index 3b812a2c4..44ed10aa8 100644 --- a/firmware/usrp3/x300/x300_main.c +++ b/firmware/usrp3/x300/x300_main.c @@ -6,7 +6,6 @@ #include "xge_phy.h" #include "ethernet.h" #include "chinch.h" -#include "mdelay.h" #include <wb_utils.h> #include <wb_uart.h> diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index e8d4571c9..2be37bd81 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -31,6 +31,55 @@ ENABLE_TESTING() list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules) ######################################################################## +# Check Compiler Version +######################################################################## +# Full C++11 came with GCC 4.7. +SET(GCC_MIN_VERSION "4.8.0") +# for c++0x or c++11 support, require: +# Apple Clang >= 500 +# or +# Clang >= 3.3.0 +SET(CLANG_MIN_VERSION "3.3.0") +SET(APPLECLANG_MIN_VERSION "500") + +IF(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + IF(DEFINED CMAKE_CXX_COMPILER_VERSION) + IF(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS ${GCC_MIN_VERSION}) + MESSAGE(WARNING "\nThe compiler selected to build UHD (GCC version ${CMAKE_CXX_COMPILER_VERSION} : ${CMAKE_CXX_COMPILER}) is older than that officially supported (${GCC_MIN_VERSION} minimum). This build may or not work. We highly recommend using a more recent GCC version.") + ENDIF() + ELSE() + MESSAGE(WARNING "\nCannot determine the version of the compiler selected to build UHD (GCC : ${CMAKE_CXX_COMPILER}). This build may or not work. We highly recommend using GCC version ${GCC_MIN_VERSION} or more recent.") + ENDIF() +ELSEIF(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + EXECUTE_PROCESS(COMMAND + ${CMAKE_CXX_COMPILER} -v + RESULT_VARIABLE res ERROR_VARIABLE err + ERROR_STRIP_TRAILING_WHITESPACE) + IF(${res} STREQUAL "0") + # output is in error stream + STRING(REGEX MATCH "^Apple.*" IS_APPLE ${err}) + IF("${IS_APPLE}" STREQUAL "") + SET(MIN_VERSION ${CLANG_MIN_VERSION}) + SET(APPLE_STR "") + # retrieve the compiler's version from it + STRING(REGEX MATCH "clang version [0-9.]+" CLANG_OTHER_VERSION ${err}) + STRING(REGEX MATCH "[0-9.]+" CLANG_VERSION ${CLANG_OTHER_VERSION}) + ELSE() + SET(MIN_VERSION ${APPLECLANG_MIN_VERSION}) + SET(APPLE_STR "Apple ") + # retrieve the compiler's version from it + STRING(REGEX MATCH "(clang-[0-9.]+)" CLANG_APPLE_VERSION ${err}) + STRING(REGEX MATCH "[0-9.]+" CLANG_VERSION ${CLANG_APPLE_VERSION}) + ENDIF() + IF(${CLANG_VERSION} VERSION_LESS "${MIN_VERSION}") + MESSAGE(WARNING "\nThe compiler selected to build UHD (${APPLE_STR}Clang version ${CLANG_VERSION} : ${CMAKE_CXX_COMPILER}) is older than that officially supported (${MIN_VERSION} minimum). This build may or not work. We highly recommend using Apple Clang version ${APPLECLANG_MIN_VERSION} or more recent, or Clang version ${CLANG_MIN_VERSION} or more recent.") + ENDIF() + ELSE() + MESSAGE(WARNING "\nCannot determine the version of the compiler selected to build UHD (${APPLE_STR}Clang : ${CMAKE_CXX_COMPILER}). This build may or not work. We highly recommend using Apple Clang version ${APPLECLANG_MIN_VERSION} or more recent, or Clang version ${CLANG_MIN_VERSION} or more recent.") + ENDIF() +ENDIF() + +######################################################################## # Packaging Variables ######################################################################## @@ -134,7 +183,8 @@ SET(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "") #force UHD_RELEASE_MODE to be a string for cmake-gui SET(UHD_RELEASE_MODE "${UHD_RELEASE_MODE}" CACHE STRING "UHD Release Mode") -IF(CMAKE_COMPILER_IS_GNUCXX) +IF(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR + ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") IF(STRIP_BINARIES) IF(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") @@ -151,7 +201,7 @@ IF(CMAKE_COMPILER_IS_GNUCXX) UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN) UHD_ADD_OPTIONAL_CXX_COMPILER_FLAG(-fvisibility-inlines-hidden HAVE_VISIBILITY_INLINES_HIDDEN) ENDIF(NOT WIN32) -ENDIF(CMAKE_COMPILER_IS_GNUCXX) +ENDIF() IF(MSVC) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/cmake/msvc) @@ -213,9 +263,9 @@ ENDIF(MSVC) SET(Boost_ADDITIONAL_VERSIONS "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.48.0" "1.49" "1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54" "1.55.0" "1.55" - "1.56.0" "1.56" + "1.56.0" "1.56" "1.57" "1.57" "1.58" "1.59" "1.60" ) -FIND_PACKAGE(Boost 1.46 COMPONENTS ${BOOST_REQUIRED_COMPONENTS}) +FIND_PACKAGE(Boost 1.53 COMPONENTS ${BOOST_REQUIRED_COMPONENTS}) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) @@ -235,8 +285,8 @@ INCLUDE(UHDGlobalDefs) INCLUDE(UHDPython) PYTHON_CHECK_MODULE( - "Python version 2.6 or greater" - "platform" "platform.python_version() >= '2.6'" + "Python version 2.7 or greater" + "platform" "platform.python_version() >= '2.7'" HAVE_PYTHON_PLAT_MIN_VERSION ) @@ -274,8 +324,8 @@ UHD_INSTALL(FILES #{{{IMG_SECTION # This section is written automatically by /images/create_imgs_package.py # Any manual changes in here will be overwritten. -SET(UHD_IMAGES_MD5SUM "07b9d06a068cf591fcd0e460552ed02d") -SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.009.003-release.zip") +SET(UHD_IMAGES_MD5SUM "360d73cf5956ab11947f43d88fbeb345") +SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.010.git-173-g2ea1f108.zip") #}}} ######################################################################## @@ -427,7 +477,7 @@ ENDIF(LIBUHD_PKG) UHD_PRINT_COMPONENT_SUMMARY() IF(UHD_VERSION_DEVEL) MESSAGE(STATUS "******************************************************") - IF(UHD_VERSION_PATCH STREQUAL "git") + IF(UHD_GIT_BRANCH STREQUAL "master") MESSAGE(STATUS "* You are building the UHD development master branch.") MESSAGE(STATUS "* For production code, we recommend our stable,") MESSAGE(STATUS "* releases or using the release branch (maint).") @@ -436,7 +486,7 @@ IF(UHD_VERSION_DEVEL) MESSAGE(STATUS "* These branches are designed to provide early access") MESSAGE(STATUS "* to UHD and USRP features, but should be considered") MESSAGE(STATUS "* unstable and/or experimental!") - ENDIF(UHD_VERSION_PATCH STREQUAL "git") + ENDIF(UHD_GIT_BRANCH STREQUAL "master") MESSAGE(STATUS "******************************************************") ENDIF(UHD_VERSION_DEVEL) MESSAGE(STATUS "Building version: ${UHD_VERSION}${PRINT_APPEND}") diff --git a/host/cmake/Modules/UHDBuildInfo.cmake b/host/cmake/Modules/UHDBuildInfo.cmake new file mode 100644 index 000000000..bc30b99ee --- /dev/null +++ b/host/cmake/Modules/UHDBuildInfo.cmake @@ -0,0 +1,60 @@ +# +# Copyright 2015 National Instruments Corp. +# +# 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/>. +# + +# +# We need this to be macro because GET_DIRECTORY_PROPERTY works with +# the current directory. +# +MACRO(UHD_LOAD_BUILD_INFO) + MESSAGE(STATUS "") + MESSAGE(STATUS "Loading build info.") + + # Build date + EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c + "import time; print(time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime()))" + OUTPUT_VARIABLE UHD_BUILD_DATE OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # Compiler name + IF(MSVC) + IF(MSVC10) + SET(UHD_C_COMPILER "MSVC 2010") + SET(UHD_CXX_COMPILER "MSVC 2010") + ELSEIF(MSVC11) + SET(UHD_C_COMPILER "MSVC 2012") + SET(UHD_CXX_COMPILER "MSVC 2012") + ELSEIF(MSVC12) + SET(UHD_C_COMPILER "MSVC 2013") + SET(UHD_CXX_COMPILER "MSVC 2013") + ELSEIF(MSVC14) + SET(UHD_C_COMPILER "MSVC 2015") + SET(UHD_CXX_COMPILER "MSVC 2015") + ELSE() + # Go with the ugly string + SET(UHD_C_COMPILER "MSVC ${CMAKE_C_COMPILER_VERSION}") + SET(UHD_CXX_COMPILER "MSVC ${CMAKE_CXX_COMPILER_VERSION}") + ENDIF(MSVC10) + ELSE() + SET(UHD_C_COMPILER "${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}") + SET(UHD_CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") + ENDIF(MSVC) + + # Compiler flags + GET_DIRECTORY_PROPERTY(uhd_flags COMPILE_DEFINITIONS) + SET(UHD_C_FLAGS "${uhd_flags}${CMAKE_C_FLAGS}") # CMAKE_C_FLAGS starts with a space + SET(UHD_CXX_FLAGS "${uhd_flags}${CMAKE_CXX_FLAGS}") # CMAKE_CXX_FLAGS starts with a space +ENDMACRO(UHD_LOAD_BUILD_INFO) diff --git a/host/cmake/Modules/UHDVersion.cmake b/host/cmake/Modules/UHDVersion.cmake index 6f906f8bf..74066e368 100644 --- a/host/cmake/Modules/UHDVersion.cmake +++ b/host/cmake/Modules/UHDVersion.cmake @@ -27,14 +27,52 @@ FIND_PACKAGE(Git QUIET) # - set UHD_VERSION_DEVEL to true for master and development branches ######################################################################## SET(UHD_VERSION_MAJOR 003) -SET(UHD_VERSION_MINOR 009) -SET(UHD_VERSION_PATCH 003) -SET(UHD_VERSION_DEVEL FALSE) +SET(UHD_VERSION_MINOR 010) +SET(UHD_VERSION_PATCH git) +SET(UHD_VERSION_DEVEL TRUE) ######################################################################## -# Set up trimmed version numbers for DLL resource files and packages +# If we're on a development branch, we skip the patch version ######################################################################## +IF(DEFINED UHD_VERSION_PATCH_OVERRIDE) + SET(UHD_VERSION_DEVEL FALSE) + SET(UHD_VERSION_PATCH ${UHD_VERSION_PATCH_OVERRIDE}) +ENDIF(DEFINED UHD_VERSION_PATCH_OVERRIDE) +IF(NOT DEFINED UHD_VERSION_DEVEL) + SET(UHD_VERSION_DEVEL FALSE) +ENDIF(NOT DEFINED UHD_VERSION_DEVEL) +SET(UHD_GIT_BRANCH "") +IF(GIT_FOUND) + EXECUTE_PROCESS( + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + OUTPUT_VARIABLE _git_branch OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _git_branch_result + ) + IF(_git_branch_result EQUAL 0) + IF(_git_branch STREQUAL "maint") + MESSAGE(STATUS "Operating on maint branch (stable).") + ELSEIF(_git_branch STREQUAL "master") + MESSAGE(STATUS "Operating on master branch.") + SET(UHD_VERSION_DEVEL TRUE) + ELSE() + MESSAGE(STATUS "Working off of feature or development branch. Updating version number.") + EXECUTE_PROCESS( + COMMAND ${PYTHON_EXECUTABLE} -c "print('${_git_branch}'.replace('/', '-'))" + OUTPUT_VARIABLE _git_safe_branch OUTPUT_STRIP_TRAILING_WHITESPACE + ) + SET(UHD_VERSION_PATCH ${_git_safe_branch}) + SET(UHD_VERSION_DEVEL TRUE) + SET(UHD_GIT_BRANCH ${_git_branch}) + ENDIF() + ELSE() + MESSAGE(STATUS "Could not determine git branch. Probably building from tarball.") + ENDIF() +ENDIF(GIT_FOUND) +######################################################################## +# Set up trimmed version numbers for DLL resource files and packages +######################################################################## FUNCTION(DEPAD_NUM input_num output_num) EXECUTE_PROCESS( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/host/cmake/msvc/inttypes.h b/host/cmake/msvc/inttypes.h deleted file mode 100644 index 1c2baa82e..000000000 --- a/host/cmake/msvc/inttypes.h +++ /dev/null @@ -1,301 +0,0 @@ -// ISO C9x compliant inttypes.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_INTTYPES_H_ // [ -#define _MSC_INTTYPES_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#include <stdint.h> - -// 7.8 Format conversion of integer types - -typedef struct { - intmax_t quot; - intmax_t rem; -} imaxdiv_t; - -// 7.8.1 Macros for format specifiers - -// The fprintf macros for signed integers are: -#define PRId8 "d" -#define PRIi8 "i" -#define PRIdLEAST8 "d" -#define PRIiLEAST8 "i" -#define PRIdFAST8 "d" -#define PRIiFAST8 "i" - -#define PRId16 "hd" -#define PRIi16 "hi" -#define PRIdLEAST16 "hd" -#define PRIiLEAST16 "hi" -#define PRIdFAST16 "hd" -#define PRIiFAST16 "hi" - -#define PRId32 "I32d" -#define PRIi32 "I32i" -#define PRIdLEAST32 "I32d" -#define PRIiLEAST32 "I32i" -#define PRIdFAST32 "I32d" -#define PRIiFAST32 "I32i" - -#define PRId64 "I64d" -#define PRIi64 "I64i" -#define PRIdLEAST64 "I64d" -#define PRIiLEAST64 "I64i" -#define PRIdFAST64 "I64d" -#define PRIiFAST64 "I64i" - -#define PRIdMAX "I64d" -#define PRIiMAX "I64i" - -#define PRIdPTR "Id" -#define PRIiPTR "Ii" - -// The fprintf macros for unsigned integers are: -#define PRIo8 "o" -#define PRIu8 "u" -#define PRIx8 "x" -#define PRIX8 "X" -#define PRIoLEAST8 "o" -#define PRIuLEAST8 "u" -#define PRIxLEAST8 "x" -#define PRIXLEAST8 "X" -#define PRIoFAST8 "o" -#define PRIuFAST8 "u" -#define PRIxFAST8 "x" -#define PRIXFAST8 "X" - -#define PRIo16 "ho" -#define PRIu16 "hu" -#define PRIx16 "hx" -#define PRIX16 "hX" -#define PRIoLEAST16 "ho" -#define PRIuLEAST16 "hu" -#define PRIxLEAST16 "hx" -#define PRIXLEAST16 "hX" -#define PRIoFAST16 "ho" -#define PRIuFAST16 "hu" -#define PRIxFAST16 "hx" -#define PRIXFAST16 "hX" - -#define PRIo32 "I32o" -#define PRIu32 "I32u" -#define PRIx32 "I32x" -#define PRIX32 "I32X" -#define PRIoLEAST32 "I32o" -#define PRIuLEAST32 "I32u" -#define PRIxLEAST32 "I32x" -#define PRIXLEAST32 "I32X" -#define PRIoFAST32 "I32o" -#define PRIuFAST32 "I32u" -#define PRIxFAST32 "I32x" -#define PRIXFAST32 "I32X" - -#define PRIo64 "I64o" -#define PRIu64 "I64u" -#define PRIx64 "I64x" -#define PRIX64 "I64X" -#define PRIoLEAST64 "I64o" -#define PRIuLEAST64 "I64u" -#define PRIxLEAST64 "I64x" -#define PRIXLEAST64 "I64X" -#define PRIoFAST64 "I64o" -#define PRIuFAST64 "I64u" -#define PRIxFAST64 "I64x" -#define PRIXFAST64 "I64X" - -#define PRIoMAX "I64o" -#define PRIuMAX "I64u" -#define PRIxMAX "I64x" -#define PRIXMAX "I64X" - -#define PRIoPTR "Io" -#define PRIuPTR "Iu" -#define PRIxPTR "Ix" -#define PRIXPTR "IX" - -// The fscanf macros for signed integers are: -#define SCNd8 "d" -#define SCNi8 "i" -#define SCNdLEAST8 "d" -#define SCNiLEAST8 "i" -#define SCNdFAST8 "d" -#define SCNiFAST8 "i" - -#define SCNd16 "hd" -#define SCNi16 "hi" -#define SCNdLEAST16 "hd" -#define SCNiLEAST16 "hi" -#define SCNdFAST16 "hd" -#define SCNiFAST16 "hi" - -#define SCNd32 "ld" -#define SCNi32 "li" -#define SCNdLEAST32 "ld" -#define SCNiLEAST32 "li" -#define SCNdFAST32 "ld" -#define SCNiFAST32 "li" - -#define SCNd64 "I64d" -#define SCNi64 "I64i" -#define SCNdLEAST64 "I64d" -#define SCNiLEAST64 "I64i" -#define SCNdFAST64 "I64d" -#define SCNiFAST64 "I64i" - -#define SCNdMAX "I64d" -#define SCNiMAX "I64i" - -#ifdef _WIN64 // [ -# define SCNdPTR "I64d" -# define SCNiPTR "I64i" -#else // _WIN64 ][ -# define SCNdPTR "ld" -# define SCNiPTR "li" -#endif // _WIN64 ] - -// The fscanf macros for unsigned integers are: -#define SCNo8 "o" -#define SCNu8 "u" -#define SCNx8 "x" -#define SCNX8 "X" -#define SCNoLEAST8 "o" -#define SCNuLEAST8 "u" -#define SCNxLEAST8 "x" -#define SCNXLEAST8 "X" -#define SCNoFAST8 "o" -#define SCNuFAST8 "u" -#define SCNxFAST8 "x" -#define SCNXFAST8 "X" - -#define SCNo16 "ho" -#define SCNu16 "hu" -#define SCNx16 "hx" -#define SCNX16 "hX" -#define SCNoLEAST16 "ho" -#define SCNuLEAST16 "hu" -#define SCNxLEAST16 "hx" -#define SCNXLEAST16 "hX" -#define SCNoFAST16 "ho" -#define SCNuFAST16 "hu" -#define SCNxFAST16 "hx" -#define SCNXFAST16 "hX" - -#define SCNo32 "lo" -#define SCNu32 "lu" -#define SCNx32 "lx" -#define SCNX32 "lX" -#define SCNoLEAST32 "lo" -#define SCNuLEAST32 "lu" -#define SCNxLEAST32 "lx" -#define SCNXLEAST32 "lX" -#define SCNoFAST32 "lo" -#define SCNuFAST32 "lu" -#define SCNxFAST32 "lx" -#define SCNXFAST32 "lX" - -#define SCNo64 "I64o" -#define SCNu64 "I64u" -#define SCNx64 "I64x" -#define SCNX64 "I64X" -#define SCNoLEAST64 "I64o" -#define SCNuLEAST64 "I64u" -#define SCNxLEAST64 "I64x" -#define SCNXLEAST64 "I64X" -#define SCNoFAST64 "I64o" -#define SCNuFAST64 "I64u" -#define SCNxFAST64 "I64x" -#define SCNXFAST64 "I64X" - -#define SCNoMAX "I64o" -#define SCNuMAX "I64u" -#define SCNxMAX "I64x" -#define SCNXMAX "I64X" - -#ifdef _WIN64 // [ -# define SCNoPTR "I64o" -# define SCNuPTR "I64u" -# define SCNxPTR "I64x" -# define SCNXPTR "I64X" -#else // _WIN64 ][ -# define SCNoPTR "lo" -# define SCNuPTR "lu" -# define SCNxPTR "lx" -# define SCNXPTR "lX" -#endif // _WIN64 ] - -// 7.8.2 Functions for greatest-width integer types - -// 7.8.2.1 The imaxabs function -#define imaxabs _abs64 - -// 7.8.2.2 The imaxdiv function - -// This is modified version of div() function from Microsoft's div.c found -// in %MSVC.NET%\crt\src\div.c -#ifdef STATIC_IMAXDIV // [ -static -#else // STATIC_IMAXDIV ][ -_inline -#endif // STATIC_IMAXDIV ] -imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) -{ - imaxdiv_t result; - - result.quot = numer / denom; - result.rem = numer % denom; - - if (numer < 0 && result.rem > 0) { - // did division wrong; must fix up - ++result.quot; - result.rem -= denom; - } - - return result; -} - -// 7.8.2.3 The strtoimax and strtoumax functions -#define strtoimax _strtoi64 -#define strtoumax _strtoui64 - -// 7.8.2.4 The wcstoimax and wcstoumax functions -#define wcstoimax _wcstoi64 -#define wcstoumax _wcstoui64 - - -#endif // _MSC_INTTYPES_H_ ] diff --git a/host/cmake/msvc/stdint.h b/host/cmake/msvc/stdint.h deleted file mode 100644 index 15333b467..000000000 --- a/host/cmake/msvc/stdint.h +++ /dev/null @@ -1,226 +0,0 @@ -// ISO C9x compliant stdint.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_STDINT_H_ // [ -#define _MSC_STDINT_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#include <limits.h> - -// For Visual Studio 6 in C++ mode wrap <wchar.h> include with 'extern "C++" {}' -// or compiler give many errors like this: -// error C2733: second C linkage of overloaded function 'wmemchr' not allowed -#if (_MSC_VER < 1300) && defined(__cplusplus) - extern "C++" { -#endif -# include <wchar.h> -#if (_MSC_VER < 1300) && defined(__cplusplus) - } -#endif - -// 7.18.1 Integer types - -// 7.18.1.1 Exact-width integer types -typedef __int8 int8_t; -typedef __int16 int16_t; -typedef __int32 int32_t; -typedef __int64 int64_t; -typedef unsigned __int8 uint8_t; -typedef unsigned __int16 uint16_t; -typedef unsigned __int32 uint32_t; -typedef unsigned __int64 uint64_t; - -// 7.18.1.2 Minimum-width integer types -typedef int8_t int_least8_t; -typedef int16_t int_least16_t; -typedef int32_t int_least32_t; -typedef int64_t int_least64_t; -typedef uint8_t uint_least8_t; -typedef uint16_t uint_least16_t; -typedef uint32_t uint_least32_t; -typedef uint64_t uint_least64_t; - -// 7.18.1.3 Fastest minimum-width integer types -typedef int8_t int_fast8_t; -typedef int16_t int_fast16_t; -typedef int32_t int_fast32_t; -typedef int64_t int_fast64_t; -typedef uint8_t uint_fast8_t; -typedef uint16_t uint_fast16_t; -typedef uint32_t uint_fast32_t; -typedef uint64_t uint_fast64_t; - -// 7.18.1.4 Integer types capable of holding object pointers -#ifdef _WIN64 // [ - typedef __int64 intptr_t; - typedef unsigned __int64 uintptr_t; -#else // _WIN64 ][ - typedef int intptr_t; - typedef unsigned int uintptr_t; -#endif // _WIN64 ] - -// 7.18.1.5 Greatest-width integer types -typedef int64_t intmax_t; -typedef uint64_t uintmax_t; - - -// 7.18.2 Limits of specified-width integer types - -#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 - -// 7.18.2.1 Limits of exact-width integer types -#define INT8_MIN ((int8_t)_I8_MIN) -#define INT8_MAX _I8_MAX -#define INT16_MIN ((int16_t)_I16_MIN) -#define INT16_MAX _I16_MAX -#define INT32_MIN ((int32_t)_I32_MIN) -#define INT32_MAX _I32_MAX -#define INT64_MIN ((int64_t)_I64_MIN) -#define INT64_MAX _I64_MAX -#define UINT8_MAX _UI8_MAX -#define UINT16_MAX _UI16_MAX -#define UINT32_MAX _UI32_MAX -#define UINT64_MAX _UI64_MAX - -// 7.18.2.2 Limits of minimum-width integer types -#define INT_LEAST8_MIN INT8_MIN -#define INT_LEAST8_MAX INT8_MAX -#define INT_LEAST16_MIN INT16_MIN -#define INT_LEAST16_MAX INT16_MAX -#define INT_LEAST32_MIN INT32_MIN -#define INT_LEAST32_MAX INT32_MAX -#define INT_LEAST64_MIN INT64_MIN -#define INT_LEAST64_MAX INT64_MAX -#define UINT_LEAST8_MAX UINT8_MAX -#define UINT_LEAST16_MAX UINT16_MAX -#define UINT_LEAST32_MAX UINT32_MAX -#define UINT_LEAST64_MAX UINT64_MAX - -// 7.18.2.3 Limits of fastest minimum-width integer types -#define INT_FAST8_MIN INT8_MIN -#define INT_FAST8_MAX INT8_MAX -#define INT_FAST16_MIN INT16_MIN -#define INT_FAST16_MAX INT16_MAX -#define INT_FAST32_MIN INT32_MIN -#define INT_FAST32_MAX INT32_MAX -#define INT_FAST64_MIN INT64_MIN -#define INT_FAST64_MAX INT64_MAX -#define UINT_FAST8_MAX UINT8_MAX -#define UINT_FAST16_MAX UINT16_MAX -#define UINT_FAST32_MAX UINT32_MAX -#define UINT_FAST64_MAX UINT64_MAX - -// 7.18.2.4 Limits of integer types capable of holding object pointers -#ifdef _WIN64 // [ -# define INTPTR_MIN INT64_MIN -# define INTPTR_MAX INT64_MAX -# define UINTPTR_MAX UINT64_MAX -#else // _WIN64 ][ -# define INTPTR_MIN INT32_MIN -# define INTPTR_MAX INT32_MAX -# define UINTPTR_MAX UINT32_MAX -#endif // _WIN64 ] - -// 7.18.2.5 Limits of greatest-width integer types -#define INTMAX_MIN INT64_MIN -#define INTMAX_MAX INT64_MAX -#define UINTMAX_MAX UINT64_MAX - -// 7.18.3 Limits of other integer types - -#ifdef _WIN64 // [ -# define PTRDIFF_MIN _I64_MIN -# define PTRDIFF_MAX _I64_MAX -#else // _WIN64 ][ -# define PTRDIFF_MIN _I32_MIN -# define PTRDIFF_MAX _I32_MAX -#endif // _WIN64 ] - -#define SIG_ATOMIC_MIN INT_MIN -#define SIG_ATOMIC_MAX INT_MAX - -#ifndef SIZE_MAX // [ -# ifdef _WIN64 // [ -# define SIZE_MAX _UI64_MAX -# else // _WIN64 ][ -# define SIZE_MAX _UI32_MAX -# endif // _WIN64 ] -#endif // SIZE_MAX ] - -// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> -#ifndef WCHAR_MIN // [ -# define WCHAR_MIN 0 -#endif // WCHAR_MIN ] -#ifndef WCHAR_MAX // [ -# define WCHAR_MAX _UI16_MAX -#endif // WCHAR_MAX ] - -#define WINT_MIN 0 -#define WINT_MAX _UI16_MAX - -#endif // __STDC_LIMIT_MACROS ] - - -// 7.18.4 Limits of other integer types - -#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 - -// 7.18.4.1 Macros for minimum-width integer constants - -#define INT8_C(val) val##i8 -#define INT16_C(val) val##i16 -#define INT32_C(val) val##i32 -#define INT64_C(val) val##i64 - -#define UINT8_C(val) val##ui8 -#define UINT16_C(val) val##ui16 -#define UINT32_C(val) val##ui32 -#define UINT64_C(val) val##ui64 - -// 7.18.4.2 Macros for greatest-width integer constants -#ifndef INTMAX_C -#define INTMAX_C INT64_C -#endif -#ifndef UINTMAX_C -#define UINTMAX_C UINT64_C -#endif - -#endif // __STDC_CONSTANT_MACROS ] - - -#endif // _MSC_STDINT_H_ ] diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt index 96f388b2e..7f510f58c 100644 --- a/host/docs/CMakeLists.txt +++ b/host/docs/CMakeLists.txt @@ -63,7 +63,6 @@ ENDIF(ENABLE_MANUAL) ######################################################################## # Setup API documentation (using Doxygen) ######################################################################## -MESSAGE(STATUS "") LIBUHD_REGISTER_COMPONENT("API/Doxygen" ENABLE_DOXYGEN ON "DOXYGEN_FOUND" OFF OFF) OPTION(ENABLE_DOXYGEN_FULL "Use Doxygen to document the entire source tree (not just API)" OFF) OPTION(ENABLE_DOXYGEN_DOT "Let Doxygen use dot (requires graphviz)" OFF) @@ -91,7 +90,6 @@ ENDIF(ENABLE_DOXYGEN) ######################################################################## # Run Doxygen (on code and/or manual, depending on CMake flags) ######################################################################## -MESSAGE(STATUS "") IF(ENABLE_MANUAL_OR_DOXYGEN) #generate the doxygen configuration file SET(CMAKE_CURRENT_BINARY_DIR_DOXYGEN ${CMAKE_CURRENT_BINARY_DIR}/doxygen) @@ -128,6 +126,7 @@ SET(man_page_sources uhd_cal_rx_iq_balance.1 uhd_cal_tx_dc_offset.1 uhd_cal_tx_iq_balance.1 + uhd_config_info.1 uhd_find_devices.1 uhd_image_loader.1 uhd_images_downloader.1 @@ -140,7 +139,6 @@ SET(man_page_sources ######################################################################## # Setup man pages ######################################################################## -MESSAGE(STATUS "") FIND_PACKAGE(GZip) # No elegant way in CMake to reverse a boolean diff --git a/host/docs/build.dox b/host/docs/build.dox index 1390a8b6d..95f7bab85 100644 --- a/host/docs/build.dox +++ b/host/docs/build.dox @@ -26,8 +26,8 @@ follow the auxiliary download URL for the Windows installer (below). The following compilers are known to work and officially supported: -- GCC >= 4.4 -- Clang >= 3.1 +- GCC >= 4.8 +- Clang >= 3.3 - MSVC >= 2012; the free <a href="https://www.visualstudio.com/en-us/products/visual-studio-express-vs.aspx">Visual Studio Express Edition for Desktop</a> works. Other compilers (or lower versions) may work, but are unsupported. @@ -35,14 +35,14 @@ Other compilers (or lower versions) may work, but are unsupported. ### CMake - **Purpose:** generates project build files -- **Minimum Version:** 2.6 +- **Minimum Version:** 2.8 - **Usage:** build time (required) - **Download URL:** http://www.cmake.org/cmake/resources/software.html ### Boost - **Purpose:** C++ library -- **Minimum Version:** 1.46 +- **Minimum Version:** 1.53 - **Usage:** build time + runtime (required) - **Download URL:** http://www.boost.org/users/download/ - **Download URL (Windows installer):** http://sourceforge.net/projects/boost/files/boost-binaries/ @@ -58,7 +58,7 @@ Other compilers (or lower versions) may work, but are unsupported. ### Python - **Purpose:** used by mako and utility scripts -- **Minimum Version:** 2.6 +- **Minimum Version:** 2.7 - **Usage:** build time + runtime utility scripts (required) - **Download URL:** http://www.python.org/download/ @@ -99,6 +99,10 @@ You can install all the dependencies through the package manager: sudo yum -y install boost-devel libusb1-devel python-mako doxygen python-docutils cmake make gcc gcc-c++ +or + + sudo dnf -y install boost-devel libusb1-devel python-mako doxygen python-docutils cmake make gcc gcc-c++ + Your actual command may differ. \section build_get_source Getting the source code @@ -116,21 +120,21 @@ This will populate the `fpga-src` submodule inside the repository. You can also git submodule init git submodule update -Our source code repository contains two branches: +Our source code repository contains of two main branches: \li \b master: This is the main development branch, with updated new features and bug fixes. \li \b maint: This branch has all bugfixes since the last major release, but there are no new features. This is what you should be using if you need a stable release. We might also be publishing experimental feature branches which can then be found in the same repository. -All of our releases are associated with tags in the repository. +All of our versioned releases are associated with tags in the repository. \li <a href="https://github.com/EttusResearch/UHD/tags">Source archives for release tags</a> \section build_pybombs Using PyBOMBS -PyBOMBS is a command-line tool for Linuxes (and some Unixes) from the GNU Radio ecosystem and will do a source build of UHD, including setting up prerequisites/dependencies (regardless of the distribution) with the following command: +PyBOMBS is a command-line tool for Linuxes (and some Unixes) from the GNU Radio ecosystem and will do a source build of UHD, including setting up prerequisites/dependencies (regardless of the distribution). Assuming you have PyBOMBS set up, you can install UHD with the following command: - $ ./pybombs install uhd + $ pybombs install uhd Head to the <a href="https://github.com/gnuradio/pybombs/#installation">PyBOMBS Homepage</a> for more instructions. PyBOMBS can install UHD (as well as GNU Radio or similar projects) both into system directories as well as into user's home directories, omitting the requirement for superuser access. @@ -297,7 +301,7 @@ If your application uses CMake as a build system, the following command will setup up your build environment to link against UHD: \code{.cmake} -find_package(UHD "3.8.0") +find_package(UHD "3.10.0") \endcode This will set the CMake variable `UHD_INCLUDE_DIRS` and `UHD_LIBRARIES` diff --git a/host/docs/uhd_config_info.1 b/host/docs/uhd_config_info.1 new file mode 100644 index 000000000..edc1b7532 --- /dev/null +++ b/host/docs/uhd_config_info.1 @@ -0,0 +1,64 @@ +.TH "uhd_find_devices" 1 "3.9.1" UHD "User Commands" +.SH NAME +uhd_config_info \- USRP Hardware Driver Build Configuration Info +.SH DESCRIPTION +Print build information corresponding to this installation of the USRP +Hardware Driver (UHD). +.LP +The UHD package is the universal hardware driver for Ettus Research +products. The goal is to provide a host driver and API for +current and future Ettus Research products. Users will be able to use +the UHD driver standalone or with 3rd party applications. + +.SH SYNOPSIS +.B uhd_config_info [OPTIONS] + +.SH OPTIONS +.IP "Print date this build was compiled:" +--boost-version +.IP "Print Boost version used:" +--build-date +.IP "Print C compiler used:" +--c-compiler +.IP "Print C++ compiler used:" +--cxx-compiler +.IP "Print C compile flags:" +--c-flags +.IP "Print C++ compile flags:" +--cxx-flags +.IP "Print UHD components included in this build (comma-delimited):" +--enabled-components +.IP "Print default install prefix for this build:" +--install-prefix +.IP "Print libusb version used" +--libusb-version +.IP "Print all information listed above:" +--print-all +.IP "Print UHD version:" +--version +.IP "This help information:" +--help + +.SH SEE ALSO +UHD documentation: +.B http://files.ettus.com/manual/ +.LP +GR-UHD documentation: +.B http://gnuradio.org/doc/doxygen/page_uhd.html + +.SH AUTHOR +This manual page was written by Nicholas Corgan +for the Debian project (but may be used by others). + +.SH COPYRIGHT +Copyright (c) 2015 National Instruments Corp. +.LP +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. +.LP +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. diff --git a/host/docs/usrp_b200.dox b/host/docs/usrp_b200.dox index 248f07e85..c846e916f 100644 --- a/host/docs/usrp_b200.dox +++ b/host/docs/usrp_b200.dox @@ -214,7 +214,7 @@ Below is a table showing the B200/B210 external connections and respective power <td>J101</td> <td>GPS Antenna</td> <td>GPSDO will supply nominal voltage to antenna.</td> </tr> <tr> - <td>J100</td> <td>External 10 MHz Input</td> <td>+15 dBm max</td + <td>J100</td> <td>External 10 MHz Input</td> <td>+15 dBm max</td> </tr> <tr> <td>J800</td> <td>RF B: TX/RX</td> <td>TX power +20dBm max<br> @@ -249,7 +249,7 @@ Below is a table showing the B200mini external connections and respective power <td>J2</td> <td>RX2</td> <td>RX power -15dBm max</td> </tr> <tr> - <td>J3</td> <td>External 10MHz/PPS Reference</td> <td>+15 dBm max</td + <td>J3</td> <td>External 10MHz/PPS Reference</td> <td>+15 dBm max</td> </tr> </table> diff --git a/host/examples/benchmark_rate.cpp b/host/examples/benchmark_rate.cpp index bf3015119..e7abf76b3 100644 --- a/host/examples/benchmark_rate.cpp +++ b/host/examples/benchmark_rate.cpp @@ -46,6 +46,7 @@ unsigned long long num_rx_samps = 0; unsigned long long num_tx_samps = 0; unsigned long long num_dropped_samps = 0; unsigned long long num_seq_errors = 0; +unsigned long long num_timeouts = 0; /*********************************************************************** * Benchmark RX Rate @@ -126,6 +127,10 @@ void benchmark_rx_rate( if (burst_timer_elapsed) { return; } + std::cerr << "Receiver error: " << md.strerror() << ", continuing..." << std::endl; + num_timeouts++; + break; + // Otherwise, it's an error default: std::cerr << "Receiver error: " << md.strerror() << std::endl; @@ -421,7 +426,11 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ " Num transmitted samples: %u\n" " Num sequence errors: %u\n" " Num underflows detected: %u\n" - ) % num_rx_samps % num_dropped_samps % num_overflows % num_tx_samps % num_seq_errors % num_underflows << std::endl; + " Num timeouts: %u\n" + ) % num_rx_samps % num_dropped_samps + % num_overflows % num_tx_samps + % num_seq_errors % num_underflows + % num_timeouts << std::endl; //finished std::cout << std::endl << "Done!" << std::endl << std::endl; diff --git a/host/examples/tx_samples_c.c b/host/examples/tx_samples_c.c index ebf368ec7..e10d585ce 100644 --- a/host/examples/tx_samples_c.c +++ b/host/examples/tx_samples_c.c @@ -21,6 +21,7 @@ #include <math.h> #include <signal.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -39,6 +40,7 @@ void print_help(void){ " -f (frequency in Hz)\n" " -r (sample rate in Hz)\n" " -g (gain)\n" + " -n (number of samples to transmit)\n" " -v (enable verbose prints)\n" " -h (print this help message)\n"); } @@ -57,12 +59,13 @@ int main(int argc, char* argv[]){ double gain = 0; char* device_args = ""; size_t channel = 0; + uint64_t total_num_samps = 0; bool verbose = false; int return_code = EXIT_SUCCESS; char error_string[512]; // Process options - while((option = getopt(argc, argv, "a:f:r:g:vh")) != -1){ + while((option = getopt(argc, argv, "a:f:r:g:n:vh")) != -1){ switch(option){ case 'a': device_args = strdup(optarg); @@ -80,6 +83,10 @@ int main(int argc, char* argv[]){ gain = atof(optarg); break; + case 'n': + total_num_samps = atoll(optarg); + break; + case 'v': verbose = true; break; @@ -198,11 +205,19 @@ int main(int argc, char* argv[]){ fprintf(stderr, "Press Ctrl+C to stop streaming...\n"); // Actual streaming - size_t num_samps_sent = 0; - while(!stop_signal_called){ + uint64_t num_acc_samps = 0; + uint64_t num_samps_sent = 0; + + while(1) { + if (stop_signal_called) break; + if (total_num_samps > 0 && num_acc_samps >= total_num_samps) break; + EXECUTE_OR_GOTO(free_tx_streamer, uhd_tx_streamer_send(tx_streamer, buffs_ptr, samps_per_buff, &md, 0.1, &num_samps_sent) ) + + num_acc_samps += num_samps_sent; + if(verbose){ fprintf(stderr, "Sent %zu samples\n", num_samps_sent); } diff --git a/host/examples/tx_waveforms.cpp b/host/examples/tx_waveforms.cpp index af8f92607..ef878722c 100644 --- a/host/examples/tx_waveforms.cpp +++ b/host/examples/tx_waveforms.cpp @@ -28,6 +28,7 @@ #include <boost/thread.hpp> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> +#include <stdint.h> #include <iostream> #include <csignal> @@ -47,7 +48,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //variables to be set by po std::string args, wave_type, ant, subdev, ref, pps, otw, channel_list; - size_t spb; + uint64_t total_num_samps, spb; double rate, freq, gain, wave_freq, bw; float ampl; @@ -56,7 +57,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ desc.add_options() ("help", "help message") ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") - ("spb", po::value<size_t>(&spb)->default_value(0), "samples per buffer, 0 for default") + ("spb", po::value<uint64_t>(&spb)->default_value(0), "samples per buffer, 0 for default") + ("nsamps", po::value<uint64_t>(&total_num_samps)->default_value(0), "total number of samples to transmit") ("rate", po::value<double>(&rate), "rate of outgoing samples") ("freq", po::value<double>(&freq), "RF center frequency in Hz") ("ampl", po::value<float>(&l)->default_value(float(0.3)), "amplitude of the waveform [0 to 0.7]") @@ -241,14 +243,22 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ md.time_spec = usrp->get_time_now() + uhd::time_spec_t(0.1); //send data until the signal handler gets called - while(not stop_signal_called){ + //or if we accumulate the number of samples specified (unless it's 0) + uint64_t num_acc_samps = 0; + while(true){ + + if (stop_signal_called) break; + if (total_num_samps > 0 and num_acc_samps >= total_num_samps) break; + //fill the buffer with the waveform for (size_t n = 0; n < buff.size(); n++){ buff[n] = wave_table(index += step); } //send the entire contents of the buffer - tx_stream->send(buffs, buff.size(), md); + num_acc_samps += tx_stream->send( + buffs, buff.size(), md + ); md.start_of_burst = false; md.has_time_spec = false; diff --git a/host/include/config.h.in b/host/include/config.h.in index bd690299e..83370f06f 100644 --- a/host/include/config.h.in +++ b/host/include/config.h.in @@ -21,4 +21,5 @@ #cmakedefine UHD_VERSION_MAJOR ${TRIMMED_VERSION_MAJOR} #cmakedefine UHD_VERSION_MINOR ${TRIMMED_VERSION_MINOR} #cmakedefine UHD_VERSION_PATCH ${TRIMMED_VERSION_PATCH} +#cmakedefine ENABLE_USB #cmakedefine UHD_VERSION @UHD_VERSION_ADDED@ diff --git a/host/include/uhd/CMakeLists.txt b/host/include/uhd/CMakeLists.txt index 083ec4951..e55141549 100644 --- a/host/include/uhd/CMakeLists.txt +++ b/host/include/uhd/CMakeLists.txt @@ -27,6 +27,7 @@ CONFIGURE_FILE( ) UHD_INSTALL(FILES + build_info.hpp config.hpp convert.hpp deprecated.hpp diff --git a/host/include/uhd/build_info.hpp b/host/include/uhd/build_info.hpp new file mode 100644 index 000000000..2e6571ad0 --- /dev/null +++ b/host/include/uhd/build_info.hpp @@ -0,0 +1,55 @@ +// +// Copyright 2015 National Instruments Corp. +// +// 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_UHD_BUILD_INFO_HPP +#define INCLUDED_UHD_BUILD_INFO_HPP + +#include <uhd/config.hpp> + +#include <string> + +namespace uhd { namespace build_info { + + //! Return the version of Boost this build was built with. + UHD_API const std::string boost_version(); + + //! Return the date and time (GMT) this UHD build was built. + UHD_API const std::string build_date(); + + //! Return the C compiler used for this build. + UHD_API const std::string c_compiler(); + + //! Return the C++ compiler used for this build. + UHD_API const std::string cxx_compiler(); + + //! Return the C flags passed into this build. + UHD_API const std::string c_flags(); + + //! Return the C++ flags passed into this build. + UHD_API const std::string cxx_flags(); + + //! Return the UHD components enabled for this build, comma-delimited. + UHD_API const std::string enabled_components(); + + //! Return the default CMake install prefix for this build. + UHD_API const std::string install_prefix(); + + //! Return the version of libusb this build was built with. + UHD_API const std::string libusb_version(); +}} + +#endif /* INCLUDED_UHD_BUILD_INFO_HPP */ diff --git a/host/include/uhd/config.hpp b/host/include/uhd/config.hpp index 8939cd773..ecd260675 100644 --- a/host/include/uhd/config.hpp +++ b/host/include/uhd/config.hpp @@ -53,6 +53,7 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT __declspec(dllexport) #define UHD_IMPORT __declspec(dllimport) #define UHD_INLINE __forceinline + #define UHD_FORCE_INLINE __forceinline #define UHD_DEPRECATED __declspec(deprecated) #define UHD_ALIGNED(x) __declspec(align(x)) #define UHD_UNUSED(x) x @@ -60,6 +61,7 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT __declspec(dllexport) #define UHD_IMPORT __declspec(dllimport) #define UHD_INLINE inline + #define UHD_FORCE_INLINE inline #define UHD_DEPRECATED __declspec(deprecated) #define UHD_ALIGNED(x) __declspec(align(x)) #define UHD_UNUSED(x) x __attribute__((unused)) @@ -67,6 +69,7 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT __attribute__((visibility("default"))) #define UHD_IMPORT __attribute__((visibility("default"))) #define UHD_INLINE inline __attribute__((always_inline)) + #define UHD_FORCE_INLINE inline __attribute__((always_inline)) #define UHD_DEPRECATED __attribute__((deprecated)) #define UHD_ALIGNED(x) __attribute__((aligned(x))) #define UHD_UNUSED(x) x __attribute__((unused)) @@ -74,6 +77,7 @@ typedef ptrdiff_t ssize_t; #define UHD_EXPORT #define UHD_IMPORT #define UHD_INLINE inline + #define UHD_FORCE_INLINE inline #define UHD_DEPRECATED #define UHD_ALIGNED(x) #define UHD_UNUSED(x) x diff --git a/host/include/uhd/error.h b/host/include/uhd/error.h index f0ac41d1f..77216dc72 100644 --- a/host/include/uhd/error.h +++ b/host/include/uhd/error.h @@ -158,7 +158,7 @@ extern "C" { * strings into a buffer that can be queried with this function. Functions that * do take in UHD structs/handles will place their error strings in both locations. */ -uhd_error uhd_get_last_error( +UHD_API uhd_error uhd_get_last_error( char* error_out, size_t strbuffer_len ); diff --git a/host/include/uhd/property_tree.hpp b/host/include/uhd/property_tree.hpp index a92654ba2..93353568a 100644 --- a/host/include/uhd/property_tree.hpp +++ b/host/include/uhd/property_tree.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011,2014 Ettus Research LLC +// Copyright 2011,2014-2016 Ettus Research // // 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 @@ -27,8 +27,51 @@ namespace uhd{ /*! - * A templated property interface for holding a value + * A templated property interface for holding the state + * associated with a property in a uhd::property_tree * and registering callbacks when that value changes. + * + * A property is defined to have two separate vales: + * - Desired value: Value requested by the user + * - Coerced value: Value that was actually possible + * given HW and other requirements + * + * By default, the desired and coerced values are + * identical as long as the property is not coerced. + * A property can be coerced in two way: + * 1. Using a coercer: A callback function that takes + * in a desired value and produces a coerced value. + * A property must have *exactly one* coercer. + * 2. Manual coercion: Manually calling the set_coerced + * API fnction to coerce the value of the propery. In + * order to use manual coercion, the propery must be + * created with the MANUAL_COERCE mode. + * If the coerce mode for a property is AUTO_COERCE then + * it always has a coercer. If the set_coercer API is + * never used, then the default coercer is used which + * simply set the coerced value to the desired value. + * + * It is possible to get notified every time the desired + * or coerced values of a property potentially change + * using subscriber callbacks. Every property can have + * zero or more desired and coerced subscribers. + * + * If storing the property readback state in software is + * not appropriate (for example if it needs to be queried + * from hardware) then it is possible to use a publisher + * callback to get the value of the property. Calling + * get on the property will always call the publisher and + * the cached desired and coerced values are updated only + * using set* calls. A preprty must have *at most one* + * publisher. It is legal to have both a coercer + * and publisher for a property but the only way to access + * the desired and coerced values in that case would be by + * notification using the desired and coerced subscribers. + * Publishers are useful for creating read-only properties. + * + * Requirements for the template type T: + * - T must have a copy constructor + * - T must have an assignment operator */ template <typename T> class property : boost::noncopyable{ public: @@ -40,60 +83,107 @@ public: /*! * Register a coercer into the property. - * A coercer is a special subscriber that coerces the value. + * A coercer is a callback function that updates the + * coerced value of a property. + * * Only one coercer may be registered per property. - * Registering a coercer replaces the previous coercer. * \param coercer the coercer callback function * \return a reference to this property for chaining + * \throws uhd::assertion_error if called more than once */ - virtual property<T> &coerce(const coercer_type &coercer) = 0; + virtual property<T> &set_coercer(const coercer_type &coercer) = 0; /*! * Register a publisher into the property. - * A publisher is a special callback the provides the value. - * Publishers are useful for creating read-only properties. + * A publisher is a callback function the provides the value + * for a property. + * * Only one publisher may be registered per property. - * Registering a publisher replaces the previous publisher. * \param publisher the publisher callback function * \return a reference to this property for chaining + * \throws uhd::assertion_error if called more than once */ - virtual property<T> &publish(const publisher_type &publisher) = 0; + virtual property<T> &set_publisher(const publisher_type &publisher) = 0; /*! * Register a subscriber into the property. - * All subscribers are called when the value changes. + * All desired subscribers are called when the desired value + * potentially changes. + * * Once a subscriber is registered, it cannot be unregistered. * \param subscriber the subscriber callback function * \return a reference to this property for chaining */ - virtual property<T> &subscribe(const subscriber_type &subscriber) = 0; + virtual property<T> &add_desired_subscriber(const subscriber_type &subscriber) = 0; + + /*! + * Register a subscriber into the property. + * All coerced subscribers are called when the coerced value + * potentially changes. + * + * Once a subscriber is registered, it cannot be unregistered. + * \param subscriber the subscriber callback function + * \return a reference to this property for chaining + */ + virtual property<T> &add_coerced_subscriber(const subscriber_type &subscriber) = 0; /*! * Update calls all subscribers w/ the current value. + * * \return a reference to this property for chaining + * \throws uhd::assertion_error */ virtual property<T> &update(void) = 0; /*! - * Set the new value and call all subscribers. - * The coercer (when provided) is called initially, - * and the coerced value is used to set the subscribers. + * Set the new value and call all the necessary subscribers. + * Order of operations: + * - The desired value of the property is updated + * - All desired subscribers are called + * - If coerce mode is AUTO then the coercer is called + * - If coerce mode is AUTO then all coerced subscribers are called + * * \param value the new value to set on this property * \return a reference to this property for chaining + * \throws uhd::assertion_error */ virtual property<T> &set(const T &value) = 0; /*! + * Set a coerced value and call all subscribers. + * The coercer is bypassed, and the specified value is + * used as the coerced value. All coerced subscribers + * are called. This function can only be used when the + * coerce mode is set to MANUAL_COERCE. + * + * \param value the new value to set on this property + * \return a reference to this property for chaining + * \throws uhd::assertion_error + */ + virtual property<T> &set_coerced(const T &value) = 0; + + /*! * Get the current value of this property. * The publisher (when provided) yields the value, - * otherwise an internal shadow is used for the value. + * otherwise an internal coerced value is returned. + * * \return the current value in the property + * \throws uhd::assertion_error */ - virtual T get(void) const = 0; + virtual const T get(void) const = 0; + + /*! + * Get the current desired value of this property. + * + * \return the current desired value in the property + * \throws uhd::assertion_error + */ + virtual const T get_desired(void) const = 0; /*! * A property is empty if it has never been set. * A property with a publisher is never empty. + * * \return true if the property is empty */ virtual bool empty(void) const = 0; @@ -129,6 +219,8 @@ class UHD_API property_tree : boost::noncopyable{ public: typedef boost::shared_ptr<property_tree> sptr; + enum coerce_mode_t { AUTO_COERCE, MANUAL_COERCE }; + virtual ~property_tree(void) = 0; //! Create a new + empty property tree @@ -147,7 +239,9 @@ public: virtual std::vector<std::string> list(const fs_path &path) const = 0; //! Create a new property entry in the tree - template <typename T> property<T> &create(const fs_path &path); + template <typename T> property<T> &create( + const fs_path &path, + coerce_mode_t coerce_mode = AUTO_COERCE); //! Get access to a property in the tree template <typename T> property<T> &access(const fs_path &path); diff --git a/host/include/uhd/property_tree.ipp b/host/include/uhd/property_tree.ipp index 93962c963..54c81870c 100644 --- a/host/include/uhd/property_tree.ipp +++ b/host/include/uhd/property_tree.ipp @@ -1,5 +1,5 @@ // -// Copyright 2011,2014 Ettus Research LLC +// Copyright 2011,2014-2016 Ettus Research // // 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 @@ -20,6 +20,7 @@ #include <uhd/exception.hpp> #include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> #include <vector> /*********************************************************************** @@ -29,23 +30,38 @@ namespace uhd{ namespace /*anon*/{ template <typename T> class property_impl : public property<T>{ public: + property_impl<T>(property_tree::coerce_mode_t mode) : _coerce_mode(mode){ + if (_coerce_mode == property_tree::AUTO_COERCE) { + _coercer = DEFAULT_COERCER; + } + } ~property_impl<T>(void){ /* NOP */ } - property<T> &coerce(const typename property<T>::coercer_type &coercer){ + property<T> &set_coercer(const typename property<T>::coercer_type &coercer){ + if (not _coercer.empty()) uhd::assertion_error("cannot register more than one coercer for a property"); + if (_coerce_mode == property_tree::MANUAL_COERCE) uhd::assertion_error("cannot register coercer for a manually coerced property"); + _coercer = coercer; return *this; } - property<T> &publish(const typename property<T>::publisher_type &publisher){ + property<T> &set_publisher(const typename property<T>::publisher_type &publisher){ + if (not _publisher.empty()) uhd::assertion_error("cannot register more than one publisher for a property"); + _publisher = publisher; return *this; } - property<T> &subscribe(const typename property<T>::subscriber_type &subscriber){ - _subscribers.push_back(subscriber); + property<T> &add_desired_subscriber(const typename property<T>::subscriber_type &subscriber){ + _desired_subscribers.push_back(subscriber); + return *this; + } + + property<T> &add_coerced_subscriber(const typename property<T>::subscriber_type &subscriber){ + _coerced_subscribers.push_back(subscriber); return *this; } @@ -54,17 +70,47 @@ public: return *this; } + void _set_coerced(const T &value){ + init_or_set_value(_coerced_value, value); + BOOST_FOREACH(typename property<T>::subscriber_type &csub, _coerced_subscribers){ + csub(get_value_ref(_coerced_value)); //let errors propagate + } + } + property<T> &set(const T &value){ - _value = boost::shared_ptr<T>(new T(_coercer.empty()? value : _coercer(value))); - BOOST_FOREACH(typename property<T>::subscriber_type &subscriber, _subscribers){ - subscriber(*_value); //let errors propagate + init_or_set_value(_value, value); + BOOST_FOREACH(typename property<T>::subscriber_type &dsub, _desired_subscribers){ + dsub(get_value_ref(_value)); //let errors propagate + } + if (not _coercer.empty()) { + _set_coerced(_coercer(get_value_ref(_value))); + } else { + if (_coerce_mode == property_tree::AUTO_COERCE) uhd::assertion_error("coercer missing for an auto coerced property"); } return *this; } - T get(void) const{ - if (empty()) throw uhd::runtime_error("Cannot get() on an empty property"); - return _publisher.empty()? *_value : _publisher(); + property<T> &set_coerced(const T &value){ + if (_coerce_mode == property_tree::AUTO_COERCE) uhd::assertion_error("cannot set coerced value an auto coerced property"); + _set_coerced(value); + return *this; + } + + const T get(void) const{ + if (empty()) throw uhd::runtime_error("Cannot get() on an uninitialized (empty) property"); + if (not _publisher.empty()) { + return _publisher(); + } else { + if (_coerced_value.get() == NULL and _coerce_mode == property_tree::MANUAL_COERCE) + throw uhd::runtime_error("uninitialized coerced value for manually coerced attribute"); + return get_value_ref(_coerced_value); + } + } + + const T get_desired(void) const{ + if (_value.get() == NULL) throw uhd::runtime_error("Cannot get_desired() on an uninitialized (empty) property"); + + return get_value_ref(_value); } bool empty(void) const{ @@ -72,10 +118,30 @@ public: } private: - std::vector<typename property<T>::subscriber_type> _subscribers; - typename property<T>::publisher_type _publisher; - typename property<T>::coercer_type _coercer; - boost::shared_ptr<T> _value; + static T DEFAULT_COERCER(const T& value) { + return value; + } + + static void init_or_set_value(boost::scoped_ptr<T>& scoped_value, const T& init_val) { + if (scoped_value.get() == NULL) { + scoped_value.reset(new T(init_val)); + } else { + *scoped_value = init_val; + } + } + + static const T& get_value_ref(const boost::scoped_ptr<T>& scoped_value) { + if (scoped_value.get() == NULL) throw uhd::assertion_error("Cannot use uninitialized property data"); + return *scoped_value.get(); + } + + const property_tree::coerce_mode_t _coerce_mode; + std::vector<typename property<T>::subscriber_type> _desired_subscribers; + std::vector<typename property<T>::subscriber_type> _coerced_subscribers; + typename property<T>::publisher_type _publisher; + typename property<T>::coercer_type _coercer; + boost::scoped_ptr<T> _value; + boost::scoped_ptr<T> _coerced_value; }; }} //namespace uhd::/*anon*/ @@ -85,8 +151,8 @@ private: **********************************************************************/ namespace uhd{ - template <typename T> property<T> &property_tree::create(const fs_path &path){ - this->_create(path, typename boost::shared_ptr<property<T> >(new property_impl<T>())); + template <typename T> property<T> &property_tree::create(const fs_path &path, coerce_mode_t coerce_mode){ + this->_create(path, typename boost::shared_ptr<property<T> >(new property_impl<T>(coerce_mode))); return this->access<T>(path); } diff --git a/host/include/uhd/types/CMakeLists.txt b/host/include/uhd/types/CMakeLists.txt index 3f34782e2..682d3cd9b 100644 --- a/host/include/uhd/types/CMakeLists.txt +++ b/host/include/uhd/types/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2011,2015 Ettus Research LLC +# Copyright 2010-2011,2015-2016 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 diff --git a/host/include/uhd/types/filters.hpp b/host/include/uhd/types/filters.hpp index 976ae233d..2c30c1007 100644 --- a/host/include/uhd/types/filters.hpp +++ b/host/include/uhd/types/filters.hpp @@ -55,12 +55,12 @@ namespace uhd{ //NOP } - inline virtual bool is_bypassed() + UHD_INLINE virtual bool is_bypassed() { return _bypass; } - inline filter_type get_type() + UHD_INLINE filter_type get_type() { return _type; } @@ -98,7 +98,7 @@ namespace uhd{ //NOP } - inline const std::string& get_analog_type() + UHD_INLINE const std::string& get_analog_type() { return _analog_type; } @@ -128,17 +128,17 @@ namespace uhd{ //NOP } - inline double get_cutoff() + UHD_INLINE double get_cutoff() { return _cutoff; } - inline double get_rolloff() + UHD_INLINE double get_rolloff() { return _cutoff; } - inline void set_cutoff(const double cutoff) + UHD_INLINE void set_cutoff(const double cutoff) { _cutoff = cutoff; } @@ -181,32 +181,32 @@ namespace uhd{ //NOP } - inline double get_output_rate() + UHD_INLINE double get_output_rate() { return (_bypass ? _rate : (_rate / _decimation * _interpolation)); } - inline double get_input_rate() + UHD_INLINE double get_input_rate() { return _rate; } - inline double get_interpolation() + UHD_INLINE double get_interpolation() { return _interpolation; } - inline double get_decimation() + UHD_INLINE double get_decimation() { return _decimation; } - inline double get_tap_full_scale() + UHD_INLINE double get_tap_full_scale() { return _tap_full_scale; } - inline std::vector<tap_t>& get_taps() + UHD_INLINE std::vector<tap_t>& get_taps() { return _taps; } diff --git a/host/include/uhd/types/sensors.hpp b/host/include/uhd/types/sensors.hpp index 529e1e3e3..de1ed014f 100644 --- a/host/include/uhd/types/sensors.hpp +++ b/host/include/uhd/types/sensors.hpp @@ -91,6 +91,12 @@ namespace uhd{ const std::string &unit ); + /*! + * Create a sensor value from another sensor value. + * \param source the source sensor value to copy + */ + sensor_value_t(const sensor_value_t& source); + //! convert the sensor value to a boolean bool to_bool(void) const; @@ -101,21 +107,21 @@ namespace uhd{ double to_real(void) const; //! The name of the sensor value - const std::string name; + std::string name; /*! * The sensor value as a string. * For integer and real number types, this will be the output of the formatter. * For boolean types, the value will be the string literal "true" or "false". */ - const std::string value; + std::string value; /*! * The sensor value's unit type. * For boolean types, this will be the one of the two units * depending upon the value of the boolean true or false. */ - const std::string unit; + std::string unit; //! Enumeration of possible data types in a sensor enum data_type_t { @@ -126,10 +132,13 @@ namespace uhd{ }; //! The data type of the value - const data_type_t type; + data_type_t type; //! Convert this sensor value into a printable string std::string to_pp_string(void) const; + + //! Assignment operator for sensor value + sensor_value_t& operator=(const sensor_value_t& value); }; } //namespace uhd diff --git a/host/include/uhd/types/serial.hpp b/host/include/uhd/types/serial.hpp index 7b565c633..5b7f34fbd 100644 --- a/host/include/uhd/types/serial.hpp +++ b/host/include/uhd/types/serial.hpp @@ -118,13 +118,19 @@ namespace uhd{ //! on what edge is the miso data valid? edge_t miso_edge; + //! Set the clock speed for this transaction + bool use_custom_divider; + + //! Optionally set the SPI clock divider for this transaction + size_t divider; + /*! * Create a new spi config. * \param edge the default edge for mosi and miso */ spi_config_t(edge_t edge = EDGE_RISE); }; - + /*! * The SPI interface class. * Provides routines to transact SPI and do other useful things which haven't been defined yet. diff --git a/host/include/uhd/usrp/CMakeLists.txt b/host/include/uhd/usrp/CMakeLists.txt index e974f808d..fd37ef560 100644 --- a/host/include/uhd/usrp/CMakeLists.txt +++ b/host/include/uhd/usrp/CMakeLists.txt @@ -18,6 +18,7 @@ UHD_INSTALL(FILES #### dboard headers ### + fe_connection.hpp dboard_base.hpp dboard_eeprom.hpp dboard_id.hpp @@ -26,6 +27,7 @@ UHD_INSTALL(FILES ### utilities ### gps_ctrl.hpp + gpio_defs.hpp mboard_eeprom.hpp subdev_spec.hpp diff --git a/host/include/uhd/usrp/dboard_base.hpp b/host/include/uhd/usrp/dboard_base.hpp index 31b3643c7..b106a1ac6 100644 --- a/host/include/uhd/usrp/dboard_base.hpp +++ b/host/include/uhd/usrp/dboard_base.hpp @@ -44,6 +44,10 @@ public: //structors dboard_base(ctor_args_t); + virtual ~dboard_base() {} + + //post-construction initializer + virtual void initialize() {} protected: std::string get_subdev_name(void); @@ -67,6 +71,7 @@ public: * Create a new xcvr dboard object, override in subclasses. */ xcvr_dboard_base(ctor_args_t); + virtual ~xcvr_dboard_base() {} }; /*! @@ -79,6 +84,7 @@ public: * Create a new rx dboard object, override in subclasses. */ rx_dboard_base(ctor_args_t); + virtual ~rx_dboard_base() {} }; /*! @@ -91,6 +97,7 @@ public: * Create a new rx dboard object, override in subclasses. */ tx_dboard_base(ctor_args_t); + virtual ~tx_dboard_base() {} }; }} //namespace diff --git a/host/include/uhd/usrp/dboard_iface.hpp b/host/include/uhd/usrp/dboard_iface.hpp index 686deb48d..add22ffa3 100644 --- a/host/include/uhd/usrp/dboard_iface.hpp +++ b/host/include/uhd/usrp/dboard_iface.hpp @@ -22,6 +22,8 @@ #include <uhd/utils/pimpl.hpp> #include <uhd/types/serial.hpp> #include <uhd/types/time_spec.hpp> +#include <uhd/usrp/fe_connection.hpp> +#include <uhd/usrp/gpio_defs.hpp> #include <boost/shared_ptr.hpp> #include <boost/cstdint.hpp> #include <string> @@ -63,16 +65,9 @@ public: //! tells the host which unit to use enum unit_t{ - UNIT_RX = int('r'), - UNIT_TX = int('t') - }; - - //! possible atr registers - enum atr_reg_t{ - ATR_REG_IDLE = int('i'), - ATR_REG_TX_ONLY = int('t'), - ATR_REG_RX_ONLY = int('r'), - ATR_REG_FULL_DUPLEX = int('f') + UNIT_RX = int('r'), + UNIT_TX = int('t'), + UNIT_BOTH = int('b'), }; //! aux dac selection enums (per unit) @@ -89,6 +84,10 @@ public: AUX_ADC_B = int('b') }; + typedef uhd::usrp::gpio_atr::gpio_atr_reg_t atr_reg_t; + + virtual ~dboard_iface(void) {}; + /*! * Get special properties information for this dboard slot. * This call helps the dboard code to handle implementation @@ -123,8 +122,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_pin_ctrl( - unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back the pin control setting. @@ -132,7 +131,7 @@ public: * \param unit which unit rx or tx * \return the 16-bit settings value */ - virtual boost::uint16_t get_pin_ctrl(unit_t unit); + virtual boost::uint32_t get_pin_ctrl(unit_t unit) = 0; /*! * Set a daughterboard ATR register. @@ -143,8 +142,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_atr_reg( - unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back an ATR register setting. @@ -153,7 +152,7 @@ public: * \param reg which ATR register * \return the 16-bit settings value */ - virtual boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg); + virtual boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg) = 0; /*! * Set daughterboard GPIO data direction setting. @@ -163,8 +162,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_gpio_ddr( - unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back the GPIO data direction setting. @@ -172,7 +171,7 @@ public: * \param unit which unit rx or tx * \return the 16-bit settings value */ - virtual boost::uint16_t get_gpio_ddr(unit_t unit); + virtual boost::uint32_t get_gpio_ddr(unit_t unit) = 0; /*! * Set daughterboard GPIO pin output setting. @@ -182,8 +181,8 @@ public: * \param mask 16-bits, 0=do not change, 1=change value */ virtual void set_gpio_out( - unit_t unit, boost::uint16_t value, boost::uint16_t mask = 0xffff - ); + unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffff + ) = 0; /*! * Read back the GPIO pin output setting. @@ -191,15 +190,7 @@ public: * \param unit which unit rx or tx * \return the 16-bit settings value */ - virtual boost::uint16_t get_gpio_out(unit_t unit); - - /*! - * Setup the GPIO debug mux. - * - * \param unit which unit rx or tx - * \param which which debug: 0, 1 - */ - virtual void set_gpio_debug(unit_t unit, int which) = 0; + virtual boost::uint32_t get_gpio_out(unit_t unit) = 0; /*! * Read daughterboard GPIO pin values. @@ -207,7 +198,7 @@ public: * \param unit which unit rx or tx * \return the value of the gpio unit */ - virtual boost::uint16_t read_gpio(unit_t unit) = 0; + virtual boost::uint32_t read_gpio(unit_t unit) = 0; /*! * Write data to SPI bus peripheral. @@ -282,29 +273,24 @@ public: virtual double get_codec_rate(unit_t unit) = 0; /*! + * Configure the front-end connection parameters. + * + * \param unit which unit rx or tx + * \param fe_conn connection parameters class + */ + virtual void set_fe_connection(unit_t unit, const uhd::usrp::fe_connection_t& fe_conn) = 0; + + /*! * Get the command time. * \return the command time */ - virtual uhd::time_spec_t get_command_time(void); + virtual uhd::time_spec_t get_command_time(void) = 0; /*! * Set the command time. * \param t the time */ - virtual void set_command_time(const uhd::time_spec_t& t); - -private: - UHD_PIMPL_DECL(impl) _impl; - - virtual void _set_pin_ctrl(unit_t unit, boost::uint16_t value) = 0; - virtual void _set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint16_t value) = 0; - virtual void _set_gpio_ddr(unit_t unit, boost::uint16_t value) = 0; - virtual void _set_gpio_out(unit_t unit, boost::uint16_t value) = 0; - -protected: - dboard_iface(void); -public: - virtual ~dboard_iface(void); + virtual void set_command_time(const uhd::time_spec_t& t) = 0; }; diff --git a/host/include/uhd/usrp/dboard_manager.hpp b/host/include/uhd/usrp/dboard_manager.hpp index d3a3ffb5c..4ce943972 100644 --- a/host/include/uhd/usrp/dboard_manager.hpp +++ b/host/include/uhd/usrp/dboard_manager.hpp @@ -45,15 +45,17 @@ public: * Register a rx or tx dboard into the system. * For single subdevice boards, omit subdev_names. * \param dboard_id the dboard id (rx or tx) - * \param dboard_ctor the dboard constructor function pointer + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) * \param name the canonical name for the dboard represented * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) */ static void register_dboard( const dboard_id_t &dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0") + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL ); /*! @@ -61,16 +63,58 @@ public: * For single subdevice boards, omit subdev_names. * \param rx_dboard_id the rx unit dboard id * \param tx_dboard_id the tx unit dboard id - * \param dboard_ctor the dboard constructor function pointer + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) * \param name the canonical name for the dboard represented * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) */ static void register_dboard( const dboard_id_t &rx_dboard_id, const dboard_id_t &tx_dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0") + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL + ); + + /*! + * Register a restricted rx or tx dboard into the system. + * A restricted dboard does not add its dboard_iface object into the property tree. + * For single subdevice boards, omit subdev_names. + * The iface for a restricted board is not registered into the property tree. + * \param dboard_id the dboard id (rx or tx) + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) + * \param name the canonical name for the dboard represented + * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) + */ + static void register_dboard_restricted( + const dboard_id_t &dboard_id, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL + ); + + /*! + * Register a restricted xcvr dboard into the system. + * A restricted dboard does not add its dboard_iface object into the property tree. + * For single subdevice boards, omit subdev_names. + * The iface for a restricted board is not registered into the property tree. + * \param rx_dboard_id the rx unit dboard id + * \param tx_dboard_id the tx unit dboard id + * \param db_subdev_ctor the dboard sub-device constructor function pointer (one instance per subdev name) + * \param name the canonical name for the dboard represented + * \param subdev_names the names of the subdevs on this dboard + * \param db_container_ctor the dboard container constructor function pointer (one instance per dboard) + */ + static void register_dboard_restricted( + const dboard_id_t &rx_dboard_id, + const dboard_id_t &tx_dboard_id, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names = std::vector<std::string>(1, "0"), + dboard_ctor_t db_container_ctor = NULL ); /*! @@ -89,6 +133,20 @@ public: dboard_iface::sptr iface, property_tree::sptr subtree ); + + virtual ~dboard_manager() {} + + /*! + * Returns a vector of RX frontend (subdev) names + * \return a vector of names + */ + virtual const std::vector<std::string>& get_rx_frontends() const = 0; + + /*! + * Returns a vector of TX frontend (subdev) names + * \return a vector of names + */ + virtual const std::vector<std::string>& get_tx_frontends() const = 0; }; }} //namespace diff --git a/host/include/uhd/usrp/fe_connection.hpp b/host/include/uhd/usrp/fe_connection.hpp new file mode 100644 index 000000000..969246087 --- /dev/null +++ b/host/include/uhd/usrp/fe_connection.hpp @@ -0,0 +1,127 @@ +// +// Copyright 2016 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_UHD_USRP_FE_CONNECTION_HPP +#define INCLUDED_UHD_USRP_FE_CONNECTION_HPP + +#include <uhd/config.hpp> +#include <boost/operators.hpp> +#include <string> + +namespace uhd { namespace usrp { + + class UHD_API fe_connection_t : boost::equality_comparable<fe_connection_t> { + public: + /** Sampling mode. + * Represents the sampling architecture for the front-end + */ + enum sampling_t { + QUADRATURE, /**< Complex sampling (Complex input, Complex output). */ + HETERODYNE, /**< Heterodyne sampling (Real input, Complex output). Only one of the I and Q inputs is used. */ + REAL /**< Real sampling (Real input, Real output). Only one of the I and Q inputs is used. */ + }; + + /*! + * Create a frontend connection class from individual settings. + * \param sampling_mode can be { QUADRATURE, HETERODYNE, REAL } + * \param iq_swapped indicates if the IQ channels are swapped (after inverion and heterodyne correction) + * \param i_inverted indicates if the I channel is inverted (negated) + * \param q_inverted indicates if the Q channel is inverted (negated) + * \param if_freq the baseband sampling frequency. + */ + fe_connection_t( + sampling_t sampling_mode, bool iq_swapped, + bool i_inverted, bool q_inverted, double if_freq = 0.0 + ); + + /*! + * Create a frontend connection class from a connection string + * The connection string can be: + * - in {I, Q}: Real mode sampling with no inversion. + * - in {Ib, Qb}: Real mode sampling with inversion. + * - in {IQ, QI}: Quadrature sampling with no inversion. + * - in {IbQb, QbIb}: Quadrature sampling with inversion. + * - in {II, QQ}: Heterodyne sampling with no inversion. + * - in {IbIb, QbQb}: Heterodyne sampling with inversion. + * + * \param conn_str the connection string. + * \param if_freq the baseband sampling frequency. + */ + fe_connection_t(const std::string& conn_str, double if_freq = 0.0); + + /*! + * Accessor for sampling mode + */ + inline sampling_t get_sampling_mode() const { + return _sampling_mode; + } + + /*! + * Accessor for IQ swap parameter + */ + inline bool is_iq_swapped() const { + return _iq_swapped; + } + + /*! + * Accessor for I inversion parameter + */ + inline bool is_i_inverted() const { + return _i_inverted; + } + + /*! + * Accessor for Q inversion parameter + */ + inline bool is_q_inverted() const { + return _q_inverted; + } + + /*! + * Accessor for IF frequency + */ + inline double get_if_freq() const { + return _if_freq; + } + + /*! + * Mutator for IF frequency + */ + inline void set_if_freq(double freq) { + _if_freq = freq; + } + + private: + sampling_t _sampling_mode; + bool _iq_swapped; + bool _i_inverted; + bool _q_inverted; + double _if_freq; + }; + + /*! + * Comparator operator overloaded for fe_connection_t. + * The boost::equality_comparable provides the !=. + * \param lhs the fe_connection_t to the left of the operator + * \param rhs the fe_connection_t to the right of the operator + * \return true when the fe connections are equal + */ + UHD_API bool operator==(const fe_connection_t &lhs, const fe_connection_t &rhs); + +}} //namespace + +#endif /* INCLUDED_UHD_USRP_FE_CONNECTION_HPP */ diff --git a/host/include/uhd/usrp/gpio_defs.hpp b/host/include/uhd/usrp/gpio_defs.hpp new file mode 100644 index 000000000..c32f22f28 --- /dev/null +++ b/host/include/uhd/usrp/gpio_defs.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2011,2014,2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_GPIO_DEFS_HPP +#define INCLUDED_LIBUHD_USRP_GPIO_DEFS_HPP + +#include <uhd/config.hpp> +#include <boost/assign.hpp> +#include <boost/utility.hpp> +#include <map> + +namespace uhd { namespace usrp { namespace gpio_atr { + +enum gpio_atr_reg_t { + ATR_REG_IDLE = int('i'), + ATR_REG_TX_ONLY = int('t'), + ATR_REG_RX_ONLY = int('r'), + ATR_REG_FULL_DUPLEX = int('f') +}; + +enum gpio_atr_mode_t { + MODE_ATR = 0, //Output driven by the auto-transmit-receive engine + MODE_GPIO = 1 //Output value is static +}; + +enum gpio_ddr_t { + DDR_INPUT = 0, + DDR_OUTPUT = 1 +}; + +enum gpio_attr_t { + GPIO_CTRL, + GPIO_DDR, + GPIO_OUT, + GPIO_ATR_0X, + GPIO_ATR_RX, + GPIO_ATR_TX, + GPIO_ATR_XX +}; + +typedef std::map<gpio_attr_t, std::string> gpio_attr_map_t; + +static const gpio_attr_map_t gpio_attr_map = + boost::assign::map_list_of + (GPIO_CTRL, "CTRL") + (GPIO_DDR, "DDR") + (GPIO_OUT, "OUT") + (GPIO_ATR_0X, "ATR_0X") + (GPIO_ATR_RX, "ATR_RX") + (GPIO_ATR_TX, "ATR_TX") + (GPIO_ATR_XX, "ATR_XX") +; + +}}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_GPIO_DEFS_HPP */ diff --git a/host/include/uhd/utils/algorithm.hpp b/host/include/uhd/utils/algorithm.hpp index 704d745d9..6c6cdf033 100644 --- a/host/include/uhd/utils/algorithm.hpp +++ b/host/include/uhd/utils/algorithm.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -39,7 +39,7 @@ namespace uhd{ * \param range the range of elements to be sorted * \return a new range with the elements sorted */ - template<typename Range> inline Range sorted(const Range &range){ + template<typename Range> UHD_INLINE Range sorted(const Range &range){ Range r(range); std::sort(boost::begin(r), boost::end(r)); return r; } @@ -53,7 +53,7 @@ namespace uhd{ * \param range the range of elements to be reversed * \return a new range with the elements reversed */ - template<typename Range> inline Range reversed(const Range &range){ + template<typename Range> UHD_INLINE Range reversed(const Range &range){ Range r(range); std::reverse(boost::begin(r), boost::end(r)); return r; } @@ -66,7 +66,7 @@ namespace uhd{ * \param value the match to look for in the range * \return true when the value is found in the range */ - template<typename Range, typename T> inline + template<typename Range, typename T> UHD_INLINE bool has(const Range &range, const T &value){ return boost::end(range) != std::find(boost::begin(range), boost::end(range), value); } @@ -78,7 +78,7 @@ namespace uhd{ * \param bound2 the upper or lower bound * \return the value clipped at the bounds */ - template<typename T> inline T clip(const T &val, const T &bound1, const T &bound2){ + template<typename T> UHD_INLINE T clip(const T &val, const T &bound1, const T &bound2){ const T minimum = std::min(bound1, bound2); if (val < minimum) return minimum; const T maximum = std::max(bound1, bound2); diff --git a/host/include/uhd/utils/atomic.hpp b/host/include/uhd/utils/atomic.hpp index 55769d2fd..8c5e6a5da 100644 --- a/host/include/uhd/utils/atomic.hpp +++ b/host/include/uhd/utils/atomic.hpp @@ -1,5 +1,5 @@ // -// Copyright 2012-2013 Ettus Research LLC +// Copyright 2012-2013,2016 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 @@ -26,11 +26,7 @@ #include <boost/interprocess/detail/atomic.hpp> #include <boost/version.hpp> -#if BOOST_VERSION >= 104800 -# define BOOST_IPC_DETAIL boost::interprocess::ipcdetail -#else -# define BOOST_IPC_DETAIL boost::interprocess::detail -#endif +#define BOOST_IPC_DETAIL boost::interprocess::ipcdetail namespace uhd{ diff --git a/host/include/uhd/utils/cast.hpp b/host/include/uhd/utils/cast.hpp index 9db92c526..869d53053 100644 --- a/host/include/uhd/utils/cast.hpp +++ b/host/include/uhd/utils/cast.hpp @@ -1,5 +1,5 @@ // -// Copyright 2014 Ettus Research LLC +// Copyright 2014-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ namespace uhd{ namespace cast{ // Example: // boost::uint16_t x = hexstr_cast<boost::uint16_t>("0xDEADBEEF"); // Uses stringstream. - template<typename T> inline T hexstr_cast(const std::string &in) + template<typename T> UHD_INLINE T hexstr_cast(const std::string &in) { T x; std::stringstream ss; diff --git a/host/include/uhd/utils/dirty_tracked.hpp b/host/include/uhd/utils/dirty_tracked.hpp index d228a9e65..561beec9b 100644 --- a/host/include/uhd/utils/dirty_tracked.hpp +++ b/host/include/uhd/utils/dirty_tracked.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -60,7 +60,7 @@ namespace uhd{ /*! * Get underlying data */ - inline const data_t& get() const { + UHD_INLINE const data_t& get() const { return _data; } @@ -68,21 +68,21 @@ namespace uhd{ * Has the underlying data changed since the last * time it was cleaned? */ - inline bool is_dirty() const { + UHD_INLINE bool is_dirty() const { return _dirty; } /*! * Mark the underlying data as clean */ - inline void mark_clean() { + UHD_INLINE void mark_clean() { _dirty = false; } /*! * Mark the underlying data as dirty */ - inline void force_dirty() { + UHD_INLINE void force_dirty() { _dirty = true; } @@ -91,7 +91,7 @@ namespace uhd{ * Store the specified value and mark it as dirty * if it is not equal to the underlying data. */ - inline dirty_tracked& operator=(const data_t& value) + UHD_INLINE dirty_tracked& operator=(const data_t& value) { if(!(_data == value)) { //data_t must have an equality operator _dirty = true; @@ -107,7 +107,7 @@ namespace uhd{ * This exists to optimize out an implicit cast from dirty_tracked * type to data type. */ - inline dirty_tracked& operator=(const dirty_tracked& source) { + UHD_INLINE dirty_tracked& operator=(const dirty_tracked& source) { if (!(_data == source._data)) { _dirty = true; _data = source._data; @@ -118,7 +118,7 @@ namespace uhd{ /*! * Explicit conversion from this type to data_t */ - inline operator const data_t&() const { + UHD_INLINE operator const data_t&() const { return get(); } diff --git a/host/include/uhd/utils/math.hpp b/host/include/uhd/utils/math.hpp index 088983167..0b35f1f17 100644 --- a/host/include/uhd/utils/math.hpp +++ b/host/include/uhd/utils/math.hpp @@ -32,19 +32,6 @@ namespace uhd { namespace math { /*! - * Numeric limits of certain types. - * - * There are many sources for getting these, including std::numeric_limits, - * `<cstdint>`, `<climits>`, and Boost. The `<cstdint>` option is preferable as it - * gives us fixed-width constants, but unfortunately is new as of C++11. - * Since this isn't available on many systems, we need to use one of the - * other options. We will use the Boost option, here, since we use Boost - * data types for portability across UHD. - */ - static const boost::int32_t BOOST_INT32_MAX = boost::numeric::bounds<boost::int32_t>::highest(); - static const boost::int32_t BOOST_INT32_MIN = boost::numeric::bounds<boost::int32_t>::lowest(); - - /*! * Define epsilon values for floating point comparisons. * * There are a lot of different sources for epsilon values that we could use diff --git a/host/include/uhd/utils/msg_task.hpp b/host/include/uhd/utils/msg_task.hpp index d46fdd69e..8ae789d72 100644 --- a/host/include/uhd/utils/msg_task.hpp +++ b/host/include/uhd/utils/msg_task.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2014 Ettus Research LLC +// Copyright 2011-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -42,7 +42,7 @@ namespace uhd{ */ virtual msg_payload_t get_msg_from_dump_queue(boost::uint32_t sid) = 0; - inline static std::vector<boost::uint8_t> buff_to_vector(boost::uint8_t* p, size_t n) { + UHD_INLINE static std::vector<boost::uint8_t> buff_to_vector(boost::uint8_t* p, size_t n) { if(p and n > 0){ std::vector<boost::uint8_t> v(n); memcpy(&v.front(), p, n); diff --git a/host/include/uhd/utils/soft_register.hpp b/host/include/uhd/utils/soft_register.hpp index a2c34a4ec..a6184bbb9 100644 --- a/host/include/uhd/utils/soft_register.hpp +++ b/host/include/uhd/utils/soft_register.hpp @@ -57,7 +57,7 @@ namespace uhd { //TODO: These hints were added to boost 1.53. /** \brief hint for the branch prediction */ -inline bool likely(bool expr) +UHD_INLINE bool likely(bool expr) { #ifdef __GNUC__ return __builtin_expect(expr, true); @@ -67,7 +67,7 @@ inline bool likely(bool expr) } /** \brief hint for the branch prediction */ -inline bool unlikely(bool expr) +UHD_INLINE bool unlikely(bool expr) { #ifdef __GNUC__ return __builtin_expect(expr, false); @@ -86,16 +86,16 @@ inline bool unlikely(bool expr) typedef boost::uint32_t soft_reg_field_t; namespace soft_reg_field { - inline size_t width(const soft_reg_field_t field) { + UHD_INLINE size_t width(const soft_reg_field_t field) { return (field & 0xFF); } - inline size_t shift(const soft_reg_field_t field) { + UHD_INLINE size_t shift(const soft_reg_field_t field) { return ((field >> 8) & 0xFF); } template<typename data_t> - inline size_t mask(const soft_reg_field_t field) { + UHD_INLINE size_t mask(const soft_reg_field_t field) { static const data_t ONE = static_cast<data_t>(1); //Behavior for the left shift operation is undefined in C++ //if the shift amount is >= bitwidth of the datatype @@ -122,7 +122,7 @@ public: * Cast the soft_register generic reference to a more specific type */ template <typename soft_reg_t> - inline static soft_reg_t& cast(soft_register_base& reg) { + UHD_INLINE static soft_reg_t& cast(soft_register_base& reg) { soft_reg_t* ptr = dynamic_cast<soft_reg_t*>(®); if (ptr) { return *ptr; @@ -172,7 +172,7 @@ public: * Can be optionally synced with hardware. * NOTE: Memory management of the iface is up to the caller */ - inline void initialize(wb_iface& iface, bool sync = false) + UHD_INLINE void initialize(wb_iface& iface, bool sync = false) { _iface = &iface; @@ -186,7 +186,7 @@ public: * Performs a read-modify-write operation so all other field are preserved. * NOTE: This does not write the value to hardware. */ - inline void set(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void set(const soft_reg_field_t field, const reg_data_t value) { _soft_copy = (_soft_copy & ~soft_reg_field::mask<reg_data_t>(field)) | ((value << soft_reg_field::shift(field)) & soft_reg_field::mask<reg_data_t>(field)); @@ -196,7 +196,7 @@ public: * Get the value of the specified field from the soft-copy. * NOTE: This does not read anything from hardware. */ - inline reg_data_t get(const soft_reg_field_t field) + UHD_INLINE reg_data_t get(const soft_reg_field_t field) { return (_soft_copy & soft_reg_field::mask<reg_data_t>(field)) >> soft_reg_field::shift(field); } @@ -204,7 +204,7 @@ public: /*! * Write the contents of the soft-copy to hardware. */ - inline void flush() + UHD_INLINE void flush() { if (writable && _iface) { //If optimized flush then poke only if soft copy is dirty @@ -223,14 +223,14 @@ public: _soft_copy.mark_clean(); } } else { - throw uhd::not_implemented_error("soft_register is not writable."); + throw uhd::not_implemented_error("soft_register is not writable or uninitialized."); } } /*! * Read the contents of the register from hardware and update the soft copy. */ - inline void refresh() + UHD_INLINE void refresh() { if (readable && _iface) { if (get_bitwidth() <= 16) { @@ -244,14 +244,14 @@ public: } _soft_copy.mark_clean(); } else { - throw uhd::not_implemented_error("soft_register is not readable."); + throw uhd::not_implemented_error("soft_register is not readable or uninitialized."); } } /*! * Shortcut for a set and a flush. */ - inline void write(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void write(const soft_reg_field_t field, const reg_data_t value) { set(field, value); flush(); @@ -260,7 +260,7 @@ public: /*! * Shortcut for refresh and get */ - inline reg_data_t read(const soft_reg_field_t field) + UHD_INLINE reg_data_t read(const soft_reg_field_t field) { refresh(); return get(field); @@ -269,7 +269,7 @@ public: /*! * Get bitwidth for this register */ - inline size_t get_bitwidth() + UHD_INLINE size_t get_bitwidth() { static const size_t BITS_IN_BYTE = 8; return sizeof(reg_data_t) * BITS_IN_BYTE; @@ -278,7 +278,7 @@ public: /*! * Is the register readable? */ - inline bool is_readable() + UHD_INLINE bool is_readable() { return readable; } @@ -286,7 +286,7 @@ public: /*! * Is the register writable? */ - inline bool is_writable() + UHD_INLINE bool is_writable() { return writable; } @@ -321,43 +321,43 @@ public: soft_register_t<reg_data_t, readable, writable>(addr, mode), _mutex() {} - inline void initialize(wb_iface& iface, bool sync = false) + UHD_INLINE void initialize(wb_iface& iface, bool sync = false) { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::initialize(iface, sync); } - inline void set(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void set(const soft_reg_field_t field, const reg_data_t value) { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::set(field, value); } - inline reg_data_t get(const soft_reg_field_t field) + UHD_INLINE reg_data_t get(const soft_reg_field_t field) { boost::lock_guard<boost::mutex> lock(_mutex); return soft_register_t<reg_data_t, readable, writable>::get(field); } - inline void flush() + UHD_INLINE void flush() { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::flush(); } - inline void refresh() + UHD_INLINE void refresh() { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::refresh(); } - inline void write(const soft_reg_field_t field, const reg_data_t value) + UHD_INLINE void write(const soft_reg_field_t field, const reg_data_t value) { boost::lock_guard<boost::mutex> lock(_mutex); soft_register_t<reg_data_t, readable, writable>::write(field, value); } - inline reg_data_t read(const soft_reg_field_t field) + UHD_INLINE reg_data_t read(const soft_reg_field_t field) { boost::lock_guard<boost::mutex> lock(_mutex); return soft_register_t<reg_data_t, readable, writable>::read(field); @@ -469,7 +469,7 @@ public: /*! * Get the name of this register map */ - virtual inline const std::string& get_name() const { return _name; } + virtual UHD_INLINE const std::string& get_name() const { return _name; } /*! * Initialize all registers in this register map using a bus. @@ -542,7 +542,7 @@ protected: /*! * Add a register to this map with an identifier "name" and visibility */ - inline void add_to_map(soft_register_base& reg, const std::string& name, const visibility_t visible = PRIVATE) { + UHD_INLINE void add_to_map(soft_register_base& reg, const std::string& name, const visibility_t visible = PRIVATE) { boost::lock_guard<boost::mutex> lock(_mutex); if (visible == PUBLIC) { //Only add to the map if this register is publicly visible diff --git a/host/include/uhd/version.hpp.in b/host/include/uhd/version.hpp.in index e2c64812d..bfa0b904a 100644 --- a/host/include/uhd/version.hpp.in +++ b/host/include/uhd/version.hpp.in @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ * The format is oldest API compatible release - ABI compat number. * The compatibility number allows pre-release ABI to be versioned. */ -#define UHD_VERSION_ABI_STRING "3.9.0-0" +#define UHD_VERSION_ABI_STRING "3.10.0-0" /*! * A macro to check UHD version at compile-time. diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index f74af1f29..a7a1cea3b 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -65,6 +65,26 @@ MACRO(INCLUDE_SUBDIRECTORY subdir) ENDMACRO(INCLUDE_SUBDIRECTORY) ######################################################################## +# Register lower level components +######################################################################## +MESSAGE(STATUS "") +# Dependencies +FIND_PACKAGE(USB1) +FIND_PACKAGE(GPSD) +LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF OFF) +LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF) +# Devices +LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) +LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) +LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF OFF) +LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) +LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF) + +######################################################################## # Include subdirectories (different than add) ######################################################################## INCLUDE_SUBDIRECTORY(ic_reg_maps) @@ -74,6 +94,17 @@ INCLUDE_SUBDIRECTORY(transport) INCLUDE_SUBDIRECTORY(usrp) INCLUDE_SUBDIRECTORY(usrp_clock) INCLUDE_SUBDIRECTORY(utils) +INCLUDE_SUBDIRECTORY(experts) + +######################################################################## +# Build info +######################################################################## +INCLUDE(UHDBuildInfo) +UHD_LOAD_BUILD_INFO() +CONFIGURE_FILE( + ${CMAKE_CURRENT_SOURCE_DIR}/build_info.cpp + ${CMAKE_CURRENT_BINARY_DIR}/build_info.cpp +@ONLY) ######################################################################## # Setup UHD_VERSION_STRING for version.cpp @@ -87,6 +118,7 @@ CONFIGURE_FILE( # Append to the list of sources for lib uhd ######################################################################## LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_BINARY_DIR}/build_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/deprecated.cpp ${CMAKE_CURRENT_SOURCE_DIR}/device.cpp ${CMAKE_CURRENT_SOURCE_DIR}/image_loader.cpp diff --git a/host/lib/build_info.cpp b/host/lib/build_info.cpp new file mode 100644 index 000000000..5ccfd0268 --- /dev/null +++ b/host/lib/build_info.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2015 National Instruments Corp. +// +// 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 <config.h> + +#include <uhd/build_info.hpp> + +#include <boost/format.hpp> +#include <boost/version.hpp> +#include <boost/algorithm/string.hpp> + +#ifdef ENABLE_USB +#include <libusb.h> +#endif + +namespace uhd { namespace build_info { + + const std::string boost_version() { + return boost::algorithm::replace_all_copy( + std::string(BOOST_LIB_VERSION), "_", "." + ); + } + + const std::string build_date() { + return "@UHD_BUILD_DATE@"; + } + + const std::string c_compiler() { + return "@UHD_C_COMPILER@"; + } + + const std::string cxx_compiler() { + return "@UHD_CXX_COMPILER@"; + } + +#ifdef _MSC_VER + static const std::string define_flag = "/D "; +#else + static const std::string define_flag = "-D"; +#endif + + const std::string c_flags() { + return boost::algorithm::replace_all_copy( + (define_flag + std::string("@UHD_C_FLAGS@")), + std::string(";"), (" " + define_flag) + ); + } + + const std::string cxx_flags() { + return boost::algorithm::replace_all_copy( + (define_flag + std::string("@UHD_CXX_FLAGS@")), + std::string(";"), (" " + define_flag) + ); + } + + const std::string enabled_components() { + return boost::algorithm::replace_all_copy( + std::string("@_uhd_enabled_components@"), + std::string(";"), std::string(", ") + ); + } + + const std::string install_prefix() { + return "@CMAKE_INSTALL_PREFIX@"; + } + + const std::string libusb_version() { + #ifdef ENABLE_USB + /* + * Versions can only be queried from 1.0.13 onward. + * Depending on if the commit came from libusbx or + * libusb (now merged), the define might be different. + */ + #ifdef LIBUSB_API_VERSION /* 1.0.18 onward */ + int major_version = LIBUSB_API_VERSION >> 24; + int minor_version = (LIBUSB_API_VERSION & 0xFF0000) >> 16; + int micro_version = ((LIBUSB_API_VERSION & 0xFFFF) - 0x100) + 18; + + return str(boost::format("%d.%d.%d") + % major_version % minor_version % micro_version); + #elif defined(LIBUSBX_API_VERSION) /* 1.0.13 - 1.0.17 */ + switch(LIBUSBX_API_VERSION & 0xFF) { + case 0x00: + return "1.0.13"; + case 0x01: + return "1.0.15"; + case 0xFF: + return "1.0.14"; + default: + return "1.0.16 or 1.0.17"; + } + #else + return "< 1.0.13"; + #endif + #else + return "N/A"; + #endif + } +}} diff --git a/host/lib/convert/convert_item32.cpp b/host/lib/convert/convert_item32.cpp index 57bd64860..d52b47a1a 100644 --- a/host/lib/convert/convert_item32.cpp +++ b/host/lib/convert/convert_item32.cpp @@ -38,7 +38,10 @@ _DECLARE_ITEM32_CONVERTER(cpu_type, sc8) \ _DECLARE_ITEM32_CONVERTER(cpu_type, sc16) +/* Create sc16<->sc16,sc8(otw) */ DECLARE_ITEM32_CONVERTER(sc16) +/* Create fc32<->sc16,sc8(otw) */ DECLARE_ITEM32_CONVERTER(fc32) +/* Create fc64<->sc16,sc8(otw) */ DECLARE_ITEM32_CONVERTER(fc64) _DECLARE_ITEM32_CONVERTER(sc8, sc8) diff --git a/host/lib/convert/gen_convert_general.py b/host/lib/convert/gen_convert_general.py index 4f9eeb747..5c62d51df 100644 --- a/host/lib/convert/gen_convert_general.py +++ b/host/lib/convert/gen_convert_general.py @@ -39,30 +39,37 @@ DECLARE_CONVERTER(item32, 1, item32, 1, PRIORITY_GENERAL) { } """ -TMPL_CONV_GEN2_ITEM32 = """ -DECLARE_CONVERTER(item32, 1, sc16_item32_{end}, 1, PRIORITY_GENERAL) {{ +# Some 32-bit types converters are also defined in convert_item32.cpp to +# take care of quirks such as I/Q ordering on the wire etc. +TMPL_CONV_ITEM32 = """ +DECLARE_CONVERTER({in_type}, 1, {out_type}, 1, PRIORITY_GENERAL) {{ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); for (size_t i = 0; i < nsamps; i++) {{ - output[i] = {to_wire}(input[i]); + output[i] = {to_wire_or_host}(input[i]); }} }} +""" -DECLARE_CONVERTER(sc16_item32_{end}, 1, item32, 1, PRIORITY_GENERAL) {{ +# 64-bit data types are two consecutive item32 items +TMPL_CONV_ITEM64 = """ +DECLARE_CONVERTER({in_type}, 1, {out_type}, 1, PRIORITY_GENERAL) {{ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); - for (size_t i = 0; i < nsamps; i++) {{ - output[i] = {to_host}(input[i]); + // An item64 is two item32_t's + for (size_t i = 0; i < nsamps * 2; i++) {{ + output[i] = {to_wire_or_host}(input[i]); }} }} """ -TMPL_CONV_U8 = """ -DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{ - const boost::uint32_t *input = reinterpret_cast<const boost::uint32_t *>(inputs[0]); - boost::uint32_t *output = reinterpret_cast<boost::uint32_t *>(outputs[0]); + +TMPL_CONV_U8S8 = """ +DECLARE_CONVERTER({us8}, 1, {us8}_item32_{end}, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); // 1) Copy all the 4-byte tuples size_t n_words = nsamps / 4; @@ -72,8 +79,8 @@ DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{ // 2) If nsamps was not a multiple of 4, copy the rest by hand size_t bytes_left = nsamps % 4; if (bytes_left) {{ - const u8_t *last_input_word = reinterpret_cast<const u8_t *>(&input[n_words]); - u8_t *last_output_word = reinterpret_cast<u8_t *>(&output[n_words]); + const {us8}_t *last_input_word = reinterpret_cast<const {us8}_t *>(&input[n_words]); + {us8}_t *last_output_word = reinterpret_cast<{us8}_t *>(&output[n_words]); for (size_t k = 0; k < bytes_left; k++) {{ last_output_word[k] = last_input_word[k]; }} @@ -81,9 +88,9 @@ DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{ }} }} -DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{ - const boost::uint32_t *input = reinterpret_cast<const boost::uint32_t *>(inputs[0]); - boost::uint32_t *output = reinterpret_cast<boost::uint32_t *>(outputs[0]); +DECLARE_CONVERTER({us8}_item32_{end}, 1, {us8}, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); // 1) Copy all the 4-byte tuples size_t n_words = nsamps / 4; @@ -93,9 +100,9 @@ DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{ // 2) If nsamps was not a multiple of 4, copy the rest by hand size_t bytes_left = nsamps % 4; if (bytes_left) {{ - boost::uint32_t last_input_word = {to_host}(input[n_words]); - const u8_t *last_input_word_ptr = reinterpret_cast<const u8_t *>(&last_input_word); - u8_t *last_output_word = reinterpret_cast<u8_t *>(&output[n_words]); + item32_t last_input_word = {to_host}(input[n_words]); + const {us8}_t *last_input_word_ptr = reinterpret_cast<const {us8}_t *>(&last_input_word); + {us8}_t *last_output_word = reinterpret_cast<{us8}_t *>(&output[n_words]); for (size_t k = 0; k < bytes_left; k++) {{ last_output_word[k] = last_input_word_ptr[k]; }} @@ -103,6 +110,40 @@ DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{ }} """ +TMPL_CONV_S16 = """ +DECLARE_CONVERTER(s16, 1, s16_item32_{end}, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + // 1) Copy all the 4-byte tuples + size_t n_words = nsamps / 2; + for (size_t i = 0; i < n_words; i++) {{ + output[i] = {to_wire}(input[i]); + }} + // 2) If nsamps was not a multiple of 2, copy the last one by hand + if (nsamps % 2) {{ + item32_t tmp = item32_t(*reinterpret_cast<const s16_t *>(&input[n_words])); + output[n_words] = {to_wire}(tmp); + }} +}} + +DECLARE_CONVERTER(s16_item32_{end}, 1, s16, 1, PRIORITY_GENERAL) {{ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + // 1) Copy all the 4-byte tuples + size_t n_words = nsamps / 2; + for (size_t i = 0; i < n_words; i++) {{ + output[i] = {to_host}(input[i]); + }} + // 2) If nsamps was not a multiple of 2, copy the last one by hand + if (nsamps % 2) {{ + item32_t tmp = {to_host}(input[n_words]); + *reinterpret_cast<s16_t *>(&output[n_words]) = s16_t(tmp); + }} +}} +""" + TMPL_CONV_USRP1_COMPLEX = """ DECLARE_CONVERTER(${cpu_type}, ${width}, sc16_item16_usrp1, 1, PRIORITY_GENERAL){ % for w in range(width): @@ -164,23 +205,52 @@ if __name__ == '__main__': file = os.path.basename(__file__) output = parse_tmpl(TMPL_HEADER, file=file) - #generate complex converters for all gen2 platforms - for end, to_host, to_wire in ( - ('be', 'uhd::ntohx', 'uhd::htonx'), - ('le', 'uhd::wtohx', 'uhd::htowx'), - ): - output += TMPL_CONV_GEN2_ITEM32.format( - end=end, to_host=to_host, to_wire=to_wire - ) - #generate raw (u8) converters: + ## Generate all data types that are exactly + ## item32 or multiples thereof: + for end in ('be', 'le'): + host_to_wire = {'be': 'uhd::htonx', 'le': 'uhd::htowx'}[end] + wire_to_host = {'be': 'uhd::ntohx', 'le': 'uhd::wtohx'}[end] + # item32 types (sc16->sc16 is a special case because it defaults + # to Q/I order on the wire: + for in_type, out_type, to_wire_or_host in ( + ('item32', 'sc16_item32_{end}', host_to_wire), + ('sc16_item32_{end}', 'item32', wire_to_host), + ('f32', 'f32_item32_{end}', host_to_wire), + ('f32_item32_{end}', 'f32', wire_to_host), + ): + output += TMPL_CONV_ITEM32.format( + end=end, to_wire_or_host=to_wire_or_host, + in_type=in_type.format(end=end), out_type=out_type.format(end=end) + ) + # 2xitem32 types: + for in_type, out_type in ( + ('fc32', 'fc32_item32_{end}'), + ('fc32_item32_{end}', 'fc32'), + ): + output += TMPL_CONV_ITEM64.format( + end=end, to_wire_or_host=to_wire_or_host, + in_type=in_type.format(end=end), out_type=out_type.format(end=end) + ) + + ## Real 16-Bit: for end, to_host, to_wire in ( ('be', 'uhd::ntohx', 'uhd::htonx'), ('le', 'uhd::wtohx', 'uhd::htowx'), ): - output += TMPL_CONV_U8.format( - end=end, to_host=to_host, to_wire=to_wire + output += TMPL_CONV_S16.format( + end=end, to_host=to_host, to_wire=to_wire ) + ## Real 8-Bit Types: + for us8 in ('u8', 's8'): + for end, to_host, to_wire in ( + ('be', 'uhd::ntohx', 'uhd::htonx'), + ('le', 'uhd::wtohx', 'uhd::htowx'), + ): + output += TMPL_CONV_U8S8.format( + us8=us8, end=end, to_host=to_host, to_wire=to_wire + ) + #generate complex converters for usrp1 format (requires Cheetah) for width in 1, 2, 4: for cpu_type, do_scale in ( diff --git a/host/lib/experts/CMakeLists.txt b/host/lib/experts/CMakeLists.txt new file mode 100644 index 000000000..db533e7fa --- /dev/null +++ b/host/lib/experts/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright 2016 Ettus Research +# +# 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 +######################################################################## +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/expert_container.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/expert_factory.cpp +) + +# Verbose Debug output for send/recv +SET( UHD_EXPERT_LOGGING OFF CACHE BOOL "Enable verbose logging for experts" ) +OPTION( UHD_EXPERT_LOGGING "Enable verbose logging for experts" "" ) +IF(UHD_EXPERT_LOGGING) + MESSAGE(STATUS "Enabling verbose logging for experts") + ADD_DEFINITIONS(-DUHD_EXPERT_LOGGING) +ENDIF() diff --git a/host/lib/experts/expert_container.cpp b/host/lib/experts/expert_container.cpp new file mode 100644 index 000000000..edfc2ebe3 --- /dev/null +++ b/host/lib/experts/expert_container.cpp @@ -0,0 +1,531 @@ +// +// Copyright 2016 Ettus Research +// +// 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 "expert_container.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread.hpp> +#include <boost/graph/graph_traits.hpp> +#include <boost/graph/depth_first_search.hpp> +#include <boost/graph/topological_sort.hpp> +#include <boost/graph/adjacency_list.hpp> + +#ifdef UHD_EXPERT_LOGGING +#define EX_LOG(depth, str) _log(depth, str) +#else +#define EX_LOG(depth, str) +#endif + +namespace uhd { namespace experts { + +typedef boost::adjacency_list< + boost::vecS, //Container used to represent the edge-list for each of the vertices. + boost::vecS, //container used to represent the vertex-list of the graph. + boost::directedS, //Directionality of graph + dag_vertex_t*, //Storage for each vertex + boost::no_property, //Storage for each edge + boost::no_property, //Storage for graph object + boost::listS //Container used to represent the edge-list for the graph. +> expert_graph_t; + +typedef std::map<std::string, expert_graph_t::vertex_descriptor> vertex_map_t; +typedef std::list<expert_graph_t::vertex_descriptor> node_queue_t; + +typedef boost::graph_traits<expert_graph_t>::edge_iterator edge_iter; +typedef boost::graph_traits<expert_graph_t>::vertex_iterator vertex_iter; + +class expert_container_impl : public expert_container +{ +private: //Visitor class for cycle detection algorithm + struct cycle_det_visitor : public boost::dfs_visitor<> + { + cycle_det_visitor(std::vector<std::string>& back_edges): + _back_edges(back_edges) {} + + template <class Edge, class Graph> + void back_edge(Edge u, const Graph& g) { + _back_edges.push_back( + g[boost::source(u,g)]->get_name() + "->" + g[boost::target(u,g)]->get_name()); + } + private: std::vector<std::string>& _back_edges; + }; + +public: + expert_container_impl(const std::string& name): + _name(name) + { + } + + ~expert_container_impl() + { + clear(); + } + + const std::string& get_name() const + { + return _name; + } + + void resolve_all(bool force = false) + { + boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, str(boost::format("resolve_all(%s)") % (force?"force":""))); + _resolve_helper("", "", force); + } + + void resolve_from(const std::string& node_name) + { + boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, str(boost::format("resolve_from(%s)") % node_name)); + _resolve_helper(node_name, "", false); + } + + void resolve_to(const std::string& node_name) + { + boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, str(boost::format("resolve_to(%s)") % node_name)); + _resolve_helper("", node_name, false); + } + + dag_vertex_t& retrieve(const std::string& name) const + { + try { + expert_graph_t::vertex_descriptor vertex = _lookup_vertex(name); + return _get_vertex(vertex); + } catch(std::exception&) { + throw uhd::lookup_error("failed to find node " + name + " in expert graph"); + } + } + + const dag_vertex_t& lookup(const std::string& name) const + { + return retrieve(name); + } + + const node_retriever_t& node_retriever() const + { + return *this; + } + + std::string to_dot() const + { + static const std::string DATA_SHAPE("ellipse"); + static const std::string WORKER_SHAPE("box"); + + std::string dot_str; + dot_str += "digraph uhd_experts_" + _name + " {\n rankdir=LR;\n"; + // Iterate through the vertices and print them out + for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag); + vi.first != vi.second; + ++vi.first + ) { + const dag_vertex_t& vertex = _get_vertex(*vi.first); + if (vertex.get_class() != CLASS_WORKER) { + dot_str += str(boost::format(" %d [label=\"%s\",shape=%s,xlabel=\"%s\"];\n") % + boost::uint32_t(*vi.first) % vertex.get_name() % + DATA_SHAPE % vertex.get_dtype()); + } else { + dot_str += str(boost::format(" %d [label=\"%s\",shape=%s];\n") % + boost::uint32_t(*vi.first) % vertex.get_name() % WORKER_SHAPE); + } + } + + // Iterate through the edges and print them out + for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); + ei.first != ei.second; + ++ei.first + ) { + dot_str += str(boost::format(" %d -> %d;\n") % + boost::uint32_t(boost::source(*(ei.first), _expert_dag)) % + boost::uint32_t(boost::target(*(ei.first), _expert_dag))); + } + dot_str += "}\n"; + return dot_str; + } + + void debug_audit() const + { +#ifdef UHD_EXPERT_LOGGING + EX_LOG(0, "debug_audit()"); + + //Test 1: Check for cycles in graph + std::vector<std::string> back_edges; + cycle_det_visitor cdet_vis(back_edges); + boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis)); + if (back_edges.empty()) { + EX_LOG(1, "cycle check ... PASSED"); + } else { + EX_LOG(1, "cycle check ... ERROR!!!"); + BOOST_FOREACH(const std::string& e, back_edges) { + EX_LOG(2, "back edge: " + e); + } + } + back_edges.clear(); + + //Test 2: Check data node input and output edges + std::vector<std::string> data_node_issues; + BOOST_FOREACH(const vertex_map_t::value_type& v, _datanode_map) { + size_t in_count = 0, out_count = 0; + for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); + ei.first != ei.second; + ++ei.first + ) { + if (boost::target(*(ei.first), _expert_dag) == v.second) + in_count++; + if (boost::source(*(ei.first), _expert_dag) == v.second) + out_count++; + } + bool prop_unused = false; + if (in_count > 1) { + data_node_issues.push_back(v.first + ": multiple writers (workers)"); + } else if (in_count > 0) { + if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY) { + data_node_issues.push_back(v.first + ": multiple writers (worker and property tree)"); + } + } else { + if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) { + data_node_issues.push_back(v.first + ": unreachable (will always hold initial value)"); + } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_write_callback()) { + if (out_count > 0) { + data_node_issues.push_back(v.first + ": needs explicit resolve after write"); + } else { + data_node_issues.push_back(v.first + ": unused (no readers or writers)"); + prop_unused = true; + } + } + } + if (out_count < 1) { + if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) { + data_node_issues.push_back(v.first + ": unused (is not read by any worker)"); + } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_read_callback()) { + if (not prop_unused) { + data_node_issues.push_back(v.first + ": needs explicit resolve to read"); + } + } + } + } + + if (data_node_issues.empty()) { + EX_LOG(1, "data node check ... PASSED"); + } else { + EX_LOG(1, "data node check ... WARNING!"); + BOOST_FOREACH(const std::string& i, data_node_issues) { + EX_LOG(2, i); + } + } + data_node_issues.clear(); + + //Test 3: Check worker node input and output edges + std::vector<std::string> worker_issues; + BOOST_FOREACH(const vertex_map_t::value_type& v, _worker_map) { + size_t in_count = 0, out_count = 0; + for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); + ei.first != ei.second; + ++ei.first + ) { + if (boost::target(*(ei.first), _expert_dag) == v.second) + in_count++; + if (boost::source(*(ei.first), _expert_dag) == v.second) + out_count++; + } + if (in_count < 1) { + worker_issues.push_back(v.first + ": no inputs (will never resolve)"); + } + if (out_count < 1) { + worker_issues.push_back(v.first + ": no outputs"); + } + } + if (worker_issues.empty()) { + EX_LOG(1, "worker check ... PASSED"); + } else { + EX_LOG(1, "worker check ... WARNING!"); + BOOST_FOREACH(const std::string& i, worker_issues) { + EX_LOG(2, i); + } + } + worker_issues.clear(); +#endif + } + + inline boost::recursive_mutex& resolve_mutex() { + return _resolve_mutex; + } + +protected: + void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + //Sanity check node pointer + if (data_node == NULL) { + throw uhd::runtime_error("NULL data node passed into expert container for registration."); + } + + //Sanity check the data node and ensure that it is not already in this graph + EX_LOG(0, str(boost::format("add_data_node(%s)") % data_node->get_name())); + if (data_node->get_class() == CLASS_WORKER) { + throw uhd::runtime_error("Supplied node " + data_node->get_name() + " is not a data/property node."); + delete data_node; + } + if (_datanode_map.find(data_node->get_name()) != _datanode_map.end()) { + throw uhd::runtime_error("Data node with name " + data_node->get_name() + " already exists"); + delete data_node; + } + + try { + //Add a vertex in this graph for the data node + expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(data_node, _expert_dag); + EX_LOG(1, str(boost::format("added vertex %s") % data_node->get_name())); + _datanode_map.insert(vertex_map_t::value_type(data_node->get_name(), gr_node)); + + //Add resolve callbacks + if (resolve_mode == AUTO_RESOLVE_ON_WRITE or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) { + EX_LOG(2, str(boost::format("added write callback"))); + data_node->set_write_callback(boost::bind(&expert_container_impl::resolve_from, this, _1)); + } + if (resolve_mode == AUTO_RESOLVE_ON_READ or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) { + EX_LOG(2, str(boost::format("added read callback"))); + data_node->set_read_callback(boost::bind(&expert_container_impl::resolve_to, this, _1)); + } + } catch (...) { + clear(); + throw uhd::assertion_error("Unknown unrecoverable error adding data node. Cleared expert container."); + } + } + + void add_worker(worker_node_t* worker) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + //Sanity check node pointer + if (worker == NULL) { + throw uhd::runtime_error("NULL worker passed into expert container for registration."); + } + + //Sanity check the data node and ensure that it is not already in this graph + EX_LOG(0, str(boost::format("add_worker(%s)") % worker->get_name())); + if (worker->get_class() != CLASS_WORKER) { + throw uhd::runtime_error("Supplied node " + worker->get_name() + " is not a worker node."); + delete worker; + } + if (_worker_map.find(worker->get_name()) != _worker_map.end()) { + throw uhd::runtime_error("Resolver with name " + worker->get_name() + " already exists."); + delete worker; + } + + try { + //Add a vertex in this graph for the worker node + expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(worker, _expert_dag); + EX_LOG(1, str(boost::format("added vertex %s") % worker->get_name())); + _worker_map.insert(vertex_map_t::value_type(worker->get_name(), gr_node)); + + //For each input, add an edge from the input to this node + BOOST_FOREACH(const std::string& node_name, worker->get_inputs()) { + vertex_map_t::const_iterator node = _datanode_map.find(node_name); + if (node != _datanode_map.end()) { + boost::add_edge((*node).second, gr_node, _expert_dag); + EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[(*node).second]->get_name() % _expert_dag[gr_node]->get_name())); + } else { + throw uhd::runtime_error("Data node with name " + node_name + " was not found"); + } + } + + //For each output, add an edge from this node to the output + BOOST_FOREACH(const std::string& node_name, worker->get_outputs()) { + vertex_map_t::const_iterator node = _datanode_map.find(node_name); + if (node != _datanode_map.end()) { + boost::add_edge(gr_node, (*node).second, _expert_dag); + EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[gr_node]->get_name() % _expert_dag[(*node).second]->get_name())); + } else { + throw uhd::runtime_error("Data node with name " + node_name + " was not found"); + } + } + } catch (uhd::runtime_error& ex) { + clear(); + //Promote runtime_error to assertion_error + throw uhd::assertion_error(std::string(ex.what()) + " (Cleared expert container because error is unrecoverable)."); + } catch (...) { + clear(); + throw uhd::assertion_error("Unknown unrecoverable error adding worker. Cleared expert container."); + } + } + + void clear() + { + boost::lock_guard<boost::mutex> lock(_mutex); + EX_LOG(0, "clear()"); + + // Iterate through the vertices and release their node storage + typedef boost::graph_traits<expert_graph_t>::vertex_iterator vertex_iter; + for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag); + vi.first != vi.second; + ++vi.first + ) { + try { + delete _expert_dag[*vi.first]; + _expert_dag[*vi.first] = NULL; + } catch (...) { + //If a dag_vertex is a worker, it has a virtual dtor which + //can possibly throw an exception. We will not let that + //terminate clear() and leave things in a bad state. + } + } + + //The following calls will not throw because they all contain + //intrinsic types. + + // Release all vertices and edges in the DAG + _expert_dag.clear(); + + // Release all nodes in the map + _worker_map.clear(); + _datanode_map.clear(); + } + +private: + void _resolve_helper(std::string start, std::string stop, bool force) + { + //Sort the graph topologically. This ensures that for all dependencies, the dependant + //is always after all of its dependencies. + node_queue_t sorted_nodes; + try { + boost::topological_sort(_expert_dag, std::front_inserter(sorted_nodes)); + } catch (boost::not_a_dag&) { + std::vector<std::string> back_edges; + cycle_det_visitor cdet_vis(back_edges); + boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis)); + if (not back_edges.empty()) { + std::string edges; + BOOST_FOREACH(const std::string& e, back_edges) { + edges += "* " + e + ""; + } + throw uhd::runtime_error("Cannot resolve expert because it has at least one cycle!\n" + "The following back-edges were found:" + edges); + } + } + if (sorted_nodes.empty()) return; + + //Determine the start and stop node. If one is not explicitly specified then + //resolve everything + expert_graph_t::vertex_descriptor start_vertex = sorted_nodes.front(); + expert_graph_t::vertex_descriptor stop_vertex = sorted_nodes.back(); + if (not start.empty()) start_vertex = _lookup_vertex(start); + if (not stop.empty()) stop_vertex = _lookup_vertex(stop); + + //First Pass: Resolve all nodes if they are dirty, in a topological order + std::list<dag_vertex_t*> resolved_workers; + bool start_node_encountered = false; + for (node_queue_t::iterator node_iter = sorted_nodes.begin(); + node_iter != sorted_nodes.end(); + ++node_iter + ) { + //Determine if we are at or beyond the starting node + if (*node_iter == start_vertex) start_node_encountered = true; + + //Only resolve if the starting node has passed + if (start_node_encountered) { + dag_vertex_t& node = _get_vertex(*node_iter); + std::string node_val; + if (force or node.is_dirty()) { + node.resolve(); + if (node.get_class() == CLASS_WORKER) { + resolved_workers.push_back(&node); + } + EX_LOG(1, str(boost::format("resolved node %s (%s) [%s]") % + node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string())); + } else { + EX_LOG(1, str(boost::format("skipped node %s (%s) [%s]") % + node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string())); + } + } + + //Determine if we are beyond the stop node + if (*node_iter == stop_vertex) break; + } + + //Second Pass: Mark all the workers clean. The policy is that a worker will mark all of + //its dependencies clean so after this step all data nodes that are not consumed by a worker + //will remain dirty (as they should because no one has consumed their value) + for (std::list<dag_vertex_t*>::iterator worker = resolved_workers.begin(); + worker != resolved_workers.end(); + ++worker + ) { + (*worker)->mark_clean(); + } + } + + expert_graph_t::vertex_descriptor _lookup_vertex(const std::string& name) const + { + expert_graph_t::vertex_descriptor vertex; + //Look for node in the data-node map + vertex_map_t::const_iterator vertex_iter = _datanode_map.find(name); + if (vertex_iter != _datanode_map.end()) { + vertex = (*vertex_iter).second; + } else { + //If not found, look in the worker-node map + vertex_iter = _worker_map.find(name); + if (vertex_iter != _worker_map.end()) { + vertex = (*vertex_iter).second; + } else { + throw uhd::lookup_error("Could not find node with name " + name); + } + } + return vertex; + } + + dag_vertex_t& _get_vertex(expert_graph_t::vertex_descriptor desc) const { + //Requirement: Node must exist in expert graph + dag_vertex_t* vertex_ptr = _expert_dag[desc]; + if (vertex_ptr) { + return *vertex_ptr; + } else { + throw uhd::assertion_error("Expert graph malformed. Found a NULL node."); + } + } + + void _log(size_t depth, const std::string& str) const + { + std::string indents; + for (size_t i = 0; i < depth; i++) indents += "- "; + UHD_MSG(fastpath) << "[expert::" + _name + "] " << indents << str << std::endl; + } + +private: + const std::string _name; + expert_graph_t _expert_dag; //The primary graph data structure as an adjacency list + vertex_map_t _worker_map; //A map from vertex name to vertex descriptor for workers + vertex_map_t _datanode_map; //A map from vertex name to vertex descriptor for data nodes + boost::mutex _mutex; + boost::recursive_mutex _resolve_mutex; +}; + +expert_container::sptr expert_container::make(const std::string& name) +{ + return boost::make_shared<expert_container_impl>(name); +} + +}} diff --git a/host/lib/experts/expert_container.hpp b/host/lib/experts/expert_container.hpp new file mode 100644 index 000000000..7e626dcc5 --- /dev/null +++ b/host/lib/experts/expert_container.hpp @@ -0,0 +1,202 @@ +// +// Copyright 2016 Ettus Research +// +// 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_UHD_EXPERTS_EXPERT_CONTAINER_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP + +#include "expert_nodes.hpp" +#include <uhd/config.hpp> +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/thread/recursive_mutex.hpp> + +namespace uhd { namespace experts { + + enum auto_resolve_mode_t { + AUTO_RESOLVE_OFF, + AUTO_RESOLVE_ON_READ, + AUTO_RESOLVE_ON_WRITE, + AUTO_RESOLVE_ON_READ_WRITE + }; + + class UHD_API expert_container : private boost::noncopyable, public node_retriever_t { + public: //Methods + typedef boost::shared_ptr<expert_container> sptr; + + virtual ~expert_container() {}; + + /*! + * Return the name of this container + */ + virtual const std::string& get_name() const = 0; + + /*! + * Resolves all the nodes in this expert graph. + * + * Dependency analysis is performed on the graph and nodes + * are resolved in a topologically sorted order to ensure + * that no nodes receive stale data. + * Nodes and their dependencies are resolved only if they are + * dirty i.e. their contained values have changed since the + * last resolve. + * This call requires an acyclic expert graph. + * + * \param force If true then ignore dirty state and resolve all nodes + * \throws uhd::runtime_error if graph cannot be resolved + */ + virtual void resolve_all(bool force = false) = 0; + + /*! + * Resolves all the nodes that depend on the specified node. + * + * Dependency analysis is performed on the graph and nodes + * are resolved in a topologically sorted order to ensure + * that no nodes receive stale data. + * Nodes and their dependencies are resolved only if they are + * dirty i.e. their contained values have changed since the + * last resolve. + * This call requires an acyclic expert graph. + * + * \param node_name Name of the node to start resolving from + * \throws uhd::lookup_error if node_name not in container + * \throws uhd::runtime_error if graph cannot be resolved + * + */ + virtual void resolve_from(const std::string& node_name) = 0; + + /*! + * Resolves all the specified node and all of its dependencies. + * + * Dependency analysis is performed on the graph and nodes + * are resolved in a topologically sorted order to ensure + * that no nodes receive stale data. + * Nodes and their dependencies are resolved only if they are + * dirty i.e. their contained values have changed since the + * last resolve. + * This call requires an acyclic expert graph. + * + * \param node_name Name of the node to resolve + * \throws uhd::lookup_error if node_name not in container + * \throws uhd::runtime_error if graph cannot be resolved + * + */ + virtual void resolve_to(const std::string& node_name) = 0; + + /*! + * Return a node retriever object for this container + */ + virtual const node_retriever_t& node_retriever() const = 0; + + /*! + * Returns a DOT (graph description language) representation + * of the expert graph. The output has labels for the node + * name, node type (data or worker) and the underlying + * data type for each node. + * + */ + virtual std::string to_dot() const = 0; + + /*! + * Runs several sanity checks on the underlying graph to + * flag dependency issues. Outputs of the checks are + * logged to the console so UHD_EXPERTS_VERBOSE_LOGGING + * must be enabled to see the results + * + */ + virtual void debug_audit() const = 0; + + private: + /*! + * Lookup a node with the specified name in the contained graph + * + * If the node is found, a reference to the node is returned. + * If the node is not found, uhd::lookup_error is thrown + * lookup can return a data or a worker node + * \implements uhd::experts::node_retriever_t + * + * \param name Name of the node to find + * + */ + virtual const dag_vertex_t& lookup(const std::string& name) const = 0; + virtual dag_vertex_t& retrieve(const std::string& name) const = 0; + + /*! + * expert_factory is a friend of expert_container and + * handles all operations that change the structure of + * the underlying dependency graph. + * The expert_container instance owns all data and worker + * nodes and is responsible for release storage on destruction. + * However, the expert_factory allocates storage for the + * node and passes them into the expert_container using the + * following "protected" API calls. + * + */ + friend class expert_factory; + + /*! + * Creates an empty instance of expert_container with the + * specified name. + * + * \param name Name of the container + */ + static sptr make(const std::string& name); + + /*! + * Returns a reference to the resolver mutex. + * + * The resolver mutex guarantees that external operations + * to data-nodes are serialized with resolves of this + * container. + * + */ + virtual boost::recursive_mutex& resolve_mutex() = 0; + + /*! + * Add a data node to the expert graph + * + * \param data_node Pointer to a fully constructed data node object + * \resolve_mode Auto resolve options: Choose from "disabled" and resolve on "read", "write" or "both" + * \throws uhd::runtime_error if node already exists or is of a wrong type (recoverable) + * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph) + * + */ + virtual void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode = AUTO_RESOLVE_OFF) = 0; + + /*! + * Add a worker node to the expert graph + * + * \param worker Pointer to a fully constructed worker object + * \throws uhd::runtime_error if worker already exists or is of a wrong type (recoverable) + * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph) + * + */ + virtual void add_worker(worker_node_t* worker) = 0; + + /*! + * Release all storage for this object. This will delete all contained + * data and worker nodes and remove all dependency relationship and + * resolve callbacks. + * + * The object will be restored to its newly constructed state. Will not + * throw. + */ + virtual void clear() = 0; + }; + +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP */ diff --git a/host/lib/experts/expert_factory.cpp b/host/lib/experts/expert_factory.cpp new file mode 100644 index 000000000..f887ad4a3 --- /dev/null +++ b/host/lib/experts/expert_factory.cpp @@ -0,0 +1,27 @@ +// +// Copyright 2016 Ettus Research +// +// 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 "expert_factory.hpp" + +namespace uhd { namespace experts { + +expert_container::sptr expert_factory::create_container(const std::string& name) +{ + return expert_container::make(name); +} + +}} diff --git a/host/lib/experts/expert_factory.hpp b/host/lib/experts/expert_factory.hpp new file mode 100644 index 000000000..83369117d --- /dev/null +++ b/host/lib/experts/expert_factory.hpp @@ -0,0 +1,337 @@ +// +// Copyright 2016 Ettus Research +// +// 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_UHD_EXPERTS_EXPERT_FACTORY_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP + +#include "expert_container.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/config.hpp> +#include <boost/noncopyable.hpp> +#include <boost/bind.hpp> +#include <memory> + +namespace uhd { namespace experts { + + /*! + * expert_factory is a friend of expert_container and + * handles all operations to create and change the structure of + * the an expert container. + * The expert_factory allocates storage for the nodes in the + * expert_container and passes allocated objects to the container + * using private APIs. The expert_container instance owns all + * data and workernodes and is responsible for releasing their + * storage on destruction. + * + */ + class UHD_API expert_factory : public boost::noncopyable { + public: + + /*! + * Creates an empty instance of expert_container with the + * specified name. + * + * \param name Name of the container + */ + static expert_container::sptr create_container( + const std::string& name + ); + + /*! + * Add a data node to the expert graph. + * + * \param container A shared pointer to the container to add the node to + * \param name The name of the data node + * \param init_val The initial value of the data node + * \param mode The auto resolve mode + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + */ + template<typename data_t> + inline static void add_data_node( + expert_container::sptr container, + const std::string& name, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + container->add_data_node(new data_node_t<data_t>(name, init_val), mode); + } + + /*! + * Add a expert property to a property tree AND an expert graph + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param name The name of the data node in the expert graph + * \param init_val The initial value of the data node + * \param mode The auto resolve mode + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + */ + template<typename data_t> + inline static property<data_t>& add_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const std::string& name, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE); + data_node_t<data_t>* node_ptr = + new data_node_t<data_t>(name, init_val, &container->resolve_mutex()); + prop.set(init_val); + prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, node_ptr, _1)); + prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, node_ptr)); + container->add_data_node(node_ptr, mode); + return prop; + } + + /*! + * Add a expert property to a property tree AND an expert graph. + * The property is registered with the path as the identifier for + * both the property subtree and the expert container + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param init_val The initial value of the data node + * \param mode The auto resolve mode + * + */ + template<typename data_t> + inline static property<data_t>& add_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + return add_prop_node(container, subtree, path, path, init_val, mode); + } + + /*! + * Add a dual expert property to a property tree AND an expert graph. + * A dual property is a desired and coerced value pair + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param desired_name The name of the desired data node in the expert graph + * \param desired_name The name of the coerced data node in the expert graph + * \param init_val The initial value of both the data nodes + * \param mode The auto resolve mode + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + */ + template<typename data_t> + inline static property<data_t>& add_dual_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const std::string& desired_name, + const std::string& coerced_name, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + bool auto_resolve_desired = (mode==AUTO_RESOLVE_ON_WRITE or mode==AUTO_RESOLVE_ON_READ_WRITE); + bool auto_resolve_coerced = (mode==AUTO_RESOLVE_ON_READ or mode==AUTO_RESOLVE_ON_READ_WRITE); + + property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE); + data_node_t<data_t>* desired_node_ptr = + new data_node_t<data_t>(desired_name, init_val, &container->resolve_mutex()); + data_node_t<data_t>* coerced_node_ptr = + new data_node_t<data_t>(coerced_name, init_val, &container->resolve_mutex()); + prop.set(init_val); + prop.set_coerced(init_val); + prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, desired_node_ptr, _1)); + prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, coerced_node_ptr)); + + container->add_data_node(desired_node_ptr, + auto_resolve_desired ? AUTO_RESOLVE_ON_WRITE : AUTO_RESOLVE_OFF); + container->add_data_node(coerced_node_ptr, + auto_resolve_coerced ? AUTO_RESOLVE_ON_READ : AUTO_RESOLVE_OFF); + return prop; + } + + /*! + * Add a dual expert property to a property tree AND an expert graph. + * A dual property is a desired and coerced value pair + * The property is registered with path/desired as the desired node + * name and path/coerced as the coerced node name + * + * \param container A shared pointer to the expert container to add the node to + * \param subtree A shared pointer to subtree to add the property to + * \param path The path of the property in the subtree + * \param init_val The initial value of both the data nodes + * \param mode The auto resolve mode + * + */ + template<typename data_t> + inline static property<data_t>& add_dual_prop_node( + expert_container::sptr container, + property_tree::sptr subtree, + const fs_path &path, + const data_t& init_val, + const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF + ) { + return add_dual_prop_node(container, subtree, path, path + "/desired", path + "/coerced", init_val, mode); + } + + /*! + * Add a worker node to the expert graph. + * The expert_container owns and manages storage for the worker + * + * \tparam worker_t Data type of the worker class + * + * \param container A shared pointer to the container to add the node to + * + */ + template<typename worker_t> + inline static void add_worker_node( + expert_container::sptr container + ) { + container->add_worker(new worker_t()); + } + + /*! + * Add a worker node to the expert graph. + * The expert_container owns and manages storage for the worker + * + * \tparam worker_t Data type of the worker class + * \tparam arg1_t Data type of the first argument to the constructor + * \tparam ... + * \tparam argN_t Data type of the Nth argument to the constructor + * + * \param container A shared pointer to the container to add the node to + * \param arg1 First arg to ctor + * \param ... + * \param argN Nth arg to ctor + * + */ + template<typename worker_t, typename arg1_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1 + ) { + container->add_worker(new worker_t(arg1)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2 + ) { + container->add_worker(new worker_t(arg1, arg2)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t, typename arg6_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5, + arg6_t const & arg6 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t, typename arg6_t, typename arg7_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5, + arg6_t const & arg6, + arg7_t const & arg7 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7)); + } + + template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, + typename arg5_t, typename arg6_t, typename arg7_t, typename arg8_t> + inline static void add_worker_node( + expert_container::sptr container, + arg1_t const & arg1, + arg2_t const & arg2, + arg3_t const & arg3, + arg4_t const & arg4, + arg5_t const & arg5, + arg6_t const & arg6, + arg7_t const & arg7, + arg7_t const & arg8 + ) { + container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); + } + }; +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP */ diff --git a/host/lib/experts/expert_nodes.hpp b/host/lib/experts/expert_nodes.hpp new file mode 100644 index 000000000..dc5cc934b --- /dev/null +++ b/host/lib/experts/expert_nodes.hpp @@ -0,0 +1,475 @@ +// +// Copyright 2016 Ettus Research +// +// 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_UHD_EXPERTS_EXPERT_NODES_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/dirty_tracked.hpp> +#include <boost/function.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/thread.hpp> +#include <boost/units/detail/utility.hpp> +#include <memory> +#include <list> + +namespace uhd { namespace experts { + + enum node_class_t { CLASS_WORKER, CLASS_DATA, CLASS_PROPERTY }; + enum node_access_t { ACCESS_READER, ACCESS_WRITER }; + enum node_author_t { AUTHOR_NONE, AUTHOR_USER, AUTHOR_EXPERT }; + + /*!--------------------------------------------------------- + * class dag_vertex_t + * + * This serves as the base class for all nodes in the expert + * graph. Data nodes and workers are derived from this class. + * --------------------------------------------------------- + */ + class dag_vertex_t : private boost::noncopyable { + public: + typedef boost::function<void(std::string)> callback_func_t; + + virtual ~dag_vertex_t() {} + + // Getters for basic info about the node + inline node_class_t get_class() const { + return _class; + } + + inline const std::string& get_name() const { + return _name; + } + + virtual const std::string& get_dtype() const = 0; + + virtual std::string to_string() const = 0; + + // Graph resolution specific + virtual bool is_dirty() const = 0; + virtual void mark_clean() = 0; + virtual void resolve() = 0; + + // External callbacks + virtual void set_write_callback(const callback_func_t& func) = 0; + virtual bool has_write_callback() const = 0; + virtual void clear_write_callback() = 0; + virtual void set_read_callback(const callback_func_t& func) = 0; + virtual bool has_read_callback() const = 0; + virtual void clear_read_callback() = 0; + + protected: + dag_vertex_t(const node_class_t c, const std::string& n): + _class(c), _name(n) {} + + private: + const node_class_t _class; + const std::string _name; + }; + + class data_node_printer { + public: + //Generic implementation + template<typename data_t> + static std::string print(const data_t& val) { + std::ostringstream os; + os << val; + return os.str(); + } + + static std::string print(const boost::uint8_t& val) { + std::ostringstream os; + os << int(val); + return os.str(); + } + }; + + /*!--------------------------------------------------------- + * class data_node_t + * + * The data node class hold a passive piece of data in the + * expert graph. A data node is clean if its underlying data + * is clean. Access to the underlying data is provided using + * two methods: + * 1. Special accessor classes (for R/W enforcement) + * 2. External clients (via commit and retrieve). This access + * is protected by the callback mutex. + * + * Requirements for data_t + * - Must have a default constructor + * - Must have a copy constructor + * - Must have an assignment operator (=) + * - Must have an equality operator (==) + * --------------------------------------------------------- + */ + template<typename data_t> + class data_node_t : public dag_vertex_t { + public: + // A data_node_t instance can have a type of CLASS_DATA or CLASS_PROPERTY + // In general a data node is a property if it can be accessed and modified + // from the outside world (of experts) using read and write callbacks. We + // assume that if a callback mutex is passed into the data node that it will + // be accessed from the outside and tag the data node as a PROPERTY. + data_node_t(const std::string& name, boost::recursive_mutex* mutex = NULL) : + dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(), _author(AUTHOR_NONE) {} + + data_node_t(const std::string& name, const data_t& value, boost::recursive_mutex* mutex = NULL) : + dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(value), _author(AUTHOR_NONE) {} + + // Basic info + virtual const std::string& get_dtype() const { + static const std::string dtype( + boost::units::detail::demangle(typeid(data_t).name())); + return dtype; + } + + virtual std::string to_string() const { + return data_node_printer::print(get()); + } + + inline node_author_t get_author() const { + return _author; + } + + // Graph resolution specific + virtual bool is_dirty() const { + return _data.is_dirty(); + } + + virtual void mark_clean() { + _data.mark_clean(); + } + + void resolve() { + //NOP + } + + // Data node specific setters and getters (for the framework) + void set(const data_t& value) { + _data = value; + _author = AUTHOR_EXPERT; + } + + const data_t& get() const { + return _data; + } + + // Data node specific setters and getters (for external entities) + void commit(const data_t& value) { + if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex"); + boost::lock_guard<boost::recursive_mutex> lock(*_callback_mutex); + set(value); + _author = AUTHOR_USER; + if (is_dirty() and has_write_callback()) { + _wr_callback(std::string(get_name())); //Put the name on the stack before calling + } + } + + const data_t retrieve() const { + if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex"); + boost::lock_guard<boost::recursive_mutex> lock(*_callback_mutex); + if (has_read_callback()) { + _rd_callback(std::string(get_name())); + } + return get(); + } + + private: + // External callbacks + virtual void set_write_callback(const callback_func_t& func) { + _wr_callback = func; + } + + virtual bool has_write_callback() const { + return not _wr_callback.empty(); + } + + virtual void clear_write_callback() { + _wr_callback.clear(); + } + + virtual void set_read_callback(const callback_func_t& func) { + _rd_callback = func; + } + + virtual bool has_read_callback() const { + return not _rd_callback.empty(); + } + + virtual void clear_read_callback() { + _rd_callback.clear(); + } + + boost::recursive_mutex* _callback_mutex; + callback_func_t _rd_callback; + callback_func_t _wr_callback; + dirty_tracked<data_t> _data; + node_author_t _author; + }; + + /*!--------------------------------------------------------- + * class node_retriever_t + * + * Node storage is managed by a framework class so we need + * and interface to find and retrieve data nodes to associate + * with accessors. + * --------------------------------------------------------- + */ + class node_retriever_t { + public: + virtual ~node_retriever_t() {} + virtual const dag_vertex_t& lookup(const std::string& name) const = 0; + private: + friend class data_accessor_t; + virtual dag_vertex_t& retrieve(const std::string& name) const = 0; + }; + + /*!--------------------------------------------------------- + * class data_accessor_t + * + * Accessors provide protected access to data nodes and help + * establish dependency relationships. + * --------------------------------------------------------- + */ + class data_accessor_t { + public: + virtual ~data_accessor_t() {} + + virtual bool is_reader() const = 0; + virtual bool is_writer() const = 0; + virtual dag_vertex_t& node() const = 0; + protected: + data_accessor_t(const node_retriever_t& r, const std::string& n): + _vertex(r.retrieve(n)) {} + dag_vertex_t& _vertex; + }; + + template<typename data_t> + class data_accessor_base : public data_accessor_t { + public: + virtual ~data_accessor_base() {} + + virtual bool is_reader() const { + return _access == ACCESS_READER; + } + + virtual bool is_writer() const { + return _access == ACCESS_WRITER; + } + + inline bool is_dirty() const { + return _datanode->is_dirty(); + } + + inline node_class_t get_class() const { + return _datanode->get_class(); + } + + inline node_author_t get_author() const { + return _datanode->get_author(); + } + + protected: + data_accessor_base( + const node_retriever_t& r, const std::string& n, const node_access_t a) : + data_accessor_t(r, n), _datanode(NULL), _access(a) + { + _datanode = dynamic_cast< data_node_t<data_t>* >(&node()); + if (_datanode == NULL) { + throw uhd::type_error("Expected data type for node " + n + + " was " + boost::units::detail::demangle(typeid(data_t).name()) + + " but got " + node().get_dtype()); + } + } + + data_node_t<data_t>* _datanode; + const node_access_t _access; + + private: + virtual dag_vertex_t& node() const { + return _vertex; + } + }; + + /*!--------------------------------------------------------- + * class data_reader_t + * + * Accessor to read the value of a data node and to establish + * a data node => worker node dependency + * --------------------------------------------------------- + */ + template<typename data_t> + class data_reader_t : public data_accessor_base<data_t> { + public: + data_reader_t(const node_retriever_t& retriever, const std::string& node) : + data_accessor_base<data_t>( + retriever, node, ACCESS_READER) {} + + inline const data_t& get() const { + return data_accessor_base<data_t>::_datanode->get(); + } + + inline operator const data_t&() const { + return get(); + } + + inline bool operator==(const data_t& rhs) { + return get() == rhs; + } + + inline bool operator!=(const data_t& rhs) { + return !(get() == rhs); + } + }; + + /*!--------------------------------------------------------- + * class data_reader_t + * + * Accessor to read and write the value of a data node and + * to establish a worker node => data node dependency + * --------------------------------------------------------- + */ + template<typename data_t> + class data_writer_t : public data_accessor_base<data_t> { + public: + data_writer_t(const node_retriever_t& retriever, const std::string& node) : + data_accessor_base<data_t>( + retriever, node, ACCESS_WRITER) {} + + inline const data_t& get() const { + return data_accessor_base<data_t>::_datanode->get(); + } + + inline operator const data_t&() const { + return get(); + } + + inline bool operator==(const data_t& rhs) { + return get() == rhs; + } + + inline bool operator!=(const data_t& rhs) { + return !(get() == rhs); + } + + inline void set(const data_t& value) { + data_accessor_base<data_t>::_datanode->set(value); + } + + inline data_writer_t<data_t>& operator=(const data_t& value) { + set(value); + return *this; + } + + inline data_writer_t<data_t>& operator=(const data_writer_t<data_t>& value) { + set(value.get()); + return *this; + } +}; + + /*!--------------------------------------------------------- + * class worker_node_t + * + * A node class to implement a function that consumes + * zero or more input data nodes and emits zeroor more output + * data nodes. The worker can also operate on other non-expert + * interfaces because worker_node_t is abstract and the client + * is required to implement the "resolve" method in a subclass. + * --------------------------------------------------------- + */ + class worker_node_t : public dag_vertex_t { + public: + worker_node_t(const std::string& name) : + dag_vertex_t(CLASS_WORKER, name) {} + + // Worker node specific + std::list<std::string> get_inputs() const { + std::list<std::string> retval; + BOOST_FOREACH(data_accessor_t* acc, _inputs) { + retval.push_back(acc->node().get_name()); + } + return retval; + } + + std::list<std::string> get_outputs() const { + std::list<std::string> retval; + BOOST_FOREACH(data_accessor_t* acc, _outputs) { + retval.push_back(acc->node().get_name()); + } + return retval; + } + + protected: + // This function is used to bind data accessors + // to this worker. Accessors can be read/write + // and the binding will ensure proper dependency + // handling. + void bind_accessor(data_accessor_t& accessor) { + if (accessor.is_reader()) { + _inputs.push_back(&accessor); + } else if (accessor.is_writer()) { + _outputs.push_back(&accessor); + } else { + throw uhd::assertion_error("Invalid accessor type"); + } + } + + private: + // Graph resolution specific + virtual bool is_dirty() const { + bool inputs_dirty = false; + BOOST_FOREACH(data_accessor_t* acc, _inputs) { + inputs_dirty |= acc->node().is_dirty(); + } + return inputs_dirty; + } + + virtual void mark_clean() { + BOOST_FOREACH(data_accessor_t* acc, _inputs) { + acc->node().mark_clean(); + } + } + + virtual void resolve() = 0; + + // Basic type info + virtual const std::string& get_dtype() const { + static const std::string dtype = "<worker>"; + return dtype; + } + + virtual std::string to_string() const { + return "<worker>"; + } + + // Workers don't have callbacks so implement stubs + virtual void set_write_callback(const callback_func_t&) {} + virtual bool has_write_callback() const { return false; } + virtual void clear_write_callback() {} + virtual void set_read_callback(const callback_func_t&) {} + virtual bool has_read_callback() const { return false; } + virtual void clear_read_callback() {} + + std::list<data_accessor_t*> _inputs; + std::list<data_accessor_t*> _outputs; + }; + +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_NODE_HPP */ diff --git a/host/lib/property_tree.cpp b/host/lib/property_tree.cpp index 039f05f12..76d7bccba 100644 --- a/host/lib/property_tree.cpp +++ b/host/lib/property_tree.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011,2014 Ettus Research LLC +// Copyright 2011,2014-2016 Ettus Research // // 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 diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt index 6abc399b4..79c8a90b7 100644 --- a/host/lib/transport/CMakeLists.txt +++ b/host/lib/transport/CMakeLists.txt @@ -22,17 +22,15 @@ ######################################################################## # Include subdirectories (different than add) ######################################################################## -INCLUDE_SUBDIRECTORY(nirio) +IF(ENABLE_X300) + INCLUDE_SUBDIRECTORY(nirio) +ENDIF(ENABLE_X300) ######################################################################## # Setup libusb ######################################################################## -MESSAGE(STATUS "") -FIND_PACKAGE(USB1) - -LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF OFF) - IF(ENABLE_USB) + MESSAGE(STATUS "") MESSAGE(STATUS "USB support enabled via libusb.") INCLUDE_DIRECTORIES(${LIBUSB_INCLUDE_DIRS}) LIBUHD_APPEND_LIBS(${LIBUSB_LIBRARIES}) @@ -128,10 +126,15 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/buffer_pool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp ${CMAKE_CURRENT_SOURCE_DIR}/udp_simple.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nirio_zero_copy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/chdr.cpp ) +IF(ENABLE_X300) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/nirio_zero_copy.cpp + ) +ENDIF(ENABLE_X300) + # Verbose Debug output for send/recv SET( UHD_TXRX_DEBUG_PRINTS OFF CACHE BOOL "Use verbose debug output for send/recv" ) OPTION( UHD_TXRX_DEBUG_PRINTS "Use verbose debug output for send/recv" "" ) diff --git a/host/lib/transport/nirio/lvbitx/CMakeLists.txt b/host/lib/transport/nirio/lvbitx/CMakeLists.txt index b9a2a9f15..5741a12f8 100644 --- a/host/lib/transport/nirio/lvbitx/CMakeLists.txt +++ b/host/lib/transport/nirio/lvbitx/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2013 Ettus Research LLC +# Copyright 2013,2015 Ettus Research LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,8 +30,8 @@ MACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM lvbitx binfile) SET(IMAGES_PATH_OPT --uhd-images-path=${UHD_IMAGES_DIR}) ADD_CUSTOM_COMMAND( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/process-lvbitx.py DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.hpp DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.cpp @@ -41,6 +41,7 @@ MACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM lvbitx binfile) ) #make libuhd depend on the output file + LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp) LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp) ENDMACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM) diff --git a/host/lib/transport/nirio/rpc/rpc_client.cpp b/host/lib/transport/nirio/rpc/rpc_client.cpp index bbaf9f235..3d62b57ae 100644 --- a/host/lib/transport/nirio/rpc/rpc_client.cpp +++ b/host/lib/transport/nirio/rpc/rpc_client.cpp @@ -1,5 +1,5 @@ /// -// Copyright 2013 Ettus Research LLC +// Copyright 2013,2016 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 @@ -55,22 +55,7 @@ rpc_client::rpc_client ( tcp::resolver::query::flags query_flags(tcp::resolver::query::passive); tcp::resolver::query query(tcp::v4(), server, port, query_flags); tcp::resolver::iterator iterator = resolver.resolve(query); - - #if BOOST_VERSION < 104700 - // default constructor creates end iterator - tcp::resolver::iterator end; - - boost::system::error_code error = boost::asio::error::host_not_found; - while (error && iterator != end) - { - _socket.close(); - _socket.connect(*iterator++, error); - } - if (error) - throw boost::system::system_error(error); - #else - boost::asio::connect(_socket, iterator); - #endif + boost::asio::connect(_socket, iterator); UHD_LOG << "rpc_client connected to server." << std::endl; @@ -109,11 +94,6 @@ rpc_client::rpc_client ( } catch (boost::exception&) { UHD_LOG << "rpc_client connection request cancelled/aborted." << std::endl; _exec_err.assign(boost::asio::error::connection_aborted, boost::asio::error::get_system_category()); -#if BOOST_VERSION < 104700 - } catch (std::exception& e) { - UHD_LOG << "rpc_client connection error: " << e.what() << std::endl; - _exec_err.assign(boost::asio::error::connection_aborted, boost::asio::error::get_system_category()); -#endif } } diff --git a/host/lib/transport/super_recv_packet_handler.hpp b/host/lib/transport/super_recv_packet_handler.hpp index 8bfa1973a..28adff0d1 100644 --- a/host/lib/transport/super_recv_packet_handler.hpp +++ b/host/lib/transport/super_recv_packet_handler.hpp @@ -127,7 +127,7 @@ public: * \param threshold number of packets per channel */ void set_alignment_failure_threshold(const size_t threshold){ - _alignment_faulure_threshold = threshold*this->size(); + _alignment_failure_threshold = threshold*this->size(); } //! Set the rate of ticks per second @@ -203,6 +203,12 @@ public: //! Overload call to issue stream commands void issue_stream_cmd(const stream_cmd_t &stream_cmd) { + if (stream_cmd.stream_now + and stream_cmd.stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS + and _props.size() > 1) { + throw uhd::runtime_error("Attempting to do multi-channel receive with stream_now == true will result in misaligned channels. Aborting."); + } + for (size_t i = 0; i < _props.size(); i++) { if (_props[i].issue_stream_cmd) _props[i].issue_stream_cmd(stream_cmd); @@ -269,7 +275,7 @@ private: size_t _header_offset_words32; double _tick_rate, _samp_rate; bool _queue_error_for_next_call; - size_t _alignment_faulure_threshold; + size_t _alignment_failure_threshold; rx_metadata_t _queue_metadata; struct xport_chan_props_type{ xport_chan_props_type(void): @@ -593,7 +599,7 @@ private: } //too many iterations: detect alignment failure - if (iterations++ > _alignment_faulure_threshold){ + if (iterations++ > _alignment_failure_threshold){ UHD_MSG(error) << boost::format( "The receive packet handler failed to time-align packets.\n" "%u received packets were processed by the handler.\n" diff --git a/host/lib/types/sensors.cpp b/host/lib/types/sensors.cpp index 52a63d14c..0406e35d4 100644 --- a/host/lib/types/sensors.cpp +++ b/host/lib/types/sensors.cpp @@ -69,6 +69,12 @@ sensor_value_t::sensor_value_t( /* NOP */ } +sensor_value_t::sensor_value_t(const sensor_value_t& source) +{ + *this = source; +} + + std::string sensor_value_t::to_pp_string(void) const{ switch(type){ case BOOLEAN: @@ -92,3 +98,12 @@ signed sensor_value_t::to_int(void) const{ double sensor_value_t::to_real(void) const{ return boost::lexical_cast<double>(value); } + +sensor_value_t& sensor_value_t::operator=(const sensor_value_t& rhs) +{ + this->name = rhs.name; + this->value = rhs.value; + this->unit = rhs.unit; + this->type = rhs.type; + return *this; +} diff --git a/host/lib/types/serial.cpp b/host/lib/types/serial.cpp index 9b8336dd8..52961691c 100644 --- a/host/lib/types/serial.cpp +++ b/host/lib/types/serial.cpp @@ -40,7 +40,8 @@ spi_config_t::spi_config_t(edge_t edge): mosi_edge(edge), miso_edge(edge) { - /* NOP */ + // By default don't use a custom clock speed for the transaction + use_custom_divider = false; } void i2c_iface::write_eeprom( diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index 5c9592970..dde4f02c3 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -18,20 +18,18 @@ ######################################################################## # This file included, use CMake directory variables ######################################################################## -find_package(GPSD) - INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/dboard_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dboard_eeprom.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dboard_id.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dboard_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/gps_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mboard_eeprom.cpp ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/subdev_spec.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fe_connection.cpp ) IF(ENABLE_C_API) @@ -43,8 +41,6 @@ IF(ENABLE_C_API) ) ENDIF(ENABLE_C_API) -LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF) - IF(ENABLE_GPSD) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/gpsd_iface.cpp @@ -62,3 +58,4 @@ INCLUDE_SUBDIRECTORY(e100) INCLUDE_SUBDIRECTORY(e300) INCLUDE_SUBDIRECTORY(x300) INCLUDE_SUBDIRECTORY(b200) +INCLUDE_SUBDIRECTORY(n230) diff --git a/host/lib/usrp/b100/CMakeLists.txt b/host/lib/usrp/b100/CMakeLists.txt index 1558cd974..66129458c 100644 --- a/host/lib/usrp/b100/CMakeLists.txt +++ b/host/lib/usrp/b100/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the B100 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_B100) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/b100_impl.cpp diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp index c4279913c..eec9f0e9a 100644 --- a/host/lib/usrp/b100/b100_impl.cpp +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -281,7 +281,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tree->create<std::string>(mb_path / "name").set("B100"); _tree->create<std::string>(mb_path / "codename").set("B-Hundo"); _tree->create<std::string>(mb_path / "load_eeprom") - .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); //////////////////////////////////////////////////////////////////// // setup the mboard eeprom @@ -289,20 +289,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, B100_EEPROM_MAP_KEY); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////////// //^^^ clock created up top, just reg props here... ^^^ _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) - .subscribe(boost::bind(&b100_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_tick_rate, this, _1)); - //subscribe the command time while we are at it + //add_coerced_subscriber the command time while we are at it _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create codec control objects @@ -313,20 +313,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(b100_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)) + .set_coercer(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)) .set(0.0); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(b100_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) - .publish(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)) + .add_coerced_subscriber(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .set_publisher(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)) .set(0.0); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&b100_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&b100_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // create frontend control objects @@ -335,27 +335,27 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE)); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////////// @@ -374,20 +374,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _rx_dsps[dspno]->set_link_rate(B100_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) - .subscribe(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////////// @@ -398,17 +398,17 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ ); _tx_dsp->set_link_rate(B100_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) - .subscribe(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); //////////////////////////////////////////////////////////////////// // create time control objects @@ -422,21 +422,21 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1)); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&b100_impl::update_clock_source, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_clock_source, this, _1)); static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); @@ -445,7 +445,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1)); //////////////////////////////////////////////////////////////////// // create dboard control objects @@ -463,32 +463,31 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); //create a new dboard interface and manager - _dboard_iface = make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); _dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1)); } //initialize io handling @@ -503,8 +502,8 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// this->update_rates(); - _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter - .subscribe(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter + .add_coerced_subscriber(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); //reset cordic rates and their properties to zero BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ diff --git a/host/lib/usrp/b100/b100_impl.hpp b/host/lib/usrp/b100/b100_impl.hpp index 5a8f70d73..7f37030d2 100644 --- a/host/lib/usrp/b100/b100_impl.hpp +++ b/host/lib/usrp/b100/b100_impl.hpp @@ -126,7 +126,6 @@ private: //dboard stuff uhd::usrp::dboard_manager::sptr _dboard_manager; - uhd::usrp::dboard_iface::sptr _dboard_iface; bool _ignore_cal_file; std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; diff --git a/host/lib/usrp/b100/dboard_iface.cpp b/host/lib/usrp/b100/dboard_iface.cpp index 325efeec1..73c97c104 100644 --- a/host/lib/usrp/b100/dboard_iface.cpp +++ b/host/lib/usrp/b100/dboard_iface.cpp @@ -66,12 +66,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -97,6 +101,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const fe_connection_t& fe_conn); private: timed_wb_iface::sptr _wb_iface; @@ -127,6 +132,7 @@ void b100_dboard_iface::set_clock_rate(unit_t unit, double rate){ switch(unit){ case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return; } } @@ -142,14 +148,15 @@ double b100_dboard_iface::get_clock_rate(unit_t unit){ switch(unit){ case UNIT_RX: return _clock->get_rx_clock_rate(); case UNIT_TX: return _clock->get_tx_clock_rate(); + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void b100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ switch(unit){ case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } @@ -160,28 +167,40 @@ double b100_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ -void b100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void b100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void b100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t b100_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void b100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void b100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t b100_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t b100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void b100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t b100_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void b100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void b100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void b100_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t b100_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t b100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ switch(unit){ case dboard_iface::UNIT_TX: return B100_SPI_SS_TX_DB; case dboard_iface::UNIT_RX: return B100_SPI_SS_RX_DB; + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void b100_dboard_iface::write_spi( @@ -268,3 +287,8 @@ uhd::time_spec_t b100_dboard_iface::get_command_time(void) { return _wb_iface->get_time(); } + +void b100_dboard_iface::set_fe_connection(unit_t, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/b200/CMakeLists.txt b/host/lib/usrp/b200/CMakeLists.txt index 76710dc65..4b9e2de55 100644 --- a/host/lib/usrp/b200/CMakeLists.txt +++ b/host/lib/usrp/b200/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the B200 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_B200) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/b200_image_loader.cpp diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp index 18dcdadba..bfb32b2c8 100644 --- a/host/lib/usrp/b200/b200_impl.cpp +++ b/host/lib/usrp/b200/b200_impl.cpp @@ -41,6 +41,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace uhd::transport; static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000); @@ -363,7 +364,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s 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)); + .add_coerced_subscriber(boost::bind(&b200_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // Identify the device type @@ -501,7 +502,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s 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)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name)); } } else @@ -578,9 +579,9 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s // 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)); + .set_coercer(boost::bind(&b200_impl::set_tick_rate, this, _1)) + .set_publisher(boost::bind(&b200_impl::get_tick_rate, this)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_tick_rate, this, _1)); _tree->create<time_spec_t>(mb_path / "time" / "cmd"); _tree->create<bool>(mb_path / "auto_tick_rate").set(false); @@ -588,7 +589,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s // 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)); + .set_publisher(boost::bind(&b200_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // create frontend mapping @@ -597,13 +598,13 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1)); //////////////////////////////////////////////////////////////////// // setup radio control @@ -627,18 +628,18 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s //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)) - .subscribe(boost::bind(&b200_impl::set_time, this, _1)) + .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&b200_impl::set_time, this, _1)) .set(0.0); //re-sync the times when the tick rate changes _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&b200_impl::sync_times, this)); + .add_coerced_subscriber(boost::bind(&b200_impl::sync_times, this)); _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) { _tree->access<time_spec_t>(mb_path / "time" / "pps") - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1)); + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1)); } //setup time source props @@ -648,8 +649,8 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options") .set(time_sources); _tree->create<std::string>(mb_path / "time_source" / "value") - .coerce(boost::bind(&check_option_valid, "time source", time_sources, _1)) - .subscribe(boost::bind(&b200_impl::update_time_source, this, _1)); + .set_coercer(boost::bind(&check_option_valid, "time source", time_sources, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_time_source, this, _1)); //setup reference source props static const std::vector<std::string> clock_sources = (_gpsdo_capable) ? boost::assign::list_of("internal")("external")("gpsdo") : @@ -657,21 +658,21 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options") .set(clock_sources); _tree->create<std::string>(mb_path / "clock_source" / "value") - .coerce(boost::bind(&check_option_valid, "clock source", clock_sources, _1)) - .subscribe(boost::bind(&b200_impl::update_clock_source, this, _1)); + .set_coercer(boost::bind(&check_option_valid, "clock source", clock_sources, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_clock_source, this, _1)); //////////////////////////////////////////////////////////////////// // front panel gpio //////////////////////////////////////////////////////////////////// - _radio_perifs[0].fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); + _radio_perifs[0].fp_gpio = gpio_atr_3000::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) { _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second) .set(0) - .subscribe(boost::bind(&b200_impl::set_fp_gpio, this, _radio_perifs[0].fp_gpio, attr.first, _1)); + .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, _radio_perifs[0].fp_gpio, attr.first, _1)); } _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .publish(boost::bind(&b200_impl::get_fp_gpio, this, _radio_perifs[0].fp_gpio)); + .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, _radio_perifs[0].fp_gpio)); //////////////////////////////////////////////////////////////////// // dboard eeproms but not really @@ -684,10 +685,14 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s //////////////////////////////////////////////////////////////////// // do some post-init tasks //////////////////////////////////////////////////////////////////// - - //init the clock rate to something reasonable - double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE); + // Init the clock rate and the auto mcr appropriately + if (not device_addr.has_key("master_clock_rate")) { + UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl; + } + // We can automatically choose a master clock rate, but not if the user specifies one + const double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE); _tree->access<double>(mb_path / "tick_rate").set(default_tick_rate); + _tree->access<bool>(mb_path / "auto_tick_rate").set(not device_addr.has_key("master_clock_rate")); //subdev spec contains full width of selections subdev_spec_t rx_spec, tx_spec; @@ -711,12 +716,6 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _radio_perifs[i].ddc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_DECIM); _radio_perifs[i].duc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_INTERP); } - // We can automatically choose a master clock rate, but not if the user specifies one - _tree->access<bool>(mb_path / "auto_tick_rate").set(not device_addr.has_key("master_clock_rate")); - if (not device_addr.has_key("master_clock_rate")) { - UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl; - } - } b200_impl::~b200_impl(void) @@ -752,22 +751,23 @@ void b200_impl::setup_radio(const size_t dspno) 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)); + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); this->register_loopback_self_test(perif.ctrl); //////////////////////////////////////////////////////////////////// // Set up peripherals //////////////////////////////////////////////////////////////////// - perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_ATR)); + perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, TOREG(SR_ATR)); + perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF); // 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), true /*is_b200?*/); perif.ddc->set_link_rate(10e9/8); //whatever - perif.ddc->set_mux("IQ", false, dspno == 1 ? true : false, dspno == 1 ? true : false); + perif.ddc->set_mux(usrp::fe_connection_t(dspno == 1 ? "IbQb" : "IQ")); perif.ddc->set_freq(rx_dsp_core_3000::DEFAULT_CORDIC_FREQ); - perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL)); + perif.deframer = tx_vita_core_3000::make_no_radio_buff(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 perif.duc->set_freq(tx_dsp_core_3000::DEFAULT_CORDIC_FREQ); @@ -784,28 +784,27 @@ void b200_impl::setup_radio(const size_t dspno) // connect rx dsp control objects //////////////////////////////////////////////////////////////////// _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)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); const fs_path rx_dsp_path = mb_path / "rx_dsps" / dspno; perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->access<double>(rx_dsp_path / "rate" / "value") - .coerce(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1)) - .subscribe(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1)) ; _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); //////////////////////////////////////////////////////////////////// // create tx dsp control objects //////////////////////////////////////////////////////////////////// _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)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); const fs_path tx_dsp_path = mb_path / "tx_dsps" / dspno; perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->access<double>(tx_dsp_path / "rate" / "value") - .coerce(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1)) - .subscribe(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1)) ; //////////////////////////////////////////////////////////////////// @@ -825,17 +824,17 @@ void b200_impl::setup_radio(const size_t dspno) // Now connect all the b200_impl-specific items _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked") - .publish(boost::bind(&b200_impl::get_fe_pll_locked, this, dir == TX_DIRECTION)) + .set_publisher(boost::bind(&b200_impl::get_fe_pll_locked, this, dir == TX_DIRECTION)) ; _tree->access<double>(rf_fe_path / "freq" / "value") - .subscribe(boost::bind(&b200_impl::update_bandsel, this, key, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_bandsel, this, key, _1)) ; if (dir == RX_DIRECTION) { 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)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1)) .set("RX2") ; @@ -971,27 +970,6 @@ void b200_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom) mb_eeprom.commit(*_iface, "B200"); } - -boost::uint32_t b200_impl::get_fp_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void b200_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value) -{ - switch (attr) - { - case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); - case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); - case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); - case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); - case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); - case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); - case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); - default: UHD_THROW_INVALID_CODE_PATH(); - } -} - /*********************************************************************** * Reference time and clock **********************************************************************/ @@ -1174,11 +1152,11 @@ void b200_impl::update_atrs(void) 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); + gpio_atr_3000::sptr atr = perif.atr; + atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF); + atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly); + atr->set_atr_reg(ATR_REG_TX_ONLY, txonly); + atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd); } if (_radio_perifs.size() > _fe2 and _radio_perifs[_fe2].atr) { @@ -1192,11 +1170,11 @@ void b200_impl::update_atrs(void) 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); + gpio_atr_3000::sptr atr = perif.atr; + atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF); + atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly); + atr->set_atr_reg(ATR_REG_TX_ONLY, txonly); + atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd); } } diff --git a/host/lib/usrp/b200/b200_impl.hpp b/host/lib/usrp/b200/b200_impl.hpp index 22dd231ce..257d9988f 100644 --- a/host/lib/usrp/b200/b200_impl.hpp +++ b/host/lib/usrp/b200/b200_impl.hpp @@ -27,7 +27,7 @@ #include "rx_vita_core_3000.hpp" #include "tx_vita_core_3000.hpp" #include "time_core_3000.hpp" -#include "gpio_core_200.hpp" +#include "gpio_atr_3000.hpp" #include "radio_ctrl_core_3000.hpp" #include "rx_dsp_core_3000.hpp" #include "tx_dsp_core_3000.hpp" @@ -49,8 +49,8 @@ #include "recv_packet_demuxer_3000.hpp" static const boost::uint8_t B200_FW_COMPAT_NUM_MAJOR = 8; static const boost::uint8_t B200_FW_COMPAT_NUM_MINOR = 0; -static const boost::uint16_t B200_FPGA_COMPAT_NUM = 13; -static const boost::uint16_t B205_FPGA_COMPAT_NUM = 4; +static const boost::uint16_t B200_FPGA_COMPAT_NUM = 14; +static const boost::uint16_t B205_FPGA_COMPAT_NUM = 5; static const double B200_BUS_CLOCK_RATE = 100e6; static const boost::uint32_t B200_GPSDO_ST_NONE = 0x83; static const size_t B200_MAX_RATE_USB2 = 53248000; // bytes/s @@ -180,8 +180,8 @@ private: struct radio_perifs_t { radio_ctrl_core_3000::sptr ctrl; - gpio_core_200_32wo::sptr atr; - gpio_core_200::sptr fp_gpio; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr atr; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr fp_gpio; time_core_3000::sptr time64; rx_vita_core_3000::sptr framer; rx_dsp_core_3000::sptr ddc; @@ -224,14 +224,10 @@ private: enum time_source_t {GPSDO=0,EXTERNAL=1,INTERNAL=2,NONE=3,UNKNOWN=4} _time_source; void update_gpio_state(void); - void reset_codec_dcm(void); void update_enables(void); void update_atrs(void); - boost::uint32_t get_fp_gpio(gpio_core_200::sptr); - void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t); - double _tick_rate; double get_tick_rate(void){return _tick_rate;} double set_tick_rate(const double rate); diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp index 93f5b22fc..98347b114 100644 --- a/host/lib/usrp/b200/b200_io_impl.cpp +++ b/host/lib/usrp/b200/b200_io_impl.cpp @@ -159,7 +159,6 @@ void b200_impl::update_tick_rate(const double new_tick_rate) 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(new_tick_rate); - perif.deframer->set_tick_rate(new_tick_rate); } } diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 9dabc4e0b..55dc92023 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -29,7 +29,7 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver") LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/adf435x_common.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp @@ -37,4 +37,5 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/fifo_ctrl_excelsior.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp3_fw_ctrl_iface.cpp ) diff --git a/host/lib/usrp/common/ad9361_ctrl.hpp b/host/lib/usrp/common/ad9361_ctrl.hpp index 5c438ee9c..8cd75d539 100644 --- a/host/lib/usrp/common/ad9361_ctrl.hpp +++ b/host/lib/usrp/common/ad9361_ctrl.hpp @@ -89,8 +89,10 @@ public: //! get the clock rate range for the frontend static uhd::meta_range_t get_clock_rate_range(void) { - //return uhd::meta_range_t(220e3, 61.44e6); - return uhd::meta_range_t(5e6, ad9361_device_t::AD9361_MAX_CLOCK_RATE); //5 MHz DCM low end + return uhd::meta_range_t( + ad9361_device_t::AD9361_MIN_CLOCK_RATE, + ad9361_device_t::AD9361_MAX_CLOCK_RATE + ); } //! set the filter bandwidth for the frontend's analog low pass diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp index 0a8a61575..bb25379c0 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp @@ -91,6 +91,7 @@ int get_num_taps(int max_num_taps) { } const double ad9361_device_t::AD9361_MAX_GAIN = 89.75; +const double ad9361_device_t::AD9361_MIN_CLOCK_RATE = 220e3; const double ad9361_device_t::AD9361_MAX_CLOCK_RATE = 61.44e6; const double ad9361_device_t::AD9361_CAL_VALID_WINDOW = 100e6; // Max bandwdith is due to filter rolloff in analog filter stage @@ -770,7 +771,7 @@ void ad9361_device_t::_calibrate_rf_dc_offset() size_t count = 0; _io_iface->poke8(0x016, 0x02); while (_io_iface->peek8(0x016) & 0x02) { - if (count > 100) { + if (count > 200) { throw uhd::runtime_error("[ad9361_device_t] RF DC Offset Calibration Failure"); break; } @@ -821,7 +822,7 @@ void ad9361_device_t::_calibrate_rx_quadrature() size_t count = 0; _io_iface->poke8(0x016, 0x20); while (_io_iface->peek8(0x016) & 0x20) { - if (count > 100) { + if (count > 1000) { throw uhd::runtime_error("[ad9361_device_t] Rx Quadrature Calibration Failure"); break; } diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h index 66bc2e8b9..73b1d9a35 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h @@ -157,6 +157,7 @@ public: //Constants static const double AD9361_MAX_GAIN; static const double AD9361_MAX_CLOCK_RATE; + static const double AD9361_MIN_CLOCK_RATE; static const double AD9361_CAL_VALID_WINDOW; static const double AD9361_RECOMMENDED_MAX_BANDWIDTH; static const double DEFAULT_RX_FREQ; diff --git a/host/lib/usrp/common/ad936x_manager.cpp b/host/lib/usrp/common/ad936x_manager.cpp index de8c4c7ab..58893395e 100644 --- a/host/lib/usrp/common/ad936x_manager.cpp +++ b/host/lib/usrp/common/ad936x_manager.cpp @@ -211,11 +211,11 @@ class ad936x_manager_impl : public ad936x_manager // Sensors subtree->create<sensor_value_t>("sensors/temp") - .publish(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) + .set_publisher(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) ; if (dir == RX_DIRECTION) { subtree->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) ; } @@ -226,7 +226,7 @@ class ad936x_manager_impl : public ad936x_manager .set(ad9361_ctrl::get_gain_range(key)); subtree->create<double>(uhd::fs_path("gains") / name / "value") .set(ad936x_manager::DEFAULT_GAIN) - .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) ; } @@ -238,19 +238,19 @@ class ad936x_manager_impl : public ad936x_manager // Analog Bandwidths subtree->create<double>("bandwidth/value") .set(ad936x_manager::DEFAULT_BANDWIDTH) - .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) ; subtree->create<meta_range_t>("bandwidth/range") - .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) ; // LO Tuning subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rf_freq_range)) ; subtree->create<double>("freq/value") - .publish(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) - .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) + .set_publisher(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) + .set_coercer(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) ; // Frontend corrections @@ -258,21 +258,21 @@ class ad936x_manager_impl : public ad936x_manager { subtree->create<bool>("dc_offset/enable" ) .set(ad936x_manager::DEFAULT_AUTO_DC_OFFSET) - .subscribe(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) ; subtree->create<bool>("iq_balance/enable" ) .set(ad936x_manager::DEFAULT_AUTO_IQ_BALANCE) - .subscribe(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) ; // AGC setup const std::list<std::string> mode_strings = boost::assign::list_of("slow")("fast"); subtree->create<bool>("gain/agc/enable") .set(DEFAULT_AGC_ENABLE) - .subscribe(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) ; subtree->create<std::string>("gain/agc/mode/value") - .subscribe(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) ; subtree->create< std::list<std::string> >("gain/agc/mode/options") .set(mode_strings) @@ -282,8 +282,8 @@ class ad936x_manager_impl : public ad936x_manager // Frontend filters BOOST_FOREACH(const std::string &filter_name, _codec_ctrl->get_filter_names(key)) { subtree->create<filter_info_base::sptr>(uhd::fs_path("filters") / filter_name / "value" ) - .publish(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) - .subscribe(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); + .set_publisher(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); } } diff --git a/host/lib/usrp/common/adf435x.cpp b/host/lib/usrp/common/adf435x.cpp new file mode 100644 index 000000000..f1ba6ad05 --- /dev/null +++ b/host/lib/usrp/common/adf435x.cpp @@ -0,0 +1,34 @@ +// +// Copyright 2013-2014 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 "adf435x.hpp" + +using namespace uhd; + +adf435x_iface::~adf435x_iface() +{ +} + +adf435x_iface::sptr adf435x_iface::make_adf4350(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4350_regs_t>(write)); +} + +adf435x_iface::sptr adf435x_iface::make_adf4351(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4351_regs_t>(write)); +} diff --git a/host/lib/usrp/common/adf435x.hpp b/host/lib/usrp/common/adf435x.hpp new file mode 100644 index 000000000..16557b514 --- /dev/null +++ b/host/lib/usrp/common/adf435x.hpp @@ -0,0 +1,330 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ADF435X_HPP +#define INCLUDED_ADF435X_HPP + +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/log.hpp> +#include <boost/function.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <vector> +#include "adf4350_regs.hpp" +#include "adf4351_regs.hpp" + +class adf435x_iface +{ +public: + typedef boost::shared_ptr<adf435x_iface> sptr; + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t; + + static sptr make_adf4350(write_fn_t write); + static sptr make_adf4351(write_fn_t write); + + virtual ~adf435x_iface() = 0; + + enum prescaler_t { PRESCALER_4_5, PRESCALER_8_9 }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + virtual void set_reference_freq(double fref) = 0; + + virtual void set_prescaler(prescaler_t prescaler) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual uhd::range_t get_int_range() = 0; + + virtual double set_frequency(double target_freq, bool int_n_mode, bool flush = false) = 0; + + virtual void commit(void) = 0; +}; + +template <typename adf435x_regs_t> +class adf435x_impl : public adf435x_iface +{ +public: + adf435x_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _fb_after_divider(false), + _reference_freq(0.0), + _N_min(-1) + {} + + virtual ~adf435x_impl() {}; + + void set_reference_freq(double fref) + { + _reference_freq = fref; + } + + void set_feedback_select(feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + } + + void set_prescaler(prescaler_t prescaler) + { + if (prescaler == PRESCALER_8_9) { + _regs.prescaler = adf435x_regs_t::PRESCALER_8_9; + _N_min = 75; + } else { + _regs.prescaler = adf435x_regs_t::PRESCALER_4_5; + _N_min = 23; + } + } + + void set_output_power(output_power_t power) + { + switch (power) { + case OUTPUT_POWER_M4DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + uhd::range_t get_int_range() + { + if (_N_min < 0) throw uhd::runtime_error("set_prescaler must be called before get_int_range"); + return uhd::range_t(_N_min, 4095); + } + + double set_frequency(double target_freq, bool int_n_mode, bool flush = false) + { + static const double REF_DOUBLER_THRESH_FREQ = 12.5e6; + static const double PFD_FREQ_MAX = 25.0e6; + static const double BAND_SEL_FREQ_MAX = 100e3; + static const double VCO_FREQ_MIN = 2.2e9; + static const double VCO_FREQ_MAX = 4.4e9; + + //Default invalid value for actual_freq + double actual_freq = 0; + + uhd::range_t rf_divider_range = _get_rfdiv_range(); + uhd::range_t int_range = get_int_range(); + + double pfd_freq = 0; + boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; + boost::uint16_t RFdiv = static_cast<boost::uint16_t>(rf_divider_range.start()); + bool D = false, T = false; + + //Reference doubler for 50% duty cycle + D = (_reference_freq <= REF_DOUBLER_THRESH_FREQ); + + //increase RF divider until acceptable VCO frequency + double vco_freq = target_freq; + while (vco_freq < VCO_FREQ_MIN && RFdiv < static_cast<boost::uint16_t>(rf_divider_range.stop())) { + vco_freq *= 2; + RFdiv *= 2; + } + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exits when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_actual = f_vco/RFdiv) + */ + double feedback_freq = _fb_after_divider ? target_freq : vco_freq; + + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = _reference_freq*(D?2:1)/(R*(T?2:1)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > PFD_FREQ_MAX) continue; + + //First, ignore fractional part of tuning + N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < static_cast<boost::uint16_t>(int_range.start())) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below band_sel_freq_max + //constraint on band select clock + if (pfd_freq/BS > BAND_SEL_FREQ_MAX) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); + if (int_n_mode) { + if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target + N++; + } + FRAC = 0; + } + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0) { + T = true; + R /= 2; + } + + //Typical phase resync time documented in data sheet pg.24 + static const double PHASE_RESYNC_TIME = 400e-6; + + //If feedback after divider, then compensation for the divider is pulled into the INT value + int rf_div_compensation = _fb_after_divider ? 1 : RFdiv; + + //Compute the actual frequency in terms of _reference_freq, N, FRAC, MOD, D, R and T. + actual_freq = ( + double((N + (double(FRAC)/double(MOD))) * + (_reference_freq*(D?2:1)/(R*(T?2:1)))) + ) / rf_div_compensation; + + _regs.frac_12_bit = FRAC; + _regs.int_16_bit = N; + _regs.mod_12_bit = MOD; + _regs.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); + _regs.feedback_select = _fb_after_divider ? + adf435x_regs_t::FEEDBACK_SELECT_DIVIDED : + adf435x_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + _regs.clock_div_mode = _fb_after_divider ? + adf435x_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : + adf435x_regs_t::CLOCK_DIV_MODE_FAST_LOCK; + _regs.r_counter_10_bit = R; + _regs.reference_divide_by_2 = T ? + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = D ? + adf435x_regs_t::REFERENCE_DOUBLER_ENABLED : + adf435x_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.band_select_clock_div = boost::uint8_t(BS); + _regs.rf_divider_select = static_cast<typename adf435x_regs_t::rf_divider_select_t>(_get_rfdiv_setting(RFdiv)); + _regs.ldf = int_n_mode ? + adf435x_regs_t::LDF_INT_N : + adf435x_regs_t::LDF_FRAC_N; + + std::string tuning_str = (int_n_mode) ? "Integer-N" : "Fractional"; + UHD_LOGV(often) + << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" + ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl + << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" + ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (_reference_freq/1e6) << std::endl + << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl + << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; + + UHD_ASSERT_THROW((_regs.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); + + UHD_ASSERT_THROW(vco_freq >= VCO_FREQ_MIN and vco_freq <= VCO_FREQ_MAX); + UHD_ASSERT_THROW(RFdiv >= static_cast<boost::uint16_t>(rf_divider_range.start())); + UHD_ASSERT_THROW(RFdiv <= static_cast<boost::uint16_t>(rf_divider_range.stop())); + UHD_ASSERT_THROW(_regs.int_16_bit >= static_cast<boost::uint16_t>(int_range.start())); + UHD_ASSERT_THROW(_regs.int_16_bit <= static_cast<boost::uint16_t>(int_range.stop())); + + if (flush) commit(); + return actual_freq; + } + + void commit() + { + //reset counters + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_ENABLED; + std::vector<boost::uint32_t> regs; + regs.push_back(_regs.get_reg(boost::uint32_t(2))); + _write_fn(regs); + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_DISABLED; + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + regs.clear(); + for (int addr = 5; addr >= 0; addr--) { + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + _write_fn(regs); + } + +protected: + uhd::range_t _get_rfdiv_range(); + int _get_rfdiv_setting(boost::uint16_t div); + + write_fn_t _write_fn; + adf435x_regs_t _regs; + double _fb_after_divider; + double _reference_freq; + int _N_min; +}; + +template <> +inline uhd::range_t adf435x_impl<adf4350_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 16); +} + +template <> +inline uhd::range_t adf435x_impl<adf4351_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 64); +} + +template <> +inline int adf435x_impl<adf4350_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV16); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <> +inline int adf435x_impl<adf4351_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV16); + case 32: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV32); + case 64: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV64); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +#endif // INCLUDED_ADF435X_HPP diff --git a/host/lib/usrp/common/adf435x_common.cpp b/host/lib/usrp/common/adf435x_common.cpp deleted file mode 100644 index 474a1c932..000000000 --- a/host/lib/usrp/common/adf435x_common.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright 2013-2014 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 "adf435x_common.hpp" - -#include <boost/math/special_functions/round.hpp> -#include <uhd/types/tune_request.hpp> -#include <uhd/utils/log.hpp> -#include <cmath> - - -using namespace uhd; - -/*********************************************************************** - * ADF 4350/4351 Tuning Utility - **********************************************************************/ -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq) -{ - //Default invalid value for actual_freq - actual_freq = 0; - - double pfd_freq = 0; - boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; - boost::uint16_t RFdiv = static_cast<boost::uint16_t>(constraints.rf_divider_range.start()); - bool D = false, T = false; - - //Reference doubler for 50% duty cycle - //If ref_freq < 12.5MHz enable the reference doubler - D = (ref_freq <= constraints.ref_doubler_threshold); - - static const double MIN_VCO_FREQ = 2.2e9; - static const double MAX_VCO_FREQ = 4.4e9; - - //increase RF divider until acceptable VCO frequency - double vco_freq = target_freq; - while (vco_freq < MIN_VCO_FREQ && RFdiv < static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())) { - vco_freq *= 2; - RFdiv *= 2; - } - - /* - * The goal here is to loop though possible R dividers, - * band select clock dividers, N (int) dividers, and FRAC - * (frac) dividers. - * - * Calculate the N and F dividers for each set of values. - * The loop exits when it meets all of the constraints. - * The resulting loop values are loaded into the registers. - * - * from pg.21 - * - * f_pfd = f_ref*(1+D)/(R*(1+T)) - * f_vco = (N + (FRAC/MOD))*f_pfd - * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD - * f_actual = f_vco/RFdiv) - */ - double feedback_freq = constraints.feedback_after_divider ? target_freq : vco_freq; - - for(R = 1; R <= 1023; R+=1){ - //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) - pfd_freq = ref_freq*(D?2:1)/(R*(T?2:1)); - - //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) - if (pfd_freq > constraints.pfd_freq_max) continue; - - //First, ignore fractional part of tuning - N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); - - //keep N > minimum int divider requirement - if (N < static_cast<boost::uint16_t>(constraints.int_range.start())) continue; - - for(BS=1; BS <= 255; BS+=1){ - //keep the band select frequency at or below band_sel_freq_max - //constraint on band select clock - if (pfd_freq/BS > constraints.band_sel_freq_max) continue; - goto done_loop; - } - } done_loop: - - //Fractional-N calculation - MOD = 4095; //max fractional accuracy - FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); - if (constraints.force_frac0) { - if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target - N++; - } - FRAC = 0; - } - - //Reference divide-by-2 for 50% duty cycle - // if R even, move one divide by 2 to to regs.reference_divide_by_2 - if(R % 2 == 0) { - T = true; - R /= 2; - } - - //Typical phase resync time documented in data sheet pg.24 - static const double PHASE_RESYNC_TIME = 400e-6; - - //If feedback after divider, then compensation for the divider is pulled into the INT value - int rf_div_compensation = constraints.feedback_after_divider ? 1 : RFdiv; - - //Compute the actual frequency in terms of ref_freq, N, FRAC, MOD, D, R and T. - actual_freq = ( - double((N + (double(FRAC)/double(MOD))) * - (ref_freq*(D?2:1)/(R*(T?2:1)))) - ) / rf_div_compensation; - - //load the settings - adf435x_tuning_settings settings; - settings.frac_12_bit = FRAC; - settings.int_16_bit = N; - settings.mod_12_bit = MOD; - settings.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); - settings.r_counter_10_bit = R; - settings.r_divide_by_2_en = T; - settings.r_doubler_en = D; - settings.band_select_clock_div = boost::uint8_t(BS); - settings.rf_divider = RFdiv; - - std::string tuning_str = (constraints.force_frac0) ? "Integer-N" : "Fractional"; - UHD_LOGV(often) - << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" - ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl - << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" - ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (ref_freq/1e6) << std::endl - << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl - << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" - ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; - - UHD_ASSERT_THROW((settings.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); - - UHD_ASSERT_THROW(vco_freq >= MIN_VCO_FREQ and vco_freq <= MAX_VCO_FREQ); - UHD_ASSERT_THROW(settings.rf_divider >= static_cast<boost::uint16_t>(constraints.rf_divider_range.start())); - UHD_ASSERT_THROW(settings.rf_divider <= static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())); - UHD_ASSERT_THROW(settings.int_16_bit >= static_cast<boost::uint16_t>(constraints.int_range.start())); - UHD_ASSERT_THROW(settings.int_16_bit <= static_cast<boost::uint16_t>(constraints.int_range.stop())); - - return settings; -} diff --git a/host/lib/usrp/common/adf435x_common.hpp b/host/lib/usrp/common/adf435x_common.hpp deleted file mode 100644 index 617b9d97f..000000000 --- a/host/lib/usrp/common/adf435x_common.hpp +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright 2014 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_ADF435X_COMMON_HPP -#define INCLUDED_ADF435X_COMMON_HPP - -#include <boost/cstdint.hpp> -#include <uhd/property_tree.hpp> -#include <uhd/types/ranges.hpp> - -//Common IO Pins -#define ADF435X_CE (1 << 3) -#define ADF435X_PDBRF (1 << 2) -#define ADF435X_MUXOUT (1 << 1) // INPUT!!! -#define LOCKDET_MASK (1 << 0) // INPUT!!! - -#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control -#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control - -struct adf435x_tuning_constraints { - bool force_frac0; - bool feedback_after_divider; - double ref_doubler_threshold; - double pfd_freq_max; - double band_sel_freq_max; - uhd::range_t rf_divider_range; - uhd::range_t int_range; -}; - -struct adf435x_tuning_settings { - boost::uint16_t frac_12_bit; - boost::uint16_t int_16_bit; - boost::uint16_t mod_12_bit; - boost::uint16_t r_counter_10_bit; - bool r_doubler_en; - bool r_divide_by_2_en; - boost::uint16_t clock_divider_12_bit; - boost::uint8_t band_select_clock_div; - boost::uint16_t rf_divider; -}; - -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq -); - -#endif /* INCLUDED_ADF435X_COMMON_HPP */ diff --git a/host/lib/usrp/common/constrained_device_args.hpp b/host/lib/usrp/common/constrained_device_args.hpp new file mode 100644 index 000000000..1bfd1df00 --- /dev/null +++ b/host/lib/usrp/common/constrained_device_args.hpp @@ -0,0 +1,283 @@ +// +// Copyright 2014 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_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/exception.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <vector> +#include <string> + +namespace uhd { +namespace usrp { + + /*! + * constrained_device_args_t provides a base and utilities to + * map key=value pairs passed in through the device creation + * args interface (device_addr_t). + * + * Inherit from this class to create typed device specific + * arguments and use the base class methods to handle parsing + * the device_addr or any key=value string to populate the args + * + * This file contains a library of different types of args the + * the user can pass in. The library can be extended to support + * non-intrinsic types by the client. + * + */ + class constrained_device_args_t { + public: //Types + + /*! + * Base argument type. All other arguments inherit from this. + */ + class generic_arg { + public: + generic_arg(const std::string& key): _key(key) {} + inline const std::string& key() const { return _key; } + inline virtual std::string to_string() const = 0; + private: + std::string _key; + }; + + /*! + * String argument type. Can be case sensitive or insensitive + */ + template<bool case_sensitive> + class str_arg : public generic_arg { + public: + str_arg(const std::string& name, const std::string& default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const std::string& value) { + _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); + } + inline const std::string& get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + set(str_rep); + } + inline virtual std::string to_string() const { + return key() + "=" + get(); + } + inline bool operator==(const std::string& rhs) const { + return get() == boost::algorithm::to_lower_copy(rhs); + } + private: + std::string _value; + }; + typedef str_arg<false> str_ci_arg; + typedef str_arg<true> str_cs_arg; + + /*! + * Numeric argument type. The template type data_t allows the + * client to constrain the type of the number. + */ + template<typename data_t> + class num_arg : public generic_arg { + public: + num_arg(const std::string& name, const data_t default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const data_t value) { + _value = value; + } + inline const data_t get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = boost::lexical_cast<data_t>(str_rep); + } catch (std::exception& ex) { + throw uhd::value_error(str(boost::format( + "Error parsing numeric parameter %s: %s.") % + key() % ex.what() + )); + } + } + inline virtual std::string to_string() const { + return key() + "=" + boost::lexical_cast<std::string>(get()); + } + private: + data_t _value; + }; + + /*! + * Enumeration argument type. The template type enum_t allows the + * client to use their own enum and specify a string mapping for + * the values of the enum + * + * NOTE: The constraint on enum_t is that the values must start with + * 0 and be sequential + */ + template<typename enum_t> + class enum_arg : public generic_arg { + public: + enum_arg( + const std::string& name, + const enum_t default_value, + const std::vector<std::string>& values) : + generic_arg(name), _str_values(values) + { set(default_value); } + + inline void set(const enum_t value) { + _value = value; + } + inline const enum_t get() const { + return _value; + } + inline void parse(const std::string& str_rep, bool assert_invalid = true) { + std::string valid_values_str; + for (size_t i = 0; i < _str_values.size(); i++) { + if (boost::algorithm::to_lower_copy(str_rep) == + boost::algorithm::to_lower_copy(_str_values[i])) + { + valid_values_str += ((i==0)?"":", ") + _str_values[i]; + set(static_cast<enum_t>(static_cast<int>(i))); + return; + } + } + //If we reach here then, the string enum value was invalid + if (assert_invalid) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s=%s (Valid: {%s})") % + key() % str_rep % valid_values_str + )); + } + } + inline virtual std::string to_string() const { + size_t index = static_cast<size_t>(static_cast<int>(_value)); + UHD_ASSERT_THROW(index < _str_values.size()); + return key() + "=" + _str_values[index]; + } + + private: + enum_t _value; + std::vector<std::string> _str_values; + }; + + /*! + * Boolean argument type. + */ + class bool_arg : public generic_arg { + public: + bool_arg(const std::string& name, const bool default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const bool value) { + _value = value; + } + inline bool get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = (boost::lexical_cast<int>(str_rep) != 0); + } catch (std::exception& ex) { + if (str_rep.empty()) { + //If str_rep is empty then the device_addr was set + //without a value which means that the user "set" the flag + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "true" || + boost::algorithm::to_lower_copy(str_rep) == "yes" || + boost::algorithm::to_lower_copy(str_rep) == "y") { + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "false" || + boost::algorithm::to_lower_copy(str_rep) == "no" || + boost::algorithm::to_lower_copy(str_rep) == "n") { + _value = false; + } else { + throw uhd::value_error(str(boost::format( + "Error parsing boolean parameter %s: %s.") % + key() % ex.what() + )); + } + } + } + inline virtual std::string to_string() const { + return key() + "=" + (get() ? "true" : "false"); + } + private: + bool _value; + }; + + public: //Methods + constrained_device_args_t() {} + virtual ~constrained_device_args_t() {} + + void parse(const std::string& str_args) { + device_addr_t dev_args(str_args); + _parse(dev_args); + } + + void parse(const device_addr_t& dev_args) { + _parse(dev_args); + } + + inline virtual std::string to_string() const = 0; + + protected: //Methods + //Override _parse to provide an implementation to parse all + //client specific device args + virtual void _parse(const device_addr_t& dev_args) = 0; + + /*! + * Utility: Ensure that the value of the device arg is between min and max + */ + template<typename num_data_t> + static inline void _enforce_range(const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max) { + if (arg.get() > max || arg.get() < min) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") % + arg.to_string() % + boost::lexical_cast<std::string>(min) % boost::lexical_cast<std::string>(max))); + } + } + + /*! + * Utility: Ensure that the value of the device arg is is contained in valid_values + */ + template<typename arg_t, typename data_t> + static inline void _enforce_discrete(const arg_t& arg, const std::vector<data_t>& valid_values) { + bool match = false; + BOOST_FOREACH(const data_t& val, valid_values) { + if (val == arg.get()) { + match = true; + break; + } + } + if (!match) { + std::string valid_values_str; + for (size_t i = 0; i < valid_values.size(); i++) { + valid_values_str += ((i==0)?"":", ") + boost::lexical_cast<std::string>(valid_values[i]); + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Valid: {%s})") % + arg.to_string() % valid_values_str + )); + } + } + } + }; +}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */ diff --git a/host/lib/usrp/common/fw_comm_protocol.h b/host/lib/usrp/common/fw_comm_protocol.h new file mode 100644 index 000000000..14adb33a9 --- /dev/null +++ b/host/lib/usrp/common/fw_comm_protocol.h @@ -0,0 +1,102 @@ +// +// Copyright 2014 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_FW_COMM_PROTOCOL +#define INCLUDED_FW_COMM_PROTOCOL + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +/*! + * Structs and constants for communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +#define FW_COMM_PROTOCOL_SIGNATURE 0xACE3 +#define FW_COMM_PROTOCOL_VERSION 0 +#define FW_COMM_MAX_DATA_WORDS 16 +#define FW_COMM_PROTOCOL_MTU 256 + +#define FW_COMM_FLAGS_ACK 0x00000001 +#define FW_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define FW_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define FW_COMM_CMD_ECHO 0x00000000 +#define FW_COMM_CMD_POKE32 0x00000010 +#define FW_COMM_CMD_PEEK32 0x00000020 +#define FW_COMM_CMD_BLOCK_POKE32 0x00000030 +#define FW_COMM_CMD_BLOCK_PEEK32 0x00000040 + +#define FW_COMM_ERR_PKT_ERROR 0x80000000 +#define FW_COMM_ERR_CMD_ERROR 0x40000000 +#define FW_COMM_ERR_SIZE_ERROR 0x20000000 + +#define FW_COMM_GENERATE_ID(prod) ((((uint32_t) FW_COMM_PROTOCOL_SIGNATURE) << 0) | \ + (((uint32_t) prod) << 16) | \ + (((uint32_t) FW_COMM_PROTOCOL_VERSION) << 24)) + +#define FW_COMM_GET_PROTOCOL_SIG(id) ((uint16_t)(id & 0xFFFF)) +#define FW_COMM_GET_PRODUCT_ID(id) ((uint8_t)(id >> 16)) +#define FW_COMM_GET_PROTOCOL_VER(id) ((uint8_t)(id >> 24)) + +typedef struct +{ + uint32_t id; //Protocol and device identifier + uint32_t flags; //Holds commands and ack messages + uint32_t sequence; //Sequence number (specific to FW communication transactions) + uint32_t data_words; //Number of data words in payload + uint32_t addr; //Address field for the command in flags + uint32_t data[FW_COMM_MAX_DATA_WORDS]; //Data field for the command in flags +} fw_comm_pkt_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +typedef void (*poke32_func)(const uint32_t addr, const uint32_t data); +typedef uint32_t (*peek32_func)(const uint32_t addr); + +/*! + * Process a firmware communication packet and compute a response. + * Args: + * - (in) request: Pointer to the request struct + * - (out) response: Pointer to the response struct + * - (in) product_id: The 8-bit usrp3 specific product ID (for request filtering) + * - (func) poke_callback, peek_callback: Callback functions for a single peek/poke + * - return value: Send a response packet + */ +bool process_fw_comm_protocol_pkt( + const fw_comm_pkt_t* request, + fw_comm_pkt_t* response, + uint8_t product_id, + uint32_t iface_id, + poke32_func poke_callback, + peek32_func peek_callback +); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_FW_COMM_PROTOCOL */ diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp new file mode 100644 index 000000000..ef541e37f --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp @@ -0,0 +1,246 @@ +// +// 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 "usrp3_fw_ctrl_iface.hpp" + +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/foreach.hpp> +#include "fw_comm_protocol.h" + +namespace uhd { namespace usrp { namespace usrp3 { + +//---------------------------------------------------------- +// Factory method +//---------------------------------------------------------- +uhd::wb_iface::sptr usrp3_fw_ctrl_iface::make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) +{ + return wb_iface::sptr(new usrp3_fw_ctrl_iface(udp_xport, product_id, verbose)); +} + +//---------------------------------------------------------- +// udp_fw_ctrl_iface +//---------------------------------------------------------- + +usrp3_fw_ctrl_iface::usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) : + _product_id(product_id), _verbose(verbose), _udp_xport(udp_xport), + _seq_num(0) +{ + flush(); + peek32(0); +} + +usrp3_fw_ctrl_iface::~usrp3_fw_ctrl_iface() +{ + flush(); +} + +void usrp3_fw_ctrl_iface::flush() +{ + boost::mutex::scoped_lock lock(_mutex); + _flush(); +} + +void usrp3_fw_ctrl_iface::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + _poke32(addr, data); + return; + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw poke32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } +} + +boost::uint32_t usrp3_fw_ctrl_iface::peek32(const wb_addr_type addr) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + return _peek32(addr); + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw peek32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } + return 0; +} + +void usrp3_fw_ctrl_iface::_poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_POKE32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = uhd::htonx(data); + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw poke32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_POKE32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + UHD_ASSERT_THROW(reply.data[0] == request.data[0]); +} + +boost::uint32_t usrp3_fw_ctrl_iface::_peek32(const wb_addr_type addr) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_PEEK32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = 0; + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw peek32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_PEEK32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + + //return result! + return uhd::ntohx<boost::uint32_t>(reply.data[0]); +} + +void usrp3_fw_ctrl_iface::_flush(void) +{ + char buff[FW_COMM_PROTOCOL_MTU] = {}; + while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { + /*NOP*/ + } +} + +std::vector<std::string> usrp3_fw_ctrl_iface::discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id) +{ + std::vector<std::string> addrs; + + //Create a UDP transport to communicate: + //Some devices will cause a throw when opened for a broadcast address. + //We print and recover so the caller can loop through all bcast addrs. + uhd::transport::udp_simple::sptr udp_bcast_xport; + try { + udp_bcast_xport = uhd::transport::udp_simple::make_broadcast(addr_hint, port); + } catch(const std::exception &e) { + UHD_MSG(error) << boost::format("Cannot open UDP transport on %s for discovery\n%s") + % addr_hint % e.what() << std::endl; + return addrs; + } + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_bcast_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + while (true) { + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050); + if (nbytes != sizeof(fw_comm_pkt_t)) break; //No more responses or responses are invalid + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + addrs.push_back(udp_bcast_xport->get_recv_addr()); + } + } + + return addrs; +} + +boost::uint32_t usrp3_fw_ctrl_iface::get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id) +{ + uhd::transport::udp_simple::sptr udp_xport = + uhd::transport::udp_simple::make_connected(addr, port); + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_xport->recv(boost::asio::buffer(buff), 1.0); + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (nbytes > 0 && + request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + return uhd::ntohx<boost::uint32_t>(reply->data[0]); + } else { + throw uhd::io_error("udp get_iface_id - bad response"); + } +} + +}}} //namespace diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp new file mode 100644 index 000000000..33286861b --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp @@ -0,0 +1,72 @@ +// +// Copyright 2014 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_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include <vector> + +namespace uhd { namespace usrp { namespace usrp3 { + +class usrp3_fw_ctrl_iface : public uhd::wb_iface +{ +public: + usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose); + virtual ~usrp3_fw_ctrl_iface(); + + // -- uhd::wb_iface -- + void poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t peek32(const wb_addr_type addr); + void flush(); + + static uhd::wb_iface::sptr make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose = true); + // -- uhd::wb_iface -- + + static std::vector<std::string> discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id); + + static boost::uint32_t get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id); + +private: + void _poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t _peek32(const wb_addr_type addr); + void _flush(void); + + const boost::uint16_t _product_id; + const bool _verbose; + uhd::transport::udp_simple::sptr _udp_xport; + boost::uint32_t _seq_num; + boost::mutex _mutex; + + static const size_t NUM_RETRIES = 3; +}; + +}}} //namespace + +#endif //INCLUDED_LIBUHD_USRP_USRP3_USRP3_UDP_FW_CTRL_HPP diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt index f28ae040f..404fc6137 100644 --- a/host/lib/usrp/cores/CMakeLists.txt +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -37,7 +37,11 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/time_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/spi_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/i2c_core_100_wb32.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dsp_core_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_core_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/gpio_atr_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_core_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_3000.cpp ) diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.cpp b/host/lib/usrp/cores/dma_fifo_core_3000.cpp new file mode 100644 index 000000000..1a9d5dd5c --- /dev/null +++ b/host/lib/usrp/cores/dma_fifo_core_3000.cpp @@ -0,0 +1,397 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dma_fifo_core_3000.hpp" +#include <uhd/exception.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <uhd/utils/soft_register.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; + +#define SR_DRAM_BIST_BASE 16 + +dma_fifo_core_3000::~dma_fifo_core_3000(void) { + /* NOP */ +} + +class dma_fifo_core_3000_impl : public dma_fifo_core_3000 +{ +protected: + class rb_addr_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADDR, /*width*/ 3, /*shift*/ 0); //[2:0] + + static const boost::uint32_t RB_FIFO_STATUS = 0; + static const boost::uint32_t RB_BIST_STATUS = 1; + static const boost::uint32_t RB_BIST_XFER_CNT = 2; + static const boost::uint32_t RB_BIST_CYC_CNT = 3; + + rb_addr_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 0) + { + //Initial values + set(ADDR, RB_FIFO_STATUS); + } + }; + + class fifo_ctrl_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(CLEAR_FIFO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_EN, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(BURST_TIMEOUT, /*width*/ 12, /*shift*/ 4); //[15:4] + UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_THRESH, /*width*/ 16, /*shift*/ 16); //[31:16] + + fifo_ctrl_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 4) + { + //Initial values + set(CLEAR_FIFO, 1); + set(RD_SUPPRESS_EN, 0); + set(BURST_TIMEOUT, 256); + set(RD_SUPPRESS_THRESH, 0); + } + }; + + class base_addr_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(BASE_ADDR, /*width*/ 30, /*shift*/ 0); //[29:0] + + base_addr_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 8) + { + //Initial values + set(BASE_ADDR, 0x00000000); + } + }; + + class addr_mask_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADDR_MASK, /*width*/ 30, /*shift*/ 0); //[29:0] + + addr_mask_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 12) + { + //Initial values + set(ADDR_MASK, 0xFF000000); + } + }; + + class bist_ctrl_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(GO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(CONTINUOUS_MODE, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(TEST_PATT, /*width*/ 2, /*shift*/ 4); //[5:4] + + static const boost::uint32_t TEST_PATT_ZERO_ONE = 0; + static const boost::uint32_t TEST_PATT_CHECKERBOARD = 1; + static const boost::uint32_t TEST_PATT_COUNT = 2; + static const boost::uint32_t TEST_PATT_COUNT_INV = 3; + + bist_ctrl_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 16) + { + //Initial values + set(GO, 0); + set(CONTINUOUS_MODE, 0); + set(TEST_PATT, TEST_PATT_ZERO_ONE); + } + }; + + class bist_cfg_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(MAX_PKTS, /*width*/ 18, /*shift*/ 0); //[17:0] + UHD_DEFINE_SOFT_REG_FIELD(MAX_PKT_SIZE, /*width*/ 13, /*shift*/ 18); //[30:18] + UHD_DEFINE_SOFT_REG_FIELD(PKT_SIZE_RAMP, /*width*/ 1, /*shift*/ 31); //[31] + + bist_cfg_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 20) + { + //Initial values + set(MAX_PKTS, 0); + set(MAX_PKT_SIZE, 0); + set(PKT_SIZE_RAMP, 0); + } + }; + + class bist_delay_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(TX_PKT_DELAY, /*width*/ 16, /*shift*/ 0); //[15:0] + UHD_DEFINE_SOFT_REG_FIELD(RX_SAMP_DELAY, /*width*/ 8, /*shift*/ 16); //[23:16] + + bist_delay_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 24) + { + //Initial values + set(TX_PKT_DELAY, 0); + set(RX_SAMP_DELAY, 0); + } + }; + + class bist_sid_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SID, /*width*/ 32, /*shift*/ 0); //[31:0] + + bist_sid_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 28) + { + //Initial values + set(SID, 0); + } + }; + +public: + class fifo_readback { + public: + fifo_readback(wb_iface::sptr iface, const size_t base, const size_t rb_addr) : + _iface(iface), _addr_reg(base), _rb_addr(rb_addr) + { + _addr_reg.initialize(*iface, true); + } + + bool is_fifo_instantiated() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x80000000; + } + + boost::uint32_t get_occupied_cnt() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x7FFFFFF; + } + + boost::uint32_t is_fifo_busy() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x40000000; + } + + struct bist_status_t { + bool running; + bool finished; + boost::uint8_t error; + }; + + bist_status_t get_bist_status() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS); + boost::uint32_t st32 = _iface->peek32(_rb_addr) & 0xF; + bist_status_t status; + status.running = st32 & 0x1; + status.finished = st32 & 0x2; + status.error = static_cast<boost::uint8_t>((st32>>2) & 0x3); + return status; + } + + bool is_ext_bist_supported() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS); + return _iface->peek32(_rb_addr) & 0x80000000; + } + + double get_xfer_ratio() { + boost::lock_guard<boost::mutex> lock(_mutex); + boost::uint32_t xfer_cnt = 0, cyc_cnt = 0; + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_XFER_CNT); + xfer_cnt = _iface->peek32(_rb_addr); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_CYC_CNT); + cyc_cnt = _iface->peek32(_rb_addr); + return (static_cast<double>(xfer_cnt)/cyc_cnt); + } + + private: + wb_iface::sptr _iface; + rb_addr_reg_t _addr_reg; + const size_t _rb_addr; + boost::mutex _mutex; + }; + +public: + dma_fifo_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback): + _iface(iface), _base(base), _fifo_readback(iface, base, readback), + _fifo_ctrl_reg(base), _base_addr_reg(base), _addr_mask_reg(base), + _bist_ctrl_reg(base), _bist_cfg_reg(base), _bist_delay_reg(base), _bist_sid_reg(base) + { + _fifo_ctrl_reg.initialize(*iface, true); + _base_addr_reg.initialize(*iface, true); + _addr_mask_reg.initialize(*iface, true); + _bist_ctrl_reg.initialize(*iface, true); + _bist_cfg_reg.initialize(*iface, true); + _has_ext_bist = _fifo_readback.is_ext_bist_supported(); + if (_has_ext_bist) { + _bist_delay_reg.initialize(*iface, true); + _bist_sid_reg.initialize(*iface, true); + } + flush(); + } + + virtual void flush() { + //Clear the FIFO and hold it in that state + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); + //Re-arm the FIFO + _wait_for_fifo_empty(); + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 0); + } + + virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) { + //Validate parameters + if (size < 8192) throw uhd::runtime_error("DMA FIFO must be larger than 8KiB"); + boost::uint32_t size_mask = size - 1; + if (size & size_mask) throw uhd::runtime_error("DMA FIFO size must be a power of 2"); + + //Clear the FIFO and hold it in that state + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); + //Write base address and mask + _base_addr_reg.write(base_addr_reg_t::BASE_ADDR, base_addr); + _addr_mask_reg.write(addr_mask_reg_t::ADDR_MASK, ~size_mask); + + //Re-arm the FIFO + flush(); + } + + virtual boost::uint32_t get_bytes_occupied() { + return _fifo_readback.get_occupied_cnt() * 8; + } + + virtual bool ext_bist_supported() { + return _fifo_readback.is_ext_bist_supported(); + } + + virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) { + return run_ext_bist(finite, 0, 0, 0, timeout_ms); + } + + virtual boost::uint8_t run_ext_bist( + bool finite, + boost::uint32_t rx_samp_delay, + boost::uint32_t tx_pkt_delay, + boost::uint32_t sid, + boost::uint32_t timeout_ms = 500 + ) { + boost::lock_guard<boost::mutex> lock(_mutex); + + _wait_for_bist_done(timeout_ms, true); //Stop previous BIST and wait (if running) + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0); //Reset + + _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKTS, (2^18)-1); + _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKT_SIZE, 8000); + _bist_cfg_reg.set(bist_cfg_reg_t::PKT_SIZE_RAMP, 0); + _bist_cfg_reg.flush(); + + if (_has_ext_bist) { + _bist_delay_reg.set(bist_delay_reg_t::RX_SAMP_DELAY, rx_samp_delay); + _bist_delay_reg.set(bist_delay_reg_t::TX_PKT_DELAY, tx_pkt_delay); + _bist_delay_reg.flush(); + + _bist_sid_reg.write(bist_sid_reg_t::SID, sid); + } else { + if (rx_samp_delay != 0 || tx_pkt_delay != 0 || sid != 0) { + throw uhd::not_implemented_error( + "dma_fifo_core_3000: Runtime delay and SID support only available on FPGA images with extended BIST enabled"); + } + } + + _bist_ctrl_reg.set(bist_ctrl_reg_t::TEST_PATT, bist_ctrl_reg_t::TEST_PATT_COUNT); + _bist_ctrl_reg.set(bist_ctrl_reg_t::CONTINUOUS_MODE, finite ? 0 : 1); + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 1); + + if (!finite) { + boost::this_thread::sleep(boost::posix_time::milliseconds(timeout_ms)); + } + + _wait_for_bist_done(timeout_ms, !finite); + if (!_fifo_readback.get_bist_status().finished) { + throw uhd::runtime_error("dma_fifo_core_3000: DRAM BIST state machine is in a bad state."); + } + + return _fifo_readback.get_bist_status().error; + } + + virtual double get_bist_throughput(double fifo_clock_rate) { + if (_has_ext_bist) { + _wait_for_bist_done(1000); + static const double BYTES_PER_CYC = 8; + return _fifo_readback.get_xfer_ratio() * fifo_clock_rate * BYTES_PER_CYC; + } else { + throw uhd::not_implemented_error( + "dma_fifo_core_3000: Throughput counter only available on FPGA images with extended BIST enabled"); + } + } + +private: + void _wait_for_fifo_empty() + { + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + while (_fifo_readback.is_fifo_busy()) { + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + if (elapsed.total_milliseconds() > 100) break; + } + } + + void _wait_for_bist_done(boost::uint32_t timeout_ms, bool force_stop = false) + { + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + while (_fifo_readback.get_bist_status().running) { + if (force_stop) { + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0); + force_stop = false; + } + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + if (elapsed.total_milliseconds() > timeout_ms) break; + } + } + +private: + wb_iface::sptr _iface; + const size_t _base; + boost::mutex _mutex; + bool _has_ext_bist; + + fifo_readback _fifo_readback; + fifo_ctrl_reg_t _fifo_ctrl_reg; + base_addr_reg_t _base_addr_reg; + addr_mask_reg_t _addr_mask_reg; + bist_ctrl_reg_t _bist_ctrl_reg; + bist_cfg_reg_t _bist_cfg_reg; + bist_delay_reg_t _bist_delay_reg; + bist_sid_reg_t _bist_sid_reg; +}; + +// +// Static make function +// +dma_fifo_core_3000::sptr dma_fifo_core_3000::make(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr) +{ + if (check(iface, set_base, rb_addr)) { + return sptr(new dma_fifo_core_3000_impl(iface, set_base, rb_addr)); + } else { + throw uhd::runtime_error(""); + } +} + +bool dma_fifo_core_3000::check(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr) +{ + dma_fifo_core_3000_impl::fifo_readback fifo_rb(iface, set_base, rb_addr); + return fifo_rb.is_fifo_instantiated(); +} diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.hpp b/host/lib/usrp/cores/dma_fifo_core_3000.hpp new file mode 100644 index 000000000..41430e5c3 --- /dev/null +++ b/host/lib/usrp/cores/dma_fifo_core_3000.hpp @@ -0,0 +1,86 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <uhd/types/wb_iface.hpp> + + +class dma_fifo_core_3000 : boost::noncopyable +{ +public: + typedef boost::shared_ptr<dma_fifo_core_3000> sptr; + virtual ~dma_fifo_core_3000(void) = 0; + + /*! + * Create a DMA FIFO controller using the given bus, settings and readback base + * Throws uhd::runtime_error if a DMA FIFO is not instantiated in the FPGA + */ + static sptr make(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr); + + /*! + * Check if a DMA FIFO is instantiated in the FPGA + */ + static bool check(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr); + + /*! + * Flush the DMA FIFO. Will clear all contents. + */ + virtual void flush() = 0; + + /*! + * Resize and rebase the DMA FIFO. Will clear all contents. + */ + virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) = 0; + + /*! + * Get the (approx) number of bytes currently in the DMA FIFO + */ + virtual boost::uint32_t get_bytes_occupied() = 0; + + /*! + * Run the built-in-self-test routine for the DMA FIFO + */ + virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) = 0; + + /*! + * Is extended BIST supported + */ + virtual bool ext_bist_supported() = 0; + + /*! + * Run the built-in-self-test routine for the DMA FIFO (extended BIST only) + */ + virtual boost::uint8_t run_ext_bist( + bool finite, + boost::uint32_t rx_samp_delay, + boost::uint32_t tx_pkt_delay, + boost::uint32_t sid, + boost::uint32_t timeout_ms = 500) = 0; + + /*! + * Get the throughput measured from the last invocation of the BIST (extended BIST only) + */ + virtual double get_bist_throughput(double fifo_clock_rate) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/dsp_core_utils.cpp b/host/lib/usrp/cores/dsp_core_utils.cpp new file mode 100644 index 000000000..aea809ae8 --- /dev/null +++ b/host/lib/usrp/cores/dsp_core_utils.cpp @@ -0,0 +1,66 @@ +// +// Copyright 2016 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 "dsp_core_utils.hpp" +#include <uhd/utils/math.hpp> +#include <uhd/exception.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> + +static const int32_t MAX_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::highest(); +static const int32_t MIN_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::lowest(); + +void get_freq_and_freq_word( + const double requested_freq, + const double tick_rate, + double &actual_freq, + int32_t &freq_word +) { + //correct for outside of rate (wrap around) + double freq = std::fmod(requested_freq, tick_rate); + if (std::abs(freq) > tick_rate/2.0) + freq -= boost::math::sign(freq) * tick_rate; + + //confirm that the target frequency is within range of the CORDIC + UHD_ASSERT_THROW(std::abs(freq) <= tick_rate/2.0); + + /* Now calculate the frequency word. It is possible for this calculation + * to cause an overflow. As the requested DSP frequency approaches the + * master clock rate, that ratio multiplied by the scaling factor (2^32) + * will generally overflow within the last few kHz of tunable range. + * Thus, we check to see if the operation will overflow before doing it, + * and if it will, we set it to the integer min or max of this system. + */ + freq_word = 0; + + static const double scale_factor = std::pow(2.0, 32); + if ((freq / tick_rate) >= (MAX_FREQ_WORD / scale_factor)) { + /* Operation would have caused a positive overflow of int32. */ + freq_word = MAX_FREQ_WORD; + + } else if ((freq / tick_rate) <= (MIN_FREQ_WORD / scale_factor)) { + /* Operation would have caused a negative overflow of int32. */ + freq_word = MIN_FREQ_WORD; + + } else { + /* The operation is safe. Perform normally. */ + freq_word = int32_t(boost::math::round((freq / tick_rate) * scale_factor)); + } + + actual_freq = (double(freq_word) / scale_factor) * tick_rate; +} + diff --git a/host/lib/usrp/cores/dsp_core_utils.hpp b/host/lib/usrp/cores/dsp_core_utils.hpp new file mode 100644 index 000000000..d5d43f236 --- /dev/null +++ b/host/lib/usrp/cores/dsp_core_utils.hpp @@ -0,0 +1,33 @@ +// +// Copyright 2016 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_LIBUHD_DSP_CORE_UTILS_HPP +#define INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP + +#include <stdint.h> + +/*! For a requested frequency and sampling rate, return the + * correct frequency word (to set the CORDIC) and the actual frequency. + */ +void get_freq_and_freq_word( + const double requested_freq, + const double tick_rate, + double &actual_freq, + int32_t &freq_word +); + +#endif /* INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP */ diff --git a/host/lib/usrp/cores/gpio_atr_3000.cpp b/host/lib/usrp/cores/gpio_atr_3000.cpp new file mode 100644 index 000000000..5844af601 --- /dev/null +++ b/host/lib/usrp/cores/gpio_atr_3000.cpp @@ -0,0 +1,341 @@ +// +// Copyright 2011,2014 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 "gpio_atr_3000.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/utils/soft_register.hpp> + +using namespace uhd; +using namespace usrp; + +//------------------------------------------------------------- +// gpio_atr_3000 +//------------------------------------------------------------- + +#define REG_ATR_IDLE_OFFSET (base + 0) +#define REG_ATR_RX_OFFSET (base + 4) +#define REG_ATR_TX_OFFSET (base + 8) +#define REG_ATR_FDX_OFFSET (base + 12) +#define REG_DDR_OFFSET (base + 16) +#define REG_ATR_DISABLE_OFFSET (base + 20) + +namespace uhd { namespace usrp { namespace gpio_atr { + +class gpio_atr_3000_impl : public gpio_atr_3000{ +public: + gpio_atr_3000_impl( + wb_iface::sptr iface, + const wb_iface::wb_addr_type base, + const wb_iface::wb_addr_type rb_addr = READBACK_DISABLED + ): + _iface(iface), _rb_addr(rb_addr), + _atr_idle_reg(REG_ATR_IDLE_OFFSET, _atr_disable_reg), + _atr_rx_reg(REG_ATR_RX_OFFSET), + _atr_tx_reg(REG_ATR_TX_OFFSET), + _atr_fdx_reg(REG_ATR_FDX_OFFSET), + _ddr_reg(REG_DDR_OFFSET), + _atr_disable_reg(REG_ATR_DISABLE_OFFSET) + { + _atr_idle_reg.initialize(*_iface, true); + _atr_rx_reg.initialize(*_iface, true); + _atr_tx_reg.initialize(*_iface, true); + _atr_fdx_reg.initialize(*_iface, true); + _ddr_reg.initialize(*_iface, true); + _atr_disable_reg.initialize(*_iface, true); + } + + virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask) + { + //Each bit in the "ATR Disable" register determines whether the respective bit in the GPIO + //output bus is driven by the ATR engine or a static register. + //For each bit position, a 1 means that the bit is static and 0 means that the bit + //is driven by the ATR state machine. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + _atr_disable_reg.set_with_mask((mode==MODE_ATR) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); + _atr_disable_reg.flush(); + } + + virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask) + { + //Each bit in the "DDR" register determines whether the respective bit in the GPIO + //bus is an input or an output. + //For each bit position, a 1 means that the bit is an output and 0 means that the bit + //is an input. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + _ddr_reg.set_with_mask((dir==DDR_INPUT) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); + _ddr_reg.flush(); + } + + virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) + { + //Set the value of the specified ATR register. For bits with ATR Disable set to 1, + //the IDLE register will hold the output state + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + masked_reg_t* reg = NULL; + switch (atr) { + case ATR_REG_IDLE: reg = &_atr_idle_reg; break; + case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break; + case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break; + case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break; + default: reg = &_atr_idle_reg; break; + } + //For protection we only write to bits that have the mode ATR by masking the user + //specified "mask" with ~atr_disable. + reg->set_with_mask(value, mask); + reg->flush(); + } + + virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) { + //Set the value of the specified GPIO output register. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + + //For protection we only write to bits that have the mode GPIO by masking the user + //specified "mask" with atr_disable. + _atr_idle_reg.set_gpio_out_with_mask(value, mask); + _atr_idle_reg.flush(); + } + + virtual boost::uint32_t read_gpio() + { + //Read the state of the GPIO pins + //If a pin is configured as an input, reads the actual value of the pin + //If a pin is configured as an output, reads the last value written to the pin + if (_rb_addr != READBACK_DISABLED) { + return _iface->peek32(_rb_addr); + } else { + throw uhd::runtime_error("read_gpio not supported for write-only interface."); + } + } + + inline virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value) + { + //An attribute based API to configure all settings for the GPIO bus in one function + //call. This API does not have a mask so it configures all bits at the same time. + switch (attr) + { + case GPIO_CTRL: + set_atr_mode(MODE_ATR, value); //Configure mode=ATR for all bits that are set + set_atr_mode(MODE_GPIO, ~value); //Configure mode=GPIO for all bits that are unset + break; + case GPIO_DDR: + set_gpio_ddr(DDR_OUTPUT, value); //Configure as output for all bits that are set + set_gpio_ddr(DDR_INPUT, ~value); //Configure as input for all bits that are unset + break; + case GPIO_OUT: + //Only set bits that are driven statically + set_gpio_out(value); + break; + case GPIO_ATR_0X: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_IDLE, value); + break; + case GPIO_ATR_RX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_RX_ONLY, value); + break; + case GPIO_ATR_TX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_TX_ONLY, value); + break; + case GPIO_ATR_XX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_FULL_DUPLEX, value); + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + } + +protected: + //Special RB addr value to indicate no readback + //This value is invalid as a real address because it is not a multiple of 4 + static const wb_iface::wb_addr_type READBACK_DISABLED = 0xFFFFFFFF; + + class masked_reg_t : public uhd::soft_reg32_wo_t { + public: + masked_reg_t(const wb_iface::wb_addr_type offset): uhd::soft_reg32_wo_t(offset) { + uhd::soft_reg32_wo_t::set(REGISTER, 0); + } + + virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + uhd::soft_reg32_wo_t::set(REGISTER, + (value&mask)|(uhd::soft_reg32_wo_t::get(REGISTER)&(~mask))); + } + + virtual boost::uint32_t get() { + return uhd::soft_reg32_wo_t::get(uhd::soft_reg32_wo_t::REGISTER); + } + + virtual void flush() { + uhd::soft_reg32_wo_t::flush(); + } + }; + + class atr_idle_reg_t : public masked_reg_t { + public: + atr_idle_reg_t(const wb_iface::wb_addr_type offset, masked_reg_t& atr_disable_reg): + masked_reg_t(offset), + _atr_idle_cache(0), _gpio_out_cache(0), + _atr_disable_reg(atr_disable_reg) + { } + + virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + _atr_idle_cache = (value&mask)|(_atr_idle_cache&(~mask)); + } + + virtual boost::uint32_t get() { + return _atr_idle_cache; + } + + void set_gpio_out_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + _gpio_out_cache = (value&mask)|(_gpio_out_cache&(~mask)); + } + + virtual boost::uint32_t get_gpio_out() { + return _gpio_out_cache; + } + + virtual void flush() { + set(REGISTER, + (_atr_idle_cache & (~_atr_disable_reg.get())) | + (_gpio_out_cache & _atr_disable_reg.get()) + ); + masked_reg_t::flush(); + } + + private: + boost::uint32_t _atr_idle_cache; + boost::uint32_t _gpio_out_cache; + masked_reg_t& _atr_disable_reg; + }; + + wb_iface::sptr _iface; + wb_iface::wb_addr_type _rb_addr; + atr_idle_reg_t _atr_idle_reg; + masked_reg_t _atr_rx_reg; + masked_reg_t _atr_tx_reg; + masked_reg_t _atr_fdx_reg; + masked_reg_t _ddr_reg; + masked_reg_t _atr_disable_reg; +}; + +gpio_atr_3000::sptr gpio_atr_3000::make( + wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr +) { + return sptr(new gpio_atr_3000_impl(iface, base, rb_addr)); +} + +gpio_atr_3000::sptr gpio_atr_3000::make_write_only( + wb_iface::sptr iface, const wb_iface::wb_addr_type base +) { + gpio_atr_3000::sptr gpio_iface(new gpio_atr_3000_impl(iface, base)); + gpio_iface->set_gpio_ddr(DDR_OUTPUT, MASK_SET_ALL); + return gpio_iface; +} + +//------------------------------------------------------------- +// db_gpio_atr_3000 +//------------------------------------------------------------- + +class db_gpio_atr_3000_impl : public gpio_atr_3000_impl, public db_gpio_atr_3000 { +public: + db_gpio_atr_3000_impl(wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr): + gpio_atr_3000_impl(iface, base, rb_addr) { /* NOP */ } + + inline void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_atr_mode(MODE_ATR, compute_mask(unit, value&mask)); + gpio_atr_3000_impl::set_atr_mode(MODE_GPIO, compute_mask(unit, (~value)&mask)); + } + + inline boost::uint32_t get_pin_ctrl(const db_unit_t unit) + { + return (~_atr_disable_reg.get()) >> compute_shift(unit); + } + + inline void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_gpio_ddr(DDR_OUTPUT, compute_mask(unit, value&mask)); + gpio_atr_3000_impl::set_gpio_ddr(DDR_INPUT, compute_mask(unit, (~value)&mask)); + } + + inline boost::uint32_t get_gpio_ddr(const db_unit_t unit) + { + return _ddr_reg.get() >> compute_shift(unit); + } + + inline void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_atr_reg(atr, value << compute_shift(unit), compute_mask(unit, mask)); + } + + inline boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr) + { + masked_reg_t* reg = NULL; + switch (atr) { + case ATR_REG_IDLE: reg = &_atr_idle_reg; break; + case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break; + case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break; + case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break; + default: reg = &_atr_idle_reg; break; + } + return (reg->get() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + + inline void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_gpio_out( + static_cast<boost::uint32_t>(value) << compute_shift(unit), + compute_mask(unit, mask)); + } + + inline boost::uint32_t get_gpio_out(const db_unit_t unit) + { + return (_atr_idle_reg.get_gpio_out() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + + inline boost::uint32_t read_gpio(const db_unit_t unit) + { + return (gpio_atr_3000_impl::read_gpio() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + +private: + inline boost::uint32_t compute_shift(const db_unit_t unit) { + switch (unit) { + case dboard_iface::UNIT_RX: return 0; + case dboard_iface::UNIT_TX: return 16; + default: return 0; + } + } + + inline boost::uint32_t compute_mask(const db_unit_t unit, const boost::uint32_t mask) { + boost::uint32_t tmp_mask = (unit == dboard_iface::UNIT_BOTH) ? mask : (mask & 0xFFFF); + return tmp_mask << (compute_shift(unit)); + } +}; + +db_gpio_atr_3000::sptr db_gpio_atr_3000::make( + wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr +) { + return sptr(new db_gpio_atr_3000_impl(iface, base, rb_addr)); +} + +}}} diff --git a/host/lib/usrp/cores/gpio_atr_3000.hpp b/host/lib/usrp/cores/gpio_atr_3000.hpp new file mode 100644 index 000000000..7b90429fe --- /dev/null +++ b/host/lib/usrp/cores/gpio_atr_3000.hpp @@ -0,0 +1,183 @@ +// +// Copyright 2011,2014,2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/wb_iface.hpp> + +namespace uhd { namespace usrp { namespace gpio_atr { + +class gpio_atr_3000 : boost::noncopyable { +public: + typedef boost::shared_ptr<gpio_atr_3000> sptr; + + static const boost::uint32_t MASK_SET_ALL = 0xFFFFFFFF; + + virtual ~gpio_atr_3000(void) {}; + + /*! + * Create a read-write GPIO ATR interface object + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + * \param base readback offset for GPIO ATR registers + */ + static sptr make( + uhd::wb_iface::sptr iface, + const uhd::wb_iface::wb_addr_type base, + const uhd::wb_iface::wb_addr_type rb_addr); + + /*! + * Create a write-only GPIO ATR interface object + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + */ + static sptr make_write_only( + uhd::wb_iface::sptr iface, const uhd::wb_iface::wb_addr_type base); + + /*! + * Select the ATR mode for all bits in the mask + * + * \param mode the mode to apply {ATR = outputs driven by ATR state machine, GPIO = outputs static} + * \param mask apply the mode to all non-zero bits in the mask + */ + virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask) = 0; + + /*! + * Select the data direction for all bits in the mask + * + * \param dir the direction {OUTPUT, INPUT} + * \param mask apply the mode to all non-zero bits in the mask + */ + virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask) = 0; + + /*! + * Write the specified (masked) value to the ATR register + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param value the value to write + * \param mask only writes to the bits where mask is non-zero + */ + virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0; + + /*! + * Write to a static GPIO output + * + * \param value the value to write + * \param mask only writes to the bits where mask is non-zero + */ + virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0; + + /*! + * Read the state of the GPIO pins + * If a pin is configured as an input, reads the actual value of the pin + * If a pin is configured as an output, reads the last value written to the pin + * + * \return the value read back + */ + virtual boost::uint32_t read_gpio() = 0; + + /*! + * Set a GPIO attribute + * + * \param attr the attribute to set + * \param value the value to write to the attribute + */ + virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value) = 0; +}; + +class db_gpio_atr_3000 { +public: + typedef boost::shared_ptr<db_gpio_atr_3000> sptr; + + typedef uhd::usrp::dboard_iface::unit_t db_unit_t; + + virtual ~db_gpio_atr_3000(void) {}; + + /*! + * Create a read-write GPIO ATR interface object for a daughterboard connector + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + * \param base readback offset for GPIO ATR registers + */ + static sptr make( + uhd::wb_iface::sptr iface, + const uhd::wb_iface::wb_addr_type base, + const uhd::wb_iface::wb_addr_type rb_addr); + + /*! + * Configure the GPIO mode for all pins in the daughterboard connector + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value if value[i] is 1, the i'th bit is in ATR mode otherwise it is in GPIO mode + */ + virtual void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_pin_ctrl(const db_unit_t unit) = 0; + + /*! + * Configure the direction for all pins in the daughterboard connector + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value if value[i] is 1, the i'th bit is an output otherwise it is an input + */ + virtual void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_gpio_ddr(const db_unit_t unit) = 0; + + /*! + * Write the specified value to the ATR register (all bits) + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value the value to write + */ + virtual void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr) = 0; + + /*! + * Write the specified value to the GPIO register (all bits) + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param value the value to write + */ + virtual void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_gpio_out(const db_unit_t unit) = 0; + + /*! + * Read the state of the GPIO pins + * If a pin is configured as an input, reads the actual value of the pin + * If a pin is configured as an output, reads the last value written to the pin + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \return the value read back + */ + virtual boost::uint32_t read_gpio(const db_unit_t unit) = 0; +}; + +}}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/gpio_core_200.cpp b/host/lib/usrp/cores/gpio_core_200.cpp index 704a71d5f..8223a0bbf 100644 --- a/host/lib/usrp/cores/gpio_core_200.cpp +++ b/host/lib/usrp/cores/gpio_core_200.cpp @@ -27,6 +27,11 @@ using namespace uhd; using namespace usrp; +template <typename T> +static void shadow_it(T &shadow, const T &value, const T &mask){ + shadow = (shadow & ~mask) | (value & mask); +} + gpio_core_200::~gpio_core_200(void){ /* NOP */ } @@ -36,13 +41,20 @@ public: gpio_core_200_impl(wb_iface::sptr iface, const size_t base, const size_t rb_addr): _iface(iface), _base(base), _rb_addr(rb_addr), _first_atr(true) { /* NOP */ } - void set_pin_ctrl(const unit_t unit, const boost::uint16_t value){ - _pin_ctrl[unit] = value; //shadow + void set_pin_ctrl(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_pin_ctrl[unit], value, mask); update(); //full update } - void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value){ - _atr_regs[unit][atr] = value; //shadow + boost::uint16_t get_pin_ctrl(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _pin_ctrl[unit]; + } + + void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_atr_regs[unit][atr], value, mask); if (_first_atr) { // To preserve legacy behavior, update all registers the first time @@ -53,20 +65,38 @@ public: update(atr); } - void set_gpio_ddr(const unit_t unit, const boost::uint16_t value){ - _gpio_ddr[unit] = value; //shadow + boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _atr_regs[unit][reg]; + } + + void set_gpio_ddr(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_gpio_ddr[unit], value, mask); _iface->poke32(REG_GPIO_DDR, //update the 32 bit register (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_RX]) << shift_by_unit(dboard_iface::UNIT_RX)) | (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_TX]) << shift_by_unit(dboard_iface::UNIT_TX)) ); } - void set_gpio_out(const unit_t unit, const boost::uint16_t value){ - _gpio_out[unit] = value; //shadow + boost::uint16_t get_gpio_ddr(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _gpio_ddr[unit]; + } + + void set_gpio_out(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_gpio_out[unit], value, mask); this->update(); //full update } + boost::uint16_t get_gpio_out(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _gpio_out[unit]; + } + boost::uint16_t read_gpio(const unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); return boost::uint16_t(_iface->peek32(_rb_addr) >> shift_by_unit(unit)); } @@ -85,26 +115,26 @@ private: } void update(void){ - update(dboard_iface::ATR_REG_IDLE); - update(dboard_iface::ATR_REG_TX_ONLY); - update(dboard_iface::ATR_REG_RX_ONLY); - update(dboard_iface::ATR_REG_FULL_DUPLEX); + update(gpio_atr::ATR_REG_IDLE); + update(gpio_atr::ATR_REG_TX_ONLY); + update(gpio_atr::ATR_REG_RX_ONLY); + update(gpio_atr::ATR_REG_FULL_DUPLEX); } void update(const atr_reg_t atr){ size_t addr; switch (atr) { - case dboard_iface::ATR_REG_IDLE: + case gpio_atr::ATR_REG_IDLE: addr = REG_GPIO_IDLE; break; - case dboard_iface::ATR_REG_TX_ONLY: + case gpio_atr::ATR_REG_TX_ONLY: addr = REG_GPIO_TX_ONLY; break; - case dboard_iface::ATR_REG_RX_ONLY: + case gpio_atr::ATR_REG_RX_ONLY: addr = REG_GPIO_RX_ONLY; break; - case dboard_iface::ATR_REG_FULL_DUPLEX: + case gpio_atr::ATR_REG_FULL_DUPLEX: addr = REG_GPIO_BOTH; break; default: @@ -148,23 +178,23 @@ public: } void set_atr_reg(const atr_reg_t atr, const boost::uint32_t value){ - if (atr == dboard_iface::ATR_REG_IDLE) + if (atr == gpio_atr::ATR_REG_IDLE) _iface->poke32(REG_GPIO_IDLE, value); - else if (atr == dboard_iface::ATR_REG_TX_ONLY) + else if (atr == gpio_atr::ATR_REG_TX_ONLY) _iface->poke32(REG_GPIO_TX_ONLY, value); - else if (atr == dboard_iface::ATR_REG_RX_ONLY) + else if (atr == gpio_atr::ATR_REG_RX_ONLY) _iface->poke32(REG_GPIO_RX_ONLY, value); - else if (atr == dboard_iface::ATR_REG_FULL_DUPLEX) + else if (atr == gpio_atr::ATR_REG_FULL_DUPLEX) _iface->poke32(REG_GPIO_BOTH, value); else UHD_THROW_INVALID_CODE_PATH(); } void set_all_regs(const boost::uint32_t value){ - set_atr_reg(dboard_iface::ATR_REG_IDLE, value); - set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, value); - set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, value); - set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, value); + set_atr_reg(gpio_atr::ATR_REG_IDLE, value); + set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, value); + set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, value); + set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, value); } private: diff --git a/host/lib/usrp/cores/gpio_core_200.hpp b/host/lib/usrp/cores/gpio_core_200.hpp index e22834fd9..67aa8bde8 100644 --- a/host/lib/usrp/cores/gpio_core_200.hpp +++ b/host/lib/usrp/cores/gpio_core_200.hpp @@ -20,6 +20,7 @@ #include <uhd/config.hpp> #include <uhd/usrp/dboard_iface.hpp> +#include <uhd/usrp/gpio_defs.hpp> #include <boost/assign.hpp> #include <boost/cstdint.hpp> #include <boost/utility.hpp> @@ -27,28 +28,6 @@ #include <uhd/types/wb_iface.hpp> #include <map> -typedef enum { - GPIO_CTRL, - GPIO_DDR, - GPIO_OUT, - GPIO_ATR_0X, - GPIO_ATR_RX, - GPIO_ATR_TX, - GPIO_ATR_XX -} gpio_attr_t; - -typedef std::map<gpio_attr_t,std::string> gpio_attr_map_t; -static const gpio_attr_map_t gpio_attr_map = - boost::assign::map_list_of - (GPIO_CTRL, "CTRL") - (GPIO_DDR, "DDR") - (GPIO_OUT, "OUT") - (GPIO_ATR_0X, "ATR_0X") - (GPIO_ATR_RX, "ATR_RX") - (GPIO_ATR_TX, "ATR_TX") - (GPIO_ATR_XX, "ATR_XX") -; - class gpio_core_200 : boost::noncopyable{ public: typedef boost::shared_ptr<gpio_core_200> sptr; @@ -59,20 +38,32 @@ public: virtual ~gpio_core_200(void) = 0; //! makes a new GPIO core from iface and slave base - static sptr make(uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr); + static sptr make( + uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr); //! 1 = ATR - virtual void set_pin_ctrl(const unit_t unit, const boost::uint16_t value) = 0; + virtual void set_pin_ctrl( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; + + virtual boost::uint16_t get_pin_ctrl(unit_t unit) = 0; + + virtual void set_atr_reg( + const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask) = 0; - virtual void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value) = 0; + virtual boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg) = 0; //! 1 = OUTPUT - virtual void set_gpio_ddr(const unit_t unit, const boost::uint16_t value) = 0; + virtual void set_gpio_ddr( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; - virtual void set_gpio_out(const unit_t unit, const boost::uint16_t value) = 0; + virtual boost::uint16_t get_gpio_ddr(unit_t unit) = 0; - virtual boost::uint16_t read_gpio(const unit_t unit) = 0; + virtual void set_gpio_out( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; + + virtual boost::uint16_t get_gpio_out(unit_t unit) = 0; + virtual boost::uint16_t read_gpio(const unit_t unit) = 0; }; //! Simple wrapper for 32 bit write only diff --git a/host/lib/usrp/cores/rx_dsp_core_200.cpp b/host/lib/usrp/cores/rx_dsp_core_200.cpp index b899085c0..e51862d3b 100644 --- a/host/lib/usrp/cores/rx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_200.cpp @@ -16,6 +16,7 @@ // #include "rx_dsp_core_200.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> @@ -24,7 +25,6 @@ #include <boost/assign/list_of.hpp> #include <boost/thread/thread.hpp> //thread sleep #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/numeric/conversion/bounds.hpp> #include <algorithm> #include <cmath> @@ -223,42 +223,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling/32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.cpp b/host/lib/usrp/cores/rx_dsp_core_3000.cpp index 035bc6a3f..eedbbef95 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.cpp @@ -16,6 +16,7 @@ // #include "rx_dsp_core_3000.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> @@ -24,7 +25,6 @@ #include <boost/assign/list_of.hpp> #include <boost/thread/thread.hpp> //thread sleep #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <algorithm> #include <cmath> @@ -69,6 +69,7 @@ public: _scaling_adjustment = 1.0; _dsp_extra_scaling = 1.0; _tick_rate = 1.0; + _dsp_freq_offset = 0.0; } ~rx_dsp_core_3000_impl(void) @@ -79,17 +80,41 @@ public: ) } - void set_mux(const std::string &mode, const bool fe_swapped, const bool invert_i, const bool invert_q){ - static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of - ("IQ", 0) - ("QI", FLAG_DSP_RX_MUX_SWAP_IQ) - ("I", FLAG_DSP_RX_MUX_REAL_MODE) - ("Q", FLAG_DSP_RX_MUX_SWAP_IQ | FLAG_DSP_RX_MUX_REAL_MODE) - ; - _iface->poke32(REG_DSP_RX_MUX, mode_to_mux[mode] - | (fe_swapped ? FLAG_DSP_RX_MUX_SWAP_IQ : 0) - | (invert_i ? FLAG_DSP_RX_MUX_INVERT_I : 0) - | (invert_q ? FLAG_DSP_RX_MUX_INVERT_Q : 0)); + void set_mux(const uhd::usrp::fe_connection_t& fe_conn){ + boost::uint32_t reg_val = 0; + switch (fe_conn.get_sampling_mode()) { + case uhd::usrp::fe_connection_t::REAL: + case uhd::usrp::fe_connection_t::HETERODYNE: + reg_val = FLAG_DSP_RX_MUX_REAL_MODE; + break; + default: + reg_val = 0; + break; + } + + if (fe_conn.is_iq_swapped()) reg_val |= FLAG_DSP_RX_MUX_SWAP_IQ; + if (fe_conn.is_i_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_I; + if (fe_conn.is_q_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_Q; + + _iface->poke32(REG_DSP_RX_MUX, reg_val); + + if (fe_conn.get_sampling_mode() == uhd::usrp::fe_connection_t::HETERODYNE) { + //1. Remember the sign of the IF frequency. + // It will be discarded in the next step + int if_freq_sign = boost::math::sign(fe_conn.get_if_freq()); + //2. Map IF frequency to the range [0, _tick_rate) + double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _tick_rate)); + //3. Map IF frequency to the range [-_tick_rate/2, _tick_rate/2) + // This is the aliased frequency + if (if_freq > (_tick_rate / 2.0)) { + if_freq -= _tick_rate; + } + //4. Set DSP offset to spin the signal in the opposite + // direction as the aliased frequency + _dsp_freq_offset = if_freq * (-if_freq_sign); + } else { + _dsp_freq_offset = 0.0; + } } void set_tick_rate(const double rate){ @@ -209,47 +234,18 @@ public: return _fxpt_scalar_correction*_host_extra_scaling/32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq + _dsp_freq_offset, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } uhd::meta_range_t get_freq_range(void){ - return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32)); + //Too keep the DSP range symmetric about 0, we use abs(_dsp_freq_offset) + const double offset = std::abs<double>(_dsp_freq_offset); + return uhd::meta_range_t(-(_tick_rate-offset)/2, +(_tick_rate-offset)/2, _tick_rate/std::pow(2.0, 32)); } void setup(const uhd::stream_args_t &stream_args){ @@ -284,18 +280,18 @@ public: void populate_subtree(property_tree::sptr subtree) { subtree->create<meta_range_t>("rate/range") - .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, this)) + .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, this)) ; subtree->create<double>("rate/value") .set(DEFAULT_RATE) - .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1)) + .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1)) ; subtree->create<double>("freq/value") .set(DEFAULT_CORDIC_FREQ) - .coerce(boost::bind(&rx_dsp_core_3000::set_freq, this, _1)) + .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, this, _1)) ; subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, this)) + .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, this)) ; } @@ -305,6 +301,7 @@ private: const bool _is_b200; //TODO: Obsolete this when we switch to the new DDC on the B200 double _tick_rate, _link_rate; double _scaling_adjustment, _dsp_extra_scaling, _host_extra_scaling, _fxpt_scalar_correction; + double _dsp_freq_offset; }; rx_dsp_core_3000::sptr rx_dsp_core_3000::make(wb_iface::sptr iface, const size_t dsp_base, const bool is_b200 /* = false */) diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.hpp b/host/lib/usrp/cores/rx_dsp_core_3000.hpp index 65801de1d..41b328357 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.hpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.hpp @@ -24,6 +24,7 @@ #include <uhd/types/stream_cmd.hpp> #include <uhd/types/wb_iface.hpp> #include <uhd/property_tree.hpp> +#include <uhd/usrp/fe_connection.hpp> #include <boost/utility.hpp> #include <boost/shared_ptr.hpp> #include <string> @@ -43,7 +44,7 @@ public: const bool is_b200 = false //TODO: Obsolete this when we switch to the new DDC on the B200 ); - virtual void set_mux(const std::string &mode, const bool fe_swapped = false, const bool invert_i = false, const bool invert_q = false) = 0; + virtual void set_mux(const uhd::usrp::fe_connection_t& fe_conn) = 0; virtual void set_tick_rate(const double rate) = 0; diff --git a/host/lib/usrp/cores/rx_frontend_core_200.cpp b/host/lib/usrp/cores/rx_frontend_core_200.cpp index 7ac920553..0a60bf87c 100644 --- a/host/lib/usrp/cores/rx_frontend_core_200.cpp +++ b/host/lib/usrp/cores/rx_frontend_core_200.cpp @@ -83,15 +83,15 @@ public: { subtree->create<std::complex<double> >("dc_offset/value") .set(DEFAULT_DC_OFFSET_VALUE) - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1)) ; subtree->create<bool>("dc_offset/enable") .set(DEFAULT_DC_OFFSET_ENABLE) - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1)) ; subtree->create<std::complex<double> >("iq_balance/value") .set(DEFAULT_IQ_BALANCE_VALUE) - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _1)) ; } diff --git a/host/lib/usrp/cores/spi_core_3000.cpp b/host/lib/usrp/cores/spi_core_3000.cpp index 0656d910a..d33624b0d 100644 --- a/host/lib/usrp/cores/spi_core_3000.cpp +++ b/host/lib/usrp/cores/spi_core_3000.cpp @@ -34,7 +34,7 @@ class spi_core_3000_impl : public spi_core_3000 { public: spi_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback): - _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0) + _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0), _divider_cache(0) { this->set_divider(30); } @@ -46,7 +46,21 @@ public: size_t num_bits, bool readback ){ - boost::mutex::scoped_lock lock(_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + + //load SPI divider + size_t spi_divider = _div; + if (config.use_custom_divider) { + //The resulting SPI frequency will be f_system/(2*(divider+1)) + //This math ensures the frequency will be equal to or less than the target + spi_divider = (config.divider-1)/2; + } + + //conditionally send SPI divider + if (spi_divider != _divider_cache) { + _iface->poke32(SPI_DIV, spi_divider); + _divider_cache = spi_divider; + } //load control word boost::uint32_t ctrl_word = 0; @@ -55,17 +69,16 @@ public: if (config.mosi_edge == spi_config_t::EDGE_FALL) ctrl_word |= (1 << 31); if (config.miso_edge == spi_config_t::EDGE_RISE) ctrl_word |= (1 << 30); - //load data word (must be in upper bits) - const boost::uint32_t data_out = data << (32 - num_bits); - //conditionally send control word if (_ctrl_word_cache != ctrl_word) { - _iface->poke32(SPI_DIV, _div); _iface->poke32(SPI_CTRL, ctrl_word); _ctrl_word_cache = ctrl_word; } + //load data word (must be in upper bits) + const boost::uint32_t data_out = data << (32 - num_bits); + //send data word _iface->poke32(SPI_DATA, data_out); @@ -91,6 +104,7 @@ private: boost::uint32_t _ctrl_word_cache; boost::mutex _mutex; size_t _div; + size_t _divider_cache; }; spi_core_3000::sptr spi_core_3000::make(wb_iface::sptr iface, const size_t base, const size_t readback) diff --git a/host/lib/usrp/cores/tx_dsp_core_200.cpp b/host/lib/usrp/cores/tx_dsp_core_200.cpp index 2ef9f4406..4c456a10d 100644 --- a/host/lib/usrp/cores/tx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/tx_dsp_core_200.cpp @@ -16,13 +16,13 @@ // #include "tx_dsp_core_200.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> #include <uhd/utils/msg.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/thread/thread.hpp> //sleep #include <algorithm> #include <cmath> @@ -163,42 +163,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling*32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } diff --git a/host/lib/usrp/cores/tx_dsp_core_3000.cpp b/host/lib/usrp/cores/tx_dsp_core_3000.cpp index 7e447ae7d..3889bbdc4 100644 --- a/host/lib/usrp/cores/tx_dsp_core_3000.cpp +++ b/host/lib/usrp/cores/tx_dsp_core_3000.cpp @@ -16,13 +16,13 @@ // #include "tx_dsp_core_3000.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> #include <uhd/utils/msg.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/thread/thread.hpp> //sleep #include <algorithm> #include <cmath> @@ -136,42 +136,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling*32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq) { + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } @@ -211,18 +180,18 @@ public: void populate_subtree(property_tree::sptr subtree) { subtree->create<meta_range_t>("rate/range") - .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, this)) + .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, this)) ; subtree->create<double>("rate/value") .set(DEFAULT_RATE) - .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1)) + .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1)) ; subtree->create<double>("freq/value") .set(DEFAULT_CORDIC_FREQ) - .coerce(boost::bind(&tx_dsp_core_3000::set_freq, this, _1)) + .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, this, _1)) ; subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, this)) + .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, this)) ; } diff --git a/host/lib/usrp/cores/tx_frontend_core_200.cpp b/host/lib/usrp/cores/tx_frontend_core_200.cpp index 0fa028571..be4f77f39 100644 --- a/host/lib/usrp/cores/tx_frontend_core_200.cpp +++ b/host/lib/usrp/cores/tx_frontend_core_200.cpp @@ -79,11 +79,11 @@ public: { subtree->create< std::complex<double> >("dc_offset/value") .set(DEFAULT_DC_OFFSET_VALUE) - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1)) ; subtree->create< std::complex<double> >("iq_balance/value") .set(DEFAULT_IQ_BALANCE_VALUE) - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1)) ; } diff --git a/host/lib/usrp/cores/tx_vita_core_3000.cpp b/host/lib/usrp/cores/tx_vita_core_3000.cpp index 71a2b7e21..c76b384d9 100644 --- a/host/lib/usrp/cores/tx_vita_core_3000.cpp +++ b/host/lib/usrp/cores/tx_vita_core_3000.cpp @@ -18,9 +18,11 @@ #include "tx_vita_core_3000.hpp" #include <uhd/utils/safe_call.hpp> -#define REG_CTRL_ERROR_POLICY _base + 0 -#define REG_DEFRAMER_CYCLE_FC_UPS _base + 2*4 + 0 -#define REG_DEFRAMER_PACKET_FC_UPS _base + 2*4 + 4 +#define REG_CTRL_ERROR_POLICY (_base + 0) +#define REG_FC_PRE_RADIO_RESP_BASE (_base + 2*4) +#define REG_FC_PRE_FIFO_RESP_BASE (_base + 4*4) +#define REG_CTRL_FC_CYCLE_OFFSET (0*4) +#define REG_CTRL_FC_PACKET_OFFSET (1*4) using namespace uhd; @@ -32,12 +34,22 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 { tx_vita_core_3000_impl( wb_iface::sptr iface, - const size_t base + const size_t base, + fc_monitor_loc fc_location ): _iface(iface), - _base(base) + _base(base), + _fc_base((fc_location==FC_PRE_RADIO or fc_location==FC_DEFAULT) ? + REG_FC_PRE_RADIO_RESP_BASE : REG_FC_PRE_FIFO_RESP_BASE), + _fc_location(fc_location) { - this->set_tick_rate(1); //init to non zero + if (fc_location != FC_DEFAULT) { + //Turn off the other FC monitoring module + const size_t other_fc_base = (fc_location==FC_PRE_RADIO) ? + REG_FC_PRE_FIFO_RESP_BASE : REG_FC_PRE_RADIO_RESP_BASE; + _iface->poke32(other_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0); + _iface->poke32(other_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0); + } this->set_underflow_policy("next_packet"); this->clear(); } @@ -56,11 +68,6 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 this->set_underflow_policy(_policy); //clears the seq } - void set_tick_rate(const double rate) - { - _tick_rate = rate; - } - void set_underflow_policy(const std::string &policy) { if (policy == "next_packet") @@ -89,23 +96,35 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up) { - if (cycs_per_up == 0) _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, 0); - else _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, (1 << 31) | ((cycs_per_up) & 0xffffff)); + if (cycs_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0); + else _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, (1 << 31) | ((cycs_per_up) & 0xffffff)); - if (pkts_per_up == 0) _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, 0); - else _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, (1 << 31) | ((pkts_per_up) & 0xffff)); + if (pkts_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0); + else _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, (1 << 31) | ((pkts_per_up) & 0xffff)); } - wb_iface::sptr _iface; - const size_t _base; - double _tick_rate; - std::string _policy; + wb_iface::sptr _iface; + const size_t _base; + const size_t _fc_base; + std::string _policy; + fc_monitor_loc _fc_location; + }; tx_vita_core_3000::sptr tx_vita_core_3000::make( wb_iface::sptr iface, + const size_t base, + fc_monitor_loc fc_location +) +{ + return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, fc_location)); +} + +tx_vita_core_3000::sptr tx_vita_core_3000::make_no_radio_buff( + wb_iface::sptr iface, const size_t base ) { - return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base)); + //No internal radio buffer so only pre-radio monitoring is supported. + return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, FC_DEFAULT)); } diff --git a/host/lib/usrp/cores/tx_vita_core_3000.hpp b/host/lib/usrp/cores/tx_vita_core_3000.hpp index 4c0052d4f..bd0f20ba4 100644 --- a/host/lib/usrp/cores/tx_vita_core_3000.hpp +++ b/host/lib/usrp/cores/tx_vita_core_3000.hpp @@ -32,17 +32,27 @@ class tx_vita_core_3000 : boost::noncopyable public: typedef boost::shared_ptr<tx_vita_core_3000> sptr; + enum fc_monitor_loc { + FC_DEFAULT, + FC_PRE_RADIO, + FC_PRE_FIFO + }; + virtual ~tx_vita_core_3000(void) = 0; static sptr make( uhd::wb_iface::sptr iface, + const size_t base, + fc_monitor_loc fc_location = FC_PRE_RADIO + ); + + static sptr make_no_radio_buff( + uhd::wb_iface::sptr iface, const size_t base ); virtual void clear(void) = 0; - virtual void set_tick_rate(const double rate) = 0; - virtual void setup(const uhd::stream_args_t &stream_args) = 0; virtual void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up) = 0; diff --git a/host/lib/usrp/cores/user_settings_core_3000.cpp b/host/lib/usrp/cores/user_settings_core_3000.cpp new file mode 100644 index 000000000..549264f57 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.cpp @@ -0,0 +1,85 @@ +// +// Copyright 2012 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 "user_settings_core_3000.hpp" +#include <uhd/exception.hpp> +#include <boost/thread/thread.hpp> + +using namespace uhd; + +#define REG_USER_SR_ADDR _sr_base_addr + 0 +#define REG_USER_SR_DATA _sr_base_addr + 4 +#define REG_USER_RB_ADDR _sr_base_addr + 8 + +class user_settings_core_3000_impl : public user_settings_core_3000 { +public: + user_settings_core_3000_impl( + wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr): + _iface(iface), _sr_base_addr(sr_base_addr), _rb_reg_addr(rb_reg_addr) + { + } + + void poke64(const wb_addr_type offset, const boost::uint64_t value) + { + if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("poke64: Incorrect address alignment"); + poke32(offset, static_cast<boost::uint32_t>(value)); + poke32(offset + 4, static_cast<boost::uint32_t>(value >> 32)); + } + + boost::uint64_t peek64(const wb_addr_type offset) + { + if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("peek64: Incorrect address alignment"); + + boost::unique_lock<boost::mutex> lock(_mutex); + _iface->poke32(REG_USER_RB_ADDR, offset >> 3); //Translate byte offset to 64-bit offset + return _iface->peek64(_rb_reg_addr); + } + + void poke32(const wb_addr_type offset, const boost::uint32_t value) + { + if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("poke32: Incorrect address alignment"); + + boost::unique_lock<boost::mutex> lock(_mutex); + _iface->poke32(REG_USER_SR_ADDR, offset >> 2); //Translate byte offset to 64-bit offset + _iface->poke32(REG_USER_SR_DATA, value); + } + + boost::uint32_t peek32(const wb_addr_type offset) + { + if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("peek32: Incorrect address alignment"); + + boost::uint64_t value = peek64((offset >> 3) << 3); + if ((offset & 0x7) == 0) { + return static_cast<boost::uint32_t>(value); + } else { + return static_cast<boost::uint32_t>(value >> 32); + } + } + +private: + wb_iface::sptr _iface; + const wb_addr_type _sr_base_addr; + const wb_addr_type _rb_reg_addr; + boost::mutex _mutex; +}; + +wb_iface::sptr user_settings_core_3000::make(wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr) +{ + return sptr(new user_settings_core_3000_impl(iface, sr_base_addr, rb_reg_addr)); +} diff --git a/host/lib/usrp/cores/user_settings_core_3000.hpp b/host/lib/usrp/cores/user_settings_core_3000.hpp new file mode 100644 index 000000000..6891b9e81 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2012 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_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/wb_iface.hpp> + +class user_settings_core_3000 : public uhd::wb_iface { +public: + virtual ~user_settings_core_3000() {} + + static sptr make( + wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr); +}; + +#endif /* INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP */ diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp index 2b30dab52..941a80ea4 100644 --- a/host/lib/usrp/dboard/db_basic_and_lf.cpp +++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp @@ -50,7 +50,7 @@ static const uhd::dict<std::string, double> subdev_bandwidth_scalar = map_list_o class basic_rx : public rx_dboard_base{ public: basic_rx(ctor_args_t args, double max_freq); - ~basic_rx(void); + virtual ~basic_rx(void); private: double _max_freq; @@ -59,7 +59,7 @@ private: class basic_tx : public tx_dboard_base{ public: basic_tx(ctor_args_t args, double max_freq); - ~basic_tx(void); + virtual ~basic_tx(void); private: double _max_freq; @@ -121,7 +121,7 @@ basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){ this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_rx_subtree()->create<double>("freq/value") - .publish(&always_zero_freq); + .set_publisher(&always_zero_freq); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(freq_range_t(-_max_freq, +_max_freq)); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -176,7 +176,7 @@ basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){ this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_tx_subtree()->create<double>("freq/value") - .publish(&always_zero_freq); + .set_publisher(&always_zero_freq); this->get_tx_subtree()->create<meta_range_t>("freq/range") .set(freq_range_t(-_max_freq, +_max_freq)); this->get_tx_subtree()->create<std::string>("antenna/value") diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp index 9d04d8e16..6e1846fb8 100644 --- a/host/lib/usrp/dboard/db_dbsrx.cpp +++ b/host/lib/usrp/dboard/db_dbsrx.cpp @@ -66,7 +66,7 @@ static const double usrp1_gpio_clock_rate_limit = 4e6; class dbsrx : public rx_dboard_base{ public: dbsrx(ctor_args_t args); - ~dbsrx(void); + virtual ~dbsrx(void); private: double _lo_freq; @@ -204,16 +204,16 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("DBSRX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&dbsrx::get_locked, this)); + .set_publisher(boost::bind(&dbsrx::get_locked, this)); BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&dbsrx::set_gain, this, _1, name)) + .set_coercer(boost::bind(&dbsrx::set_gain, this, _1, name)) .set(dbsrx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(dbsrx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&dbsrx::set_lo_freq, this, _1)); + .set_coercer(boost::bind(&dbsrx::set_lo_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(dbsrx_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -227,7 +227,7 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&dbsrx::set_bandwidth, this, _1)); + .set_coercer(boost::bind(&dbsrx::set_bandwidth, this, _1)); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(dbsrx_bandwidth_range); diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp index 1debe3c8f..11d706ed6 100644 --- a/host/lib/usrp/dboard/db_dbsrx2.cpp +++ b/host/lib/usrp/dboard/db_dbsrx2.cpp @@ -60,7 +60,7 @@ static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges = map_list_ class dbsrx2 : public rx_dboard_base{ public: dbsrx2(ctor_args_t args); - ~dbsrx2(void); + virtual ~dbsrx2(void); private: double _lo_freq; @@ -191,16 +191,16 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("DBSRX2"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&dbsrx2::get_locked, this)); + .set_publisher(boost::bind(&dbsrx2::get_locked, this)); BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&dbsrx2::set_gain, this, _1, name)) + .set_coercer(boost::bind(&dbsrx2::set_gain, this, _1, name)) .set(dbsrx2_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(dbsrx2_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&dbsrx2::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&dbsrx2::set_lo_freq, this, _1)) .set(dbsrx2_freq_range.start()); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(dbsrx2_freq_range); @@ -218,7 +218,7 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&dbsrx2::set_bandwidth, this, _1)) + .set_coercer(boost::bind(&dbsrx2::set_bandwidth, this, _1)) .set(2.0*(0.8*codec_rate/2.0)); //bandwidth in lowpass, convert to complex bandpass //default to anti-alias at different codec_rate this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") diff --git a/host/lib/usrp/dboard/db_e3x0.cpp b/host/lib/usrp/dboard/db_e3x0.cpp index 523927d49..c7cc52d73 100644 --- a/host/lib/usrp/dboard/db_e3x0.cpp +++ b/host/lib/usrp/dboard/db_e3x0.cpp @@ -29,7 +29,7 @@ class e310_dboard : public xcvr_dboard_base{ public: e310_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} - ~e310_dboard(void) {} + virtual ~e310_dboard(void) {} }; /*********************************************************************** @@ -40,7 +40,7 @@ class e300_dboard : public xcvr_dboard_base{ public: e300_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} - ~e300_dboard(void) {} + virtual ~e300_dboard(void) {} }; /*********************************************************************** diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp index 1342c913d..dbb1600ec 100644 --- a/host/lib/usrp/dboard/db_rfx.cpp +++ b/host/lib/usrp/dboard/db_rfx.cpp @@ -78,7 +78,7 @@ public: const freq_range_t &freq_range, bool rx_div2, bool tx_div2 ); - ~rfx_xcvr(void); + virtual ~rfx_xcvr(void); private: const freq_range_t _freq_range; @@ -183,20 +183,20 @@ rfx_xcvr::rfx_xcvr( else this->get_rx_subtree()->create<std::string>("name").set("RFX RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name)) .set(_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((_freq_range.start() + _freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&rfx_xcvr::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(rfx_rx_antennas); @@ -219,14 +219,14 @@ rfx_xcvr::rfx_xcvr( else this->get_tx_subtree()->create<std::string>("name").set("RFX TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((_freq_range.start() + _freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0)); + .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(rfx_tx_antennas); this->get_tx_subtree()->create<std::string>("connection").set("IQ"); @@ -248,15 +248,15 @@ rfx_xcvr::rfx_xcvr( this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables); //setup the tx atr (this does not change with antenna) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); //setup the rx atr (this does not change with antenna) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); } rfx_xcvr::~rfx_xcvr(void){ @@ -272,14 +272,14 @@ void rfx_xcvr::set_rx_ant(const std::string &ant){ //set the rx atr regs that change with antenna setting if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX ); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX ); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ((ant == "TX/RX")? ANT_TXRX : ANT_RX2)); } @@ -292,12 +292,12 @@ void rfx_xcvr::set_tx_ant(const std::string &ant){ //set the tx atr regs that change with antenna setting if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); } } diff --git a/host/lib/usrp/dboard/db_sbx_common.cpp b/host/lib/usrp/dboard/db_sbx_common.cpp index ce5166c4c..be02cf77a 100644 --- a/host/lib/usrp/dboard/db_sbx_common.cpp +++ b/host/lib/usrp/dboard/db_sbx_common.cpp @@ -159,20 +159,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ else this->get_rx_subtree()->create<std::string>("name").set("SBX/CBX RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name)) .set(sbx_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(sbx_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((freq_range.start() + freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&sbx_xcvr::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(sbx_rx_antennas); @@ -200,20 +200,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ else this->get_tx_subtree()->create<std::string>("name").set("SBX/CBX TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){ this->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name)) .set(sbx_tx_gain_ranges[name].start()); this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(sbx_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((freq_range.start() + freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&sbx_xcvr::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_tx_ant, this, _1)) .set(sbx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(sbx_tx_antennas); @@ -237,8 +237,8 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - //flash LEDs - flash_leds(); + //Initialize ATR registers after direction and pin ctrl configuration + update_atr(); UHD_LOGV(often) << boost::format( "SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" @@ -265,39 +265,39 @@ void sbx_xcvr::update_atr(void){ //setup the tx atr (this does not change with antenna) this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, 0 | tx_lo_lpf_en \ + gpio_atr::ATR_REG_IDLE, 0 | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_XX | TX_MIXER_DIS); //setup the rx atr (this does not change with antenna) this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_XX | RX_MIXER_DIS); //set the RX atr regs that change with antenna setting this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ | ((_rx_ant != "RX2")? ANT_TXRX : ANT_RX2)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_DIS \ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); //set the TX atr regs that change with antenna setting this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \ + gpio_atr::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_DIS \ | ((_rx_ant != "RX2")? ANT_RX : ANT_TX)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \ + gpio_atr::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \ + gpio_atr::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); } @@ -352,45 +352,3 @@ sensor_value_t sbx_xcvr::get_locked(dboard_iface::unit_t unit) { return sensor_value_t("LO", locked, "locked", "unlocked"); } - - -void sbx_xcvr::flash_leds(void) { - //Remove LED gpios from ATR control temporarily and set to outputs - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|RX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, \ - TX_LED_TXRX|TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, \ - RX_LED_RX1RX2|RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - //Put LED gpios back in ATR control and update atr - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); -} - diff --git a/host/lib/usrp/dboard/db_sbx_common.hpp b/host/lib/usrp/dboard/db_sbx_common.hpp index 4800bbd83..c0e29f263 100644 --- a/host/lib/usrp/dboard/db_sbx_common.hpp +++ b/host/lib/usrp/dboard/db_sbx_common.hpp @@ -16,10 +16,15 @@ // #include <uhd/types/device_addr.hpp> - -#include "adf435x_common.hpp" +#include "adf435x.hpp" #include "max287x.hpp" +// LO Related +#define ADF435X_CE (1 << 3) +#define ADF435X_PDBRF (1 << 2) +#define ADF435X_MUXOUT (1 << 1) // INPUT!!! +#define LOCKDET_MASK (1 << 0) // INPUT!!! + // Common IO Pins #define LO_LPF_EN (1 << 15) @@ -36,6 +41,8 @@ #define RX_LED_LD (1 << 6) // LED for RX Lock Detect #define DIS_POWER_RX (1 << 5) // on UNIT_RX, 0 powers up RX #define RX_DISABLE (1 << 4) // on UNIT_RX, 1 disables RX Mixer and Baseband +#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control +#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control // TX Attenuator Pins #define TX_ATTN_SHIFT 8 // lsb of TX Attenuator Control @@ -184,12 +191,16 @@ protected: class sbx_version3 : public sbx_versionx { public: sbx_version3(sbx_xcvr *_self_sbx_xcvr); - ~sbx_version3(void); + virtual ~sbx_version3(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); /*! This is the registered instance of the wrapper class, sbx_base. */ sbx_xcvr *self_base; + private: + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; /*! @@ -200,12 +211,16 @@ protected: class sbx_version4 : public sbx_versionx { public: sbx_version4(sbx_xcvr *_self_sbx_xcvr); - ~sbx_version4(void); + virtual ~sbx_version4(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); /*! This is the registered instance of the wrapper class, sbx_base. */ sbx_xcvr *self_base; + private: + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; /*! @@ -218,7 +233,7 @@ protected: class cbx : public sbx_versionx { public: cbx(sbx_xcvr *_self_sbx_xcvr); - ~cbx(void); + virtual ~cbx(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); diff --git a/host/lib/usrp/dboard/db_sbx_version3.cpp b/host/lib/usrp/dboard/db_sbx_version3.cpp index b848097d1..76ad7b04f 100644 --- a/host/lib/usrp/dboard/db_sbx_version3.cpp +++ b/host/lib/usrp/dboard/db_sbx_version3.cpp @@ -16,9 +16,7 @@ // -#include "adf4350_regs.hpp" #include "db_sbx_common.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <boost/algorithm/string.hpp> @@ -32,12 +30,21 @@ using namespace boost::assign; sbx_xcvr::sbx_version3::sbx_version3(sbx_xcvr *_self_sbx_xcvr) { //register the handle to our base SBX class self_base = _self_sbx_xcvr; + _txlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); } sbx_xcvr::sbx_version3::~sbx_version3(void){ /* NOP */ } +void sbx_xcvr::sbx_version3::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) +{ + BOOST_FOREACH(boost::uint32_t reg, regs) + { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} /*********************************************************************** * Tuning @@ -57,95 +64,27 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); - //clip the input - target_freq = sbx_freq_range.clip(target_freq); - - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4350_regs_t::prescaler_t prescaler = target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); - tuning_constraints.feedback_after_divider = true; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit)); - double actual_freq; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - target_freq, self_base->get_iface()->get_clock_rate(unit), - tuning_constraints, actual_freq); + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(target_freq > 3e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - //load the register values - adf4350_regs_t regs; + //Configure the LO + double actual_freq = 0.0; + actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n); - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) - regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "SBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "SBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_sbx_version4.cpp b/host/lib/usrp/dboard/db_sbx_version4.cpp index 8f7e747bc..639bce250 100644 --- a/host/lib/usrp/dboard/db_sbx_version4.cpp +++ b/host/lib/usrp/dboard/db_sbx_version4.cpp @@ -16,9 +16,7 @@ // -#include "adf4351_regs.hpp" #include "db_sbx_common.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <boost/algorithm/string.hpp> @@ -32,6 +30,8 @@ using namespace boost::assign; sbx_xcvr::sbx_version4::sbx_version4(sbx_xcvr *_self_sbx_xcvr) { //register the handle to our base SBX class self_base = _self_sbx_xcvr; + _txlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); } @@ -39,6 +39,14 @@ sbx_xcvr::sbx_version4::~sbx_version4(void){ /* NOP */ } +void sbx_xcvr::sbx_version4::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) +{ + BOOST_FOREACH(boost::uint32_t reg, regs) + { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} + /*********************************************************************** * Tuning @@ -58,99 +66,27 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); - //clip the input - target_freq = sbx_freq_range.clip(target_freq); - - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4351_regs_t::PRESCALER_4_5 - (1,75) //adf4351_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) - ; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4351_regs_t::prescaler_t prescaler = target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 64); - tuning_constraints.feedback_after_divider = true; - - double actual_freq; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - target_freq, self_base->get_iface()->get_clock_rate(unit), - tuning_constraints, actual_freq); - - //load the register values - adf4351_regs_t regs; - - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) - regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4351_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4351_regs_t::LDF_INT_N : - adf4351_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); - std::string board_name = (rx_id == 0x0083) ? "SBX-120" : "SBX"; - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "%s SPI Reg (0x%02x): 0x%08x" - ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit)); + + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(target_freq > 3.6e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + + //Configure the LO + double actual_freq = 0.0; + actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n); + + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "%s tune: actual frequency %f MHz" - ) % board_name.c_str() % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp index e9f60f765..0f84cd68a 100644 --- a/host/lib/usrp/dboard/db_tvrx.cpp +++ b/host/lib/usrp/dboard/db_tvrx.cpp @@ -134,7 +134,7 @@ static const double reference_freq = 4.0e6; class tvrx : public rx_dboard_base{ public: tvrx(ctor_args_t args); - ~tvrx(void); + virtual ~tvrx(void); private: uhd::dict<std::string, double> _gains; @@ -190,12 +190,12 @@ tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&tvrx::set_gain, this, _1, name)); + .set_coercer(boost::bind(&tvrx::set_gain, this, _1, name)); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(get_tvrx_gain_ranges()[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&tvrx::set_freq, this, _1)); + .set_coercer(boost::bind(&tvrx::set_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(tvrx_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp index 00c2fef50..3a5bc1198 100644 --- a/host/lib/usrp/dboard/db_tvrx2.cpp +++ b/host/lib/usrp/dboard/db_tvrx2.cpp @@ -751,7 +751,7 @@ static const uhd::dict<std::string, gain_range_t> tvrx2_gain_ranges = map_list_o class tvrx2 : public rx_dboard_base{ public: tvrx2(ctor_args_t args); - ~tvrx2(void); + virtual ~tvrx2(void); private: double _freq_scalar; @@ -957,19 +957,19 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("TVRX2"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&tvrx2::get_locked, this)); + .set_publisher(boost::bind(&tvrx2::get_locked, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&tvrx2::get_rssi, this)); + .set_publisher(boost::bind(&tvrx2::get_rssi, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/temperature") - .publish(boost::bind(&tvrx2::get_temp, this)); + .set_publisher(boost::bind(&tvrx2::get_temp, this)); BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&tvrx2::set_gain, this, _1, name)); + .set_coercer(boost::bind(&tvrx2::set_gain, this, _1, name)); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(tvrx2_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&tvrx2::set_lo_freq, this, _1)); + .set_coercer(boost::bind(&tvrx2::set_lo_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(tvrx2_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -979,12 +979,12 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("connection") .set(tvrx2_sd_name_to_conn[get_subdev_name()]); this->get_rx_subtree()->create<bool>("enabled") - .coerce(boost::bind(&tvrx2::set_enabled, this, _1)) + .set_coercer(boost::bind(&tvrx2::set_enabled, this, _1)) .set(_enabled); this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&tvrx2::set_bandwidth, this, _1)) + .set_coercer(boost::bind(&tvrx2::set_bandwidth, this, _1)) .set(_bandwidth); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(tvrx2_bandwidth_range); diff --git a/host/lib/usrp/dboard/db_ubx.cpp b/host/lib/usrp/dboard/db_ubx.cpp index 2966cadf6..3334c2cd0 100644 --- a/host/lib/usrp/dboard/db_ubx.cpp +++ b/host/lib/usrp/dboard/db_ubx.cpp @@ -27,7 +27,9 @@ #include <uhd/usrp/dboard_manager.hpp> #include <uhd/utils/assert_has.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> #include <uhd/utils/static.hpp> +#include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/shared_ptr.hpp> #include <boost/math/special_functions/round.hpp> @@ -319,14 +321,14 @@ public: write_gpio(); // Configure ATR - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _tx_gpio_reg.atr_idle); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _rx_gpio_reg.atr_idle); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _tx_gpio_reg.atr_idle); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _rx_gpio_reg.atr_idle); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex); // Engage ATR control (1 is ATR control, 0 is manual control) _iface->set_pin_ctrl(dboard_iface::UNIT_TX, _tx_gpio_reg.atr_mask); @@ -385,23 +387,23 @@ public: get_rx_subtree()->create<std::vector<std::string> >("power_mode/options") .set(ubx_power_modes); get_rx_subtree()->create<std::string>("power_mode/value") - .subscribe(boost::bind(&ubx_xcvr::set_power_mode, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_power_mode, this, _1)) .set("performance"); get_rx_subtree()->create<std::vector<std::string> >("xcvr_mode/options") .set(ubx_xcvr_modes); get_rx_subtree()->create<std::string>("xcvr_mode/value") - .subscribe(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1)) .set("FDX"); get_tx_subtree()->create<std::vector<std::string> >("power_mode/options") .set(ubx_power_modes); get_tx_subtree()->create<std::string>("power_mode/value") - .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1)) - .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value"))); + .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1)) + .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value"))); get_tx_subtree()->create<std::vector<std::string> >("xcvr_mode/options") .set(ubx_xcvr_modes); get_tx_subtree()->create<std::string>("xcvr_mode/value") - .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1)) - .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value"))); + .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1)) + .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value"))); //////////////////////////////////////////////////////////////////// // Register TX properties @@ -410,20 +412,20 @@ public: get_tx_subtree()->create<device_addr_t>("tune_args") .set(device_addr_t()); get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&ubx_xcvr::get_locked, this, "TXLO")); + .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "TXLO")); get_tx_subtree()->create<double>("gains/PGA0/value") - .coerce(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0); + .set_coercer(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0); get_tx_subtree()->create<meta_range_t>("gains/PGA0/range") .set(ubx_tx_gain_range); get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&ubx_xcvr::set_tx_freq, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_tx_freq, this, _1)) .set(ubx_freq_range.start()); get_tx_subtree()->create<meta_range_t>("freq/range") .set(ubx_freq_range); get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(ubx_tx_antennas); get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&ubx_xcvr::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_tx_ant, this, _1)) .set(ubx_tx_antennas.at(0)); get_tx_subtree()->create<std::string>("connection") .set("QI"); @@ -436,7 +438,7 @@ public: get_tx_subtree()->create<meta_range_t>("bandwidth/range") .set(freq_range_t(bw, bw)); get_tx_subtree()->create<int64_t>("sync_delay") - .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1)) .set(-8); //////////////////////////////////////////////////////////////////// @@ -446,21 +448,21 @@ public: get_rx_subtree()->create<device_addr_t>("tune_args") .set(device_addr_t()); get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&ubx_xcvr::get_locked, this, "RXLO")); + .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "RXLO")); get_rx_subtree()->create<double>("gains/PGA0/value") - .coerce(boost::bind(&ubx_xcvr::set_rx_gain, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_rx_gain, this, _1)) .set(0); get_rx_subtree()->create<meta_range_t>("gains/PGA0/range") .set(ubx_rx_gain_range); get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&ubx_xcvr::set_rx_freq, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_rx_freq, this, _1)) .set(ubx_freq_range.start()); get_rx_subtree()->create<meta_range_t>("freq/range") .set(ubx_freq_range); get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(ubx_rx_antennas); get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2"); + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2"); get_rx_subtree()->create<std::string>("connection") .set("IQ"); get_rx_subtree()->create<bool>("enabled") @@ -472,35 +474,38 @@ public: get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(freq_range_t(bw, bw)); get_rx_subtree()->create<int64_t>("sync_delay") - .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1)) .set(-8); } - ~ubx_xcvr(void) + virtual ~ubx_xcvr(void) { - // Shutdown synthesizers - _txlo1->shutdown(); - _txlo2->shutdown(); - _rxlo1->shutdown(); - _rxlo2->shutdown(); + UHD_SAFE_CALL + ( + // Shutdown synthesizers + _txlo1->shutdown(); + _txlo2->shutdown(); + _rxlo1->shutdown(); + _rxlo2->shutdown(); - // Reset CPLD values - _cpld_reg.value = 0; - write_cpld_reg(); + // Reset CPLD values + _cpld_reg.value = 0; + write_cpld_reg(); - // Reset GPIO values - set_gpio_field(TX_GAIN, 0); - set_gpio_field(CPLD_RST_N, 0); - set_gpio_field(RX_ANT, 1); - set_gpio_field(TX_EN_N, 1); - set_gpio_field(RX_EN_N, 1); - set_gpio_field(SPI_ADDR, 0x7); - set_gpio_field(RX_GAIN, 0); - set_gpio_field(TXLO1_SYNC, 0); - set_gpio_field(TXLO2_SYNC, 0); - set_gpio_field(RXLO1_SYNC, 0); - set_gpio_field(RXLO1_SYNC, 0); - write_gpio(); + // Reset GPIO values + set_gpio_field(TX_GAIN, 0); + set_gpio_field(CPLD_RST_N, 0); + set_gpio_field(RX_ANT, 1); + set_gpio_field(TX_EN_N, 1); + set_gpio_field(RX_EN_N, 1); + set_gpio_field(SPI_ADDR, 0x7); + set_gpio_field(RX_GAIN, 0); + set_gpio_field(TXLO1_SYNC, 0); + set_gpio_field(TXLO2_SYNC, 0); + set_gpio_field(RXLO1_SYNC, 0); + set_gpio_field(RXLO1_SYNC, 0); + write_gpio(); + ) } private: @@ -638,23 +643,23 @@ private: uint16_t mask = lo1_field_info.mask | lo2_field_info.mask; dboard_iface::unit_t unit = lo1_field_info.unit; UHD_ASSERT_THROW(lo1_field_info.unit == lo2_field_info.unit); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask); // De-assert SYNC // Head of line blocking means the command time does not need to be set. - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, 0, mask); } } @@ -751,6 +756,20 @@ private: device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); UHD_LOGV(rarely) << boost::format("UBX TX: the requested frequency is %f MHz") % (freq/1e6) << std::endl; + double target_pfd_freq = _tx_target_pfd_freq; + if (is_int_n and tune_args.has_key("int_n_step")) + { + target_pfd_freq = tune_args.cast<double>("int_n_step", _tx_target_pfd_freq); + if (target_pfd_freq > _tx_target_pfd_freq) + { + UHD_MSG(warning) + << boost::format("Requested int_n_step of %f MHz too large, clipping to %f MHz") + % (target_pfd_freq/1e6) + % (_tx_target_pfd_freq/1e6) + << std::endl; + target_pfd_freq = _tx_target_pfd_freq; + } + } // Clip the frequency to the valid range freq = ubx_freq_range.clip(freq); @@ -787,10 +806,10 @@ private: set_cpld_field(TXLB_SEL, 1); set_cpld_field(TXHB_SEL, 0); // Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage) - freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _txlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= (500*fMHz)) && (freq <= (800*fMHz))) @@ -800,7 +819,7 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (800*fMHz)) && (freq <= (1000*fMHz))) @@ -810,7 +829,7 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq > (1000*fMHz)) && (freq <= (2200*fMHz))) @@ -820,7 +839,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2200*fMHz)) && (freq <= (2500*fMHz))) @@ -830,7 +849,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2500*fMHz)) && (freq <= (6000*fMHz))) @@ -840,7 +859,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } @@ -893,6 +912,20 @@ private: property_tree::sptr subtree = this->get_rx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double target_pfd_freq = _rx_target_pfd_freq; + if (is_int_n and tune_args.has_key("int_n_step")) + { + target_pfd_freq = tune_args.cast<double>("int_n_step", _rx_target_pfd_freq); + if (target_pfd_freq > _rx_target_pfd_freq) + { + UHD_MSG(warning) + << boost::format("Requested int_n_step of %f Mhz too large, clipping to %f MHz") + % (target_pfd_freq/1e6) + % (_rx_target_pfd_freq/1e6) + << std::endl; + target_pfd_freq = _rx_target_pfd_freq; + } + } // Clip the frequency to the valid range freq = ubx_freq_range.clip(freq); @@ -931,10 +964,10 @@ private: set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); // Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to minimize LO leakage) - freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _rxlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 100*fMHz) && (freq < 500*fMHz)) @@ -947,10 +980,10 @@ private: set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); // Set LO1 to IF of 2440 (center of filter) - freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 500*fMHz) && (freq < 800*fMHz)) @@ -962,7 +995,7 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 800*fMHz) && (freq < 1000*fMHz)) @@ -974,7 +1007,7 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq >= 1000*fMHz) && (freq < 1500*fMHz)) @@ -986,7 +1019,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 1500*fMHz) && (freq < 2200*fMHz)) @@ -998,7 +1031,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2200*fMHz) && (freq < 2500*fMHz)) @@ -1010,7 +1043,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2500*fMHz) && (freq <= 6000*fMHz)) @@ -1022,7 +1055,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp index 97357bc90..6539e798a 100644 --- a/host/lib/usrp/dboard/db_wbx_common.cpp +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -69,17 +69,17 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::set_rx_gain, this, _1, name)) .set(wbx_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_rx_gain_ranges[name]); } this->get_rx_subtree()->create<std::string>("connection").set("IQ"); this->get_rx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::set_rx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::set_rx_enabled, this, _1)) .set(true); //start enabled this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); @@ -94,7 +94,7 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX)); this->get_tx_subtree()->create<std::string>("connection").set("IQ"); this->get_tx_subtree()->create<bool>("use_lo_offset").set(false); @@ -156,3 +156,9 @@ sensor_value_t wbx_base::get_locked(dboard_iface::unit_t unit){ const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; return sensor_value_t("LO", locked, "locked", "unlocked"); } + +void wbx_base::wbx_versionx::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) { + BOOST_FOREACH(boost::uint32_t reg, regs) { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp index 6a4224048..0e339e4a3 100644 --- a/host/lib/usrp/dboard/db_wbx_common.hpp +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -19,8 +19,13 @@ #define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP #include <uhd/types/device_addr.hpp> +#include "adf435x.hpp" -#include "../common/adf435x_common.hpp" +// LO Related +#define ADF435X_CE (1 << 3) +#define ADF435X_PDBRF (1 << 2) +#define ADF435X_MUXOUT (1 << 1) // INPUT!!! +#define LOCKDET_MASK (1 << 0) // INPUT!!! // TX IO Pins #define TX_PUP_5V (1 << 7) // enables 5.0V power supply @@ -40,6 +45,9 @@ #define TX_ATTN_1 (1 << 1) #define TX_ATTN_MASK (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1) // valid bits of TX Attenuator Control +#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control +#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control + // Mixer functions #define TX_MIXER_ENB (TXMOD_EN|ADF435X_PDBRF) // for v3, TXMOD_EN tied to ADF435X_PDBRF rather than separate #define TX_MIXER_DIS 0 @@ -142,6 +150,10 @@ protected: property_tree::sptr get_tx_subtree(void){ return self_base->get_tx_subtree(); } + + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; @@ -153,7 +165,7 @@ protected: class wbx_version2 : public wbx_versionx { public: wbx_version2(wbx_base *_self_wbx_base); - ~wbx_version2(void); + virtual ~wbx_version2(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); @@ -168,7 +180,7 @@ protected: class wbx_version3 : public wbx_versionx { public: wbx_version3(wbx_base *_self_wbx_base); - ~wbx_version3(void); + virtual ~wbx_version3(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); @@ -183,7 +195,7 @@ protected: class wbx_version4 : public wbx_versionx { public: wbx_version4(wbx_base *_self_wbx_base); - ~wbx_version4(void); + virtual ~wbx_version4(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp index c8f2be155..062e1294b 100644 --- a/host/lib/usrp/dboard/db_wbx_simple.cpp +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -46,7 +46,7 @@ static const std::vector<std::string> wbx_rx_antennas = list_of("TX/RX")("RX2")( class wbx_simple : public wbx_base{ public: wbx_simple(ctor_args_t args); - ~wbx_simple(void); + virtual ~wbx_simple(void); private: void set_rx_ant(const std::string &ant); @@ -88,7 +88,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ std::string(str(boost::format("%s+GDB") % this->get_rx_subtree()->access<std::string>("name").get() ))); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&wbx_simple::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_simple::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(wbx_rx_antennas); @@ -100,7 +100,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ std::string(str(boost::format("%s+GDB") % this->get_tx_subtree()->access<std::string>("name").get() ))); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&wbx_simple::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_simple::set_tx_ant, this, _1)) .set(wbx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(wbx_tx_antennas); @@ -112,14 +112,14 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); //setup ATR for the antenna switches (constant) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); - - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); } wbx_simple::~wbx_simple(void){ @@ -138,14 +138,14 @@ void wbx_simple::set_rx_ant(const std::string &ant){ //write the new antenna setting to atr regs if (_rx_ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); } } @@ -154,11 +154,11 @@ void wbx_simple::set_tx_ant(const std::string &ant){ //write the new antenna setting to atr regs if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); } } diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp index 93047fb7a..18dc383b7 100644 --- a/host/lib/usrp/dboard/db_wbx_version2.cpp +++ b/host/lib/usrp/dboard/db_wbx_version2.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4350_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> @@ -77,13 +75,15 @@ static double tx_pga0_gain_to_dac_volts(double &gain){ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create<std::string>("name").set("WBXv2 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); @@ -93,17 +93,17 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { this->get_tx_subtree()->create<std::string>("name").set("WBXv2 TX"); BOOST_FOREACH(const std::string &name, wbx_v2_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name)) .set(wbx_v2_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v2_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -117,15 +117,15 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF435X_CE|RXBB_PDB|ADF435X_PDBRF|RX_ATTN_MASK); //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } wbx_base::wbx_version2::~wbx_version2(void){ @@ -178,108 +178,42 @@ double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //frequency must 2x the target frequency double synth_target_freq = target_freq * 2; - //TODO: Document why the following has to be true - bool div_resync_enabled = (target_freq > reference_freq); - - adf4350_regs_t::prescaler_t prescaler = - synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); + + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + //When divider resync is enabled, a 180 deg phase error is introduced when syncing //multiple WBX boards. Switching to fundamental mode works arounds this issue. - tuning_constraints.feedback_after_divider = div_resync_enabled; + //TODO: Document why the following has to be true + lo_iface->set_feedback_select((target_freq > reference_freq) ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4350_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = div_resync_enabled ? - adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "WBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "WBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp index 6927ae4e4..2add8d25d 100644 --- a/host/lib/usrp/dboard/db_wbx_version3.cpp +++ b/host/lib/usrp/dboard/db_wbx_version3.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4350_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> #include <uhd/types/ranges.hpp> @@ -82,13 +80,15 @@ static int tx_pga0_gain_to_iobits(double &gain){ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create<std::string>("name").set("WBXv3 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); @@ -98,17 +98,17 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { this->get_tx_subtree()->create<std::string>("name").set("WBXv3 TX"); BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name)) .set(wbx_v3_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v3_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -129,29 +129,29 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { //slip between bursts). set TX gain iobits to min gain (max attenuation) //when RX_ONLY or IDLE to suppress LO leakage self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, v3_tx_mod, \ + gpio_atr::ATR_REG_IDLE, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, v3_tx_mod, \ + gpio_atr::ATR_REG_RX_ONLY, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, v3_tx_mod, \ + gpio_atr::ATR_REG_TX_ONLY, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, \ + gpio_atr::ATR_REG_FULL_DUPLEX, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, \ + gpio_atr::ATR_REG_IDLE, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, \ + gpio_atr::ATR_REG_TX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, \ + gpio_atr::ATR_REG_RX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, \ + gpio_atr::ATR_REG_FULL_DUPLEX, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } @@ -181,8 +181,8 @@ double wbx_base::wbx_version3::set_tx_gain(double gain, const std::string &name) //write the new gain to tx gpio outputs //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); } else UHD_THROW_INVALID_CODE_PATH(); return self_base->_tx_gains[name]; //shadow @@ -209,108 +209,42 @@ double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //frequency must 2x the target frequency double synth_target_freq = target_freq * 2; - //TODO: Document why the following has to be true - bool div_resync_enabled = (target_freq > reference_freq); - - adf4350_regs_t::prescaler_t prescaler = - synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); + + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + //When divider resync is enabled, a 180 deg phase error is introduced when syncing //multiple WBX boards. Switching to fundamental mode works arounds this issue. - tuning_constraints.feedback_after_divider = div_resync_enabled; + //TODO: Document why the following has to be true + lo_iface->set_feedback_select((target_freq > reference_freq) ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4350_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = div_resync_enabled ? - adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "WBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "WBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp index 81cdaefac..dc351af1d 100644 --- a/host/lib/usrp/dboard/db_wbx_version4.cpp +++ b/host/lib/usrp/dboard/db_wbx_version4.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4351_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> #include <uhd/types/ranges.hpp> @@ -83,6 +81,8 @@ static int tx_pga0_gain_to_iobits(double &gain){ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties @@ -92,7 +92,7 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { if(rx_id == 0x0063) this->get_rx_subtree()->create<std::string>("name").set("WBXv4 RX"); else if(rx_id == 0x0081) this->get_rx_subtree()->create<std::string>("name").set("WBX-120 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); @@ -105,17 +105,17 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { else if(rx_id == 0x0081) this->get_tx_subtree()->create<std::string>("name").set("WBX-120 TX"); BOOST_FOREACH(const std::string &name, wbx_v4_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) .set(wbx_v4_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v4_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -136,29 +136,29 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //between bursts) set TX gain iobits to min gain (max attenuation) when //RX_ONLY or IDLE to suppress LO leakage self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, v4_tx_mod, \ + gpio_atr::ATR_REG_IDLE, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, v4_tx_mod, \ + gpio_atr::ATR_REG_RX_ONLY, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, v4_tx_mod, \ + gpio_atr::ATR_REG_TX_ONLY, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, v4_tx_mod, \ + gpio_atr::ATR_REG_FULL_DUPLEX, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, \ + gpio_atr::ATR_REG_IDLE, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, \ + gpio_atr::ATR_REG_TX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, \ + gpio_atr::ATR_REG_RX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, \ + gpio_atr::ATR_REG_FULL_DUPLEX, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } @@ -188,8 +188,8 @@ double wbx_base::wbx_version4::set_tx_gain(double gain, const std::string &name) //write the new gain to tx gpio outputs //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); } else UHD_THROW_INVALID_CODE_PATH(); @@ -217,116 +217,46 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (adf4351_regs_t::PRESCALER_4_5, 23) - (adf4351_regs_t::PRESCALER_8_9, 75) - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //frequency must 2x the target frequency. This introduces a 180 degree phase //ambiguity when trying to synchronize the phase of multiple boards. double synth_target_freq = target_freq * 2; - adf4351_regs_t::prescaler_t prescaler = - synth_target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3.6e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 64); //The feedback of the divided frequency must be disabled whenever the target frequency //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) //will have too much ambiguity to synchronize. - tuning_constraints.feedback_after_divider = - (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); + lo_iface->set_feedback_select( + (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL)); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4351_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM - : adf4351_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM - : adf4351_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4351_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4351_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4351_regs_t::LDF_INT_N : - adf4351_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); - std::string board_name = (rx_id == 0x0081) ? "WBX-120" : "WBX"; - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "%s SPI Reg (0x%02x): 0x%08x" - ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "%s tune: actual frequency %f MHz" - ) % board_name.c_str() % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); return actual_freq; } diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp index 50c67991a..4a3f69f69 100644 --- a/host/lib/usrp/dboard/db_xcvr2450.cpp +++ b/host/lib/usrp/dboard/db_xcvr2450.cpp @@ -112,7 +112,7 @@ static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list class xcvr2450 : public xcvr_dboard_base{ public: xcvr2450(ctor_args_t args); - ~xcvr2450(void); + virtual ~xcvr2450(void); private: double _lo_freq; @@ -231,23 +231,23 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("XCVR2450 RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&xcvr2450::get_locked, this)); + .set_publisher(boost::bind(&xcvr2450::get_locked, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&xcvr2450::get_rssi, this)); + .set_publisher(boost::bind(&xcvr2450::get_rssi, this)); BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&xcvr2450::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&xcvr2450::set_rx_gain, this, _1, name)) .set(xcvr_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(xcvr_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1)) .set(double(2.45e9)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(xcvr_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&xcvr2450::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&xcvr2450::set_rx_ant, this, _1)) .set(xcvr_antennas.at(0)); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(xcvr_antennas); @@ -258,7 +258,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth + .set_coercer(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth .set(2.0*_rx_bandwidth); //_rx_bandwidth in lowpass, convert to complex bandpass this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(xcvr_rx_bandwidth_range); @@ -269,21 +269,21 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<std::string>("name") .set("XCVR2450 TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&xcvr2450::get_locked, this)); + .set_publisher(boost::bind(&xcvr2450::get_locked, this)); BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){ this->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&xcvr2450::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&xcvr2450::set_tx_gain, this, _1, name)) .set(xcvr_tx_gain_ranges[name].start()); this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(xcvr_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1)) .set(double(2.45e9)); this->get_tx_subtree()->create<meta_range_t>("freq/range") .set(xcvr_freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&xcvr2450::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&xcvr2450::set_tx_ant, this, _1)) .set(xcvr_antennas.at(1)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(xcvr_antennas); @@ -294,7 +294,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_tx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth + .set_coercer(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth .set(2.0*_tx_bandwidth); //_tx_bandwidth in lowpass, convert to complex bandpass this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") .set(xcvr_tx_bandwidth_range); @@ -315,12 +315,12 @@ xcvr2450::~xcvr2450(void){ void xcvr2450::spi_reset(void){ //spi reset mode: global enable = off, tx and rx enable = on - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, TX_ENB_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); //take it back out of spi reset mode and wait a bit - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } @@ -337,16 +337,16 @@ void xcvr2450::update_atr(void){ int ad9515div = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO; //set the tx registers - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); //set the rx registers - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); } /*********************************************************************** diff --git a/host/lib/usrp/dboard_base.cpp b/host/lib/usrp/dboard_base.cpp index fe14c02b9..465b9e489 100644 --- a/host/lib/usrp/dboard_base.cpp +++ b/host/lib/usrp/dboard_base.cpp @@ -32,7 +32,7 @@ struct dboard_base::impl{ dboard_base::dboard_base(ctor_args_t args){ _impl = UHD_PIMPL_MAKE(impl, ()); - _impl->args = *static_cast<dboard_ctor_args_t *>(args); + _impl->args = dboard_ctor_args_t::cast(args); } std::string dboard_base::get_subdev_name(void){ diff --git a/host/lib/usrp/dboard_ctor_args.hpp b/host/lib/usrp/dboard_ctor_args.hpp index 99c071ff8..c8e4006d1 100644 --- a/host/lib/usrp/dboard_ctor_args.hpp +++ b/host/lib/usrp/dboard_ctor_args.hpp @@ -26,11 +26,17 @@ namespace uhd{ namespace usrp{ - struct dboard_ctor_args_t{ + class dboard_ctor_args_t { + public: std::string sd_name; dboard_iface::sptr db_iface; dboard_id_t rx_id, tx_id; property_tree::sptr rx_subtree, tx_subtree; + dboard_base::sptr rx_container, tx_container; + + static const dboard_ctor_args_t& cast(dboard_base::ctor_args_t args) { + return *static_cast<dboard_ctor_args_t*>(args); + } }; }} //namespace diff --git a/host/lib/usrp/dboard_iface.cpp b/host/lib/usrp/dboard_iface.cpp deleted file mode 100644 index 092e005f0..000000000 --- a/host/lib/usrp/dboard_iface.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright 2010-2013,2015 Ettus Research LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#include <uhd/usrp/dboard_iface.hpp> -#include <uhd/types/dict.hpp> - -using namespace uhd::usrp; - -struct dboard_iface::impl{ - uhd::dict<unit_t, boost::uint16_t> pin_ctrl_shadow; - uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > atr_reg_shadow; - uhd::dict<unit_t, boost::uint16_t> gpio_ddr_shadow; - uhd::dict<unit_t, boost::uint16_t> gpio_out_shadow; -}; - -dboard_iface::dboard_iface(void){ - _impl = UHD_PIMPL_MAKE(impl, ()); -} - -dboard_iface::~dboard_iface(void) -{ - //empty -} - -template <typename T> -static T shadow_it(T &shadow, const T &value, const T &mask){ - shadow = (shadow & ~mask) | (value & mask); - return shadow; -} - -void dboard_iface::set_pin_ctrl( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_pin_ctrl(unit, shadow_it(_impl->pin_ctrl_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_pin_ctrl(unit_t unit){ - return _impl->pin_ctrl_shadow[unit]; -} - -void dboard_iface::set_atr_reg( - unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask -){ - _set_atr_reg(unit, reg, shadow_it(_impl->atr_reg_shadow[unit][reg], value, mask)); -} - -boost::uint16_t dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ - return _impl->atr_reg_shadow[unit][reg]; -} - -void dboard_iface::set_gpio_ddr( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_gpio_ddr(unit, shadow_it(_impl->gpio_ddr_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_gpio_ddr(unit_t unit){ - return _impl->gpio_ddr_shadow[unit]; -} - -void dboard_iface::set_gpio_out( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_gpio_out(unit, shadow_it(_impl->gpio_out_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_gpio_out(unit_t unit){ - return _impl->gpio_out_shadow[unit]; -} - -void dboard_iface::set_command_time(const uhd::time_spec_t&) -{ - throw uhd::not_implemented_error("timed command feature not implemented on this hardware"); -} - -uhd::time_spec_t dboard_iface::get_command_time() -{ - return uhd::time_spec_t(0.0); -} diff --git a/host/lib/usrp/dboard_manager.cpp b/host/lib/usrp/dboard_manager.cpp index 340c1d3f9..6099adcbb 100644 --- a/host/lib/usrp/dboard_manager.cpp +++ b/host/lib/usrp/dboard_manager.cpp @@ -37,11 +37,11 @@ using namespace uhd::usrp; **********************************************************************/ class dboard_key_t{ public: - dboard_key_t(const dboard_id_t &id = dboard_id_t::none()): - _rx_id(id), _tx_id(id), _xcvr(false){} + dboard_key_t(const dboard_id_t &id = dboard_id_t::none(), bool restricted = false): + _rx_id(id), _tx_id(id), _xcvr(false), _restricted(restricted) {} - dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id): - _rx_id(rx_id), _tx_id(tx_id), _xcvr(true){} + dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id, bool restricted = false): + _rx_id(rx_id), _tx_id(tx_id), _xcvr(true), _restricted(restricted) {} dboard_id_t xx_id(void) const{ UHD_ASSERT_THROW(not this->is_xcvr()); @@ -62,9 +62,14 @@ public: return this->_xcvr; } + bool is_restricted(void) const{ + return this->_restricted; + } + private: dboard_id_t _rx_id, _tx_id; bool _xcvr; + bool _restricted; }; bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ @@ -78,8 +83,8 @@ bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ /*********************************************************************** * storage and registering for dboards **********************************************************************/ -//dboard registry tuple: dboard constructor, canonical name, subdev names -typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string> > args_t; +//dboard registry tuple: dboard constructor, canonical name, subdev names, container constructor +typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string>, dboard_manager::dboard_ctor_t> args_t; //map a dboard id to a dboard constructor typedef uhd::dict<dboard_key_t, args_t> id_to_args_map_t; @@ -87,9 +92,10 @@ UHD_SINGLETON_FCN(id_to_args_map_t, get_id_to_args_map) static void register_dboard_key( const dboard_key_t &dboard_key, - dboard_manager::dboard_ctor_t dboard_ctor, + dboard_manager::dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_manager::dboard_ctor_t db_container_ctor ){ UHD_LOGV(always) << "registering: " << name << std::endl; if (get_id_to_args_map().has_key(dboard_key)){ @@ -103,26 +109,49 @@ static void register_dboard_key( ) % dboard_key.xx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>())); } - get_id_to_args_map()[dboard_key] = args_t(dboard_ctor, name, subdev_names); + get_id_to_args_map()[dboard_key] = args_t(db_subdev_ctor, name, subdev_names, db_container_ctor); } void dboard_manager::register_dboard( const dboard_id_t &dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor ){ - register_dboard_key(dboard_key_t(dboard_id), dboard_ctor, name, subdev_names); + register_dboard_key(dboard_key_t(dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor); } void dboard_manager::register_dboard( const dboard_id_t &rx_dboard_id, const dboard_id_t &tx_dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor +){ + register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor); +} + +void dboard_manager::register_dboard_restricted( + const dboard_id_t &dboard_id, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor +){ + register_dboard_key(dboard_key_t(dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor); +} + +void dboard_manager::register_dboard_restricted( + const dboard_id_t &rx_dboard_id, + const dboard_id_t &tx_dboard_id, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor ){ - register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), dboard_ctor, name, subdev_names); + register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor); } std::string dboard_id_t::to_cname(void) const{ @@ -155,7 +184,15 @@ public: dboard_iface::sptr iface, property_tree::sptr subtree ); - ~dboard_manager_impl(void); + virtual ~dboard_manager_impl(void); + + inline const std::vector<std::string>& get_rx_frontends() const { + return _rx_frontends; + } + + inline const std::vector<std::string>& get_tx_frontends() const { + return _tx_frontends; + } private: void init(dboard_id_t, dboard_id_t, property_tree::sptr); @@ -164,6 +201,8 @@ private: //the subdevice proxy is internal to the cpp file uhd::dict<std::string, dboard_base::sptr> _rx_dboards; uhd::dict<std::string, dboard_base::sptr> _tx_dboards; + std::vector<std::string> _rx_frontends; + std::vector<std::string> _tx_frontends; dboard_iface::sptr _iface; void set_nice_dboard_if(void); }; @@ -210,6 +249,7 @@ dboard_manager_impl::dboard_manager_impl( //clean up the stuff added by the call above if (subtree->exists("rx_frontends")) subtree->remove("rx_frontends"); if (subtree->exists("tx_frontends")) subtree->remove("tx_frontends"); + if (subtree->exists("iface")) subtree->remove("iface"); this->init(dboard_id_t::none(), dboard_id_t::none(), subtree); } } @@ -244,6 +284,11 @@ void dboard_manager_impl::init( //initialize the gpio pins before creating subdevs set_nice_dboard_if(); + //conditionally register the dboard iface in the tree + if (not (rx_dboard_key.is_restricted() or tx_dboard_key.is_restricted() or xcvr_dboard_key.is_restricted())) { + subtree->create<dboard_iface::sptr>("iface").set(_iface); + } + //dboard constructor args dboard_ctor_args_t db_ctor_args; db_ctor_args.db_iface = _iface; @@ -252,42 +297,81 @@ void dboard_manager_impl::init( if (xcvr_dboard_key.is_xcvr()){ //extract data for the xcvr dboard key - dboard_ctor_t dboard_ctor; std::string name; std::vector<std::string> subdevs; - boost::tie(dboard_ctor, name, subdevs) = get_id_to_args_map()[xcvr_dboard_key]; + dboard_ctor_t subdev_ctor; std::string name; std::vector<std::string> subdevs; dboard_ctor_t container_ctor; + boost::tie(subdev_ctor, name, subdevs, container_ctor) = get_id_to_args_map()[xcvr_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + if (container_ctor) { + db_ctor_args.rx_container = container_ctor(&db_ctor_args); + } else { + db_ctor_args.rx_container = dboard_base::sptr(); + } + db_ctor_args.tx_container = db_ctor_args.rx_container; //Same TX and RX container //create the xcvr object for each subdevice BOOST_FOREACH(const std::string &subdev, subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = rx_dboard_id; - db_ctor_args.tx_id = tx_dboard_id; - db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); - db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); - dboard_base::sptr xcvr_dboard = dboard_ctor(&db_ctor_args); + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + dboard_base::sptr xcvr_dboard = subdev_ctor(&db_ctor_args); _rx_dboards[subdev] = xcvr_dboard; _tx_dboards[subdev] = xcvr_dboard; + xcvr_dboard->initialize(); } + + //initialize the container after all subdevs have been created + if (container_ctor) { + db_ctor_args.rx_container->initialize(); + } + + //Populate frontend names in-order. + //We cannot use _xx_dboards.keys() here because of the ordering requirement + _rx_frontends = subdevs; + _tx_frontends = subdevs; } //make tx and rx subdevs (separate subdevs for rx and tx dboards) - else{ - + else + { //force the rx key to the unknown board for bad combinations if (rx_dboard_key.is_xcvr() or rx_dboard_key.xx_id() == dboard_id_t::none()){ rx_dboard_key = dboard_key_t(0xfff1); } //extract data for the rx dboard key - dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; - boost::tie(rx_dboard_ctor, rx_name, rx_subdevs) = get_id_to_args_map()[rx_dboard_key]; + dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; dboard_ctor_t rx_cont_ctor; + boost::tie(rx_dboard_ctor, rx_name, rx_subdevs, rx_cont_ctor) = get_id_to_args_map()[rx_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = dboard_id_t::none(); + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = property_tree::sptr(); + if (rx_cont_ctor) { + db_ctor_args.rx_container = rx_cont_ctor(&db_ctor_args); + } else { + db_ctor_args.rx_container = dboard_base::sptr(); + } //make the rx subdevs BOOST_FOREACH(const std::string &subdev, rx_subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = rx_dboard_id; - db_ctor_args.tx_id = dboard_id_t::none(); - db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); - db_ctor_args.tx_subtree = property_tree::sptr(); //null + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); _rx_dboards[subdev] = rx_dboard_ctor(&db_ctor_args); + _rx_dboards[subdev]->initialize(); + } + + //initialize the container after all subdevs have been created + if (rx_cont_ctor) { + db_ctor_args.rx_container->initialize(); } //force the tx key to the unknown board for bad combinations @@ -296,18 +380,39 @@ void dboard_manager_impl::init( } //extract data for the tx dboard key - dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; - boost::tie(tx_dboard_ctor, tx_name, tx_subdevs) = get_id_to_args_map()[tx_dboard_key]; + dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; dboard_ctor_t tx_cont_ctor; + boost::tie(tx_dboard_ctor, tx_name, tx_subdevs, tx_cont_ctor) = get_id_to_args_map()[tx_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = dboard_id_t::none(); + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = property_tree::sptr(); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + if (tx_cont_ctor) { + db_ctor_args.tx_container = tx_cont_ctor(&db_ctor_args); + } else { + db_ctor_args.tx_container = dboard_base::sptr(); + } //make the tx subdevs BOOST_FOREACH(const std::string &subdev, tx_subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = dboard_id_t::none(); - db_ctor_args.tx_id = tx_dboard_id; - db_ctor_args.rx_subtree = property_tree::sptr(); //null - db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); _tx_dboards[subdev] = tx_dboard_ctor(&db_ctor_args); + _tx_dboards[subdev]->initialize(); } + + //initialize the container after all subdevs have been created + if (tx_cont_ctor) { + db_ctor_args.tx_container->initialize(); + } + + //Populate frontend names in-order. + //We cannot use _xx_dboards.keys() here because of the ordering requirement + _rx_frontends = rx_subdevs; + _tx_frontends = tx_subdevs; } } diff --git a/host/lib/usrp/e100/CMakeLists.txt b/host/lib/usrp/e100/CMakeLists.txt index 2a1e14eab..da77b85dc 100644 --- a/host/lib/usrp/e100/CMakeLists.txt +++ b/host/lib/usrp/e100/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP-E100 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF OFF) - IF(ENABLE_E100) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/host/lib/usrp/e100/dboard_iface.cpp b/host/lib/usrp/e100/dboard_iface.cpp index b5baf6c56..faa0ff7d4 100644 --- a/host/lib/usrp/e100/dboard_iface.cpp +++ b/host/lib/usrp/e100/dboard_iface.cpp @@ -66,12 +66,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -97,6 +101,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const fe_connection_t& fe_conn); private: timed_wb_iface::sptr _wb_iface; @@ -127,6 +132,7 @@ void e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ switch(unit){ case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return; } } @@ -142,14 +148,15 @@ double e100_dboard_iface::get_clock_rate(unit_t unit){ switch(unit){ case UNIT_RX: return _clock->get_rx_clock_rate(); case UNIT_TX: return _clock->get_tx_clock_rate(); + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ switch(unit){ case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } @@ -160,28 +167,40 @@ double e100_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ -void e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void e100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t e100_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void e100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t e100_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t e100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void e100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t e100_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void e100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void e100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void e100_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t e100_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t e100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ switch(unit){ case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void e100_dboard_iface::write_spi( @@ -268,3 +287,8 @@ void e100_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _wb_iface->set_time(t); } + +void e100_dboard_iface::set_fe_connection(unit_t, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp index 6d3c08534..1f8fe84cb 100644 --- a/host/lib/usrp/e100/e100_impl.cpp +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -217,20 +217,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////////// //^^^ clock created up top, just reg props here... ^^^ _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) - .subscribe(boost::bind(&e100_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_tick_rate, this, _1)); - //subscribe the command time while we are at it + //add_coerced_subscriber the command time while we are at it _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create codec control objects @@ -241,18 +241,18 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(e100_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); + .set_coercer(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(e100_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) - .publish(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + .add_coerced_subscriber(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .set_publisher(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&e100_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&e100_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // Create the GPSDO control @@ -272,7 +272,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ 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)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name)); } } else @@ -288,27 +288,27 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE)); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////////// @@ -327,20 +327,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _rx_dsps[dspno]->set_link_rate(E100_RX_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) - .subscribe(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////////// @@ -351,17 +351,17 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ ); _tx_dsp->set_link_rate(E100_TX_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) - .subscribe(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); //////////////////////////////////////////////////////////////////// // create time control objects @@ -375,21 +375,21 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1)); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&e100_impl::update_clock_source, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_clock_source, this, _1)); std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); if (_gps and _gps->gps_detected()) clock_sources.push_back("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); @@ -399,7 +399,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1)); //////////////////////////////////////////////////////////////////// // create dboard control objects @@ -417,32 +417,31 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); //create a new dboard interface and manager - _dboard_iface = make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); _dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); } //initialize io handling @@ -457,8 +456,8 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// this->update_rates(); - _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter - .subscribe(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter + .add_coerced_subscriber(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); //reset cordic rates and their properties to zero BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp index d00668224..b05053f84 100644 --- a/host/lib/usrp/e100/e100_impl.hpp +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -111,7 +111,6 @@ private: //dboard stuff uhd::usrp::dboard_manager::sptr _dboard_manager; - uhd::usrp::dboard_iface::sptr _dboard_iface; bool _ignore_cal_file; std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; diff --git a/host/lib/usrp/e300/CMakeLists.txt b/host/lib/usrp/e300/CMakeLists.txt index 9c8aa29b9..68c3520e4 100644 --- a/host/lib/usrp/e300/CMakeLists.txt +++ b/host/lib/usrp/e300/CMakeLists.txt @@ -24,8 +24,6 @@ ######################################################################## find_package(UDev) -LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_E300) LIST(APPEND E300_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/e300_impl.cpp diff --git a/host/lib/usrp/e300/e300_fpga_defs.hpp b/host/lib/usrp/e300/e300_fpga_defs.hpp index 594461518..36dd47383 100644 --- a/host/lib/usrp/e300/e300_fpga_defs.hpp +++ b/host/lib/usrp/e300/e300_fpga_defs.hpp @@ -21,7 +21,7 @@ namespace uhd { namespace usrp { namespace e300 { namespace fpga { static const size_t NUM_RADIOS = 2; -static const boost::uint32_t COMPAT_MAJOR = 14; +static const boost::uint32_t COMPAT_MAJOR = 16; static const boost::uint32_t COMPAT_MINOR = 0; }}}} // namespace diff --git a/host/lib/usrp/e300/e300_impl.cpp b/host/lib/usrp/e300/e300_impl.cpp index a57c86c1d..5a589a7fd 100644 --- a/host/lib/usrp/e300/e300_impl.cpp +++ b/host/lib/usrp/e300/e300_impl.cpp @@ -48,6 +48,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace uhd::transport; namespace fs = boost::filesystem; namespace asio = boost::asio; @@ -470,14 +471,14 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) BOOST_FOREACH(const std::string &name, _sensor_manager->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); + .set_publisher(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); } #ifdef E300_GPSD if (_gps) { BOOST_FOREACH(const std::string &name, _gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gpsd_iface::get_sensor, _gps, name)); + .set_publisher(boost::bind(&gpsd_iface::get_sensor, _gps, name)); } } #endif @@ -487,7 +488,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) //////////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(_eeprom_manager->get_mb_eeprom()) // set first... - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_mb_eeprom, _eeprom_manager, _1)); @@ -495,9 +496,9 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) // clocking //////////////////////////////////////////////////////////////////// _tree->create<double>(mb_path / "tick_rate") - .coerce(boost::bind(&e300_impl::_set_tick_rate, this, _1)) - .publish(boost::bind(&e300_impl::_get_tick_rate, this)) - .subscribe(boost::bind(&e300_impl::_update_tick_rate, this, _1)); + .set_coercer(boost::bind(&e300_impl::_set_tick_rate, this, _1)) + .set_publisher(boost::bind(&e300_impl::_get_tick_rate, this)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_tick_rate, this, _1)); //default some chains on -- needed for setup purposes _codec_ctrl->set_active_chains(true, false, true, false); @@ -517,34 +518,34 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) //////////////////////////////////////////////////////////////////// // internal gpios //////////////////////////////////////////////////////////////////// - gpio_core_200::sptr fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); + gpio_atr_3000::sptr fp_gpio = gpio_atr_3000::make(_radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) { _tree->create<boost::uint32_t>(mb_path / "gpio" / "INT0" / attr.second) - .subscribe(boost::bind(&e300_impl::_set_internal_gpio, this, fp_gpio, attr.first, _1)) + .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, fp_gpio, attr.first, _1)) .set(0); } _tree->create<boost::uint8_t>(mb_path / "gpio" / "INT0" / "READBACK") - .publish(boost::bind(&e300_impl::_get_internal_gpio, this, fp_gpio)); + .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, fp_gpio)); //////////////////////////////////////////////////////////////////// // register the time keepers - only one can be the highlander //////////////////////////////////////////////////////////////////// _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) - .subscribe(boost::bind(&e300_impl::_set_time, this, _1)) + .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&e300_impl::_set_time, this, _1)) .set(0.0); //re-sync the times when the tick rate changes _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&e300_impl::_sync_times, this)); + .add_coerced_subscriber(boost::bind(&e300_impl::_sync_times, this)); _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1)); + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source" / "value") - .subscribe(boost::bind(&e300_impl::_update_time_source, this, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_time_source, this, _1)) .set(e300::DEFAULT_TIME_SRC); #ifdef E300_GPSD static const std::vector<std::string> time_sources = boost::assign::list_of("none")("internal")("external")("gpsdo"); @@ -554,7 +555,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _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(&e300_impl::_update_clock_source, this, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_clock_source, this, _1)) .set(e300::DEFAULT_CLOCK_SRC); static const std::vector<std::string> clock_sources = boost::assign::list_of("internal"); //external,gpsdo not supported _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources); @@ -565,13 +566,13 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) dboard_eeprom_t db_eeprom; _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom") .set(_eeprom_manager->get_db_eeprom()) - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_db_eeprom, _eeprom_manager, _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom") .set(_eeprom_manager->get_db_eeprom()) - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_db_eeprom, _eeprom_manager, _1)); @@ -604,10 +605,10 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1)); //////////////////////////////////////////////////////////////////// // do some post-init tasks @@ -631,37 +632,6 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); } -boost::uint8_t e300_impl::_get_internal_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void e300_impl::_set_internal_gpio( - gpio_core_200::sptr gpio, - const gpio_attr_t attr, - const boost::uint32_t value) -{ - switch (attr) - { - case GPIO_CTRL: - return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); - case GPIO_DDR: - return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); - case GPIO_OUT: - return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); - case GPIO_ATR_0X: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); - case GPIO_ATR_RX: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); - case GPIO_ATR_TX: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); - case GPIO_ATR_XX: - return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); - default: - UHD_THROW_INVALID_CODE_PATH(); - } -} - uhd::sensor_value_t e300_impl::_get_fe_pll_lock(const bool is_tx) { const boost::uint32_t st = @@ -1001,7 +971,8 @@ void e300_impl::_setup_radio(const size_t dspno) //////////////////////////////////////////////////////////////////// // Set up peripherals //////////////////////////////////////////////////////////////////// - perif.atr = gpio_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::GPIO)); + perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, radio::sr_addr(radio::GPIO)); + perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF); perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); @@ -1036,26 +1007,25 @@ void e300_impl::_setup_radio(const size_t dspno) // connect rx dsp control objects //////////////////////////////////////////////////////////////////// _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)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(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); perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->access<double>(rx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) ; _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); //////////////////////////////////////////////////////////////////// // create tx dsp control objects //////////////////////////////////////////////////////////////////// _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)); + .add_coerced_subscriber(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); perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->access<double>(tx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) ; //////////////////////////////////////////////////////////////////// @@ -1075,10 +1045,10 @@ void e300_impl::_setup_radio(const size_t dspno) // This will connect all the e300_impl-specific items _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked") - .publish(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION)) + .set_publisher(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION)) ; _tree->access<double>(rf_fe_path / "freq" / "value") - .subscribe(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) ; // Antenna Setup @@ -1086,7 +1056,7 @@ void e300_impl::_setup_radio(const size_t dspno) 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(&e300_impl::_update_antenna_sel, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1)) .set("RX2"); } else if (dir == TX_DIRECTION) { @@ -1315,11 +1285,11 @@ void e300_impl::_update_atrs(void) if (enb_tx) fd_reg |= tx_enables | xx_leds; - gpio_core_200_32wo::sptr atr = _radio_perifs[instance].atr; - atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, oo_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rx_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd_reg); + gpio_atr_3000::sptr atr = _radio_perifs[instance].atr; + atr->set_atr_reg(ATR_REG_IDLE, oo_reg); + atr->set_atr_reg(ATR_REG_RX_ONLY, rx_reg); + atr->set_atr_reg(ATR_REG_TX_ONLY, tx_reg); + atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd_reg); } } diff --git a/host/lib/usrp/e300/e300_impl.hpp b/host/lib/usrp/e300/e300_impl.hpp index 595b42679..e9a0b4b9a 100644 --- a/host/lib/usrp/e300/e300_impl.hpp +++ b/host/lib/usrp/e300/e300_impl.hpp @@ -41,7 +41,7 @@ #include "tx_dsp_core_3000.hpp" #include "ad9361_ctrl.hpp" #include "ad936x_manager.hpp" -#include "gpio_core_200.hpp" +#include "gpio_atr_3000.hpp" #include "e300_global_regs.hpp" #include "e300_i2c.hpp" @@ -147,7 +147,7 @@ private: // types struct radio_perifs_t { radio_ctrl_core_3000::sptr ctrl; - gpio_core_200_32wo::sptr atr; + gpio_atr::gpio_atr_3000::sptr atr; time_core_3000::sptr time64; rx_vita_core_3000::sptr framer; rx_dsp_core_3000::sptr ddc; @@ -283,14 +283,6 @@ private: // methods // get frontend lock sensor uhd::sensor_value_t _get_fe_pll_lock(const bool is_tx); - // internal gpios - boost::uint8_t _get_internal_gpio(gpio_core_200::sptr); - - void _set_internal_gpio( - gpio_core_200::sptr gpio, - const gpio_attr_t attr, - const boost::uint32_t value); - private: // members uhd::device_addr_t _device_addr; xport_t _xport_path; diff --git a/host/lib/usrp/e300/e300_io_impl.cpp b/host/lib/usrp/e300/e300_io_impl.cpp index 29d250c8f..c84042e98 100644 --- a/host/lib/usrp/e300/e300_io_impl.cpp +++ b/host/lib/usrp/e300/e300_io_impl.cpp @@ -87,7 +87,6 @@ void e300_impl::_update_tick_rate(const double rate) 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); } } @@ -158,10 +157,8 @@ void e300_impl::_update_subdev_spec( const std::string conn = _tree->access<std::string>( mb_path / "dboards" / spec[i].db_name / ("rx_frontends") / spec[i].sd_name / "connection").get(); - - const bool fe_swapped = (conn == "QI" or conn == "Q"); - _radio_perifs[i].ddc->set_mux(conn, fe_swapped); - _radio_perifs[i].rx_fe->set_mux(fe_swapped); + _radio_perifs[i].ddc->set_mux(usrp::fe_connection_t(conn)); + _radio_perifs[i].rx_fe->set_mux(false); } } diff --git a/host/lib/usrp/e300/e300_regs.hpp b/host/lib/usrp/e300/e300_regs.hpp index 846c759a4..74e45df00 100644 --- a/host/lib/usrp/e300/e300_regs.hpp +++ b/host/lib/usrp/e300/e300_regs.hpp @@ -41,7 +41,7 @@ static const uint32_t TIME = 128; static const uint32_t RX_DSP = 144; static const uint32_t TX_DSP = 184; static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 200; +static const uint32_t FP_GPIO = 201; static const uint32_t RX_FRONT = 208; static const uint32_t TX_FRONT = 216; static const uint32_t CODEC_IDLE = 250; diff --git a/host/lib/usrp/fe_connection.cpp b/host/lib/usrp/fe_connection.cpp new file mode 100644 index 000000000..071f5ecf2 --- /dev/null +++ b/host/lib/usrp/fe_connection.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2016 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 <uhd/usrp/fe_connection.hpp> +#include <uhd/exception.hpp> +#include <boost/regex.hpp> +#include <uhd/utils/math.hpp> + +using namespace uhd::usrp; + +fe_connection_t::fe_connection_t( + sampling_t sampling_mode, bool iq_swapped, + bool i_inverted, bool q_inverted, double if_freq +) : _sampling_mode(sampling_mode), _iq_swapped(iq_swapped), + _i_inverted(i_inverted), _q_inverted(q_inverted), _if_freq(if_freq) +{ +} + +fe_connection_t::fe_connection_t(const std::string& conn_str, double if_freq) { + static const boost::regex conn_regex("([IQ])(b?)(([IQ])(b?))?"); + boost::cmatch matches; + if (boost::regex_match(conn_str.c_str(), matches, conn_regex)) { + if (matches[3].length() == 0) { + //Connection in {I, Q, Ib, Qb} + _sampling_mode = REAL; + _iq_swapped = (matches[1].str() == "Q"); + _i_inverted = (matches[2].length() != 0); + _q_inverted = false; //IQ is swapped after inversion + } else { + //Connection in {I(b?)Q(b?), Q(b?)I(b?), I(b?)I(b?), Q(b?)Q(b?)} + _sampling_mode = (matches[1].str() == matches[4].str()) ? HETERODYNE : QUADRATURE; + _iq_swapped = (matches[1].str() == "Q"); + size_t i_idx = _iq_swapped ? 5 : 2, q_idx = _iq_swapped ? 2 : 5; + _i_inverted = (matches[i_idx].length() != 0); + _q_inverted = (matches[q_idx].length() != 0); + + if (_sampling_mode == HETERODYNE and _i_inverted != _q_inverted) { + throw uhd::value_error("Invalid connection string: " + conn_str); + } + } + _if_freq = if_freq; + } else { + throw uhd::value_error("Invalid connection string: " + conn_str); + } +} + +bool uhd::usrp::operator==(const fe_connection_t &lhs, const fe_connection_t &rhs){ + return ((lhs.get_sampling_mode() == rhs.get_sampling_mode()) and + (lhs.is_iq_swapped() == rhs.is_iq_swapped()) and + (lhs.is_i_inverted() == rhs.is_i_inverted()) and + (lhs.is_q_inverted() == rhs.is_q_inverted()) and + uhd::math::frequencies_are_equal(lhs.get_if_freq(), rhs.get_if_freq())); +} diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 396237e24..dbc0ebed2 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -438,8 +438,10 @@ public: ******************************************************************/ void set_master_clock_rate(double rate, size_t mboard){ if (mboard != ALL_MBOARDS){ - if (_tree->exists(mb_root(mboard) / "auto_tick_rate")) { + if (_tree->exists(mb_root(mboard) / "auto_tick_rate") + and _tree->access<bool>(mb_root(mboard) / "auto_tick_rate").get()) { _tree->access<bool>(mb_root(mboard) / "auto_tick_rate").set(false); + UHD_MSG(status) << "Setting master clock rate selection to 'manual'." << std::endl; } _tree->access<double>(mb_root(mboard) / "tick_rate").set(rate); return; @@ -1346,10 +1348,10 @@ public: if (attr == "CTRL") iface->set_pin_ctrl(unit, boost::uint16_t(value), boost::uint16_t(mask)); if (attr == "DDR") iface->set_gpio_ddr(unit, boost::uint16_t(value), boost::uint16_t(mask)); if (attr == "OUT") iface->set_gpio_out(unit, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_0X") iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_RX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_TX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_XX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_0X") iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_RX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_TX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_XX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask)); } } @@ -1367,10 +1369,10 @@ public: if (attr == "CTRL") return iface->get_pin_ctrl(unit); if (attr == "DDR") return iface->get_gpio_ddr(unit); if (attr == "OUT") return iface->get_gpio_out(unit); - if (attr == "ATR_0X") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_IDLE); - if (attr == "ATR_RX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY); - if (attr == "ATR_TX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY); - if (attr == "ATR_XX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX); + if (attr == "ATR_0X") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_IDLE); + if (attr == "ATR_RX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY); + if (attr == "ATR_TX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY); + if (attr == "ATR_XX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX); if (attr == "READBACK") return iface->read_gpio(unit); } return 0; diff --git a/host/lib/usrp/n230/CMakeLists.txt b/host/lib/usrp/n230/CMakeLists.txt new file mode 100644 index 000000000..9eaccffba --- /dev/null +++ b/host/lib/usrp/n230/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# 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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the N230 support +######################################################################## +IF(ENABLE_N230) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/n230_cores.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_resource_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_eeprom_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_stream_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_clk_pps_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_frontend_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_uart.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_image_loader.cpp + ) +ENDIF(ENABLE_N230) diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp new file mode 100644 index 000000000..9d704b702 --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp @@ -0,0 +1,158 @@ +// +// Copyright 2013-2014 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 "n230_clk_pps_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <stdexcept> +#include <cmath> +#include <cstdlib> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl_impl : public n230_clk_pps_ctrl +{ +public: + n230_clk_pps_ctrl_impl( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores + ): _codec_ctrl(codec_ctrl), + _ref_pll_ctrl(ref_pll_ctrl), + _core_misc_reg(core_misc_reg), + _core_pps_sel_reg(core_pps_sel), + _core_status_reg(core_status_reg), + _time_cores(time_cores), + _tick_rate(0.0), + _clock_source("<undefined>"), + _time_source("<undefined>") + { + } + + virtual ~n230_clk_pps_ctrl_impl() + { + } + + double set_tick_rate(const double rate) + { + UHD_MSG(status) << "Configuring a tick rate of " << rate/1e6 << " MHz... "; + _tick_rate = _codec_ctrl->set_clock_rate(rate); + UHD_MSG(status) << "got " << _tick_rate/1e6 << " MHz\n"; + + BOOST_FOREACH(time_core_3000::sptr& time_core, _time_cores) { + time_core->set_tick_rate(_tick_rate); + time_core->self_test(); + } + + return _tick_rate; + } + + double get_tick_rate() + { + return _tick_rate; + } + + void set_clock_source(const std::string &source) + { + if (_clock_source == source) return; + + if (source == "internal") { + _ref_pll_ctrl->set_lock_to_ext_ref(false); + } else if (source == "external" || source == "gpsdo") { + _ref_pll_ctrl->set_lock_to_ext_ref(true); + } else { + throw uhd::key_error("set_clock_source: unknown source: " + source); + } + _core_misc_reg.write(fpga::core_misc_reg_t::REF_SEL, (source == "gpsdo") ? 1 : 0); + + _clock_source = source; + } + + const std::string& get_clock_source() + { + return _clock_source; + } + + uhd::sensor_value_t get_ref_locked() + { + bool locked = false; + if (_clock_source == "external" || _clock_source == "gpsdo") { + locked = (_core_status_reg.read(fpga::core_status_reg_t::REF_LOCKED) == 1); + } else { + //If the source is internal, the charge pump on the ADF4001 is tristated which + //means that the 40MHz VCTXXO is free running i.e. always "locked" + locked = true; + } + return sensor_value_t("Ref", locked, "locked", "unlocked"); + } + + void set_pps_source(const std::string &source) + { + if (_time_source == source) return; + + if (source == "none" or source == "gpsdo") { + _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 0); + } else if (source == "external") { + _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 1); + } else { + throw uhd::key_error("update_time_source: unknown source: " + source); + } + + _time_source = source; + } + + const std::string& get_pps_source() + { + return _time_source; + } + +private: + ad9361_ctrl::sptr _codec_ctrl; + n230_ref_pll_ctrl::sptr _ref_pll_ctrl; + fpga::core_misc_reg_t& _core_misc_reg; + fpga::core_pps_sel_reg_t& _core_pps_sel_reg; + fpga::core_status_reg_t& _core_status_reg; + std::vector<time_core_3000::sptr> _time_cores; + double _tick_rate; + std::string _clock_source; + std::string _time_source; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_clk_pps_ctrl::sptr n230_clk_pps_ctrl::make( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel_reg, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores) +{ + return sptr(new n230_clk_pps_ctrl_impl( + codec_ctrl, ref_pll_ctrl, core_misc_reg, core_pps_sel_reg, core_status_reg, time_cores)); +} + diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp new file mode 100644 index 000000000..3e0a21e04 --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp @@ -0,0 +1,89 @@ +// +// Copyright 2013-2014 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_N230_CLK_PPS_CTRL_HPP +#define INCLUDED_N230_CLK_PPS_CTRL_HPP + +#include "time_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<n230_clk_pps_ctrl> sptr; + + static sptr make( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel_reg, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores); + + virtual ~n230_clk_pps_ctrl() {} + + /*********************************************************************** + * Tick Rate + **********************************************************************/ + /*! Set the master clock rate of the device. + * \return the clock frequency in Hz + */ + virtual double set_tick_rate(const double rate) = 0; + + /*! Get the master clock rate of the device. + * \return the clock frequency in Hz + */ + virtual double get_tick_rate() = 0; + + /*********************************************************************** + * Reference clock + **********************************************************************/ + /*! Set the reference clock source of the device. + */ + virtual void set_clock_source(const std::string &source) = 0; + + /*! Get the reference clock source of the device. + */ + virtual const std::string& get_clock_source() = 0; + + /*! Get the reference clock lock status. + */ + virtual uhd::sensor_value_t get_ref_locked() = 0; + + /*********************************************************************** + * Time source + **********************************************************************/ + /*! Set the time source of the device. + */ + virtual void set_pps_source(const std::string &source) = 0; + + /*! Get the reference clock source of the device. + */ + virtual const std::string& get_pps_source() = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_CLK_PPS_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_cores.cpp b/host/lib/usrp/n230/n230_cores.cpp new file mode 100644 index 000000000..58c702ec1 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.cpp @@ -0,0 +1,91 @@ +// +// Copyright 2013-2014 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 "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_fw_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +n230_core_spi_core::n230_core_spi_core( + uhd::wb_iface::sptr iface, + perif_t default_perif) : + _spi_core(spi_core_3000::make(iface, + fpga::sr_addr(fpga::SR_CORE_SPI), + fpga::rb_addr(fpga::RB_CORE_SPI))), + _current_perif(default_perif), + _last_perif(default_perif) +{ + change_perif(default_perif); +} + +boost::uint32_t n230_core_spi_core::transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback) +{ + boost::mutex::scoped_lock lock(_mutex); + return _spi_core->transact_spi(which_slave, config, data, num_bits, readback); +} + +void n230_core_spi_core::change_perif(perif_t perif) +{ + boost::mutex::scoped_lock lock(_mutex); + _last_perif = _current_perif; + _current_perif = perif; + + switch (_current_perif) { + case CODEC: + _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::CODEC_SPI_CLOCK_FREQ); + break; + case PLL: + _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::ADF4001_SPI_CLOCK_FREQ); + break; + } +} + +void n230_core_spi_core::restore_perif() +{ + change_perif(_last_perif); +} + +n230_ref_pll_ctrl::n230_ref_pll_ctrl(n230_core_spi_core::sptr spi) : + adf4001_ctrl(spi, fpga::ADF4001_SPI_SLAVE_NUM), + _spi(spi) +{ +} + +void n230_ref_pll_ctrl::set_lock_to_ext_ref(bool external) +{ + _spi->change_perif(n230_core_spi_core::PLL); + adf4001_ctrl::set_lock_to_ext_ref(external); + _spi->restore_perif(); +} + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_core_spi_core::sptr n230_core_spi_core::make( + uhd::wb_iface::sptr iface, n230_core_spi_core::perif_t default_perif) +{ + return sptr(new n230_core_spi_core(iface, default_perif)); +} + diff --git a/host/lib/usrp/n230/n230_cores.hpp b/host/lib/usrp/n230/n230_cores.hpp new file mode 100644 index 000000000..3f56c1889 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.hpp @@ -0,0 +1,71 @@ +// +// Copyright 2013-2014 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_N230_CORES_HPP +#define INCLUDED_N230_CORES_HPP + +#include "spi_core_3000.hpp" +#include "adf4001_ctrl.hpp" +#include <boost/thread/mutex.hpp> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_core_spi_core : boost::noncopyable, public uhd::spi_iface { + +public: + typedef boost::shared_ptr<n230_core_spi_core> sptr; + + enum perif_t { + CODEC, PLL + }; + + n230_core_spi_core(uhd::wb_iface::sptr iface, perif_t default_perif); + + virtual boost::uint32_t transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback); + + void change_perif(perif_t perif); + void restore_perif(); + + static sptr make(uhd::wb_iface::sptr iface, perif_t default_perif = CODEC); + +private: + spi_core_3000::sptr _spi_core; + perif_t _current_perif; + perif_t _last_perif; + boost::mutex _mutex; +}; + +class n230_ref_pll_ctrl : public adf4001_ctrl { +public: + typedef boost::shared_ptr<n230_ref_pll_ctrl> sptr; + + n230_ref_pll_ctrl(n230_core_spi_core::sptr spi); + void set_lock_to_ext_ref(bool external); + +private: + n230_core_spi_core::sptr _spi; +}; + + +}}} //namespace + +#endif /* INCLUDED_N230_CORES_HPP */ diff --git a/host/lib/usrp/n230/n230_defaults.h b/host/lib/usrp/n230/n230_defaults.h new file mode 100644 index 000000000..a25978585 --- /dev/null +++ b/host/lib/usrp/n230/n230_defaults.h @@ -0,0 +1,65 @@ +// +// Copyright 2014 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_N230_DEFAULTS_H +#define INCLUDED_N230_DEFAULTS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/transport/udp_constants.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { + +static const double DEFAULT_TICK_RATE = 46.08e6; +static const double MAX_TICK_RATE = 50e6; +static const double MIN_TICK_RATE = 1e6; + +static const double DEFAULT_TX_SAMP_RATE = 1.0e6; +static const double DEFAULT_RX_SAMP_RATE = 1.0e6; +static const double DEFAULT_DDC_FREQ = 0.0; +static const double DEFAULT_DUC_FREQ = 0.0; + +static const double DEFAULT_FE_GAIN = 0.0; +static const double DEFAULT_FE_FREQ = 1.0e9; +static const double DEFAULT_FE_BW = 56e6; + +static const std::string DEFAULT_TIME_SRC = "none"; +static const std::string DEFAULT_CLOCK_SRC = "internal"; + +static const size_t DEFAULT_FRAME_SIZE = 1500 - 20 - 8; //default ipv4 mtu - ipv4 header - udp header +static const size_t MAX_FRAME_SIZE = 8000; +static const size_t MIN_FRAME_SIZE = IP_PROTOCOL_MIN_MTU_SIZE; + +static const size_t DEFAULT_NUM_FRAMES = 32; + +//A 1MiB SRAM is shared between two radios so we allocate each +//radio 0.5MiB minus 8 packets worth of buffering to ensure +//that the FIFO does not overflow +static const size_t DEFAULT_SEND_BUFF_SIZE = 500*1024; +#if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) +static const size_t DEFAULT_RECV_BUFF_SIZE = 0x100000; //1Mib +#elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) +static const size_t DEFAULT_RECV_BUFF_SIZE = 0x2000000;//32MiB +#endif + +}}} //namespace + +#endif /* INCLUDED_N230_DEFAULTS_H */ diff --git a/host/lib/usrp/n230/n230_device_args.hpp b/host/lib/usrp/n230/n230_device_args.hpp new file mode 100644 index 000000000..014a6cd14 --- /dev/null +++ b/host/lib/usrp/n230/n230_device_args.hpp @@ -0,0 +1,128 @@ +// +// Copyright 2014 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_N230_DEV_ARGS_HPP +#define INCLUDED_N230_DEV_ARGS_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include "../common/constrained_device_args.hpp" +#include "n230_defaults.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_device_args_t : public constrained_device_args_t +{ +public: + enum loopback_mode_t { LOOPBACK_OFF=0, LOOPBACK_RADIO=1, LOOPBACK_CODEC=2 }; + + n230_device_args_t(): + _master_clock_rate("master_clock_rate", n230::DEFAULT_TICK_RATE), + _send_frame_size("send_frame_size", n230::DEFAULT_FRAME_SIZE), + _recv_frame_size("recv_frame_size", n230::DEFAULT_FRAME_SIZE), + _num_send_frames("num_send_frames", n230::DEFAULT_NUM_FRAMES), + _num_recv_frames("num_recv_frames", n230::DEFAULT_NUM_FRAMES), + _send_buff_size("send_buff_size", n230::DEFAULT_SEND_BUFF_SIZE), + _recv_buff_size("recv_buff_size", n230::DEFAULT_RECV_BUFF_SIZE), + _safe_mode("safe_mode", false), + _loopback_mode("loopback_mode", LOOPBACK_OFF, boost::assign::list_of("off")("radio")("codec")) + {} + + double get_master_clock_rate() const { + return _master_clock_rate.get(); + } + size_t get_send_frame_size() const { + return _send_frame_size.get(); + } + size_t get_recv_frame_size() const { + return _recv_frame_size.get(); + } + size_t get_num_send_frames() const { + return _num_send_frames.get(); + } + size_t get_num_recv_frames() const { + return _num_recv_frames.get(); + } + size_t get_send_buff_size() const { + return _send_buff_size.get(); + } + size_t get_recv_buff_size() const { + return _recv_buff_size.get(); + } + bool get_safe_mode() const { + return _safe_mode.get(); + } + loopback_mode_t get_loopback_mode() const { + return _loopback_mode.get(); + } + + inline virtual std::string to_string() const { + return _master_clock_rate.to_string() + ", " + + _send_frame_size.to_string() + ", " + + _recv_frame_size.to_string() + ", " + + _num_send_frames.to_string() + ", " + + _num_recv_frames.to_string() + ", " + + _send_buff_size.to_string() + ", " + + _recv_buff_size.to_string() + ", " + + _safe_mode.to_string() + ", " + + _loopback_mode.to_string(); + } +private: + virtual void _parse(const device_addr_t& dev_args) { + //Extract parameters from dev_args + if (dev_args.has_key(_master_clock_rate.key())) + _master_clock_rate.parse(dev_args[_master_clock_rate.key()]); + if (dev_args.has_key(_send_frame_size.key())) + _send_frame_size.parse(dev_args[_send_frame_size.key()]); + if (dev_args.has_key(_recv_frame_size.key())) + _recv_frame_size.parse(dev_args[_recv_frame_size.key()]); + if (dev_args.has_key(_num_send_frames.key())) + _num_send_frames.parse(dev_args[_num_send_frames.key()]); + if (dev_args.has_key(_num_recv_frames.key())) + _num_recv_frames.parse(dev_args[_num_recv_frames.key()]); + if (dev_args.has_key(_send_buff_size.key())) + _send_buff_size.parse(dev_args[_send_buff_size.key()]); + if (dev_args.has_key(_recv_buff_size.key())) + _recv_buff_size.parse(dev_args[_recv_buff_size.key()]); + if (dev_args.has_key(_safe_mode.key())) + _safe_mode.parse(dev_args[_safe_mode.key()]); + if (dev_args.has_key(_loopback_mode.key())) + _loopback_mode.parse(dev_args[_loopback_mode.key()], false /* assert invalid */); + + //Sanity check params + _enforce_range(_master_clock_rate, MIN_TICK_RATE, MAX_TICK_RATE); + _enforce_range(_send_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); + _enforce_range(_recv_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); + _enforce_range(_num_send_frames, (size_t)2, (size_t)UINT_MAX); + _enforce_range(_num_recv_frames, (size_t)2, (size_t)UINT_MAX); + } + + constrained_device_args_t::num_arg<double> _master_clock_rate; + constrained_device_args_t::num_arg<size_t> _send_frame_size; + constrained_device_args_t::num_arg<size_t> _recv_frame_size; + constrained_device_args_t::num_arg<size_t> _num_send_frames; + constrained_device_args_t::num_arg<size_t> _num_recv_frames; + constrained_device_args_t::num_arg<size_t> _send_buff_size; + constrained_device_args_t::num_arg<size_t> _recv_buff_size; + constrained_device_args_t::bool_arg _safe_mode; + constrained_device_args_t::enum_arg<loopback_mode_t> _loopback_mode; +}; + +}}} //namespace + +#endif //INCLUDED_N230_DEV_ARGS_HPP diff --git a/host/lib/usrp/n230/n230_eeprom.h b/host/lib/usrp/n230/n230_eeprom.h new file mode 100644 index 000000000..b6c2a0c76 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom.h @@ -0,0 +1,124 @@ +// +// Copyright 2014 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_N230_EEPROM_H +#define INCLUDED_N230_EEPROM_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define N230_NUM_ETH_PORTS 2 +#define N230_MAX_NUM_ETH_PORTS 2 + +#if (N230_NUM_ETH_PORTS > N230_MAX_NUM_ETH_PORTS) +#error +#endif + +#define N230_EEPROM_VER_MAJOR 1 +#define N230_EEPROM_VER_MINOR 1 +#define N230_EEPROM_SERIAL_LEN 9 +#define N230_EEPROM_NAME_LEN 32 + +typedef struct +{ + uint8_t mac_addr[6]; + uint8_t _pad[2]; + uint32_t subnet; + uint32_t ip_addr; +} n230_eth_eeprom_map_t; + +typedef struct +{ + //Data format version + uint16_t data_version_major; + uint16_t data_version_minor; + + //HW identification info + uint16_t hw_revision; + uint16_t hw_product; + uint8_t serial[N230_EEPROM_SERIAL_LEN]; + uint8_t _pad_serial; + uint16_t hw_revision_compat; + uint8_t _pad0[18 - (N230_EEPROM_SERIAL_LEN + 1)]; + + //Ethernet specific + uint32_t gateway; + n230_eth_eeprom_map_t eth_info[N230_MAX_NUM_ETH_PORTS]; + + //User specific + uint8_t user_name[N230_EEPROM_NAME_LEN]; +} n230_eeprom_map_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +/*! + * Read the eeprom and update caches. + * Returns true if read was successful. + * If the read was not successful then the cache is initialized with + * default values and marked as dirty. + */ +bool read_n230_eeprom(); + +/*! + * Write the contents of the cache to the eeprom. + * Returns true if write was successful. + */ +bool write_n230_eeprom(); + +/*! + * Returns the dirty state of the cache. + */ +bool is_n230_eeprom_cache_dirty(); + +/*! + * Returns a const pointer to the EEPROM map. + */ +const n230_eeprom_map_t* get_n230_const_eeprom_map(); + +/*! + * Returns the settings for the the 'iface'th ethernet interface + */ +const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface); + +/*! + * Returns a non-const pointer to the EEPROM map. Will mark the cache as dirty. + */ +n230_eeprom_map_t* get_n230_eeprom_map(); + +/*! + * FPGA Image operations + */ +inline void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes); + +inline bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes); + +inline bool erase_n230_fpga_image_sector(uint32_t offset); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_N230_EEPROM_H */ diff --git a/host/lib/usrp/n230/n230_eeprom_manager.cpp b/host/lib/usrp/n230/n230_eeprom_manager.cpp new file mode 100644 index 000000000..b19deb23a --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.cpp @@ -0,0 +1,207 @@ +// +// Copyright 2013-2014,2016 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 "n230_eeprom.h" +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/mac_addr.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include "n230_eeprom_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +const double n230_eeprom_manager::UDP_TIMEOUT_IN_SEC = 2.0; + +n230_eeprom_manager::n230_eeprom_manager(const std::string& addr): + _seq_num(0) +{ + _udp_xport = transport::udp_simple::make_connected( + addr, BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); + read_mb_eeprom(); +} + +static const std::string _bytes_to_string(const uint8_t* bytes, size_t max_len) +{ + std::string out; + for (size_t i = 0; i < max_len; i++) { + if (bytes[i] < 32 or bytes[i] > 127) return out; + out += bytes[i]; + } + return out; +} + +static void _string_to_bytes(const std::string &string, size_t max_len, uint8_t* buffer) +{ + byte_vector_t bytes; + const size_t len = std::min(string.size(), max_len); + for (size_t i = 0; i < len; i++){ + buffer[i] = string[i]; + } + if (len < max_len - 1) buffer[len] = '\0'; +} + +const mboard_eeprom_t& n230_eeprom_manager::read_mb_eeprom() +{ + boost::mutex::scoped_lock lock(_mutex); + + //Read EEPROM from device + _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); + const n230_eeprom_map_t* map_ptr = reinterpret_cast<const n230_eeprom_map_t*>(_response.data); + const n230_eeprom_map_t& map = *map_ptr; + + uint16_t ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + _mb_eeprom["product"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_product)); + _mb_eeprom["revision"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_revision)); + //The revision_compat field does not exist in version 1.0 + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision + if (ver_major == 1 and ver_minor == 0) { + _mb_eeprom["revision_compat"] = _mb_eeprom["revision"]; + } else { + _mb_eeprom["revision_compat"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_revision_compat)); + } + _mb_eeprom["serial"] = _bytes_to_string( + map.serial, N230_EEPROM_SERIAL_LEN); + + //Extract ethernet info + _mb_eeprom["gateway"] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.gateway)).to_string(); + for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { + const std::string n(1, i+'0'); + _mb_eeprom["ip-addr"+n] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.eth_info[i].ip_addr)).to_string(); + _mb_eeprom["subnet"+n] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.eth_info[i].subnet)).to_string(); + byte_vector_t mac_addr(map.eth_info[i].mac_addr, map.eth_info[i].mac_addr + 6); + _mb_eeprom["mac-addr"+n] = mac_addr_t::from_bytes(mac_addr).to_string(); + } + + _mb_eeprom["name"] = _bytes_to_string( + map.user_name, N230_EEPROM_NAME_LEN); + + return _mb_eeprom; +} + +void n230_eeprom_manager::write_mb_eeprom(const mboard_eeprom_t& eeprom) +{ + boost::mutex::scoped_lock lock(_mutex); + + _mb_eeprom = eeprom; + + n230_eeprom_map_t* map_ptr = reinterpret_cast<n230_eeprom_map_t*>(_request.data); + memset(map_ptr, 0xff, sizeof(n230_eeprom_map_t)); //Initialize to erased state + //Read EEPROM from device + _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); + memcpy(map_ptr, _response.data, sizeof(n230_eeprom_map_t)); + n230_eeprom_map_t& map = *map_ptr; + + // Automatic version upgrade handling + uint16_t old_ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t old_ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + //The revision_compat field does not exist for version 1.0 so force write it + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision for the upgrade + bool force_write_version_compat = (old_ver_major == 1 and old_ver_minor == 0); + + map.data_version_major = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MAJOR); + map.data_version_minor = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MINOR); + + if (_mb_eeprom.has_key("product")) { + map.hw_product = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["product"])); + } + if (_mb_eeprom.has_key("revision")) { + map.hw_revision = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision"])); + } + if (_mb_eeprom.has_key("revision_compat")) { + map.hw_revision_compat = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision_compat"])); + } else if (force_write_version_compat) { + map.hw_revision_compat = map.hw_revision; + } + if (_mb_eeprom.has_key("serial")) { + _string_to_bytes(_mb_eeprom["serial"], N230_EEPROM_SERIAL_LEN, map.serial); + } + + //Push ethernet info + if (_mb_eeprom.has_key("gateway")){ + map.gateway = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["gateway"]).to_ulong()); + } + for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { + const std::string n(1, i+'0'); + if (_mb_eeprom.has_key("ip-addr"+n)){ + map.eth_info[i].ip_addr = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["ip-addr"+n]).to_ulong()); + } + if (_mb_eeprom.has_key("subnet"+n)){ + map.eth_info[i].subnet = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["subnet"+n]).to_ulong()); + } + if (_mb_eeprom.has_key("mac-addr"+n)) { + byte_vector_t mac_addr = mac_addr_t::from_string(_mb_eeprom["mac-addr"+n]).to_bytes(); + std::copy(mac_addr.begin(), mac_addr.end(), map.eth_info[i].mac_addr); + } + } + //store the name + if (_mb_eeprom.has_key("name")) { + _string_to_bytes(_mb_eeprom["name"], N230_EEPROM_NAME_LEN, map.user_name); + } + + //Write EEPROM to device + _transact(N230_FLASH_COMM_CMD_WRITE_NV_DATA); +} + +void n230_eeprom_manager::_transact(const boost::uint32_t command) +{ + //Load request struct + _request.flags = uhd::htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK | command); + _request.seq = uhd::htonx<boost::uint32_t>(_seq_num++); + + //Send request + _flush_xport(); + _udp_xport->send(boost::asio::buffer(&_request, sizeof(_request))); + + //Recv reply + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&_response, sizeof(_response)), UDP_TIMEOUT_IN_SEC); + if (nbytes == 0) throw uhd::io_error("n230_eeprom_manager::_transact failure"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(_response.flags); + UHD_ASSERT_THROW(nbytes == sizeof(_response)); + UHD_ASSERT_THROW(_response.seq == _request.seq); + UHD_ASSERT_THROW(flags & command); +} + +void n230_eeprom_manager::_flush_xport() +{ + char buff[sizeof(n230_flash_prog_t)] = {}; + while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { + /*NOP*/ + } +} + +}}}; //namespace diff --git a/host/lib/usrp/n230/n230_eeprom_manager.hpp b/host/lib/usrp/n230/n230_eeprom_manager.hpp new file mode 100644 index 000000000..cc5aee9f3 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.hpp @@ -0,0 +1,58 @@ +// +// Copyright 2013-2014 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_N230_EEPROM_MANAGER_HPP +#define INCLUDED_N230_EEPROM_MANAGER_HPP + +#include <boost/thread/mutex.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include "n230_fw_host_iface.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_eeprom_manager : boost::noncopyable +{ +public: + n230_eeprom_manager(const std::string& addr); + + const mboard_eeprom_t& read_mb_eeprom(); + void write_mb_eeprom(const mboard_eeprom_t& eeprom); + + inline const mboard_eeprom_t& get_mb_eeprom() { + return _mb_eeprom; + } + +private: //Functions + void _transact(const boost::uint32_t command); + void _flush_xport(); + +private: //Members + mboard_eeprom_t _mb_eeprom; + transport::udp_simple::sptr _udp_xport; + n230_flash_prog_t _request; + n230_flash_prog_t _response; + boost::uint32_t _seq_num; + boost::mutex _mutex; + + static const double UDP_TIMEOUT_IN_SEC; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_EEPROM_MANAGER_HPP */ diff --git a/host/lib/usrp/n230/n230_fpga_defs.h b/host/lib/usrp/n230/n230_fpga_defs.h new file mode 100644 index 000000000..3aa96643f --- /dev/null +++ b/host/lib/usrp/n230/n230_fpga_defs.h @@ -0,0 +1,207 @@ +// +// Copyright 2014 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_N230_FPGA_DEFS_H +#define INCLUDED_N230_FPGA_DEFS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/utils/soft_register.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { +namespace fpga { + +static inline uint32_t sr_addr(uint32_t offset) { + return (offset*4); +} + +static inline uint32_t rb_addr(uint32_t offset) { + return (offset*8); +} + +static const size_t NUM_RADIOS = 2; +static const double BUS_CLK_RATE = 80e6; + +/******************************************************************* + * CVITA Routing + *******************************************************************/ +static const uint32_t CVITA_UDP_PORT = 49153; +static const bool CVITA_BIG_ENDIAN = true; + +enum xb_endpoint_t { + N230_XB_DST_E0 = 0, + N230_XB_DST_E1 = 1, + N230_XB_DST_R0 = 2, + N230_XB_DST_R1 = 3, + N230_XB_DST_GCTRL = 4, + N230_XB_DST_UART = 5 +}; + +static const boost::uint8_t RADIO_CTRL_SUFFIX = 0x00; +static const boost::uint8_t RADIO_FC_SUFFIX = 0x01; +static const boost::uint8_t RADIO_DATA_SUFFIX = 0x02; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_CORE_RADIO_CONTROL = 3; +static const uint32_t SR_CORE_LOOPBACK = 4; +static const uint32_t SR_CORE_BIST1 = 5; +static const uint32_t SR_CORE_BIST2 = 6; +static const uint32_t SR_CORE_SPI = 8; +static const uint32_t SR_CORE_MISC = 16; +static const uint32_t SR_CORE_DATA_DELAY = 17; +static const uint32_t SR_CORE_CLK_DELAY = 18; +static const uint32_t SR_CORE_COMPAT = 24; +static const uint32_t SR_CORE_READBACK = 32; +static const uint32_t SR_CORE_GPSDO_ST = 40; +static const uint32_t SR_CORE_PPS_SEL = 48; +static const uint32_t SR_CORE_MS0_GPIO = 50; +static const uint32_t SR_CORE_MS1_GPIO = 58; + +static const uint32_t RB_CORE_SIGNATUE = 0; +static const uint32_t RB_CORE_SPI = 1; +static const uint32_t RB_CORE_STATUS = 2; +static const uint32_t RB_CORE_BIST = 3; +static const uint32_t RB_CORE_VERSION_HASH = 4; +static const uint32_t RB_CORE_MS0_GPIO = 5; +static const uint32_t RB_CORE_MS1_GPIO = 6; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_RADIO_SPI = 8; +static const uint32_t SR_RADIO_ATR = 12; +static const uint32_t SR_RADIO_SW_RST = 20; +static const uint32_t SR_RADIO_TEST = 21; +static const uint32_t SR_RADIO_CODEC_IDLE = 22; +static const uint32_t SR_RADIO_READBACK = 32; +static const uint32_t SR_RADIO_TX_CTRL = 64; +static const uint32_t SR_RADIO_RX_CTRL = 96; +static const uint32_t SR_RADIO_RX_DSP = 144; +static const uint32_t SR_RADIO_TX_DSP = 184; +static const uint32_t SR_RADIO_TIME = 128; +static const uint32_t SR_RADIO_RX_FMT = 136; +static const uint32_t SR_RADIO_TX_FMT = 138; +static const uint32_t SR_RADIO_USER_SR = 253; + +static const uint32_t RB_RADIO_TEST = 0; +static const uint32_t RB_RADIO_TIME_NOW = 1; +static const uint32_t RB_RADIO_TIME_PPS = 2; +static const uint32_t RB_RADIO_CODEC_DATA = 3; +static const uint32_t RB_RADIO_DEBUG = 4; +static const uint32_t RB_RADIO_FRAMER = 5; +static const uint32_t SR_RADIO_USER_RB = 7; + +static const uint32_t AD9361_SPI_SLAVE_NUM = 0x1; +static const uint32_t ADF4001_SPI_SLAVE_NUM = 0x2; + +static const uint32_t RB_N230_PRODUCT_ID = 1; +static const uint32_t RB_N230_COMPAT_MAJOR = 0x20; +static const uint32_t RB_N230_COMPAT_SAFE = 0xC0; + +/******************************************************************* + * Codec Interface Specific + *******************************************************************/ + +// Matches delay setting of 0x00 in AD9361 register 0x006 +static const uint32_t CODEC_DATA_DELAY = 0; +static const uint32_t CODEC_CLK_DELAY = 16; + +//This number must be < 46.08MHz to make sure we don't +//violate timing for radio_clk. It is only used during +//initialization so the exact value does not matter. +static const double CODEC_DEFAULT_CLK_RATE = 40e6; + +/******************************************************************* + * Link Specific + *******************************************************************/ +static const double N230_LINK_RATE_BPS = 1e9/8; + +/******************************************************************* + * GPSDO + *******************************************************************/ +static const uint32_t GPSDO_UART_BAUDRATE = 115200; +static const uint32_t GPSDO_ST_ABSENT = 0x83; +/******************************************************************* + * Register Objects + *******************************************************************/ +class core_radio_ctrl_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(MIMO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(CODEC_ARST, /*width*/ 1, /*shift*/ 1); //[1] + + core_radio_ctrl_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_RADIO_CONTROL)) + { + //Initial values + set(CODEC_ARST, 0); + set(MIMO, 1); //MIMO always ON for now + } +}; + +class core_misc_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(REF_SEL, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_C, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_B, /*width*/ 1, /*shift*/ 2); //[2] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_A, /*width*/ 1, /*shift*/ 3); //[3] + UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_B, /*width*/ 1, /*shift*/ 4); //[4] + UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_A, /*width*/ 1, /*shift*/ 5); //[5] + + core_misc_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_MISC)) + { + //Initial values + set(REF_SEL, 0); + set(RX_BANDSEL_C, 0); + set(RX_BANDSEL_B, 0); + set(RX_BANDSEL_A, 0); + set(TX_BANDSEL_B, 0); + set(TX_BANDSEL_A, 0); + } +}; + +class core_pps_sel_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(EXT_PPS_EN, /*width*/ 1, /*shift*/ 0); //[0] + + core_pps_sel_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_PPS_SEL)) + { + //Initial values + set(EXT_PPS_EN, 0); + } +}; + +class core_status_reg_t : public soft_reg64_ro_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(REF_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(GPSDO_STATUS, /*width*/ 8, /*shift*/ 32); //[32:39] + + core_status_reg_t(): + soft_reg64_ro_t(fpga::rb_addr(fpga::RB_CORE_STATUS)) + { } +}; + +}}}} //namespace + +#endif /* INCLUDED_N230_FPGA_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.cpp b/host/lib/usrp/n230/n230_frontend_ctrl.cpp new file mode 100644 index 000000000..e0820d9b2 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2013-2014,2016 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 "n230_frontend_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +/* ATR Control Bits */ +static const boost::uint32_t TX_ENABLE = (1 << 7); +static const boost::uint32_t SFDX_RX = (1 << 6); +static const boost::uint32_t SFDX_TX = (1 << 5); +static const boost::uint32_t SRX_RX = (1 << 4); +static const boost::uint32_t SRX_TX = (1 << 3); +static const boost::uint32_t LED_RX = (1 << 2); +static const boost::uint32_t LED_TXRX_RX = (1 << 1); +static const boost::uint32_t LED_TXRX_TX = (1 << 0); + +/* ATR State Definitions. */ +static const boost::uint32_t STATE_OFF = 0x00; +static const boost::uint32_t STATE_RX_RX2 = (SFDX_RX + | SFDX_TX + | LED_RX); +static const boost::uint32_t STATE_RX_TXRX = (SRX_RX + | SRX_TX + | LED_TXRX_RX); +static const boost::uint32_t STATE_FDX_TXRX = (TX_ENABLE + | SFDX_RX + | SFDX_TX + | LED_TXRX_TX + | LED_RX); +static const boost::uint32_t STATE_TX_TXRX = (TX_ENABLE + | SFDX_RX + | SFDX_TX + | LED_TXRX_TX); + +using namespace uhd::usrp; + +class n230_frontend_ctrl_impl : public n230_frontend_ctrl +{ +public: + n230_frontend_ctrl_impl( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores + ): _core_ctrl(core_ctrl), + _codec_ctrl(codec_ctrl), + _gpio_cores(gpio_cores), + _core_misc_reg(core_misc_reg) + { + } + + virtual ~n230_frontend_ctrl_impl() + { + } + + void set_antenna_sel(const size_t which, const std::string &ant) + { + if (ant != "TX/RX" and ant != "RX2") + throw uhd::value_error("n230: unknown RX antenna option: " + ant); + + _fe_states[which].rx_ant = ant; + _flush_atr_state(); + } + + void set_stream_state(const fe_state_t fe0_state_, const fe_state_t fe1_state_) + { + //Update soft-state + _fe_states[0].state = fe0_state_; + _fe_states[1].state = fe1_state_; + + const fe_state_t fe0_state = _fe_states[0].state; + const fe_state_t fe1_state = (_gpio_cores.size() > 1) ? _fe_states[1].state : NONE_STREAMING; + + const size_t num_tx = (_is_tx(fe0_state) ? 1 : 0) + (_is_tx(fe1_state) ? 1 : 0); + const size_t num_rx = (_is_rx(fe0_state) ? 1 : 0) + (_is_rx(fe1_state) ? 1 : 0); + + //setup the active chains in the codec + if ((num_rx + num_tx) == 0) { + _codec_ctrl->set_active_chains( + true, false, + true, false); //enable something + } else { + _codec_ctrl->set_active_chains( + _is_tx(fe0_state), _is_tx(fe1_state), + _is_rx(fe0_state), _is_rx(fe1_state)); + } + + _core_misc_reg.flush(); + //atrs change based on enables + _flush_atr_state(); + } + + + void set_stream_state(const size_t which, const fe_state_t state) + { + if (which == 0) { + set_stream_state(state, _fe_states[1].state); + } else if (which == 1) { + set_stream_state(_fe_states[0].state, state); + } else { + throw uhd::value_error( + str(boost::format("n230: unknown stream index option: %d") % which) + ); + } + } + + void set_bandsel(const std::string& which, double freq) + { + using namespace n230::fpga; + + if(which[0] == 'R') { + if(freq < 2.2e9) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 1); + } else if((freq >= 2.2e9) && (freq < 4e9)) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 1); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); + } else if((freq >= 4e9) && (freq <= 6e9)) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 1); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else if(which[0] == 'T') { + if(freq < 2.5e9) { + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 1); + } else if((freq >= 2.5e9) && (freq <= 6e9)) { + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 1); + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 0); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + + _core_misc_reg.flush(); + } + + void set_self_test_mode(self_test_mode_t mode) + { + switch (mode) { + case LOOPBACK_RADIO: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x1); + } break; + case LOOPBACK_CODEC: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); + _codec_ctrl->data_port_loopback(true); + } break; + //Default = disable + default: + case LOOPBACK_DISABLED: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); + _codec_ctrl->data_port_loopback(false); + } break; + } + } + +private: + void _flush_atr_state() + { + for (size_t i = 0; i < _gpio_cores.size(); i++) { + const fe_state_cache_t& fe_state_cache = _fe_states[i]; + const bool enb_rx = _is_rx(fe_state_cache.state); + const bool enb_tx = _is_tx(fe_state_cache.state); + const bool is_rx2 = (fe_state_cache.rx_ant == "RX2"); + const size_t rxonly = (enb_rx)? ((is_rx2)? STATE_RX_RX2 : STATE_RX_TXRX) : STATE_OFF; + const size_t txonly = (enb_tx)? (STATE_TX_TXRX) : STATE_OFF; + size_t fd = STATE_OFF; + if (enb_rx and enb_tx) fd = STATE_FDX_TXRX; + if (enb_rx and not enb_tx) fd = rxonly; + if (not enb_rx and enb_tx) fd = txonly; + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_IDLE, STATE_OFF); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, rxonly); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, txonly); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, fd); + } + } + + inline static bool _is_tx(const fe_state_t state) + { + return state == TX_STREAMING || state == TXRX_STREAMING; + } + + inline static bool _is_rx(const fe_state_t state) + { + return state == RX_STREAMING || state == TXRX_STREAMING; + } + +private: + struct fe_state_cache_t { + fe_state_cache_t() : state(NONE_STREAMING), rx_ant("RX2") + {} + fe_state_t state; + std::string rx_ant; + }; + + radio_ctrl_core_3000::sptr _core_ctrl; + ad9361_ctrl::sptr _codec_ctrl; + std::vector<gpio_atr::gpio_atr_3000::sptr> _gpio_cores; + fpga::core_misc_reg_t& _core_misc_reg; + uhd::dict<size_t, fe_state_cache_t> _fe_states; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; + +n230_frontend_ctrl::sptr n230_frontend_ctrl::make( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores) +{ + return sptr(new n230_frontend_ctrl_impl(core_ctrl, core_misc_reg, codec_ctrl, gpio_cores)); +} + diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.hpp b/host/lib/usrp/n230/n230_frontend_ctrl.hpp new file mode 100644 index 000000000..377d23ba8 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2013-2014 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_N230_FRONTEND_CTRL_HPP +#define INCLUDED_N230_FRONTEND_CTRL_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "gpio_atr_3000.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +enum fe_state_t { + NONE_STREAMING, TX_STREAMING, RX_STREAMING, TXRX_STREAMING +}; + +enum self_test_mode_t { + LOOPBACK_DISABLED, LOOPBACK_RADIO, LOOPBACK_CODEC +}; + + +class n230_frontend_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<n230_frontend_ctrl> sptr; + + static sptr make( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores); + + virtual ~n230_frontend_ctrl() {} + + virtual void set_antenna_sel( + const size_t which, + const std::string &ant) = 0; + + virtual void set_stream_state( + const size_t which, + const fe_state_t state) = 0; + + virtual void set_stream_state( + const fe_state_t fe0_state, + const fe_state_t fe1_state) = 0; + + virtual void set_bandsel( + const std::string& which, + double freq) = 0; + + virtual void set_self_test_mode( + self_test_mode_t mode) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_FRONTEND_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_fw_defs.h b/host/lib/usrp/n230/n230_fw_defs.h new file mode 100644 index 000000000..fbdc67ebb --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_defs.h @@ -0,0 +1,137 @@ +// +// Copyright 2014 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_N230_FW_DEFS_H +#define INCLUDED_N230_FW_DEFS_H + +#include <stdint.h> + +/*! + * Constants specific to N230 firmware. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + * However, if it is included from within the host code, + * it will be namespaced appropriately + */ +#ifdef __cplusplus +namespace uhd { +namespace usrp { +namespace n230 { +namespace fw { +#endif + +static inline uint32_t reg_addr(uint32_t base, uint32_t offset) { + return ((base) + (offset)*4); +} + +/******************************************************************* + * Global + *******************************************************************/ +static const uint32_t CPU_CLOCK_FREQ = 80000000; +static const uint32_t PER_MILLISEC_CRON_JOBID = 0; +static const uint32_t PER_SECOND_CRON_JOBID = 1; + +/******************************************************************* + * Wishbone slave addresses + *******************************************************************/ +static const uint32_t WB_MAIN_RAM_BASE = 0x0000; +static const uint32_t WB_PKT_RAM_BASE = 0x8000; +static const uint32_t WB_SBRB_BASE = 0xa000; +static const uint32_t WB_SPI_FLASH_BASE = 0xb000; +static const uint32_t WB_ETH0_MAC_BASE = 0xc000; +static const uint32_t WB_ETH1_MAC_BASE = 0xd000; +static const uint32_t WB_XB_SBRB_BASE = 0xe000; +static const uint32_t WB_ETH0_I2C_BASE = 0xf600; +static const uint32_t WB_ETH1_I2C_BASE = 0xf700; +static const uint32_t WB_DBG_UART_BASE = 0xf900; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_ZPU_SW_RST = 0; +static const uint32_t SR_ZPU_BOOT_DONE = 1; +static const uint32_t SR_ZPU_LEDS = 2; +static const uint32_t SR_ZPU_XB_LOCAL = 4; +static const uint32_t SR_ZPU_SFP_CTRL0 = 16; +static const uint32_t SR_ZPU_SFP_CTRL1 = 17; +static const uint32_t SR_ZPU_ETHINT0 = 64; +static const uint32_t SR_ZPU_ETHINT1 = 80; + +static const uint32_t SR_ZPU_SW_RST_NONE = 0x0; +static const uint32_t SR_ZPU_SW_RST_PHY = 0x1; +static const uint32_t SR_ZPU_SW_RST_RADIO = 0x2; + +/******************************************************************* + * Readback addresses + *******************************************************************/ +static const uint32_t RB_ZPU_COMPAT = 0; +static const uint32_t RB_ZPU_COUNTER = 1; +static const uint32_t RB_ZPU_SFP_STATUS0 = 2; +static const uint32_t RB_ZPU_SFP_STATUS1 = 3; +static const uint32_t RB_ZPU_ETH0_PKT_CNT = 6; +static const uint32_t RB_ZPU_ETH1_PKT_CNT = 7; + +/******************************************************************* + * Ethernet + *******************************************************************/ +static const uint32_t WB_PKT_RAM_CTRL_OFFSET = 0x1FFC; + +static const uint32_t SR_ZPU_ETHINT_FRAMER_BASE = 0; +static const uint32_t SR_ZPU_ETHINT_DISPATCHER_BASE = 8; + +//Eth framer constants +static const uint32_t ETH_FRAMER_SRC_MAC_HI = 0; +static const uint32_t ETH_FRAMER_SRC_MAC_LO = 1; +static const uint32_t ETH_FRAMER_SRC_IP_ADDR = 2; +static const uint32_t ETH_FRAMER_SRC_UDP_PORT = 3; +static const uint32_t ETH_FRAMER_DST_RAM_ADDR = 4; +static const uint32_t ETH_FRAMER_DST_IP_ADDR = 5; +static const uint32_t ETH_FRAMER_DST_UDP_MAC = 6; +static const uint32_t ETH_FRAMER_DST_MAC_LO = 7; + +/******************************************************************* + * CODEC + *******************************************************************/ +static const uint32_t CODEC_SPI_CLOCK_FREQ = 4000000; //4MHz +static const uint32_t ADF4001_SPI_CLOCK_FREQ = 200000; //200kHz + +/******************************************************************* + * UART + *******************************************************************/ +static const uint32_t DBG_UART_BAUD = 115200; + +/******************************************************************* + * Build Compatability Numbers + *******************************************************************/ +static const uint8_t PRODUCT_NUM = 0x01; +static const uint8_t COMPAT_MAJOR = 0x00; +static const uint16_t COMPAT_MINOR = 0x0000; + +static inline uint8_t get_prod_num(uint32_t compat_reg) { + return (compat_reg >> 24) & 0xFF; +} +static inline uint8_t get_compat_major(uint32_t compat_reg) { + return (compat_reg >> 16) & 0xFF; +} +static inline uint8_t get_compat_minor(uint32_t compat_reg) { + return compat_reg & 0xFFFF; +} + +#ifdef __cplusplus +}}}} //namespace +#endif +#endif /* INCLUDED_N230_FW_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_fw_host_iface.h b/host/lib/usrp/n230/n230_fw_host_iface.h new file mode 100644 index 000000000..0391af0d9 --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_host_iface.h @@ -0,0 +1,128 @@ +// +// Copyright 2014 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_N230_FW_HOST_IFACE_H +#define INCLUDED_N230_FW_HOST_IFACE_H + +#include <stdint.h> + +/*! + * Structs and constants for N230 communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------- +// Ethernet related +// +#define N230_DEFAULT_ETH0_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0xff} +#define N230_DEFAULT_ETH1_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0x33} +#define N230_DEFAULT_ETH0_IP (192 << 24 | 168 << 16 | 10 << 8 | 2 << 0) +#define N230_DEFAULT_ETH1_IP (192 << 24 | 168 << 16 | 20 << 8 | 2 << 0) +#define N230_DEFAULT_ETH0_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_ETH1_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_GATEWAY (192 << 24 | 168 << 16 | 10 << 8 | 1 << 0) + +#define N230_FW_COMMS_UDP_PORT 49152 +#define N230_FW_COMMS_CVITA_PORT 49153 +#define N230_FW_COMMS_FLASH_PROG_PORT 49154 +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Memory shared with host +// +#define N230_FW_HOST_SHMEM_BASE_ADDR 0x10000 +#define N230_FW_HOST_SHMEM_RW_BASE_ADDR 0x1000C +#define N230_FW_HOST_SHMEM_NUM_WORDS (sizeof(n230_host_shared_mem_data_t)/sizeof(uint32_t)) + +#define N230_FW_HOST_SHMEM_MAX_ADDR \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((N230_FW_HOST_SHMEM_NUM_WORDS - 1) * sizeof(uint32_t))) + +#define N230_FW_HOST_SHMEM_OFFSET(member) \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((uint32_t)offsetof(n230_host_shared_mem_data_t, member))) + +//The shared memory block can only be accessed on 32-bit boundaries +typedef struct { //All fields must be 32-bit wide to avoid packing directives + //Read-Only fields (N230_FW_HOST_SHMEM_BASE_ADDR) + uint32_t fw_compat_num; //Compat number must be at offset 0 + uint32_t fw_version_hash; + uint32_t claim_status; + + //Read-Write fields (N230_FW_HOST_SHMEM_RW_BASE_ADDR) + uint32_t scratch; + uint32_t claim_time; + uint32_t claim_src; +} n230_host_shared_mem_data_t; + +typedef union +{ + uint32_t buff[N230_FW_HOST_SHMEM_NUM_WORDS]; + n230_host_shared_mem_data_t data; +} n230_host_shared_mem_t; + +#define N230_FW_PRODUCT_ID 1 +#define N230_FW_COMPAT_NUM_MAJOR 32 +#define N230_FW_COMPAT_NUM_MINOR 0 +#define N230_FW_COMPAT_NUM (((N230_FW_COMPAT_NUM_MAJOR & 0xFF) << 16) | (N230_FW_COMPAT_NUM_MINOR & 0xFFFF)) +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Flash read-write interface for host +// +#define N230_FLASH_COMM_FLAGS_ACK 0x00000001 +#define N230_FLASH_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define N230_FLASH_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define N230_FLASH_COMM_CMD_READ_NV_DATA 0x00000010 +#define N230_FLASH_COMM_CMD_WRITE_NV_DATA 0x00000020 +#define N230_FLASH_COMM_CMD_READ_FPGA 0x00000030 +#define N230_FLASH_COMM_CMD_WRITE_FPGA 0x00000040 +#define N230_FLASH_COMM_CMD_ERASE_FPGA 0x00000050 + +#define N230_FLASH_COMM_ERR_PKT_ERROR 0x80000000 +#define N230_FLASH_COMM_ERR_CMD_ERROR 0x40000000 +#define N230_FLASH_COMM_ERR_SIZE_ERROR 0x20000000 + +#define N230_FLASH_COMM_MAX_PAYLOAD_SIZE 128 + +typedef struct +{ + uint32_t flags; + uint32_t seq; + uint32_t offset; + uint32_t size; + uint8_t data[N230_FLASH_COMM_MAX_PAYLOAD_SIZE]; +} n230_flash_prog_t; +// +//-------------------------------------------------- + +#define N230_HW_REVISION_COMPAT 1 +#define N230_HW_REVISION_MIN 1 + + +#define N230_CLAIMER_TIMEOUT_IN_MS 2000 + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_N230_FW_HOST_IFACE_H */ diff --git a/host/lib/usrp/n230/n230_image_loader.cpp b/host/lib/usrp/n230/n230_image_loader.cpp new file mode 100644 index 000000000..9dd4a252d --- /dev/null +++ b/host/lib/usrp/n230/n230_image_loader.cpp @@ -0,0 +1,209 @@ +// +// Copyright 2016 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 <fstream> +#include <algorithm> +#include <uhd/image_loader.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include "n230_fw_host_iface.h" +#include "n230_impl.hpp" + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +struct xil_bitfile_hdr_t { + xil_bitfile_hdr_t(): + valid(false), userid(0), product(""), + fpga(""), timestamp(""), filesize(0) + {} + + bool valid; + boost::uint32_t userid; + std::string product; + std::string fpga; + std::string timestamp; + boost::uint32_t filesize; +}; + +static inline boost::uint16_t _to_uint16(boost::uint8_t* buf) { + return (static_cast<boost::uint16_t>(buf[0]) << 8) | + (static_cast<boost::uint16_t>(buf[1]) << 0); +} + +static inline boost::uint32_t _to_uint32(boost::uint8_t* buf) { + return (static_cast<boost::uint32_t>(buf[0]) << 24) | + (static_cast<boost::uint32_t>(buf[1]) << 16) | + (static_cast<boost::uint32_t>(buf[2]) << 8) | + (static_cast<boost::uint32_t>(buf[3]) << 0); +} + +static void _parse_bitfile_header(const std::string& filepath, xil_bitfile_hdr_t& hdr) { + // Read header into memory + std::ifstream img_file(filepath.c_str(), std::ios::binary); + static const size_t MAX_HDR_SIZE = 1024; + boost::scoped_array<char> hdr_buf(new char[MAX_HDR_SIZE]); + img_file.seekg(0, std::ios::beg); + img_file.read(hdr_buf.get(), MAX_HDR_SIZE); + img_file.close(); + + //Parse header + size_t ptr = 0; + boost::uint8_t* buf = reinterpret_cast<boost::uint8_t*>(hdr_buf.get()); //Shortcut + + boost::uint8_t signature[10] = {0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0}; + if (memcmp(buf, signature, 10) == 0) { //Validate signature + ptr += _to_uint16(buf + ptr) + 2; + ptr += _to_uint16(buf + ptr) + 1; + + std::string fields[4]; + for (size_t i = 0; i < 4; i++) { + size_t key = buf[ptr++] - 'a'; + boost::uint16_t len = _to_uint16(buf + ptr); ptr += 2; + fields[key] = std::string(reinterpret_cast<char*>(buf + ptr), size_t(len)); ptr += len; + } + + hdr.filesize = _to_uint32(buf + ++ptr); ptr += 4; + hdr.fpga = fields[1]; + hdr.timestamp = fields[2] + std::string(" ") + fields[3]; + + std::vector<std::string> tokens; + boost::split(tokens, fields[0], boost::is_any_of(";")); + if (tokens.size() == 3) { + hdr.product = tokens[0]; + std::vector<std::string> uidtokens; + boost::split(uidtokens, tokens[1], boost::is_any_of("=")); + if (uidtokens.size() == 2 and uidtokens[0] == "UserID") { + std::stringstream stream; + stream << uidtokens[1]; + stream >> std::hex >> hdr.userid; + hdr.valid = true; + } + } + } +} + +static size_t _send_and_recv( + udp_simple::sptr xport, + n230_flash_prog_t& out, n230_flash_prog_t& in) +{ + static boost::uint32_t seqno = 0; + out.seq = htonx<boost::uint32_t>(++seqno); + xport->send(boost::asio::buffer(&out, sizeof(n230_flash_prog_t))); + size_t len = xport->recv(boost::asio::buffer(&in, udp_simple::mtu), 0.5); + if (len != sizeof(n230_flash_prog_t) or ntohx<boost::uint32_t>(in.seq) != seqno) { + throw uhd::io_error("Error communicating with the device."); + } + return len; +} + + +static bool n230_image_loader(const image_loader::image_loader_args_t &loader_args){ + // Run discovery routine and ensure that exactly one N230 is specified + device_addrs_t devs = usrp::n230::n230_impl::n230_find(loader_args.args); + if (devs.size() == 0 or !loader_args.load_fpga) return false; + if (devs.size() > 1) { + throw uhd::runtime_error("Multiple devices match the specified args. To avoid accidentally updating the " + "wrong device, please narrow the search by specifying a unique \"addr\" argument."); + } + device_addr_t dev = devs[0]; + + // Sanity check the specified bitfile + std::string fpga_img_path = loader_args.fpga_path; + bool fpga_path_specified = !loader_args.fpga_path.empty(); + if (not fpga_path_specified) { + fpga_img_path = ( + fs::path(uhd::get_pkg_path()) / "share" / "uhd" / "images" / "usrp_n230_fpga.bit" + ).string(); + } + + if (not boost::filesystem::exists(fpga_img_path)) { + if (fpga_path_specified) { + throw uhd::runtime_error(str(boost::format("The file \"%s\" does not exist.") % fpga_img_path)); + } else { + throw uhd::runtime_error(str(boost::format( + "Could not find the default FPGA image: %s.\n" + "Either specify the --fpga-path argument or download the latest prebuilt images:\n" + "%s\n") + % fpga_img_path % print_utility_error("uhd_images_downloader.py"))); + } + } + xil_bitfile_hdr_t hdr; + _parse_bitfile_header(fpga_img_path, hdr); + + // Create a UDP communication link + udp_simple::sptr udp_xport = + udp_simple::make_connected(dev["addr"],BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); + + if (hdr.valid and hdr.product == "n230") { + if (hdr.userid != 0x5AFE0000) { + std::cout << boost::format("Unit: USRP N230 (%s, %s)\n-- FPGA Image: %s\n") + % dev["addr"] % dev["serial"] % fpga_img_path; + + // Write image + std::ifstream image(fpga_img_path.c_str(), std::ios::binary); + size_t image_size = boost::filesystem::file_size(fpga_img_path); + + static const size_t SECTOR_SIZE = 65536; + static const size_t IMAGE_BASE = 0x400000; + + n230_flash_prog_t out, in; + size_t bytes_written = 0; + while (bytes_written < image_size) { + size_t payload_size = std::min<size_t>(image_size - bytes_written, N230_FLASH_COMM_MAX_PAYLOAD_SIZE); + if (bytes_written % SECTOR_SIZE == 0) { + out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA); + out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); + out.size = htonx<boost::uint32_t>(payload_size); + _send_and_recv(udp_xport, out, in); + } + out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA); + out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); + out.size = htonx<boost::uint32_t>(payload_size); + image.read((char*)out.data, payload_size); + _send_and_recv(udp_xport, out, in); + bytes_written += ntohx<boost::uint32_t>(in.size); + std::cout << boost::format("\r-- Loading FPGA image: %d%%") + % (int(double(bytes_written) / double(image_size) * 100.0)) + << std::flush; + } + std::cout << std::endl << "FPGA image loaded successfully." << std::endl; + std::cout << std::endl << "Power-cycle the device to run the image." << std::endl; + return true; + } else { + throw uhd::runtime_error("This utility cannot burn a failsafe image!"); + } + } else { + throw uhd::runtime_error(str(boost::format("The file at path \"%s\" is not a valid USRP N230 FPGA image.") + % fpga_img_path)); + } +} + +UHD_STATIC_BLOCK(register_n230_image_loader){ + std::string recovery_instructions = "Aborting. Your USRP N230 device will likely boot in safe mode.\n" + "Please re-run this command with the additional \"safe_mode\" device argument\n" + "to recover your device."; + + image_loader::register_image_loader("n230", n230_image_loader, recovery_instructions); +} diff --git a/host/lib/usrp/n230/n230_impl.cpp b/host/lib/usrp/n230/n230_impl.cpp new file mode 100644 index 000000000..5e8aa37b7 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.cpp @@ -0,0 +1,591 @@ +// +// Copyright 2014 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 "n230_impl.hpp" + +#include "usrp3_fw_ctrl_iface.hpp" +#include "validate_subdev_spec.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/make_shared.hpp> + +#include "../common/fw_comm_protocol.h" +#include "n230_defaults.h" +#include "n230_fpga_defs.h" +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.h" + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; +namespace asio = boost::asio; + +//---------------------------------------------------------- +// Static device registration with framework +//---------------------------------------------------------- +UHD_STATIC_BLOCK(register_n230_device) +{ + device::register_device(&n230_impl::n230_find, &n230_impl::n230_make, device::USRP); +} + +//---------------------------------------------------------- +// Device discovery +//---------------------------------------------------------- +uhd::device_addrs_t n230_impl::n230_find(const uhd::device_addr_t &multi_dev_hint) +{ + //handle the multi-device discovery + device_addrs_t hints = separate_device_addr(multi_dev_hint); + if (hints.size() > 1){ + device_addrs_t found_devices; + std::string error_msg; + BOOST_FOREACH(const device_addr_t &hint_i, hints){ + device_addrs_t found_devices_i = n230_find(hint_i); + if (found_devices_i.size() != 1) error_msg += str(boost::format( + "Could not resolve device hint \"%s\" to a single device." + ) % hint_i.to_string()); + else found_devices.push_back(found_devices_i[0]); + } + if (found_devices.empty()) return device_addrs_t(); + if (not error_msg.empty()) throw uhd::value_error(error_msg); + return device_addrs_t(1, combine_device_addrs(found_devices)); + } + + //initialize the hint for a single device case + UHD_ASSERT_THROW(hints.size() <= 1); + hints.resize(1); //in case it was empty + device_addr_t hint = hints[0]; + device_addrs_t n230_addrs; + + //return an empty list of addresses when type is set to non-n230 + if (hint.has_key("type") and hint["type"] != "n230") return n230_addrs; + + //Return an empty list of addresses when a resource is specified, + //since a resource is intended for a different, non-networked, device. + if (hint.has_key("resource")) return n230_addrs; + + //if no address was specified, send a broadcast on each interface + if (not hint.has_key("addr")) { + BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()) { + //avoid the loopback device + if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + + //create a new hint with this broadcast address + device_addr_t new_hint = hint; + new_hint["addr"] = if_addrs.bcast; + + //call discover with the new hint and append results + device_addrs_t new_n230_addrs = n230_find(new_hint); + n230_addrs.insert(n230_addrs.begin(), + new_n230_addrs.begin(), new_n230_addrs.end() + ); + } + return n230_addrs; + } + + std::vector<std::string> discovered_addrs = + usrp3::usrp3_fw_ctrl_iface::discover_devices( + hint["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); + + BOOST_FOREACH(const std::string& addr, discovered_addrs) + { + device_addr_t new_addr; + new_addr["type"] = "n230"; + new_addr["addr"] = addr; + + //Attempt a simple 2-way communication with a connected socket. + //Reason: Although the USRP will respond the broadcast above, + //we may not be able to communicate directly (non-broadcast). + udp_simple::sptr ctrl_xport = udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)); + + //Corner case: If two devices have the same IP but different MAC + //addresses and are used back-to-back it takes a while for ARP tables + //on the host to update in which period brodcasts will respond but + //connected communication can fail. Retry the following call to allow + //the stack to update + size_t first_conn_retries = 10; + usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl; + while (first_conn_retries > 0) { + try { + fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make(ctrl_xport, N230_FW_PRODUCT_ID, false /*verbose*/); + break; + } catch (uhd::io_error& ex) { + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + first_conn_retries--; + } + } + if (first_conn_retries > 0) { + uint32_t compat_reg = fw_ctrl->peek32(fw::reg_addr(fw::WB_SBRB_BASE, fw::RB_ZPU_COMPAT)); + if (fw::get_prod_num(compat_reg) == fw::PRODUCT_NUM) { + if (!n230_resource_manager::is_device_claimed(fw_ctrl)) { + //Not claimed by another process or host + try { + //Try to read the EEPROM to get the name and serial + n230_eeprom_manager eeprom_mgr(new_addr["addr"]); + const mboard_eeprom_t& eeprom = eeprom_mgr.get_mb_eeprom(); + new_addr["name"] = eeprom["name"]; + new_addr["serial"] = eeprom["serial"]; + } + catch(const std::exception &) + { + //set these values as empty string so the device may still be found + //and the filter's below can still operate on the discovered device + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + //filter the discovered device below by matching optional keys + if ((not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"])) + { + n230_addrs.push_back(new_addr); + } + } + } + } + } + + return n230_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +device::sptr n230_impl::n230_make(const device_addr_t &device_addr) +{ + return device::sptr(new n230_impl(device_addr)); +} + +/*********************************************************************** + * n230_impl::ctor + **********************************************************************/ +n230_impl::n230_impl(const uhd::device_addr_t& dev_addr) +{ + UHD_MSG(status) << "N230 initialization sequence..." << std::endl; + _dev_args.parse(dev_addr); + _tree = uhd::property_tree::make(); + + //TODO: Only supports one motherboard per device class. + const fs_path mb_path = "/mboards/0"; + + //Initialize addresses + std::vector<std::string> ip_addrs(1, dev_addr["addr"]); + if (dev_addr.has_key("secondary-addr")) { + ip_addrs.push_back(dev_addr["secondary-addr"]); + } + + //Read EEPROM and perform version checks before talking to HW + _eeprom_mgr = boost::make_shared<n230_eeprom_manager>(ip_addrs[0]); + const mboard_eeprom_t& mb_eeprom = _eeprom_mgr->get_mb_eeprom(); + bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); + if (recover_mb_eeprom) { + UHD_MSG(warning) << "UHD is operating in EEPROM Recovery Mode which disables hardware version " + "checks.\nOperating in this mode may cause hardware damage and unstable " + "radio performance!"<< std::endl; + } + boost::uint16_t hw_rev = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision"]); + boost::uint16_t hw_rev_compat = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision_compat"]); + if (not recover_mb_eeprom) { + if (hw_rev_compat > N230_HW_REVISION_COMPAT) { + throw uhd::runtime_error(str(boost::format( + "Hardware is too new for this software. Please upgrade to a driver that supports hardware revision %d.") + % hw_rev)); + } + } + + //Initialize all subsystems + _resource_mgr = boost::make_shared<n230_resource_manager>(ip_addrs, _dev_args.get_safe_mode()); + _stream_mgr = boost::make_shared<n230_stream_manager>(_dev_args, _resource_mgr, _tree); + + //Build property tree + _initialize_property_tree(mb_path); + + //Debug loopback mode + switch(_dev_args.get_loopback_mode()) { + case n230_device_args_t::LOOPBACK_RADIO: + UHD_MSG(status) << "DEBUG: Running in TX->RX Radio loopback mode.\n"; + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_RADIO); + break; + case n230_device_args_t::LOOPBACK_CODEC: + UHD_MSG(status) << "DEBUG: Running in TX->RX CODEC loopback mode.\n"; + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_CODEC); + break; + default: + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_DISABLED); + break; + } +} + +/*********************************************************************** + * n230_impl::dtor + **********************************************************************/ +n230_impl::~n230_impl() +{ + _stream_mgr.reset(); + _eeprom_mgr.reset(); + _resource_mgr.reset(); + _tree.reset(); +} + +/*********************************************************************** + * n230_impl::get_rx_stream + **********************************************************************/ +rx_streamer::sptr n230_impl::get_rx_stream(const uhd::stream_args_t &args) +{ + return _stream_mgr->get_rx_stream(args); +} + +/*********************************************************************** + * n230_impl::get_tx_stream + **********************************************************************/ +tx_streamer::sptr n230_impl::get_tx_stream(const uhd::stream_args_t &args) +{ + return _stream_mgr->get_tx_stream(args); +} + +/*********************************************************************** + * n230_impl::recv_async_msg + **********************************************************************/ +bool n230_impl::recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout) +{ + return _stream_mgr->recv_async_msg(async_metadata, timeout); +} + +/*********************************************************************** + * _initialize_property_tree + **********************************************************************/ +void n230_impl::_initialize_property_tree(const fs_path& mb_path) +{ + //------------------------------------------------------------------ + // General info + //------------------------------------------------------------------ + _tree->create<std::string>("/name").set("N230 Device"); + + _tree->create<std::string>(mb_path / "name").set("N230"); + _tree->create<std::string>(mb_path / "codename").set("N230"); + _tree->create<std::string>(mb_path / "dboards").set("none"); //No dboards. + + _tree->create<std::string>(mb_path / "fw_version").set(str(boost::format("%u.%u") + % _resource_mgr->get_version(FIRMWARE, COMPAT_MAJOR) + % _resource_mgr->get_version(FIRMWARE, COMPAT_MINOR))); + _tree->create<std::string>(mb_path / "fw_version_hash").set(str(boost::format("%s") + % _resource_mgr->get_version_hash(FIRMWARE))); + _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") + % _resource_mgr->get_version(FPGA, COMPAT_MAJOR) + % _resource_mgr->get_version(FPGA, COMPAT_MINOR))); + _tree->create<std::string>(mb_path / "fpga_version_hash").set(str(boost::format("%s") + % _resource_mgr->get_version_hash(FPGA))); + + _tree->create<double>(mb_path / "link_max_rate").set(_resource_mgr->get_max_link_rate()); + + //------------------------------------------------------------------ + // EEPROM + //------------------------------------------------------------------ + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(_eeprom_mgr->get_mb_eeprom()) //Set first... + .add_coerced_subscriber(boost::bind(&n230_eeprom_manager::write_mb_eeprom, _eeprom_mgr, _1)); //..then enable writer + + //------------------------------------------------------------------ + // Create codec nodes + //------------------------------------------------------------------ + const fs_path rx_codec_path = mb_path / ("rx_codecs") / "A"; + _tree->create<std::string>(rx_codec_path / "name") + .set("N230 RX dual ADC"); + _tree->create<int>(rx_codec_path / "gains"); //Empty because gains are in frontend + + const fs_path tx_codec_path = mb_path / ("tx_codecs") / "A"; + _tree->create<std::string>(tx_codec_path / "name") + .set("N230 TX dual DAC"); + _tree->create<int>(tx_codec_path / "gains"); //Empty because gains are in frontend + + //------------------------------------------------------------------ + // Create clock and time control nodes + //------------------------------------------------------------------ + _tree->create<double>(mb_path / "tick_rate") + .set_coercer(boost::bind(&n230_clk_pps_ctrl::set_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr())) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tick_rate, _stream_mgr, _1)); + + //Register time now and pps onto available radio cores + //radio0 is the master + _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + _tree->create<time_spec_t>(mb_path / "time" / "now") + .set_publisher(boost::bind(&time_core_3000::get_time_now, _resource_mgr->get_radio(0).time)); + _tree->create<time_spec_t>(mb_path / "time" / "pps") + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _resource_mgr->get_radio(0).time)); + + //Setup time source props + _tree->create<std::string>(mb_path / "time_source" / "value") + .add_coerced_subscriber(boost::bind(&n230_impl::_check_time_source, this, _1)) + .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_pps_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set(n230::DEFAULT_TIME_SRC); + 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") + .add_coerced_subscriber(boost::bind(&n230_impl::_check_clock_source, this, _1)) + .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_clock_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set(n230::DEFAULT_CLOCK_SRC); + 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); + _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") + .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_ref_locked, _resource_mgr->get_clk_pps_ctrl_sptr())); + + //------------------------------------------------------------------ + // Create frontend mapping + //------------------------------------------------------------------ + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .set(subdev_spec_t()) + .add_coerced_subscriber(boost::bind(&n230_impl::_update_rx_subdev_spec, this, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .set(subdev_spec_t()) + .add_coerced_subscriber(boost::bind(&n230_impl::_update_tx_subdev_spec, this, _1)); + + //------------------------------------------------------------------ + // Create a fake dboard to put frontends in + //------------------------------------------------------------------ + //For completeness we give it a fake EEPROM as well + dboard_eeprom_t db_eeprom; //Default state: ID is 0xffff, Version and serial empty + _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); + + //------------------------------------------------------------------ + // Create radio specific nodes + //------------------------------------------------------------------ + for (size_t radio_instance = 0; radio_instance < fpga::NUM_RADIOS; radio_instance++) { + _initialize_radio_properties(mb_path, radio_instance); + } + //Update tick rate on newly created radio objects + _tree->access<double>(mb_path / "tick_rate").set(_dev_args.get_master_clock_rate()); + + //------------------------------------------------------------------ + // Initialize subdev specs + //------------------------------------------------------------------ + 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); + + //------------------------------------------------------------------ + // MiniSAS GPIO + //------------------------------------------------------------------ + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "DDR") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "DDR") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "OUT") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "OUT") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(0))); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(1))); + + //------------------------------------------------------------------ + // GPSDO sensors + //------------------------------------------------------------------ + if (_resource_mgr->is_gpsdo_present()) { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + BOOST_FOREACH(const std::string &name, gps_ctrl->get_sensors()) + { + _tree->create<sensor_value_t>(mb_path / "sensors" / name) + .set_publisher(boost::bind(&gps_ctrl::get_sensor, gps_ctrl, name)); + } + } +} + +/*********************************************************************** + * _initialize_radio_properties + **********************************************************************/ +void n230_impl::_initialize_radio_properties(const fs_path& mb_path, size_t instance) +{ + radio_resource_t& perif = _resource_mgr->get_radio(instance); + + //Time + _tree->access<time_spec_t>(mb_path / "time" / "cmd") + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); + _tree->access<time_spec_t>(mb_path / "time" / "now") + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_now, perif.time, _1)); + _tree->access<time_spec_t>(mb_path / "time" / "pps") + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time, _1)); + + //RX DSP + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(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") % instance); + _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") + .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); + _tree->create<double>(rx_dsp_path / "rate" / "value") + .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_rx_samp_rate, _stream_mgr, instance, _1)) + .set(n230::DEFAULT_RX_SAMP_RATE); + _tree->create<double>(rx_dsp_path / "freq" / "value") + .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) + .set(n230::DEFAULT_DDC_FREQ); + _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") + .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + + //TX DSP + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(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") % instance); + _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") + .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); + _tree->create<double>(tx_dsp_path / "rate" / "value") + .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tx_samp_rate, _stream_mgr, instance, _1)) + .set(n230::DEFAULT_TX_SAMP_RATE); + _tree->create<double>(tx_dsp_path / "freq" / "value") + .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) + .set(n230::DEFAULT_DUC_FREQ); + _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") + .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + + //RF Frontend Interfacing + static const std::vector<direction_t> data_directions = boost::assign::list_of(RX_DIRECTION)(TX_DIRECTION); + BOOST_FOREACH(direction_t direction, data_directions) { + const std::string dir_str = (direction == RX_DIRECTION) ? "rx" : "tx"; + const std::string key = boost::to_upper_copy(dir_str) + str(boost::format("%u") % (instance + 1)); + const fs_path rf_fe_path = mb_path / "dboards" / "A" / (dir_str + "_frontends") / ((instance==0)?"A":"B"); + + //CODEC subtree + _resource_mgr->get_codec_mgr().populate_frontend_subtree(_tree->subtree(rf_fe_path), key, direction); + + //User settings + _tree->create<uhd::wb_iface::sptr>(rf_fe_path / "user_settings" / "iface") + .set(perif.user_settings); + + //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") + .add_coerced_subscriber(boost::bind(&n230_frontend_ctrl::set_antenna_sel, _resource_mgr->get_frontend_ctrl_sptr(), instance, _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"); + } + } +} + +void n230_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() <= fpga::NUM_RADIOS); + + 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"); + } + + _stream_mgr->update_stream_states(); +} + +void n230_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() <= fpga::NUM_RADIOS); + + 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"); + } + + _stream_mgr->update_stream_states(); +} + +void n230_impl::_check_time_source(std::string source) +{ + if (source == "gpsdo") + { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + if (not (gps_ctrl and gps_ctrl->gps_detected())) + throw uhd::runtime_error("GPSDO time source not available"); + } +} + +void n230_impl::_check_clock_source(std::string source) +{ + if (source == "gpsdo") + { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + if (not (gps_ctrl and gps_ctrl->gps_detected())) + throw uhd::runtime_error("GPSDO clock source not available"); + } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_impl.hpp b/host/lib/usrp/n230/n230_impl.hpp new file mode 100644 index 000000000..b644dd8a3 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.hpp @@ -0,0 +1,81 @@ +// +// Copyright 2014 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_N230_IMPL_HPP +#define INCLUDED_N230_IMPL_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/device.hpp> +#include <uhd/usrp/subdev_spec.hpp> + +#include "n230_device_args.hpp" +#include "n230_eeprom_manager.hpp" +#include "n230_resource_manager.hpp" +#include "n230_stream_manager.hpp" +#include "recv_packet_demuxer_3000.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_impl : public uhd::device +{ +public: //Functions + // ctor and dtor + n230_impl(const uhd::device_addr_t& device_addr); + virtual ~n230_impl(void); + + //--------------------------------------------------------------------- + // uhd::device interface + // + static sptr make(const uhd::device_addr_t &hint, size_t which = 0); + + //! Make a new receive streamer from the streamer arguments + virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + + //! Make a new transmit streamer from the streamer arguments + virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + + //!Receive and asynchronous message from the device. + virtual bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout = 0.1); + + //!Registration methods the discovery and factory system. + //[static void register_device(const find_t &find, const make_t &make)] + static uhd::device_addrs_t n230_find(const uhd::device_addr_t &hint); + static uhd::device::sptr n230_make(const uhd::device_addr_t &device_addr); + // + //--------------------------------------------------------------------- + + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + +private: //Functions + void _initialize_property_tree(const fs_path& mb_path); + void _initialize_radio_properties(const fs_path& mb_path, size_t instance); + + void _update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void _update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void _check_time_source(std::string); + void _check_clock_source(std::string); + +private: //Classes and Members + n230_device_args_t _dev_args; + boost::shared_ptr<n230_resource_manager> _resource_mgr; + boost::shared_ptr<n230_eeprom_manager> _eeprom_mgr; + boost::shared_ptr<n230_stream_manager> _stream_mgr; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_IMPL_HPP */ diff --git a/host/lib/usrp/n230/n230_resource_manager.cpp b/host/lib/usrp/n230/n230_resource_manager.cpp new file mode 100644 index 000000000..f13dd0b33 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.cpp @@ -0,0 +1,569 @@ +// +// 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 "n230_resource_manager.hpp" + +#include "usrp3_fw_ctrl_iface.hpp" +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/platform.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/functional/hash.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/make_shared.hpp> +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.h" + +#define IF_DATA_I_MASK 0xFFF00000 +#define IF_DATA_Q_MASK 0x0000FFF0 + +namespace uhd { namespace usrp { namespace n230 { + +//Constants +static const uint8_t N230_HOST_SRC_ADDR_ETH0 = 0; +static const uint8_t N230_HOST_SRC_ADDR_ETH1 = 1; +static const uint8_t N230_HOST_DEST_ADDR = 2; + +static const uint8_t N230_ETH0_IFACE_ID = 0; +static const uint8_t N230_ETH1_IFACE_ID = 1; + +class n230_ad9361_client_t : public ad9361_params { +public: + ~n230_ad9361_client_t() {} + double get_band_edge(frequency_band_t band) { + switch (band) { + case AD9361_RX_BAND0: return 2.2e9; + case AD9361_RX_BAND1: return 4.0e9; + case AD9361_TX_BAND0: return 2.5e9; + default: return 0; + } + } + clocking_mode_t get_clocking_mode() { + return AD9361_XTAL_N_CLK_PATH; + } + digital_interface_mode_t get_digital_interface_mode() { + return AD9361_DDR_FDD_LVDS; + } + digital_interface_delays_t get_digital_interface_timing() { + digital_interface_delays_t delays; + delays.rx_clk_delay = 0; + delays.rx_data_delay = 0; + delays.tx_clk_delay = 0; + delays.tx_data_delay = 2; + return delays; + } +}; + +n230_resource_manager::n230_resource_manager( + const std::vector<std::string> ip_addrs, + const bool safe_mode +) : + _safe_mode(safe_mode), + _last_host_enpoint(0) +{ + if (_safe_mode) UHD_MSG(warning) << "Initializing device in safe mode\n"; + UHD_MSG(status) << "Setup basic communication...\n"; + + //Discover ethernet interfaces + bool dual_eth_expected = (ip_addrs.size() > 1); + BOOST_FOREACH(const std::string& addr, ip_addrs) { + n230_eth_conn_t conn_iface; + conn_iface.ip_addr = addr; + + boost::uint32_t iface_id = 0xFFFFFFFF; + try { + iface_id = usrp3::usrp3_fw_ctrl_iface::get_iface_id( + conn_iface.ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); + } catch (uhd::io_error&) { + throw uhd::io_error(str(boost::format( + "Could not communicate with the device over address %s") % + conn_iface.ip_addr)); + } + switch (iface_id) { + case N230_ETH0_IFACE_ID: conn_iface.type = ETH0; break; + case N230_ETH1_IFACE_ID: conn_iface.type = ETH1; break; + default: { + if (dual_eth_expected) { + throw uhd::runtime_error("N230 Initialization Error: Could not detect ethernet port number."); + } else { + //For backwards compatibility, if only one port is specified, assume that a detection + //failure means that the device does not support dual-ethernet behavior. + conn_iface.type = ETH0; break; + } + } + } + _eth_conns.push_back(conn_iface); + } + if (_eth_conns.size() < 1) { + throw uhd::runtime_error("N230 Initialization Error: No eth interfaces specified.)"); + } + + //Create firmware communication interface + _fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make( + transport::udp_simple::make_connected( + _get_conn(PRI_ETH).ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)), N230_FW_PRODUCT_ID); + if (_fw_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create n230_ctrl_iface.)"); + } + _check_fw_compat(); + + //Start the device claimer + _claimer_task = uhd::task::make(boost::bind(&n230_resource_manager::_claimer_loop, this)); + + //Create common settings interface + const sid_t core_sid = _generate_sid(CORE, _get_conn(PRI_ETH).type); + + transport::udp_zero_copy::buff_params dummy_out_params; + transport::zero_copy_if::sptr core_xport = + _create_transport(_get_conn(PRI_ETH), core_sid, device_addr_t(), dummy_out_params); + if (core_xport.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create settings transport.)"); + } + _core_ctrl = radio_ctrl_core_3000::make( + fpga::CVITA_BIG_ENDIAN, core_xport, core_xport, core_sid.get()); + if (_core_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create settings ctrl.)"); + } + _check_fpga_compat(); + + UHD_MSG(status) << boost::format("Version signatures... Firmware:%s FPGA:%s...\n") + % _fw_version.get_hash_str() % _fpga_version.get_hash_str(); + + _core_radio_ctrl_reg.initialize(*_core_ctrl, true /*flush*/); + _core_misc_reg.initialize(*_core_ctrl, true /*flush*/); + _core_pps_sel_reg.initialize(*_core_ctrl, true /*flush*/); + _core_status_reg.initialize(*_core_ctrl); + + //Create common SPI interface + _core_spi_ctrl = n230_core_spi_core::make(_core_ctrl); + if (_core_spi_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create SPI ctrl.)"); + } + + //Create AD9361 interface + UHD_MSG(status) << "Initializing CODEC...\n"; + _codec_ctrl = ad9361_ctrl::make_spi( + boost::make_shared<n230_ad9361_client_t>(), _core_spi_ctrl, fpga::AD9361_SPI_SLAVE_NUM); + if (_codec_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create Catalina ctrl.)"); + } + _codec_ctrl->set_clock_rate(fpga::CODEC_DEFAULT_CLK_RATE); + _codec_mgr = ad936x_manager::make(_codec_ctrl, fpga::NUM_RADIOS); + _codec_mgr->init_codec(); + + //Create AD4001 interface + _ref_pll_ctrl = boost::make_shared<n230_ref_pll_ctrl>(_core_spi_ctrl); + if (_ref_pll_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create ADF4001 ctrl.)"); + } + + //Reset SERDES interface and synchronize to frame sync from AD9361 + _reset_codec_digital_interface(); + + std::vector<time_core_3000::sptr> time_cores; + std::vector<gpio_atr::gpio_atr_3000::sptr> gpio_cores; + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + _initialize_radio(i); + time_cores.push_back(_radios[i].time); + gpio_cores.push_back(_radios[i].gpio_atr); + } + + //Create clock and PPS control interface + _clk_pps_ctrl = n230_clk_pps_ctrl::make( + _codec_ctrl, _ref_pll_ctrl, _core_misc_reg, _core_pps_sel_reg, _core_status_reg, time_cores); + if (_clk_pps_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create clock and PPS ctrl.)"); + } + + //Create front-end control interface + _frontend_ctrl = n230_frontend_ctrl::make(_core_ctrl, _core_misc_reg, _codec_ctrl, gpio_cores); + if (_frontend_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create front-end ctrl.)"); + } + + //Create miniSAS GPIO interfaces + _ms0_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS0_GPIO), fpga::rb_addr(fpga::RB_CORE_MS0_GPIO)); + _ms0_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _ms1_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS1_GPIO), fpga::rb_addr(fpga::RB_CORE_MS1_GPIO)); + _ms1_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //Create GPSDO interface + if (_core_status_reg.read(fpga::core_status_reg_t::GPSDO_STATUS) != fpga::GPSDO_ST_ABSENT) { + UHD_MSG(status) << "Detecting GPSDO.... " << std::flush; + try { + const sid_t gps_uart_sid = _generate_sid(GPS_UART, _get_conn(PRI_ETH).type); + transport::zero_copy_if::sptr gps_uart_xport = + _create_transport(_get_conn(PRI_ETH), gps_uart_sid, device_addr_t(), dummy_out_params); + _gps_uart = n230_uart::make(gps_uart_xport, uhd::htonx(gps_uart_sid.get())); + _gps_uart->set_baud_divider(fpga::BUS_CLK_RATE/fpga::GPSDO_UART_BAUDRATE); + _gps_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 + _gps_ctrl = gps_ctrl::make(_gps_uart); + } catch(std::exception &e) { + UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; + } + if (not is_gpsdo_present()) { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_GPSDO_ST), fpga::GPSDO_ST_ABSENT); + } + } + + //Perform data self-tests + _frontend_ctrl->set_stream_state(TXRX_STREAMING, TXRX_STREAMING); + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + _frontend_ctrl->set_self_test_mode(LOOPBACK_RADIO); + bool radio_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); + if (!radio_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Data loopback test failed.)"); + } + + _frontend_ctrl->set_self_test_mode(LOOPBACK_CODEC); + bool codec_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); + if (!codec_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Codec loopback test failed.)"); + } + } + _frontend_ctrl->set_self_test_mode(LOOPBACK_DISABLED); + _frontend_ctrl->set_stream_state(NONE_STREAMING, NONE_STREAMING); +} + +n230_resource_manager::~n230_resource_manager() +{ + _claimer_task.reset(); + { //Critical section + boost::mutex::scoped_lock(_claimer_mutex); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), 0); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), 0); + } +} + +transport::zero_copy_if::sptr n230_resource_manager::create_transport( + n230_data_dir_t direction, + size_t radio_instance, + const device_addr_t ¶ms, + sid_t& sid_pair, + transport::udp_zero_copy::buff_params& buff_out_params) +{ + const n230_eth_conn_t& conn = _get_conn((radio_instance==1)?SEC_ETH:PRI_ETH); + const sid_t temp_sid_pair = + _generate_sid(direction==RX_DATA?RADIO_RX_DATA:RADIO_TX_DATA, conn.type, radio_instance); + transport::zero_copy_if::sptr xport = _create_transport(conn, temp_sid_pair, params, buff_out_params); + if (xport.get() == NULL) { + throw uhd::runtime_error("N230 Create Data Transport: Could not create data transport.)"); + } else { + sid_pair = temp_sid_pair; + } + return xport; +} + +bool n230_resource_manager::is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl) +{ + boost::mutex::scoped_lock(_claimer_mutex); + + //If timed out then device is definitely unclaimed + if (fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_status)) == 0) + return false; + + //otherwise check claim src to determine if another thread with the same src has claimed the device + return fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_src)) != get_process_hash(); +} + +void n230_resource_manager::_claimer_loop() +{ + { //Critical section + boost::mutex::scoped_lock(_claimer_mutex); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), time(NULL)); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), get_process_hash()); + } + boost::this_thread::sleep(boost::posix_time::milliseconds(N230_CLAIMER_TIMEOUT_IN_MS / 2)); +} + +void n230_resource_manager::_initialize_radio(size_t instance) +{ + radio_resource_t& radio = _radios[instance]; + + //Create common settings interface + const sid_t ctrl_sid = _generate_sid(RADIO_CONTROL, _get_conn(PRI_ETH).type, instance); + transport::udp_zero_copy::buff_params buff_out_params; + transport::zero_copy_if::sptr ctrl_xport = + _create_transport(_get_conn(PRI_ETH), ctrl_sid, device_addr_t(), buff_out_params); + if (ctrl_xport.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create radio transport.)"); + } + radio.ctrl = radio_ctrl_core_3000::make( + fpga::CVITA_BIG_ENDIAN, ctrl_xport, ctrl_xport, ctrl_sid.get()); + if (radio.ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create radio ctrl.)"); + } + + //Perform register loopback test to verify the radio clock + bool reg_selftest_pass = _radio_register_loopback_self_test(radio.ctrl); + if (!reg_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Register loopback test failed.)"); + } + + //Write-only ATR interface + radio.gpio_atr = gpio_atr::gpio_atr_3000::make_write_only(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_ATR)); + radio.gpio_atr->set_atr_mode(gpio_atr::MODE_ATR,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //Core VITA time interface + time_core_3000::readback_bases_type time_bases; + time_bases.rb_now = fpga::rb_addr(fpga::RB_RADIO_TIME_NOW); + time_bases.rb_pps = fpga::rb_addr(fpga::RB_RADIO_TIME_PPS); + radio.time = time_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TIME), time_bases); + if (radio.time.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create time core.)"); + } + + //RX DSP + radio.framer = rx_vita_core_3000::make( + radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_CTRL)); + radio.ddc = rx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_DSP), true /*old DDC?*/); + if (radio.framer.get() == NULL || radio.ddc.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); + } + radio.ddc->set_link_rate(fpga::N230_LINK_RATE_BPS); + + //TX DSP + radio.deframer = tx_vita_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_CTRL)); + radio.duc = tx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_DSP)); + if (radio.deframer.get() == NULL || radio.duc.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); + } + radio.duc->set_link_rate(fpga::N230_LINK_RATE_BPS); + + //User settings + radio.user_settings = user_settings_core_3000::make(radio.ctrl, + fpga::sr_addr(fpga::SR_RADIO_USER_SR), fpga::rb_addr(fpga::SR_RADIO_USER_RB)); + if (radio.user_settings.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create user settings bus.)"); + } +} + +boost::uint8_t xb_ep_to_sid(fpga::xb_endpoint_t ep) { + return static_cast<boost::uint8_t>(ep) << 4; +} + +const sid_t n230_resource_manager::_generate_sid(const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance) +{ + fpga::xb_endpoint_t xb_dest_ep; + boost::uint8_t sid_dest_ep = 0; + fpga::xb_endpoint_t xb_ret_ep = (xport == ETH1) ? fpga::N230_XB_DST_E1 : fpga::N230_XB_DST_E0; + boost::uint8_t sid_ret_addr = (xport == ETH1) ? N230_HOST_SRC_ADDR_ETH1 : N230_HOST_SRC_ADDR_ETH0; + + if (type == CORE or type == GPS_UART) { + //Non-radio endpoints + xb_dest_ep = (type == CORE) ? fpga::N230_XB_DST_GCTRL : fpga::N230_XB_DST_UART; + sid_dest_ep = xb_ep_to_sid(xb_dest_ep); + } else { + //Radio endpoints + xb_dest_ep = (instance == 1) ? fpga::N230_XB_DST_R1 : fpga::N230_XB_DST_R0; + sid_dest_ep = xb_ep_to_sid(xb_dest_ep); + switch (type) { + case RADIO_TX_DATA: + sid_dest_ep |= fpga::RADIO_DATA_SUFFIX; + break; + case RADIO_RX_DATA: + sid_dest_ep |= fpga::RADIO_FC_SUFFIX; + break; + default: + sid_dest_ep |= fpga::RADIO_CTRL_SUFFIX; + break; + } + } + + //Increment last host logical endpoint + sid_t sid(sid_ret_addr, ++_last_host_enpoint, N230_HOST_DEST_ADDR, sid_dest_ep); + + //Program the crossbar addr + _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, fw::SR_ZPU_XB_LOCAL), sid.get_dst_addr()); + // Program CAM entry for returning packets to us + // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM + _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, sid.get_src_addr()), static_cast<boost::uint32_t>(xb_ret_ep)); + // Program CAM entry for outgoing packets matching a N230 resource (for example a Radio) + // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM + _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, 256 + sid.get_dst_endpoint()), static_cast<boost::uint32_t>(xb_dest_ep)); + + return sid; +} + +transport::zero_copy_if::sptr n230_resource_manager::_create_transport( + const n230_eth_conn_t& eth_conn, + const sid_t& sid, const device_addr_t &buff_params, + transport::udp_zero_copy::buff_params& buff_params_out) +{ + transport::zero_copy_xport_params default_buff_args; + default_buff_args.recv_frame_size = transport::udp_simple::mtu; + default_buff_args.send_frame_size = transport::udp_simple::mtu; + default_buff_args.num_recv_frames = 32; + default_buff_args.num_send_frames = 32; + + transport::zero_copy_if::sptr xport = transport::udp_zero_copy::make( + eth_conn.ip_addr, boost::lexical_cast<std::string>(fpga::CVITA_UDP_PORT), + default_buff_args, buff_params_out, buff_params); + + if (xport.get()) { + _program_dispatcher(*xport, eth_conn.type, sid); + } + return xport; +} + +void n230_resource_manager::_program_dispatcher( + transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid) +{ + //Send a mini packet with SID into the ZPU + //ZPU will reprogram the ethernet framer + transport::managed_send_buffer::sptr buff = xport.get_send_buff(); + buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 + buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid.get()); + buff->commit(8); + buff.reset(); + + //reprogram the ethernet dispatcher's udp port (should be safe to always set) + uint32_t disp_base_offset = + ((port == ETH1) ? fw::SR_ZPU_ETHINT1 : fw::SR_ZPU_ETHINT0) + fw::SR_ZPU_ETHINT_DISPATCHER_BASE; + _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, disp_base_offset + fw::ETH_FRAMER_SRC_UDP_PORT), fpga::CVITA_UDP_PORT); + + //Do a peek to an arbitrary address to guarantee that the + //ethernet framer has been programmed before we return. + _fw_ctrl->peek32(0); +} + +void n230_resource_manager::_reset_codec_digital_interface() +{ + //Set timing registers + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_DATA_DELAY), fpga::CODEC_DATA_DELAY); + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_CLK_DELAY), fpga::CODEC_CLK_DELAY); + + _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 1); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 0); +} + +bool n230_resource_manager::_radio_register_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + size_t hash = static_cast<size_t>(time(NULL)); + for (size_t i = 0; i < 100; i++) { + boost::hash_combine(hash, i); + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_TEST), boost::uint32_t(hash)); + test_fail = iface->peek32(fpga::rb_addr(fpga::RB_RADIO_TEST)) != boost::uint32_t(hash); + if (test_fail) break; //exit loop on any failure + } + return !test_fail; +} + +bool n230_resource_manager::_radio_data_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + 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) & (IF_DATA_I_MASK | IF_DATA_Q_MASK); + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), word32); + iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); //block until request completes + boost::this_thread::sleep(boost::posix_time::microseconds(100)); //wait for loopback to propagate through codec + const boost::uint64_t rb_word64 = iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); + 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) + UHD_MSG(fastpath) << boost::format("mismatch (exp:%x, got:%x and %x)... ") % word32 % rb_tx % rb_rx; + break; //exit loop on any failure + } + + /* Zero out the idle data. */ + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), 0); + return !test_fail; +} + +std::string n230_resource_manager::_get_fpga_upgrade_msg() { + std::string img_loader_path = + (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); + + return str(boost::format( + "\nDownload the appropriate FPGA images for this version of UHD.\n" + "%s\n\n" + "Then burn a new image to the on-board flash storage of your\n" + "USRP N230 device using the image loader utility. Use this command:\n" + "\n \"%s\" --args=\"type=n230,addr=%s\"\n") + % print_utility_error("uhd_images_downloader.py") + % img_loader_path % _get_conn(PRI_ETH).ip_addr); + +} + +void n230_resource_manager::_check_fw_compat() +{ + boost::uint32_t compat_num = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_compat_num)); + _fw_version.compat_major = compat_num >> 16; + _fw_version.compat_minor = compat_num; + _fw_version.version_hash = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_version_hash)); + + if (_fw_version.compat_major != N230_FW_COMPAT_NUM_MAJOR){ + throw uhd::runtime_error(str(boost::format( + "Expected firmware compatibility number %d.x, but got %d.%d\n" + "The firmware build is not compatible with the host code build.\n" + "%s" + ) % static_cast<boost::uint32_t>(N230_FW_COMPAT_NUM_MAJOR) + % static_cast<boost::uint32_t>(_fw_version.compat_major) + % static_cast<boost::uint32_t>(_fw_version.compat_minor) + % _get_fpga_upgrade_msg())); + } +} + +void n230_resource_manager::_check_fpga_compat() +{ + const boost::uint64_t compat = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_SIGNATUE)); + const boost::uint32_t signature = boost::uint32_t(compat >> 32); + const boost::uint16_t product_id = boost::uint8_t(compat >> 24); + _fpga_version.compat_major = static_cast<boost::uint8_t>(compat >> 16); + _fpga_version.compat_minor = static_cast<boost::uint16_t>(compat); + + const boost::uint64_t version_hash = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_VERSION_HASH)); + _fpga_version.version_hash = boost::uint32_t(version_hash); + + if (signature != 0x0ACE0BA5E || product_id != fpga::RB_N230_PRODUCT_ID) + throw uhd::runtime_error("Signature check failed. Please contact support."); + + bool is_safe_image = (_fpga_version.compat_major > fpga::RB_N230_COMPAT_SAFE); + + if (is_safe_image && !_safe_mode) { + throw uhd::runtime_error( + "The device appears to have the failsafe FPGA image loaded\n" + "This could have happened because the production FPGA image in the flash was either corrupt or non-existent\n" + "To remedy this error, please burn a valid FPGA image to the flash.\n" + "To continue using the failsafe image with UHD, create the UHD device with the \"safe_mode\" device arg.\n" + "Radio functionality/performance not guaranteed when operating in safe mode.\n"); + } else if (_fpga_version.compat_major != fpga::RB_N230_COMPAT_MAJOR && !is_safe_image) { + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number %d.x, but got %d.%d:\n" + "The FPGA build is not compatible with the host code build.\n" + "%s" + ) % static_cast<boost::uint32_t>(fpga::RB_N230_COMPAT_MAJOR) + % static_cast<boost::uint32_t>(_fpga_version.compat_major) + % static_cast<boost::uint32_t>(_fpga_version.compat_minor) + % _get_fpga_upgrade_msg())); + } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_resource_manager.hpp b/host/lib/usrp/n230/n230_resource_manager.hpp new file mode 100644 index 000000000..0a1178bd2 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.hpp @@ -0,0 +1,318 @@ +// +// Copyright 2014 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_N230_RESOURCE_MANAGER_HPP +#define INCLUDED_N230_RESOURCE_MANAGER_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "spi_core_3000.hpp" +#include "gpio_atr_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "user_settings_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "ad936x_manager.hpp" +#include <uhd/utils/tasks.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/soft_register.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +#include "usrp3_fw_ctrl_iface.hpp" +#include "n230_clk_pps_ctrl.hpp" +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_frontend_ctrl.hpp" +#include "n230_uart.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +enum n230_eth_port_t { + ETH0, + ETH1 +}; + +enum n230_eth_pref_t { + PRI_ETH, + SEC_ETH +}; + +enum n230_endpoint_t { + RADIO_TX_DATA, + RADIO_RX_DATA, + RADIO_CONTROL, + CORE, + GPS_UART +}; + +enum n230_ver_src_t { + SOFTWARE, + FIRMWARE, + FPGA +}; + +enum n230_version_t { + COMPAT_MAJOR, + COMPAT_MINOR +}; + +enum n230_data_dir_t { + RX_DATA, TX_DATA +}; + +//Radio resources +class radio_resource_t : public boost::noncopyable { +public: + radio_ctrl_core_3000::sptr ctrl; + gpio_atr::gpio_atr_3000::sptr gpio_atr; + time_core_3000::sptr time; + rx_vita_core_3000::sptr framer; + rx_dsp_core_3000::sptr ddc; + tx_vita_core_3000::sptr deframer; + tx_dsp_core_3000::sptr duc; + user_settings_core_3000::sptr user_settings; +}; + +class n230_resource_manager : public boost::noncopyable +{ +public: //Methods + n230_resource_manager(const std::vector<std::string> ip_addrs, const bool safe_mode); + virtual ~n230_resource_manager(); + + static bool is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl); + + inline bool is_device_claimed() { + if (_fw_ctrl.get()) { + return is_device_claimed(_fw_ctrl); + } else { + return false; + } + } + + inline boost::uint32_t get_version(n230_ver_src_t src, n230_version_t type) { + switch (src) { + case FPGA: return _fpga_version.get(type); + case FIRMWARE: return _fw_version.get(type); + default: return 0; + } + } + + inline const std::string get_version_hash(n230_ver_src_t src) { + switch (src) { + case FPGA: return _fpga_version.get_hash_str(); + case FIRMWARE: return _fw_version.get_hash_str(); + default: return ""; + } + } + + //Firmware control interface + inline wb_iface& get_fw_ctrl() const { + return *_fw_ctrl; + } + inline wb_iface::sptr get_fw_ctrl_sptr() { + return _fw_ctrl; + } + + //Core settings control interface + inline radio_ctrl_core_3000& get_core_ctrl() const { + return *_core_ctrl; + } + inline radio_ctrl_core_3000::sptr get_core_ctrl_sptr() { + return _core_ctrl; + } + + //AD931 control interface + inline ad9361_ctrl& get_codec_ctrl() const { + return *_codec_ctrl; + } + inline ad9361_ctrl::sptr get_codec_ctrl_sptr() { + return _codec_ctrl; + } + inline uhd::usrp::ad936x_manager& get_codec_mgr() const { + return *_codec_mgr; + } + + //Clock PPS controls + inline n230_ref_pll_ctrl& get_ref_pll_ctrl() const { + return *_ref_pll_ctrl; + } + inline n230_ref_pll_ctrl::sptr get_ref_pll_ctrl_sptr() { + return _ref_pll_ctrl; + } + + //Clock PPS controls + inline n230_clk_pps_ctrl& get_clk_pps_ctrl() const { + return *_clk_pps_ctrl; + } + inline n230_clk_pps_ctrl::sptr get_clk_pps_ctrl_sptr() { + return _clk_pps_ctrl; + } + + //Front-end control + inline n230_frontend_ctrl& get_frontend_ctrl() const { + return *_frontend_ctrl; + } + inline n230_frontend_ctrl::sptr get_frontend_ctrl_sptr() { + return _frontend_ctrl; + } + + //MiniSAS GPIO control + inline gpio_atr::gpio_atr_3000::sptr get_minisas_gpio_ctrl_sptr(size_t idx) { + return idx == 0 ? _ms0_gpio : _ms1_gpio; + } + + inline gpio_atr::gpio_atr_3000& get_minisas_gpio_ctrl(size_t idx) { + return *get_minisas_gpio_ctrl_sptr(idx); + } + + //GPSDO control + inline bool is_gpsdo_present() { + return _gps_ctrl.get() and _gps_ctrl->gps_detected(); + } + + inline uhd::gps_ctrl::sptr get_gps_ctrl(void) { + return _gps_ctrl; + } + + inline radio_resource_t& get_radio(size_t instance) { + return _radios[instance]; + } + + //Transport to stream data + transport::zero_copy_if::sptr create_transport( + n230_data_dir_t direction, size_t radio_instance, + const device_addr_t ¶ms, sid_t& sid, + transport::udp_zero_copy::buff_params& buff_out_params); + + //Misc + inline double get_max_link_rate() { + return fpga::N230_LINK_RATE_BPS * _eth_conns.size(); + } + +private: + struct ver_info_t { + boost::uint8_t compat_major; + boost::uint16_t compat_minor; + boost::uint32_t version_hash; + + boost::uint32_t get(n230_version_t type) { + switch (type) { + case COMPAT_MAJOR: return compat_major; + case COMPAT_MINOR: return compat_minor; + default: return 0; + } + } + + const std::string get_hash_str() { + return (str(boost::format("%07x%s") + % (version_hash & 0x0FFFFFFF) + % ((version_hash & 0xF0000000) ? "(modified)" : ""))); + } + }; + + struct n230_eth_conn_t { + std::string ip_addr; + n230_eth_port_t type; + }; + + //-- Functions -- + + void _claimer_loop(); + + void _initialize_radio(size_t instance); + + std::string _get_fpga_upgrade_msg(); + void _check_fw_compat(); + void _check_fpga_compat(); + + const sid_t _generate_sid( + const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance = 0); + + transport::zero_copy_if::sptr _create_transport( + const n230_eth_conn_t& eth_conn, + const sid_t& sid, const device_addr_t &buff_params, + transport::udp_zero_copy::buff_params& buff_params_out); + + void _program_dispatcher( + transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid); + + void _reset_codec_digital_interface(); + + bool _radio_register_loopback_self_test(wb_iface::sptr iface); + + bool _radio_data_loopback_self_test(wb_iface::sptr iface); + + inline const n230_eth_conn_t& _get_conn(const n230_eth_pref_t pref) { + if (_eth_conns.size() == 1) + return _eth_conns[0]; + else + return _eth_conns[(pref==PRI_ETH)?0:1]; + } + + //-- Members -- + + std::vector<n230_eth_conn_t> _eth_conns; + const bool _safe_mode; + ver_info_t _fw_version; + ver_info_t _fpga_version; + + //Firmware register interface + uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr _fw_ctrl; + uhd::task::sptr _claimer_task; + static boost::mutex _claimer_mutex; //All claims and checks in this process are serialized + + //Transport + boost::uint8_t _last_host_enpoint; + + //Radio settings interface + radio_ctrl_core_3000::sptr _core_ctrl; + n230_core_spi_core::sptr _core_spi_ctrl; + ad9361_ctrl::sptr _codec_ctrl; + uhd::usrp::ad936x_manager::sptr _codec_mgr; + + //Core Registers + fpga::core_radio_ctrl_reg_t _core_radio_ctrl_reg; + fpga::core_misc_reg_t _core_misc_reg; + fpga::core_pps_sel_reg_t _core_pps_sel_reg; + fpga::core_status_reg_t _core_status_reg; + + //Radio peripherals + radio_resource_t _radios[fpga::NUM_RADIOS]; + + //Misc IO peripherals + n230_ref_pll_ctrl::sptr _ref_pll_ctrl; + n230_clk_pps_ctrl::sptr _clk_pps_ctrl; + n230_frontend_ctrl::sptr _frontend_ctrl; + + //miniSAS GPIO + gpio_atr::gpio_atr_3000::sptr _ms0_gpio; + gpio_atr::gpio_atr_3000::sptr _ms1_gpio; + + //GPSDO + n230_uart::sptr _gps_uart; + uhd::gps_ctrl::sptr _gps_ctrl; + +}; + +}}} //namespace + +#endif //INCLUDED_N230_RESOURCE_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_stream_manager.cpp b/host/lib/usrp/n230/n230_stream_manager.cpp new file mode 100644 index 000000000..e7624ecd6 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.cpp @@ -0,0 +1,562 @@ +// +// 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 "n230_stream_manager.hpp" + +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "async_packet_handler.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/log.hpp> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +static const double N230_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full. +static const size_t N230_RX_FC_REQUEST_FREQ = 32; //per flow-control window +static const size_t N230_TX_MAX_ASYNC_MESSAGES = 1000; +static const size_t N230_TX_MAX_SPP = 4092; +static const size_t N230_TX_FC_RESPONSE_FREQ = 10; //per flow-control window + +static const boost::uint32_t N230_EVENT_CODE_FLOW_CTRL = 0; + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; + +n230_stream_manager::~n230_stream_manager() +{ +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +n230_stream_manager::n230_stream_manager( + const n230_device_args_t& dev_args, + boost::shared_ptr<n230_resource_manager> resource_mgr, + boost::weak_ptr<property_tree> prop_tree +) : + _dev_args(dev_args), + _resource_mgr(resource_mgr), + _tree(prop_tree) +{ + _async_md_queue.reset(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr n230_stream_manager::get_rx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_setup_mutex); + + stream_args_t args = args_; + + //setup defaults for unspecified values + if (args.otw_format.empty()) 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_resource_t& perif = _resource_mgr->get_radio(chan); + + //setup transport hints (default to a large recv buff) + //TODO: Propagate the device_args class into streamer in the future + device_addr_t device_addr = args.args; + if (not device_addr.has_key("recv_buff_size")) { + device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_buff_size()); + } + if (not device_addr.has_key("recv_frame_size")) { + device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_frame_size()); + } + if (not device_addr.has_key("num_recv_frames")) { + device_addr["num_recv_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_recv_frames()); + } + + transport::udp_zero_copy::buff_params buff_params_out; + sid_t sid; + zero_copy_if::sptr xport = _resource_mgr->create_transport( + RX_DATA, chan, device_addr, sid, buff_params_out); + + //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) //no longer using 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 = xport->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + spp = std::min<size_t>(N230_TX_MAX_SPP, spp); //FPGA FIFO maximum for framing at full rate + + //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(&n230_stream_manager::_cvita_hdr_unpack); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_be"; + 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.reversed().get()); + perif.framer->setup(args); + perif.ddc->setup(args); + + //Give the streamer a functor to get the recv_buffer + //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&zero_copy_if::get_recv_buff, xport, _1), + true /*flush*/ + ); + + my_streamer->set_overflow_handler(stream_i, boost::bind( + &n230_stream_manager::_handle_overflow, this, chan + )); + + my_streamer->set_issue_stream_cmd(stream_i, boost::bind( + &rx_vita_core_3000::issue_stream_command, perif.framer, _1 + )); + + const size_t fc_window = _get_rx_flow_control_window( + xport->get_recv_frame_size(), buff_params_out.recv_buff_size); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / N230_RX_FC_REQUEST_FREQ); + + perif.framer->configure_flow_control(fc_window); + + //Give the streamer a functor to send flow control messages + //handle_rx_flowctrl is static and has no lifetime issues + boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl( + stream_i, boost::bind(&n230_stream_manager::_handle_rx_flowctrl, sid.get(), xport, fc_cache, _1), + fc_handle_window, + true/*init*/ + ); + + //Store a weak pointer to prevent a streamer->manager->streamer circular dependency + _rx_streamers[chan] = my_streamer; //store weak pointer + _rx_stream_cached_args[chan] = args; + + //Sets tick and samp rates on all streamer + update_tick_rate(_get_tick_rate()); + + //TODO: Find a way to remove this dependency + property_tree::sptr prop_tree = _tree.lock(); + if (prop_tree) { + //TODO: Update this to support multiple motherboards + const fs_path mb_path = "/mboards/0"; + prop_tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); + } + } + update_stream_states(); + + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr n230_stream_manager::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_setup_mutex); + + uhd::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("n230_impl::get_tx_stream only supports otw_format sc16"); + } + args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //shared async queue for all channels in streamer + boost::shared_ptr<async_md_queue_t> async_md(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); + + 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_resource_t& perif = _resource_mgr->get_radio(chan); + + //setup transport hints (default to a large recv buff) + //TODO: Propagate the device_args class into streamer in the future + device_addr_t device_addr = args.args; + if (not device_addr.has_key("send_buff_size")) { + device_addr["send_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_buff_size()); + } + if (not device_addr.has_key("send_frame_size")) { + device_addr["send_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_frame_size()); + } + if (not device_addr.has_key("num_send_frames")) { + device_addr["num_send_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_send_frames()); + } + + transport::udp_zero_copy::buff_params buff_params_out; + sid_t sid; + zero_copy_if::sptr xport = _resource_mgr->create_transport( + TX_DATA, chan, device_addr, sid, buff_params_out); + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::num_vrl_words32*sizeof(boost::uint32_t) + + 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 = xport->get_send_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::send_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_packer(&n230_stream_manager::_cvita_hdr_pack); + + //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_be"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.deframer->clear(); + perif.deframer->setup(args); + perif.duc->setup(args); + + //flow control setup + size_t fc_window = _get_tx_flow_control_window( + bpp, device_addr.cast<size_t>("send_buff_size", _dev_args.get_send_buff_size())); + //In packets + const size_t fc_handle_window = (fc_window / N230_TX_FC_RESPONSE_FREQ); + + perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); + boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = chan; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md_queue; + + tick_rate_retriever_t get_tick_rate_fn = boost::bind(&n230_stream_manager::_get_tick_rate, this); + task::sptr task = task::make( + boost::bind(&n230_stream_manager::_handle_tx_async_msgs, + fc_cache, xport, get_tick_rate_fn)); + + //Give the streamer a functor to get the send buffer + //get_tx_buff_with_flowctrl is static so bind has no lifetime issues + //xport.send (sptr) is required to add streamer->data-transport lifetime dependency + //task (sptr) is required to add a streamer->async-handler lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&n230_stream_manager::_get_tx_buff_with_flowctrl, task, fc_cache, xport, fc_window, _1) + ); + //Give the streamer a functor handled received async messages + my_streamer->set_async_receiver( + boost::bind(&async_md_queue_t::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, sid.get()); + my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet + + //Store a weak pointer to prevent a streamer->manager->streamer circular dependency + _tx_streamers[chan] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); + _tx_stream_cached_args[chan] = args; + + //Sets tick and samp rates on all streamer + update_tick_rate(_get_tick_rate()); + + //TODO: Find a way to remove this dependency + property_tree::sptr prop_tree = _tree.lock(); + if (prop_tree) { + //TODO: Update this to support multiple motherboards + const fs_path mb_path = "/mboards/0"; + prop_tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); + } + } + update_stream_states(); + + return my_streamer; +} + +/*********************************************************************** + * Async Message Receiver + **********************************************************************/ +bool n230_stream_manager::recv_async_msg(async_metadata_t &async_metadata, double timeout) +{ + return _async_md_queue->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Sample Rate Updaters + **********************************************************************/ +void n230_stream_manager::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>(_rx_streamers[dspno].lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _resource_mgr->get_radio(dspno).ddc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void n230_stream_manager::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>(_tx_streamers[dspno].lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _resource_mgr->get_radio(dspno).duc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +/*********************************************************************** + * Tick Rate Updater + **********************************************************************/ +void n230_stream_manager::update_tick_rate(const double rate) +{ + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + radio_resource_t& perif = _resource_mgr->get_radio(i); + + boost::shared_ptr<sph::recv_packet_streamer> my_rx_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_rx_streamer) my_rx_streamer->set_tick_rate(rate); + perif.framer->set_tick_rate(rate); + + boost::shared_ptr<sph::send_packet_streamer> my_tx_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock()); + if (my_tx_streamer) my_tx_streamer->set_tick_rate(rate); + } +} + +/*********************************************************************** + * Stream State Updater + **********************************************************************/ +void n230_stream_manager::update_stream_states() +{ + //extract settings from state variables + const bool enb_tx0 = bool(_tx_streamers[0].lock()); + const bool enb_rx0 = bool(_rx_streamers[0].lock()); + const bool enb_tx1 = bool(_tx_streamers[1].lock()); + const bool enb_rx1 = bool(_rx_streamers[1].lock()); + + fe_state_t fe0_state = NONE_STREAMING; + if (enb_tx0 && enb_rx0) fe0_state = TXRX_STREAMING; + else if (enb_tx0) fe0_state = TX_STREAMING; + else if (enb_rx0) fe0_state = RX_STREAMING; + + fe_state_t fe1_state = NONE_STREAMING; + if (enb_tx1 && enb_rx1) fe1_state = TXRX_STREAMING; + else if (enb_tx1) fe1_state = TX_STREAMING; + else if (enb_rx1) fe1_state = RX_STREAMING; + + _resource_mgr->get_frontend_ctrl().set_stream_state(fe0_state, fe1_state); +} + +size_t n230_stream_manager::_get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size) +{ + double sw_buff_max = sw_buff_size * N230_RX_SW_BUFF_FULL_FACTOR; + size_t window_in_pkts = (static_cast<size_t>(sw_buff_max) / frame_size); + if (window_in_pkts == 0) { + throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); + } + return window_in_pkts; +} + +void n230_stream_manager::_handle_overflow(const size_t i) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_streamer->get_num_channels() == 2) { + //MIMO + //find out if we were in continuous mode before stopping + const bool in_continuous_streaming_mode = _resource_mgr->get_radio(i).framer->in_continuous_streaming_mode(); + //stop streaming + my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + //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 = _resource_mgr->get_radio(i).time->get_time_now() + time_spec_t(0.01); + my_streamer->issue_stream_cmd(stream_cmd); + } + } else { + _resource_mgr->get_radio(i).framer->handle_overflow(); + } +} + +void n230_stream_manager::_handle_rx_flowctrl( + const sid_t& sid, + zero_copy_if::sptr xport, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq) +{ + static const size_t RXFC_PACKET_LEN_IN_WORDS = 2; + static const size_t RXFC_CMD_CODE_OFFSET = 0; + static const size_t RXFC_SEQ_NUM_OFFSET = 1; + + managed_send_buffer::sptr buff = xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + //recover seq32 + size_t& seq_sw = fc_cache->last_seq_in; + const size_t seq_hw = seq_sw & HW_SEQ_NUM_MASK; + if (last_seq < seq_hw) seq_sw += (HW_SEQ_NUM_MASK + 1); + seq_sw &= ~HW_SEQ_NUM_MASK; + seq_sw |= last_seq; + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; + packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = seq_sw; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = sid.get(); + 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; + + //load header + _cvita_hdr_pack(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32 + RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(N230_EVENT_CODE_FLOW_CTRL); + pkt[packet_info.num_header_words32 + RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq_sw); + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +void n230_stream_manager::_handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + tick_rate_retriever_t get_tick_rate) +{ + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) return; + + //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 + uint32_t (*endian_conv)(uint32_t) = uhd::ntohx; + try { + _cvita_hdr_unpack(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } catch(const std::exception &ex) { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff( + endian_conv, metadata, if_packet_info, packet_buff, + get_tick_rate(), fc_cache->stream_channel); + + //The FC response and the burst ack are two indicators that the radio + //consumed packets. Use them to update the FC metadata + if (metadata.event_code == N230_EVENT_CODE_FLOW_CTRL or + metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK + ) { + const size_t seq = metadata.user_payload[0]; + fc_cache->seq_queue.push_with_pop_on_full(seq); + } + + //FC responses don't propagate up to the user so filter them here + if (metadata.event_code != N230_EVENT_CODE_FLOW_CTRL) { + fc_cache->async_queue->push_with_pop_on_full(metadata); + metadata.channel = fc_cache->device_channel; + fc_cache->old_async_queue->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + } +} + +managed_send_buffer::sptr n230_stream_manager::_get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + size_t fc_pkt_window, + const double timeout) +{ + while (true) + { + const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); + if ((delta & HW_SEQ_NUM_MASK) <= fc_pkt_window) break; + + const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); + if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control + } + + managed_send_buffer::sptr buff = xport->get_send_buff(timeout); + if (buff) fc_cache->last_seq_out++; //update seq, this will actually be a send + return buff; +} + +size_t n230_stream_manager::_get_tx_flow_control_window( + size_t payload_size, + size_t hw_buff_size) +{ + size_t window_in_pkts = hw_buff_size / payload_size; + if (window_in_pkts == 0) { + throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); + } + return window_in_pkts; +} + +double n230_stream_manager::_get_tick_rate() +{ + return _resource_mgr->get_clk_pps_ctrl().get_tick_rate(); +} + +void n230_stream_manager::_cvita_hdr_unpack( + 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_be(packet_buff, if_packet_info); +} + +void n230_stream_manager::_cvita_hdr_pack( + 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_be(packet_buff, if_packet_info); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_stream_manager.hpp b/host/lib/usrp/n230/n230_stream_manager.hpp new file mode 100644 index 000000000..7a496c4e9 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.hpp @@ -0,0 +1,151 @@ +// +// Copyright 2014 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_N230_STREAM_MANAGER_HPP +#define INCLUDED_N230_STREAM_MANAGER_HPP + +#include "time_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/tasks.hpp> +#include <boost/smart_ptr.hpp> +#include "n230_device_args.hpp" +#include "n230_resource_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_stream_manager : public boost::noncopyable +{ +public: //Methods + n230_stream_manager( + const n230_device_args_t& dev_args, + boost::shared_ptr<n230_resource_manager> resource_mgr, + boost::weak_ptr<property_tree> prop_tree); + virtual ~n230_stream_manager(); + + rx_streamer::sptr get_rx_stream( + const uhd::stream_args_t &args); + + tx_streamer::sptr get_tx_stream( + const uhd::stream_args_t &args_); + + bool recv_async_msg( + async_metadata_t &async_metadata, + double timeout); + + void update_stream_states(); + + void update_rx_samp_rate( + const size_t dspno, const double rate); + + void update_tx_samp_rate( + const size_t dspno, const double rate); + + void update_tick_rate( + const double rate); + +private: + typedef transport::bounded_buffer<async_metadata_t> async_md_queue_t; + + struct rx_fc_cache_t + { + rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; + }; + + struct tx_fc_cache_t + { + tx_fc_cache_t(): + stream_channel(0), + device_channel(0), + last_seq_out(0), + last_seq_ack(0), + seq_queue(1){} + size_t stream_channel; + size_t device_channel; + size_t last_seq_out; + size_t last_seq_ack; + transport::bounded_buffer<size_t> seq_queue; + boost::shared_ptr<async_md_queue_t> async_queue; + boost::shared_ptr<async_md_queue_t> old_async_queue; + }; + + typedef boost::function<double(void)> tick_rate_retriever_t; + + void _handle_overflow(const size_t i); + + double _get_tick_rate(); + + static size_t _get_rx_flow_control_window( + size_t frame_size, size_t sw_buff_size); + + static void _handle_rx_flowctrl( + const sid_t& sid, + transport::zero_copy_if::sptr xport, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq); + + static void _handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> guts, + transport::zero_copy_if::sptr xport, + tick_rate_retriever_t get_tick_rate); + + static transport::managed_send_buffer::sptr _get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> guts, + transport::zero_copy_if::sptr xport, + size_t fc_pkt_window, + const double timeout); + + static size_t _get_tx_flow_control_window( + size_t payload_size, + size_t hw_buff_size); + + static void _cvita_hdr_unpack( + const boost::uint32_t *packet_buff, + transport::vrt::if_packet_info_t &if_packet_info); + + static void _cvita_hdr_pack( + boost::uint32_t *packet_buff, + transport::vrt::if_packet_info_t &if_packet_info); + + const n230_device_args_t _dev_args; + boost::shared_ptr<n230_resource_manager> _resource_mgr; + //TODO: Find a way to remove this dependency + boost::weak_ptr<property_tree> _tree; + + boost::mutex _stream_setup_mutex; + uhd::msg_task::sptr _async_task; + boost::shared_ptr<async_md_queue_t> _async_md_queue; + boost::weak_ptr<uhd::tx_streamer> _tx_streamers[fpga::NUM_RADIOS]; + boost::weak_ptr<uhd::rx_streamer> _rx_streamers[fpga::NUM_RADIOS]; + stream_args_t _tx_stream_cached_args[fpga::NUM_RADIOS]; + stream_args_t _rx_stream_cached_args[fpga::NUM_RADIOS]; + + static const boost::uint32_t HW_SEQ_NUM_MASK = 0xFFF; +}; + +}}} //namespace + +#endif //INCLUDED_N230_STREAM_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_uart.cpp b/host/lib/usrp/n230/n230_uart.cpp new file mode 100644 index 000000000..20936c303 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.cpp @@ -0,0 +1,131 @@ +// +// 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 "n230_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; + +namespace uhd { namespace usrp { namespace n230 { + +struct n230_uart_impl : n230_uart +{ + n230_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); + + //create a task to handle incoming packets + _recv_task = uhd::task::make(boost::bind(&n230_uart_impl::handle_recv, this)); + } + + 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::htonx(boost::uint32_t(_baud_div)); + packet_buff[packet_info.num_header_words32+1] = uhd::htonx(boost::uint32_t(ch)); + buff->commit(packet_info.num_packet_words32*sizeof(boost::uint32_t)); + } + + void write_uart(const std::string &buff) + { + static bool r_sent = false; + for (size_t i = 0; i < buff.size(); i++) + { + if (buff[i] == '\n' and not r_sent) this->send_char('\r'); + this->send_char(buff[i]); + r_sent = (buff[i] == '\r'); + } + } + + 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_recv(void) + { + managed_recv_buffer::sptr buff = _xport->get_recv_buff(); + if (not buff) + return; + 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_be(packet_buff, packet_info); + const char ch = char(uhd::ntohx(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; + uhd::task::sptr _recv_task; +}; + + +n230_uart::sptr n230_uart::make(zero_copy_if::sptr xport, const boost::uint32_t sid) +{ + return n230_uart::sptr(new n230_uart_impl(xport, sid)); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_uart.hpp b/host/lib/usrp/n230/n230_uart.hpp new file mode 100644 index 000000000..0bde12ab2 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.hpp @@ -0,0 +1,38 @@ +// +// 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_N230_UART_HPP +#define INCLUDED_N230_UART_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/serial.hpp> //uart iface +#include <uhd/utils/tasks.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +namespace uhd { namespace usrp { namespace n230 { + +class n230_uart: boost::noncopyable, public uhd::uart_iface +{ +public: + typedef boost::shared_ptr<n230_uart> sptr; + static sptr make(uhd::transport::zero_copy_if::sptr, const boost::uint32_t sid); + virtual void set_baud_divider(const double baud_div) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_UART_HPP */ diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt index 47344e841..6924ba3b0 100644 --- a/host/lib/usrp/usrp1/CMakeLists.txt +++ b/host/lib/usrp/usrp1/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP1 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_USRP1) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp index 4c3141d9e..b8d651694 100644 --- a/host/lib/usrp/usrp1/dboard_iface.cpp +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -63,6 +63,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace boost::assign; static const dboard_id_t tvrx_id(0x0040); @@ -106,12 +107,23 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void _set_pin_ctrl(unit_t, boost::uint16_t); void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); void _set_gpio_ddr(unit_t, boost::uint16_t); void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + + void set_command_time(const uhd::time_spec_t& t); + uhd::time_spec_t get_command_time(void); void write_i2c(boost::uint16_t, const byte_vector_t &); byte_vector_t read_i2c(boost::uint16_t, size_t); @@ -131,6 +143,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const fe_connection_t& fe_conn); private: usrp1_iface::sptr _iface; @@ -139,6 +152,8 @@ private: const usrp1_impl::dboard_slot_t _dboard_slot; const double &_master_clock_rate; const dboard_id_t _rx_dboard_id; + uhd::dict<unit_t, boost::uint16_t> _pin_ctrl, _gpio_out, _gpio_ddr; + uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > _atr_regs; }; /*********************************************************************** @@ -217,6 +232,65 @@ double usrp1_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ +template <typename T> +static T shadow_it(T &shadow, const T &value, const T &mask){ + shadow = (shadow & ~mask) | (value & mask); + return shadow; +} + +void usrp1_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_pin_ctrl(unit, shadow_it(_pin_ctrl[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_pin_ctrl(unit_t unit){ + return _pin_ctrl[unit]; +} + +void usrp1_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _set_atr_reg(unit, reg, shadow_it(_atr_regs[unit][reg], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return _atr_regs[unit][reg]; +} + +void usrp1_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_gpio_ddr(unit, shadow_it(_gpio_ddr[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_gpio_ddr(unit_t unit){ + return _gpio_ddr[unit]; +} + +void usrp1_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_gpio_out(unit, shadow_it(_gpio_out[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_gpio_out(unit_t unit){ + return _gpio_out[unit]; +} + +boost::uint32_t usrp1_dboard_iface::read_gpio(unit_t unit) +{ + boost::uint32_t out_value; + + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + out_value = _iface->peek32(1); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + out_value = _iface->peek32(2); + else + UHD_THROW_INVALID_CODE_PATH(); + + switch(unit) { + case UNIT_RX: + return (boost::uint32_t)((out_value >> 16) & 0x0000ffff); + case UNIT_TX: + return (boost::uint32_t)((out_value >> 0) & 0x0000ffff); + default: UHD_THROW_INVALID_CODE_PATH(); + } + UHD_ASSERT_THROW(false); +} + void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) { switch(unit) { @@ -232,6 +306,7 @@ void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_MASK_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } @@ -250,6 +325,7 @@ void usrp1_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_OE_2, 0xffff0000 | value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } @@ -268,34 +344,10 @@ void usrp1_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_IO_2, 0xffff0000 | value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } -void usrp1_dboard_iface::set_gpio_debug(unit_t, int) -{ - /* NOP */ -} - -boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit) -{ - boost::uint32_t out_value; - - if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) - out_value = _iface->peek32(1); - else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) - out_value = _iface->peek32(2); - else - UHD_THROW_INVALID_CODE_PATH(); - - switch(unit) { - case UNIT_RX: - return (boost::uint16_t)((out_value >> 16) & 0x0000ffff); - case UNIT_TX: - return (boost::uint16_t)((out_value >> 0) & 0x0000ffff); - } - UHD_ASSERT_THROW(false); -} - void usrp1_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value) { @@ -316,6 +368,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit, else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_RXVAL_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } else if (atr == ATR_REG_FULL_DUPLEX) { switch(unit) { @@ -331,6 +384,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit, else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_TXVAL_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } } @@ -361,6 +415,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit, return SPI_ENABLE_RX_B; else break; + default: + break; } UHD_THROW_INVALID_CODE_PATH(); } @@ -429,3 +485,23 @@ double usrp1_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); } + +/*********************************************************************** + * Unsupported + **********************************************************************/ + +void usrp1_dboard_iface::set_command_time(const uhd::time_spec_t&) +{ + throw uhd::not_implemented_error("timed command support not implemented"); +} + +uhd::time_spec_t usrp1_dboard_iface::get_command_time() +{ + throw uhd::not_implemented_error("timed command support not implemented"); +} + +void usrp1_dboard_iface::set_fe_connection(unit_t, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} + diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp index dbd5408e8..5e1a70a8f 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.cpp +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -209,13 +209,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ const fs_path mb_path = "/mboards/0"; _tree->create<std::string>(mb_path / "name").set("USRP1"); _tree->create<std::string>(mb_path / "load_eeprom") - .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create user-defined control objects //////////////////////////////////////////////////////////////////// _tree->create<std::pair<boost::uint8_t, boost::uint32_t> >(mb_path / "user" / "regs") - .subscribe(boost::bind(&usrp1_impl::set_reg, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_reg, this, _1)); //////////////////////////////////////////////////////////////////// // setup the mboard eeprom @@ -223,7 +223,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, USRP1_EEPROM_MAP_KEY); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects @@ -247,7 +247,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ } UHD_MSG(status) << boost::format("Using FPGA clock rate of %fMHz...") % (_master_clock_rate/1e6) << std::endl; _tree->create<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&usrp1_impl::update_tick_rate, this, _1)) + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tick_rate, this, _1)) .set(_master_clock_rate); //////////////////////////////////////////////////////////////////// @@ -260,13 +260,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)) + .set_coercer(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)) .set(0.0); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) - .publish(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)) + .add_coerced_subscriber(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) + .set_publisher(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)) .set(0.0); } @@ -281,18 +281,18 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); BOOST_FOREACH(const std::string &db, _dbc.keys()){ const fs_path rx_fe_path = mb_path / "rx_frontends" / db; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1)) + .set_coercer(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1)) + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1)) .set(true); } @@ -303,19 +303,19 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ for (size_t dspno = 0; dspno < get_num_ddcs(); dspno++){ fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this)); + .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this)); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default rate - .coerce(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this)); + .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this)); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd"); if (dspno == 0){ - //only subscribe the callback for dspno 0 since it will stream all dsps + //only add_coerced_subscriber the callback for dspno 0 since it will stream all dsps _tree->access<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); } } @@ -326,22 +326,22 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ for (size_t dspno = 0; dspno < get_num_ducs(); dspno++){ fs_path tx_dsp_path = mb_path / str(boost::format("tx_dsps/%u") % dspno); _tree->create<meta_range_t>(tx_dsp_path / "rate/range") - .publish(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this)); + .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this)); _tree->create<double>(tx_dsp_path / "rate/value") .set(1e6) //some default rate - .coerce(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1)); _tree->create<double>(tx_dsp_path / "freq/value") - .coerce(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); _tree->create<meta_range_t>(tx_dsp_path / "freq/range") - .publish(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this)); + .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this)); } //////////////////////////////////////////////////////////////////// // create time control objects //////////////////////////////////////////////////////////////////// _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) - .subscribe(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); + .set_publisher(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) + .add_coerced_subscriber(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(std::vector<std::string>(1, "internal")); _tree->create<std::vector<std::string> >(mb_path / "time_source/options").set(std::vector<std::string>(1, "none")); @@ -365,24 +365,23 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); //create a new dboard interface and manager - _dbc[db].dboard_iface = make_dboard_iface( + dboard_iface::sptr dboard_iface = make_dboard_iface( _iface, _dbc[db].codec, (db == "A")? DBOARD_SLOT_A : DBOARD_SLOT_B, _master_clock_rate, rx_db_eeprom.id ); - _tree->create<dboard_iface::sptr>(mb_path / "dboards" / db/ "iface").set(_dbc[db].dboard_iface); _dbc[db].dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dbc[db].dboard_iface, _tree->subtree(mb_path / "dboards" / db) + dboard_iface, _tree->subtree(mb_path / "dboards" / db) ); //init the subdev specs if we have a dboard (wont leave this loop empty) diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp index 012bc0794..da901bd6c 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.hpp +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -92,7 +92,6 @@ private: uhd::transport::usb_zero_copy::sptr _data_transport; struct db_container_type{ usrp1_codec_ctrl::sptr codec; - uhd::usrp::dboard_iface::sptr dboard_iface; uhd::usrp::dboard_manager::sptr dboard_manager; }; uhd::dict<std::string, db_container_type> _dbc; diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt index d9894adaf..edf77a654 100644 --- a/host/lib/usrp/usrp2/CMakeLists.txt +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP2 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_USRP2) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp index 7bb69c7b7..b4066898e 100644 --- a/host/lib/usrp/usrp2/dboard_iface.cpp +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -54,12 +54,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -71,6 +75,7 @@ public: std::vector<double> get_clock_rates(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const fe_connection_t& fe_conn); void write_spi( unit_t unit, @@ -149,18 +154,22 @@ usrp2_dboard_iface::~usrp2_dboard_iface(void){ * Clocks **********************************************************************/ void usrp2_dboard_iface::set_clock_rate(unit_t unit, double rate){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _clock_rates[unit] = rate; //set to shadow switch(unit){ case UNIT_RX: _clock_ctrl->set_rate_rx_dboard_clock(rate); return; case UNIT_TX: _clock_ctrl->set_rate_tx_dboard_clock(rate); return; + default: UHD_THROW_INVALID_CODE_PATH(); } } double usrp2_dboard_iface::get_clock_rate(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_rates[unit]; //get from shadow } std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit){ case UNIT_RX: return _clock_ctrl->get_rates_rx_dboard_clock(); case UNIT_TX: return _clock_ctrl->get_rates_tx_dboard_clock(); @@ -169,40 +178,56 @@ std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ } void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit){ - case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; - case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; + case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; + case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } -double usrp2_dboard_iface::get_codec_rate(unit_t){ +double usrp2_dboard_iface::get_codec_rate(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_ctrl->get_master_clock_rate(); } + /*********************************************************************** * GPIO **********************************************************************/ -void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void usrp2_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t usrp2_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void usrp2_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t usrp2_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void usrp2_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t usrp2_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void usrp2_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void usrp2_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t usrp2_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t usrp2_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -219,6 +244,7 @@ void usrp2_dboard_iface::write_spi( boost::uint32_t data, size_t num_bits ){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _spi_iface->write_spi(unit_to_spi_dev[unit], config, data, num_bits); } @@ -228,6 +254,7 @@ boost::uint32_t usrp2_dboard_iface::read_write_spi( boost::uint32_t data, size_t num_bits ){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _spi_iface->read_spi(unit_to_spi_dev[unit], config, data, num_bits); } @@ -250,6 +277,7 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ (UNIT_RX, SPI_SS_RX_DAC) (UNIT_TX, SPI_SS_TX_DAC) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _spi_iface->write_spi( unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, _dac_regs[unit].get_reg(), 24 @@ -257,6 +285,8 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ } void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; @@ -285,6 +315,8 @@ double usrp2_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which){ (UNIT_TX, SPI_SS_TX_ADC) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + //setup spi config args spi_config_t config; config.mosi_edge = spi_config_t::EDGE_FALL; @@ -320,3 +352,8 @@ void usrp2_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _wb_iface->set_time(t); } + +void usrp2_dboard_iface::set_fe_connection(unit_t, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 7b59dfaf1..b0c29392c 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -474,15 +474,15 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : //////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(_mbc[mb].iface->mb_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); //////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////// _mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface, _mbc[mb].spiface); _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) - .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); //////////////////////////////////////////////////////////////// // create codec control objects @@ -500,10 +500,10 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : _tree->create<std::string>(rx_codec_path / "name").set("ads62p44"); _tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5)); _tree->create<double>(rx_codec_path / "gains/digital/value") - .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); + .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); _tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05)); _tree->create<double>(rx_codec_path / "gains/fine/value") - .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); + .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); }break; case usrp2_iface::USRP2_REV3: @@ -550,7 +550,7 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); } } else @@ -563,9 +563,9 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : // and do the misc mboard sensors //////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked") - .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); + .set_publisher(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); + .set_publisher(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); //////////////////////////////////////////////////////////////// // create frontend control objects @@ -578,27 +578,27 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : ); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////// @@ -613,20 +613,20 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){ _mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) - .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////// @@ -637,17 +637,17 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : ); _mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) - .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp)); //setup dsp flow control const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20); @@ -670,22 +670,22 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)) .set("none"); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)) .set("internal"); std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo"); if (_mbc[mb].gps and _mbc[mb].gps->gps_detected()) clock_sources.push_back("gpsdo"); @@ -697,18 +697,18 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : case usrp2_iface::USRP_N200_R4: case usrp2_iface::USRP_N210_R4: _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1)); default: break; //otherwise, do not register } _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create user-defined control objects //////////////////////////////////////////////////////////////////// _mbc[mb].user = user_settings_core_200::make(_mbc[mb].wbiface, U2_REG_SR_ADDR(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1)); //////////////////////////////////////////////////////////////// // create dboard control objects @@ -726,32 +726,31 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); //create a new dboard interface and manager - _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface); _mbc[mb].dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _mbc[mb].dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1)); } } diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp index 07cd98b4c..47fcec657 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.hpp +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -102,7 +102,6 @@ private: uhd::transport::zero_copy_if::sptr tx_dsp_xport; uhd::transport::zero_copy_if::sptr fifo_ctrl_xport; uhd::usrp::dboard_manager::sptr dboard_manager; - uhd::usrp::dboard_iface::sptr dboard_iface; size_t rx_chan_occ, tx_chan_occ; mb_container_type(void): rx_chan_occ(0), tx_chan_occ(0){} }; diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index 3d6348eec..f8b129f89 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the X300 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_X300) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp diff --git a/host/lib/usrp/x300/x300_dboard_iface.cpp b/host/lib/usrp/x300/x300_dboard_iface.cpp index 502630109..884848624 100644 --- a/host/lib/usrp/x300/x300_dboard_iface.cpp +++ b/host/lib/usrp/x300/x300_dboard_iface.cpp @@ -45,17 +45,19 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); - void write_i2c(boost::uint16_t, const byte_vector_t &); byte_vector_t read_i2c(boost::uint16_t, size_t); @@ -78,6 +80,7 @@ public: boost::uint32_t data, size_t num_bits ); + void set_fe_connection(unit_t unit, const fe_connection_t& fe_conn); const x300_dboard_iface_config_t _config; uhd::dict<unit_t, ad5623_regs_t> _dac_regs; @@ -116,27 +119,6 @@ x300_dboard_iface::x300_dboard_iface(const x300_dboard_iface_config_t &config): this->set_clock_enabled(UNIT_RX, false); this->set_clock_enabled(UNIT_TX, false); - - - //some test code - /* - { - - this->write_aux_dac(UNIT_TX, AUX_DAC_A, .1); - this->write_aux_dac(UNIT_TX, AUX_DAC_B, 1); - this->write_aux_dac(UNIT_RX, AUX_DAC_A, 2); - this->write_aux_dac(UNIT_RX, AUX_DAC_B, 3); - while (1) - { - UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_A)); - UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_B)); - UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_A)); - UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_B)); - sleep(1); - } - } - */ - } x300_dboard_iface::~x300_dboard_iface(void) @@ -153,6 +135,8 @@ x300_dboard_iface::~x300_dboard_iface(void) **********************************************************************/ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + // Just return if the requested rate is already set if (std::fabs(_clock_rates[unit] - rate) < std::numeric_limits<double>::epsilon()) return; @@ -165,17 +149,21 @@ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) case UNIT_TX: _config.clock->set_dboard_rate(_config.which_tx_clk, rate); break; + default: + UHD_THROW_INVALID_CODE_PATH(); } _clock_rates[unit] = rate; //set to shadow } double x300_dboard_iface::get_clock_rate(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_rates[unit]; //get from shadow } std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit) { case UNIT_RX: @@ -189,6 +177,7 @@ std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit) { case UNIT_RX: @@ -200,57 +189,74 @@ void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) } } -double x300_dboard_iface::get_codec_rate(unit_t) +double x300_dboard_iface::get_codec_rate(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _config.clock->get_master_clock_rate(); } /*********************************************************************** * GPIO **********************************************************************/ -void x300_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) +void x300_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_pin_ctrl(unit, value); + _config.gpio->set_pin_ctrl(unit, value, mask); } -void x300_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) +boost::uint32_t x300_dboard_iface::get_pin_ctrl(unit_t unit) { - return _config.gpio->set_gpio_ddr(unit, value); + return _config.gpio->get_pin_ctrl(unit); } -void x300_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) +void x300_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_gpio_out(unit, value); + _config.gpio->set_atr_reg(unit, reg, value, mask); } -boost::uint16_t x300_dboard_iface::read_gpio(unit_t unit) +boost::uint32_t x300_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg) { - return _config.gpio->read_gpio(unit); + return _config.gpio->get_atr_reg(unit, reg); } -void x300_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value) +void x300_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_atr_reg(unit, atr, value); + _config.gpio->set_gpio_ddr(unit, value, mask); } -void x300_dboard_iface::set_gpio_debug(unit_t, int) +boost::uint32_t x300_dboard_iface::get_gpio_ddr(unit_t unit) { - throw uhd::not_implemented_error("no set_gpio_debug implemented"); + return _config.gpio->get_gpio_ddr(unit); +} + +void x300_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask) +{ + _config.gpio->set_gpio_out(unit, value, mask); +} + +boost::uint32_t x300_dboard_iface::get_gpio_out(unit_t unit) +{ + return _config.gpio->get_gpio_out(unit); +} + +boost::uint32_t x300_dboard_iface::read_gpio(unit_t unit) +{ + return _config.gpio->read_gpio(unit); } /*********************************************************************** * SPI **********************************************************************/ -#define toslaveno(unit) \ - (((unit) == dboard_iface::UNIT_TX)? _config.tx_spi_slaveno : _config.rx_spi_slaveno) - void x300_dboard_iface::write_spi( unit_t unit, const spi_config_t &config, boost::uint32_t data, size_t num_bits ){ - _config.spi->write_spi(toslaveno(unit), config, data, num_bits); + boost::uint32_t slave = 0; + if (unit == UNIT_TX) slave |= _config.tx_spi_slaveno; + if (unit == UNIT_RX) slave |= _config.rx_spi_slaveno; + + _config.spi->write_spi(int(slave), config, data, num_bits); } boost::uint32_t x300_dboard_iface::read_write_spi( @@ -259,7 +265,10 @@ boost::uint32_t x300_dboard_iface::read_write_spi( boost::uint32_t data, size_t num_bits ){ - return _config.spi->read_spi(toslaveno(unit), config, data, num_bits); + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + return _config.spi->read_spi( + (unit==dboard_iface::UNIT_TX)?_config.tx_spi_slaveno:_config.rx_spi_slaveno, + config, data, num_bits); } /*********************************************************************** @@ -284,6 +293,7 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit) (UNIT_RX, DB_RX_LSDAC_SEN) (UNIT_TX, DB_TX_LSDAC_SEN) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _config.spi->write_spi( unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, _dac_regs[unit].get_reg(), 24 @@ -292,6 +302,8 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit) void x300_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; @@ -321,6 +333,8 @@ double x300_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which) (UNIT_TX, DB_TX_LSADC_SEN) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + //setup spi config args spi_config_t config; config.mosi_edge = spi_config_t::EDGE_FALL; @@ -356,3 +370,12 @@ void x300_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _config.cmd_time_ctrl->set_time(t); } + +void x300_dboard_iface::set_fe_connection(unit_t unit, const fe_connection_t& fe_conn) +{ + if (unit == UNIT_RX) { + _config.rx_dsp->set_mux(fe_conn); + } else { + throw uhd::not_implemented_error("frontend connection not configurable for TX"); + } +} diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index 549fc9dfa..6039ee376 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -33,7 +33,7 @@ extern "C" { #define X300_REVISION_MIN 2 #define X300_FW_COMPAT_MAJOR 4 #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 19 +#define X300_FPGA_COMPAT_MAJOR 20 //shared memory sections - in between the stack and the program space #define X300_FW_SHMEM_BASE 0x6000 diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index c13c2ac07..b9d4e56b1 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -47,23 +47,38 @@ using namespace uhd; using namespace uhd::usrp; using namespace uhd::transport; using namespace uhd::niusrprio; +using namespace uhd::usrp::gpio_atr; using namespace uhd::usrp::x300; namespace asio = boost::asio; -/*********************************************************************** - * Discovery over the udp and pcie transport - **********************************************************************/ +static bool has_dram_buff(wb_iface::sptr zpu_ctrl) { + bool dramR0 = dma_fifo_core_3000::check( + zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO0), SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO0)); + bool dramR1 = dma_fifo_core_3000::check( + zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO1), SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO1)); + return (dramR0 and dramR1); +} + static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { - //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM - //HGS = {0:1G, 1:10G} w/ SRAM, XGS = {0:10G, 1:10G} w/ SRAM + //Possible options: + //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM + //1GS = {0:1G, 1:1G} w/ SRAM, HGS = {0:1G, 1:10G} w/ SRAM, XGS = {0:10G, 1:10G} w/ SRAM - //In the default configuration, UHD does not support the HG and XG images so - //they are never autodetected. + std::string option; bool eth0XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE0)) == 0x1); bool eth1XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE1)) == 0x1); - return (eth0XG && eth1XG) ? "XGS" : (eth1XG ? "HGS" : "1G"); + option = (eth0XG && eth1XG) ? "XG" : (eth1XG ? "HG" : "1G"); + + if (not has_dram_buff(zpu_ctrl)) { + option += "S"; + } + return option; } +/*********************************************************************** + * Discovery over the udp and pcie transport + **********************************************************************/ + //@TODO: Refactor the find functions to collapse common code for ethernet and PCIe static device_addrs_t x300_find_with_addr(const device_addr_t &hint) { @@ -348,6 +363,8 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_nam if ((i & 0x1fff) == 0) UHD_MSG(status) << "." << std::flush; } + //Wait for fimrware to reboot. 3s is an upper bound + boost::this_thread::sleep(boost::posix_time::milliseconds(3000)); UHD_MSG(status) << " done!" << std::endl; } @@ -561,7 +578,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) const mboard_eeprom_t mb_eeprom(*eeprom16, "X300"); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1)); bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); if (recover_mb_eeprom) { @@ -684,7 +701,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) // create clock properties //////////////////////////////////////////////////////////////////// _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)); + .set_publisher(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)); _tree->create<time_spec_t>(mb_path / "time" / "cmd"); @@ -713,7 +730,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) BOOST_FOREACH(const std::string &name, mb.gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, mb.gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, mb.gps, name)); } } else @@ -730,6 +747,36 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) } //////////////////////////////////////////////////////////////////// + // DRAM FIFO initialization + //////////////////////////////////////////////////////////////////// + mb.has_dram_buff = has_dram_buff(mb.zpu_ctrl); + if (mb.has_dram_buff) { + for (size_t i = 0; i < mboard_members_t::NUM_RADIOS; i++) { + static const size_t NUM_REGS = 8; + mb.dram_buff_ctrl[i] = dma_fifo_core_3000::make( + mb.zpu_ctrl, + SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO0+(i*NUM_REGS)), + SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO0+i)); + mb.dram_buff_ctrl[i]->resize(X300_DRAM_FIFO_SIZE * i, X300_DRAM_FIFO_SIZE); + + if (mb.dram_buff_ctrl[i]->ext_bist_supported()) { + UHD_MSG(status) << boost::format("Running BIST for DRAM FIFO %d... ") % i; + boost::uint32_t bisterr = mb.dram_buff_ctrl[i]->run_bist(); + if (bisterr != 0) { + throw uhd::runtime_error(str(boost::format("DRAM FIFO BIST failed! (code: %d)\n") % bisterr)); + } else { + double throughput = mb.dram_buff_ctrl[i]->get_bist_throughput(X300_BUS_CLOCK_RATE); + UHD_MSG(status) << (boost::format("pass (Throughput: %.1fMB/s)") % (throughput/1e6)) << std::endl; + } + } else { + if (mb.dram_buff_ctrl[i]->run_bist() != 0) { + throw uhd::runtime_error(str(boost::format("DRAM FIFO %d BIST failed!\n") % i)); + } + } + } + } + + //////////////////////////////////////////////////////////////////// // setup radios //////////////////////////////////////////////////////////////////// this->setup_radio(mb_i, "A", dev_addr); @@ -750,40 +797,40 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //////////////////////////////////////////////////////////////////// // front panel gpio //////////////////////////////////////////////////////////////////// - mb.fp_gpio = gpio_core_200::make(mb.radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); + mb.fp_gpio = gpio_atr_3000::make(mb.radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) { _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second) .set(0) - .subscribe(boost::bind(&x300_impl::set_fp_gpio, this, mb.fp_gpio, attr.first, _1)); + .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, mb.fp_gpio, attr.first, _1)); } _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .publish(boost::bind(&x300_impl::get_fp_gpio, this, mb.fp_gpio)); + .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, mb.fp_gpio)); //////////////////////////////////////////////////////////////////// // register the time keepers - only one can be the highlander //////////////////////////////////////////////////////////////////// _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, mb.radio_perifs[0].time64)) - .subscribe(boost::bind(&x300_impl::sync_times, this, mb, _1)) + .set_publisher(boost::bind(&time_core_3000::get_time_now, mb.radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&x300_impl::sync_times, this, mb, _1)) .set(0.0); _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, mb.radio_perifs[0].time64)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[0].time64, _1)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[1].time64, _1)); + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, mb.radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[0].time64, _1)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[1].time64, _1)); //////////////////////////////////////////////////////////////////// // setup time sources and properties //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "time_source" / "value") .set("internal") - .subscribe(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1)); static const std::vector<std::string> time_sources = boost::assign::list_of("internal")("external")("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); //setup the time output, default to ON _tree->create<bool>(mb_path / "time_source" / "output") - .subscribe(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1)) + .add_coerced_subscriber(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1)) .set(true); //////////////////////////////////////////////////////////////////// @@ -791,7 +838,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "clock_source" / "value") .set("internal") - .subscribe(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); static const std::vector<std::string> clock_source_options = boost::assign::list_of("internal")("external")("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_source_options); @@ -807,12 +854,12 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //setup the clock output, default to ON _tree->create<bool>(mb_path / "clock_source" / "output") - .subscribe(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); + .add_coerced_subscriber(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); //initialize tick rate (must be done before setting time) _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&x300_impl::set_tick_rate, this, boost::ref(mb), _1)) - .subscribe(boost::bind(&x300_impl::update_tick_rate, this, boost::ref(mb), _1)) + .add_coerced_subscriber(boost::bind(&x300_impl::set_tick_rate, this, boost::ref(mb), _1)) + .add_coerced_subscriber(boost::bind(&x300_impl::update_tick_rate, this, boost::ref(mb), _1)) .set(mb.clock->get_master_clock_rate()); //////////////////////////////////////////////////////////////////// @@ -822,15 +869,15 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&x300_impl::update_subdev_spec, this, "rx", mb_i, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::update_subdev_spec, this, "rx", mb_i, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&x300_impl::update_subdev_spec, this, "tx", mb_i, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::update_subdev_spec, this, "tx", mb_i, _1)); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") - .publish(boost::bind(&x300_impl::get_ref_locked, this, mb)); + .set_publisher(boost::bind(&x300_impl::get_ref_locked, this, mb)); //////////////////////////////////////////////////////////////////// // do some post-init tasks @@ -931,7 +978,8 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, con perif.spi = spi_core_3000::make(perif.ctrl, radio::sr_addr(radio::SPI), radio::RB32_SPI); perif.adc = x300_adc_ctrl::make(perif.spi, DB_ADC_SEN); perif.dac = x300_dac_ctrl::make(perif.spi, DB_DAC_SEN, mb.clock->get_master_clock_rate()); - perif.leds = gpio_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::LEDS)); + perif.leds = gpio_atr_3000::make_write_only(perif.ctrl, radio::sr_addr(radio::LEDS)); + perif.leds->set_atr_mode(MODE_ATR, 0xFFFFFFFF); perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); @@ -941,9 +989,14 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, con perif.framer = rx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_CTRL)); perif.ddc = rx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_DSP)); perif.ddc->set_link_rate(10e9/8); //whatever - perif.deframer = tx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_CTRL)); + perif.ddc->set_tick_rate(mb.clock->get_master_clock_rate()); + //The DRAM FIFO is treated as in internal radio FIFO for flow control purposes + tx_vita_core_3000::fc_monitor_loc fc_loc = + mb.has_dram_buff ? tx_vita_core_3000::FC_PRE_FIFO : tx_vita_core_3000::FC_PRE_RADIO; + perif.deframer = tx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_CTRL), fc_loc); perif.duc = tx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_DSP)); perif.duc->set_link_rate(10e9/8); //whatever + perif.duc->set_tick_rate(mb.clock->get_master_clock_rate()); //////////////////////////////////////////////////////////////////// // create time control objects @@ -958,7 +1011,7 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, con self_cal_adc_capture_delay(mb, radio_index, dev_addr.has_key("self_cal_adc_delay")); _tree->access<time_spec_t>(mb_path / "time" / "cmd") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); //////////////////////////////////////////////////////////////// // create codec control objects @@ -970,7 +1023,7 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, con _tree->create<meta_range_t>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); _tree->create<double>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "value") - .subscribe(boost::bind(&x300_adc_ctrl::set_gain, perif.adc, _1)).set(0); + .add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, perif.adc, _1)).set(0); //////////////////////////////////////////////////////////////////// // front end corrections @@ -984,10 +1037,10 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, con const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % radio_index); perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->access<double>(rx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&x300_impl::update_rx_samp_rate, this, boost::ref(mb), radio_index, _1)) + .add_coerced_subscriber(boost::bind(&x300_impl::update_rx_samp_rate, this, boost::ref(mb), radio_index, _1)) ; _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); //////////////////////////////////////////////////////////////////// // connect tx dsp control objects @@ -995,7 +1048,7 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, con const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % radio_index); perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->access<double>(tx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&x300_impl::update_tx_samp_rate, this, boost::ref(mb), radio_index, _1)) + .add_coerced_subscriber(boost::bind(&x300_impl::update_tx_samp_rate, this, boost::ref(mb), radio_index, _1)) ; //////////////////////////////////////////////////////////////////// @@ -1005,54 +1058,53 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, con const size_t j = (slot_name == "B")? 0x2 : 0x0; _tree->create<dboard_eeprom_t>(db_path / "rx_eeprom") .set(mb.db_eeproms[X300_DB0_RX_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_RX_EEPROM | j), _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_RX_EEPROM | j), _1)); _tree->create<dboard_eeprom_t>(db_path / "tx_eeprom") .set(mb.db_eeproms[X300_DB0_TX_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_TX_EEPROM | j), _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_TX_EEPROM | j), _1)); _tree->create<dboard_eeprom_t>(db_path / "gdb_eeprom") .set(mb.db_eeproms[X300_DB0_GDB_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_GDB_EEPROM | j), _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_GDB_EEPROM | j), _1)); //create a new dboard interface x300_dboard_iface_config_t db_config; - db_config.gpio = gpio_core_200::make(perif.ctrl, radio::sr_addr(radio::GPIO), radio::RB32_GPIO); + db_config.gpio = db_gpio_atr_3000::make(perif.ctrl, radio::sr_addr(radio::GPIO), radio::RB32_GPIO); db_config.spi = perif.spi; db_config.rx_spi_slaveno = DB_RX_SEN; db_config.tx_spi_slaveno = DB_TX_SEN; db_config.i2c = mb.zpu_i2c; db_config.clock = mb.clock; + db_config.rx_dsp = mb.radio_perifs[radio_index].ddc; db_config.which_rx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; db_config.which_tx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; db_config.dboard_slot = (slot_name == "A")? 0 : 1; db_config.cmd_time_ctrl = perif.ctrl; - _dboard_ifaces[db_path] = x300_make_dboard_iface(db_config); //create a new dboard manager - _tree->create<dboard_iface::sptr>(db_path / "iface").set(_dboard_ifaces[db_path]); _dboard_managers[db_path] = dboard_manager::make( mb.db_eeproms[X300_DB0_RX_EEPROM | j].id, mb.db_eeproms[X300_DB0_TX_EEPROM | j].id, mb.db_eeproms[X300_DB0_GDB_EEPROM | j].id, - _dboard_ifaces[db_path], + x300_make_dboard_iface(db_config), _tree->subtree(db_path) ); //now that dboard is created -- register into rx antenna event const std::string fe_name = _tree->list(db_path / "rx_frontends").front(); _tree->access<std::string>(db_path / "rx_frontends" / fe_name / "antenna" / "value") - .subscribe(boost::bind(&x300_impl::update_atr_leds, this, mb.radio_perifs[radio_index].leds, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::update_atr_leds, this, mb.radio_perifs[radio_index].leds, _1)); this->update_atr_leds(mb.radio_perifs[radio_index].leds, ""); //init anyway, even if never called //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = db_path / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&x300_impl::set_tx_fe_corrections, this, mb_path, slot_name, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_tx_fe_corrections, this, mb_path, slot_name, _1)); } const fs_path db_rx_fe_path = db_path / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&x300_impl::set_rx_fe_corrections, this, mb_path, slot_name, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_rx_fe_corrections, this, mb_path, slot_name, _1)); } } @@ -1144,7 +1196,7 @@ x300_impl::both_xports_t x300_impl::make_transport( * connection type.*/ size_t eth_data_rec_frame_size = 0; - if (mb.loaded_fpga_image == "HGS") { + if (mb.loaded_fpga_image.substr(0,2) == "HG") { if (mb.router_dst_here == X300_XB_DST_E0) { eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_1GIGE); @@ -1152,7 +1204,7 @@ x300_impl::both_xports_t x300_impl::make_transport( eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE); } - } else if (mb.loaded_fpga_image == "XGS") { + } else if (mb.loaded_fpga_image.substr(0,2) == "XG") { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE); } @@ -1296,16 +1348,16 @@ boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t return sid; } -void x300_impl::update_atr_leds(gpio_core_200_32wo::sptr leds, const std::string &rx_ant) +void x300_impl::update_atr_leds(gpio_atr_3000::sptr leds, const std::string &rx_ant) { const bool is_txrx = (rx_ant == "TX/RX"); const int rx_led = (1 << 2); const int tx_led = (1 << 1); const int txrx_led = (1 << 0); - leds->set_atr_reg(dboard_iface::ATR_REG_IDLE, 0); - leds->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); - leds->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_led); - leds->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, rx_led | tx_led); + leds->set_atr_reg(ATR_REG_IDLE, 0); + leds->set_atr_reg(ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); + leds->set_atr_reg(ATR_REG_TX_ONLY, tx_led); + leds->set_atr_reg(ATR_REG_FULL_DUPLEX, rx_led | tx_led); } void x300_impl::set_tick_rate(mboard_members_t &mb, const double rate) @@ -1315,7 +1367,6 @@ void x300_impl::set_tick_rate(mboard_members_t &mb, const double rate) perif.time64->set_tick_rate(rate); perif.framer->set_tick_rate(rate); perif.ddc->set_tick_rate(rate); - perif.deframer->set_tick_rate(rate); perif.duc->set_tick_rate(rate); } } @@ -1518,30 +1569,6 @@ void x300_impl::set_mb_eeprom(i2c_iface::sptr i2c, const mboard_eeprom_t &mb_eep } /*********************************************************************** - * front-panel GPIO - **********************************************************************/ - -boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void x300_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value) -{ - switch (attr) - { - case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); - case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); - case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); - case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); - case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); - case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); - case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); - default: UHD_THROW_INVALID_CODE_PATH(); - } -} - -/*********************************************************************** * claimer logic **********************************************************************/ diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index e04b06d65..dd94708b7 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -41,7 +41,8 @@ #include "radio_ctrl_core_3000.hpp" #include "rx_frontend_core_200.hpp" #include "tx_frontend_core_200.hpp" -#include "gpio_core_200.hpp" +#include "gpio_atr_3000.hpp" +#include "dma_fifo_core_3000.hpp" #include <boost/weak_ptr.hpp> #include <uhd/usrp/gps_ctrl.hpp> #include <uhd/usrp/mboard_eeprom.hpp> @@ -57,8 +58,11 @@ static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; //Hz static const double X300_BUS_CLOCK_RATE = 166.666667e6; //Hz -static const size_t X300_TX_HW_BUFF_SIZE = 520*1024; //512K SRAM buffer + 8K 2Clk FIFO -static const size_t X300_TX_FC_RESPONSE_FREQ = 8; //per flow-control window +static const size_t X300_TX_HW_BUFF_SIZE_SRAM = 520*1024; //512K SRAM buffer + 8K 2Clk FIFO +static const size_t X300_TX_FC_RESPONSE_FREQ_SRAM = 8; //per flow-control window +static const size_t X300_TX_HW_BUFF_SIZE_DRAM = 128*1024; +static const size_t X300_TX_FC_RESPONSE_FREQ_DRAM = 32; +static const boost::uint32_t X300_DRAM_FIFO_SIZE = 32*1024*1024; static const size_t X300_RX_SW_BUFF_SIZE_ETH = 0x2000000;//32MiB For an ~8k frame size any size >32MiB is just wasted buffer space static const size_t X300_RX_SW_BUFF_SIZE_ETH_MACOS = 0x100000; //1Mib @@ -124,12 +128,13 @@ enum struct x300_dboard_iface_config_t { - gpio_core_200::sptr gpio; + uhd::usrp::gpio_atr::db_gpio_atr_3000::sptr gpio; spi_core_3000::sptr spi; size_t rx_spi_slaveno; size_t tx_spi_slaveno; i2c_core_100_wb32::sptr i2c; x300_clock_ctrl::sptr clock; + rx_dsp_core_3000::sptr rx_dsp; x300_clock_which_t which_rx_clk; x300_clock_which_t which_tx_clk; boost::uint8_t dboard_slot; @@ -186,7 +191,7 @@ private: rx_dsp_core_3000::sptr ddc; tx_vita_core_3000::sptr deframer; tx_dsp_core_3000::sptr duc; - gpio_core_200_32wo::sptr leds; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr leds; rx_frontend_core_200::sptr rx_fe; tx_frontend_core_200::sptr tx_fe; //Registers @@ -227,10 +232,14 @@ private: return slot_name == "A" ? 0 : 1; } + bool has_dram_buff; + dma_fifo_core_3000::sptr dram_buff_ctrl[NUM_RADIOS]; + + //other perifs on mboard x300_clock_ctrl::sptr clock; uhd::gps_ctrl::sptr gps; - gpio_core_200::sptr fp_gpio; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr fp_gpio; uhd::usrp::x300::fw_regmap_t::sptr fw_regmap; @@ -328,7 +337,6 @@ private: //////////////////////////////////////////////////////////////////// uhd::dict<std::string, uhd::usrp::dboard_manager::sptr> _dboard_managers; - uhd::dict<std::string, uhd::usrp::dboard_iface::sptr> _dboard_ifaces; void set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); void set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); @@ -367,9 +375,7 @@ private: void check_fw_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); void check_fpga_compat(const uhd::fs_path &mb_path, const mboard_members_t &members); - void update_atr_leds(gpio_core_200_32wo::sptr, const std::string &ant); - boost::uint32_t get_fp_gpio(gpio_core_200::sptr); - void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t); + void update_atr_leds(uhd::usrp::gpio_atr::gpio_atr_3000::sptr, const std::string &ant); void self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status = false); double self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay = false); diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp index e3515af0c..a4621c89f 100644 --- a/host/lib/usrp/x300/x300_io_impl.cpp +++ b/host/lib/usrp/x300/x300_io_impl.cpp @@ -106,17 +106,16 @@ void x300_impl::update_subdev_spec(const std::string &tx_rx, const size_t mb_i, chan_to_dsp_map[i] = radio_idx; //extract connection - const std::string conn = _tree->access<std::string>(mb_root / "dboards" / spec[i].db_name / (tx_rx + "_frontends") / spec[i].sd_name / "connection").get(); - + const fs_path fe_path(mb_root / "dboards" / spec[i].db_name / (tx_rx + "_frontends") / spec[i].sd_name); + const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); if (tx_rx == "tx") { //swap condition _mb[mb_i].radio_perifs[radio_idx].tx_fe->set_mux(conn); } else { - //swap condition - const bool fe_swapped = (conn == "QI" or conn == "Q"); - _mb[mb_i].radio_perifs[radio_idx].ddc->set_mux(conn, fe_swapped); - //see usrp/io_impl.cpp if multiple DSPs share the frontend: - _mb[mb_i].radio_perifs[radio_idx].rx_fe->set_mux(fe_swapped); + double if_freq = (_tree->exists(fe_path / "if_freq/value")) ? + _tree->access<double>(fe_path / "if_freq/value").get() : 0.0; + _mb[mb_i].radio_perifs[radio_idx].ddc->set_mux(usrp::fe_connection_t(conn, if_freq)); + _mb[mb_i].radio_perifs[radio_idx].rx_fe->set_mux(false); } } @@ -216,9 +215,10 @@ struct x300_tx_fc_guts_t * FC credit we have is C = F + M - N (i.e. we can send C more packets * before getting another ack). */ -static size_t get_tx_flow_control_window(size_t frame_size, const device_addr_t& tx_args) +static size_t get_tx_flow_control_window(size_t frame_size, const bool dram_buff, const device_addr_t& tx_args) { - double hw_buff_size = tx_args.cast<double>("send_buff_size", X300_TX_HW_BUFF_SIZE); + double default_buff_size = dram_buff ? X300_TX_HW_BUFF_SIZE_DRAM : X300_TX_HW_BUFF_SIZE_SRAM; + double hw_buff_size = tx_args.cast<double>("send_buff_size", default_buff_size); size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / frame_size); if (window_in_pkts == 0) { throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); @@ -580,8 +580,9 @@ tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_) perif.duc->setup(args); //flow control setup - size_t fc_window = get_tx_flow_control_window(xport.send->get_send_frame_size(), device_addr); //In packets - const size_t fc_handle_window = std::max<size_t>(1, fc_window/X300_TX_FC_RESPONSE_FREQ); + size_t fc_window = get_tx_flow_control_window(xport.send->get_send_frame_size(), mb.has_dram_buff, device_addr); //In packets + const size_t fc_handle_window = std::max<size_t>(1, + fc_window/ (mb.has_dram_buff ? X300_TX_FC_RESPONSE_FREQ_DRAM : X300_TX_FC_RESPONSE_FREQ_SRAM)); UHD_LOG << "TX Flow Control Window = " << fc_window << ", TX Flow Control Handler Window = " << fc_handle_window << std::endl; diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index 3e0966c83..de3a3161a 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -42,7 +42,7 @@ static const uint32_t TIME = 128; static const uint32_t RX_DSP = 144; static const uint32_t TX_DSP = 184; static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 200; +static const uint32_t FP_GPIO = 201; static const uint32_t RX_FRONT = 208; static const uint32_t TX_FRONT = 216; @@ -77,6 +77,8 @@ localparam ZPU_SR_XB_LOCAL = 03; localparam ZPU_SR_SPI = 32; localparam ZPU_SR_ETHINT0 = 40; localparam ZPU_SR_ETHINT1 = 56; +localparam ZPU_SR_DRAM_FIFO0 = 72; +localparam ZPU_SR_DRAM_FIFO1 = 80; //reset bits #define ZPU_SR_SW_RST_ETH_PHY (1<<0) @@ -89,6 +91,8 @@ localparam ZPU_RB_CLK_STATUS = 3; localparam ZPU_RB_COMPAT_NUM = 6; localparam ZPU_RB_ETH_TYPE0 = 4; localparam ZPU_RB_ETH_TYPE1 = 5; +localparam ZPU_RB_DRAM_FIFO0 = 10; +localparam ZPU_RB_DRAM_FIFO1 = 11; //spi slaves on radio #define DB_DAC_SEN (1 << 7) diff --git a/host/lib/usrp_clock/octoclock/CMakeLists.txt b/host/lib/usrp_clock/octoclock/CMakeLists.txt index a54d27c52..d2b70e356 100644 --- a/host/lib/usrp_clock/octoclock/CMakeLists.txt +++ b/host/lib/usrp_clock/octoclock/CMakeLists.txt @@ -18,8 +18,6 @@ ######################################################################## # Conditionally configure the OctoClock support ######################################################################## -LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_OCTOCLOCK) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_eeprom.cpp diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp index 22c26b42c..cd851976f 100644 --- a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp +++ b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp @@ -236,21 +236,21 @@ octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){ _oc_dict[oc].eeprom = octoclock_eeprom_t(_oc_dict[oc].ctrl_xport); _tree->create<octoclock_eeprom_t>(oc_path / "eeprom") .set(_oc_dict[oc].eeprom) - .subscribe(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1)); + .add_coerced_subscriber(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1)); //////////////////////////////////////////////////////////////////// // Initialize non-GPSDO sensors //////////////////////////////////////////////////////////////////// _tree->create<boost::uint32_t>(oc_path / "time") - .publish(boost::bind(&octoclock_impl::_get_time, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_get_time, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/ext_ref_detected") - .publish(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/gps_detected") - .publish(boost::bind(&octoclock_impl::_gps_detected, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_gps_detected, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/using_ref") - .publish(boost::bind(&octoclock_impl::_which_ref, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_which_ref, this, oc)); _tree->create<sensor_value_t>(oc_path / "sensors/switch_pos") - .publish(boost::bind(&octoclock_impl::_switch_pos, this, oc)); + .set_publisher(boost::bind(&octoclock_impl::_switch_pos, this, oc)); //////////////////////////////////////////////////////////////////// // Check reference and GPSDO @@ -270,7 +270,7 @@ octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){ if(_oc_dict[oc].gps and _oc_dict[oc].gps->gps_detected()){ BOOST_FOREACH(const std::string &name, _oc_dict[oc].gps->get_sensors()){ _tree->create<sensor_value_t>(oc_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name)); } } else{ diff --git a/host/lib/utils/log.cpp b/host/lib/utils/log.cpp index 8d42af9c4..4e58ce894 100644 --- a/host/lib/utils/log.cpp +++ b/host/lib/utils/log.cpp @@ -1,5 +1,5 @@ // -// Copyright 2012,2014 Ettus Research LLC +// Copyright 2012,2014,2016 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 @@ -24,19 +24,7 @@ #include <boost/thread/mutex.hpp> #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/thread/locks.hpp> -#ifdef BOOST_MSVC -//whoops! https://svn.boost.org/trac/boost/ticket/5287 -//enjoy this useless dummy class instead -namespace boost{ namespace interprocess{ - struct file_lock{ - file_lock(const char * = NULL){} - void lock(void){} - void unlock(void){} - }; -}} //namespace -#else #include <boost/interprocess/sync/file_lock.hpp> -#endif #include <fstream> #include <cctype> diff --git a/host/lib/utils/msg.cpp b/host/lib/utils/msg.cpp index de98ada64..95879a116 100644 --- a/host/lib/utils/msg.cpp +++ b/host/lib/utils/msg.cpp @@ -79,6 +79,8 @@ void uhd::msg::register_handler(const handler_t &handler){ } static void default_msg_handler(uhd::msg::type_t type, const std::string &msg){ + static boost::mutex msg_mutex; + boost::mutex::scoped_lock lock(msg_mutex); switch(type){ case uhd::msg::fastpath: std::cerr << msg << std::flush; diff --git a/host/lib/utils/paths.cpp b/host/lib/utils/paths.cpp index 9cbc83062..38839c8d4 100644 --- a/host/lib/utils/paths.cpp +++ b/host/lib/utils/paths.cpp @@ -17,7 +17,6 @@ #include <uhd/config.hpp> #include <uhd/exception.hpp> -#include <uhd/transport/nirio/nifpga_lvbitx.h> #include <uhd/utils/paths.hpp> #include <boost/algorithm/string.hpp> diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 8b12c961f..c5f25913e 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -45,6 +45,8 @@ SET(test_sources subdev_spec_test.cpp time_spec_test.cpp vrt_test.cpp + expert_test.cpp + fe_conn_test.cpp ) #turn each test cpp file into an executable with an int main() function @@ -77,3 +79,5 @@ IF(MSVC OR APPLE OR LINUX) ADD_LIBRARY(module_test MODULE module_test.cpp) TARGET_LINK_LIBRARIES(module_test uhd) ENDIF() + +ADD_SUBDIRECTORY(devtest) diff --git a/host/tests/convert_test.cpp b/host/tests/convert_test.cpp index d71d756dd..8d359d2e2 100644 --- a/host/tests/convert_test.cpp +++ b/host/tests/convert_test.cpp @@ -417,16 +417,16 @@ BOOST_AUTO_TEST_CASE(test_convert_types_sc16_and_sc8){ } /*********************************************************************** - * Test short conversion + * Test u8 conversion **********************************************************************/ static void test_convert_types_u8( size_t nsamps, convert::id_type &id ){ //fill the input samples std::vector<boost::uint8_t> input(nsamps), output(nsamps); - //BOOST_FOREACH(boost::uint8_t &in, input) in = boost::uint8_t(std::rand() & 0xFF); - boost::uint32_t d = 48; - BOOST_FOREACH(boost::uint8_t &in, input) in = d++; + BOOST_FOREACH(boost::uint8_t &in, input) in = boost::uint8_t(std::rand() & 0xFF); + //boost::uint32_t d = 48; + //BOOST_FOREACH(boost::uint8_t &in, input) in = d++; //run the loopback and test convert::id_type in_id = id; @@ -455,3 +455,158 @@ BOOST_AUTO_TEST_CASE(test_convert_types_u8_and_u8){ test_convert_types_u8(nsamps, id); } } + +/*********************************************************************** + * Test s8 conversion + **********************************************************************/ +static void test_convert_types_s8( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector<boost::int8_t> input(nsamps), output(nsamps); + BOOST_FOREACH(boost::int8_t &in, input) in = boost::int8_t(std::rand() & 0xFF); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_s8_and_s8){ + convert::id_type id; + id.input_format = "s8"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "s8_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s8(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "s8_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s8(nsamps, id); + } +} + +/*********************************************************************** + * Test s16 conversion + **********************************************************************/ +static void test_convert_types_s16( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector<boost::int16_t> input(nsamps), output(nsamps); + BOOST_FOREACH(boost::int16_t &in, input) in = boost::int16_t(std::rand() & 0xFFFF); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_s16_and_s16){ + convert::id_type id; + id.input_format = "s16"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "s16_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s16(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "s16_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_s16(nsamps, id); + } +} + +/*********************************************************************** + * Test fc32 -> fc32 conversion + **********************************************************************/ +static void test_convert_types_fc32( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector< std::complex<float> > input(nsamps), output(nsamps); + BOOST_FOREACH(fc32_t &in, input) in = fc32_t( + (std::rand()/float(RAND_MAX/2)) - 1, + (std::rand()/float(RAND_MAX/2)) - 1 + ); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_fc32_and_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "fc32_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_fc32(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "fc32_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_fc32(nsamps, id); + } +} + +/*********************************************************************** + * Test f32 -> f32 conversion + **********************************************************************/ +static void test_convert_types_f32( + size_t nsamps, convert::id_type &id +){ + //fill the input samples + std::vector<float> input(nsamps), output(nsamps); + BOOST_FOREACH(float &in, input) in = float((std::rand()/float(RAND_MAX/2)) - 1); + + //run the loopback and test + convert::id_type in_id = id; + convert::id_type out_id = id; + std::swap(out_id.input_format, out_id.output_format); + std::swap(out_id.num_inputs, out_id.num_outputs); + loopback(nsamps, in_id, out_id, input, output); + BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end()); +} + +BOOST_AUTO_TEST_CASE(test_convert_types_f32_and_f32){ + convert::id_type id; + id.input_format = "f32"; + id.num_inputs = 1; + id.num_outputs = 1; + + //try various lengths to test edge cases + id.output_format = "f32_item32_le"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_f32(nsamps, id); + } + + //try various lengths to test edge cases + id.output_format = "f32_item32_be"; + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_f32(nsamps, id); + } +} diff --git a/host/tests/devtest/CMakeLists.txt b/host/tests/devtest/CMakeLists.txt new file mode 100644 index 000000000..6fa921bbd --- /dev/null +++ b/host/tests/devtest/CMakeLists.txt @@ -0,0 +1,58 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +# Formatting +MESSAGE(STATUS "") + +# All devtest files get installed: +FILE(GLOB py_devtest_files "*.py") +UHD_INSTALL(PROGRAMS + ${py_devtest_files} + DESTINATION ${PKG_LIB_DIR}/tests/devtest + COMPONENT tests +) + +# Arguments: +# - pattern: This will be used to identify which devtest_*.py is to be executed. +# - filter: Will be used in args strings as "type=<filter>". +# - devtype: A descriptive string. Is only used for CMake output. +MACRO(ADD_DEVTEST pattern filter devtype) + MESSAGE(STATUS "Adding ${devtype} device test target") + ADD_CUSTOM_TARGET("test_${pattern}" + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_testsuite.py + "--src-dir" "${CMAKE_CURRENT_SOURCE_DIR}" + "--devtest-pattern" "${pattern}" + "--device-filter" "${filter}" + "--build-type" "${CMAKE_BUILD_TYPE}" + "--build-dir" "${CMAKE_BINARY_DIR}" + COMMENT "Running device test on all connected ${devtype} devices:" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ) +ENDMACRO(ADD_DEVTEST) + +IF(ENABLE_B200) + ADD_DEVTEST("b2xx" "b200" "B2XX") +ENDIF(ENABLE_B200) +IF(ENABLE_X300) + ADD_DEVTEST("x3x0" "x300" "X3x0") +ENDIF(ENABLE_X300) +IF(ENABLE_E300) + ADD_DEVTEST("e3xx" "e3x0" "E3XX") +ENDIF(ENABLE_E300) + +# Formatting +MESSAGE(STATUS "") diff --git a/host/tests/devtest/README.md b/host/tests/devtest/README.md new file mode 100644 index 000000000..ee1ff3c9f --- /dev/null +++ b/host/tests/devtest/README.md @@ -0,0 +1,28 @@ +# Device Tests + +These are a set of tests to be run with one or more attached devices. +None of these tests require special configuration; e.g., the X3x0 test +will work regardless of attached daughterboards, FPGIO wiring etc. + +## Adding new tests + +To add new tests, add new files with classes that derive from unittest.TestCase. +Most of the time, you'll want to derive from `uhd_test_case` or +`uhd_example_test_case`. + +## Adding new devices + +To add new devices, follow these steps: + +1) Add an entry to the CMakeLists.txt file in this directory using the + `ADD_DEVTEST()` macro. +2) Add a `devtest_pattern.py` file to this directory, where `pattern` is + the same pattern used in the `ADD_DEVTEST()` macro. +3) Edit this devtest file to import all the tests you want to run. Some + may require parameterization. + +The devtest file is 'executed' using Python's unittest module, so it doesn't +require any actual commands. If the device needs special initialization, +commands inside this file will be executed *if* they are *not* in a +`if __name__ == "__main__"` conditional. + diff --git a/host/tests/devtest/benchmark_rate_test.py b/host/tests/devtest/benchmark_rate_test.py new file mode 100755 index 000000000..6c5a75d7f --- /dev/null +++ b/host/tests/devtest/benchmark_rate_test.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# Copyright 2015-2016 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/>. +# +""" Test using benchmark_rate. """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_benchmark_rate_test(uhd_example_test_case): + """ + Run benchmark_rate in various configurations. + """ + tests = {} + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_benchmark_rate_test.tests + + def run_test(self, test_name, test_args): + """ + Runs benchmark_rate with the given parameters. Parses output and writes + results to file. + + We always run both tx and rx. + """ + rel_samp_err_threshold = 0.1 # 10% off is still quite generous + samp_rate = test_args.get('rate', 1e6) + duration = test_args.get('duration', 1) + chan = test_args.get('chan', '0') + n_chans = len(chan.split(",")) + expected_samples = n_chans * duration * samp_rate + self.log.info('Running test {n}, Channel = {c}, Sample Rate = {r}'.format( + n=test_name, c=chan, r=samp_rate, + )) + args = [ + self.create_addr_args_str(), + '--duration', str(duration), + '--channels', str(chan), + ] + if 'tx' in test_args.get('direction', ''): + args.append('--tx_rate') + args.append(str(samp_rate)) + if 'rx' in test_args.get('direction', ''): + args.append('--rx_rate') + args.append(str(samp_rate)) + (app, run_results) = self.run_example('benchmark_rate', args) + match = re.search(r'(Num received samples):\s*(.*)', app.stdout) + run_results['num_rx_samples'] = int(match.group(2)) if match else -1 + if run_results['num_rx_samples'] != -1: + run_results['rel_rx_samples_error'] = 1.0 * abs(run_results['num_rx_samples'] - expected_samples) / expected_samples + else: + run_results['rel_rx_samples_error'] = 100 + match = re.search(r'(Num dropped samples):\s*(.*)', app.stdout) + run_results['num_rx_dropped'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num overflows detected):\s*(.*)', app.stdout) + run_results['num_rx_overruns'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num transmitted samples):\s*(.*)', app.stdout) + run_results['num_tx_samples'] = int(match.group(2)) if match else -1 + if run_results['num_tx_samples'] != -1: + run_results['rel_tx_samples_error'] = 1.0 * abs(run_results['num_tx_samples'] - expected_samples) / expected_samples + else: + run_results['rel_tx_samples_error'] = 100 + match = re.search(r'(Num sequence errors):\s*(.*)', app.stdout) + run_results['num_tx_seqerrs'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num underflows detected):\s*(.*)', app.stdout) + run_results['num_tx_underruns'] = int(match.group(2)) if match else -1 + match = re.search(r'(Num timeouts):\s*(.*)', app.stdout) + run_results['num_timeouts'] = int(match.group(2)) if match else -1 + run_results['passed'] = all([ + run_results['return_code'] == 0, + run_results['num_rx_dropped'] == 0, + run_results['num_tx_seqerrs'] == 0, + run_results['num_tx_underruns'] <= test_args.get('acceptable-underruns', 0), + run_results['num_rx_samples'] > 0, + run_results['num_tx_samples'] > 0, + run_results['num_timeouts'] == 0, + run_results['rel_rx_samples_error'] < rel_samp_err_threshold, + run_results['rel_tx_samples_error'] < rel_samp_err_threshold, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/devtest_b2xx.py b/host/tests/devtest/devtest_b2xx.py new file mode 100755 index 000000000..4b81f0afe --- /dev/null +++ b/host/tests/devtest/devtest_b2xx.py @@ -0,0 +1,76 @@ +# +# Copyright 2015-2016 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/>. +# +""" +Run device tests for the B2xx series. +""" +from usrp_probe_test import uhd_usrp_probe_test +from benchmark_rate_test import uhd_benchmark_rate_test +uhd_benchmark_rate_test.tests = { + 'mimo': { + 'duration': 1, + 'direction': 'tx,rx', + 'channels': ['0,1',], + 'sample-rates': [1e6], + 'products': ['B210',], + 'acceptable-underruns': 500, + }, + 'siso_chan0_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0', + 'rate': 1e6, + 'acceptable-underruns': 50, + }, + #'siso_chan0_fast': { + #'duration': 1, + #'direction': 'tx,rx', + #'chan': '0', + #'rate': 40e6, + #'acceptable-underruns': 500, + #}, + 'siso_chan1_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '1', + 'rate': 1e6, + 'acceptable-underruns': 50, + 'products': ['B210',], + }, + #'siso_chan1_fast': { + #'duration': 1, + #'direction': 'tx,rx', + #'chan': '1', + #'rate': 40e6, + #'acceptable-underruns': 500, + #'products': ['B210',], + #}, +} + +from rx_samples_to_file_test import rx_samples_to_file_test +rx_samples_to_file_test.tests = { + 'default': { + 'duration': 1, + 'subdev': 'A:A', + 'rate': 5e6, + 'products': ['B210', 'B200',], + }, +} + +from tx_bursts_test import uhd_tx_bursts_test +from test_pps_test import uhd_test_pps_test +from gpio_test import gpio_test + diff --git a/host/tests/devtest/devtest_e3xx.py b/host/tests/devtest/devtest_e3xx.py new file mode 100755 index 000000000..1cab44184 --- /dev/null +++ b/host/tests/devtest/devtest_e3xx.py @@ -0,0 +1,58 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Run device tests for the E3XX series. +""" +from usrp_probe_test import uhd_usrp_probe_test +from benchmark_rate_test import uhd_benchmark_rate_test +uhd_benchmark_rate_test.tests = { + 'mimo': { + 'duration': 1, + 'direction': 'tx,rx', + 'channels': '0,1', + 'rate': 1e6, + 'acceptable-underruns': 500, + }, + 'siso_chan0_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0', + 'rate': 1e6, + 'acceptable-underruns': 50, + }, + 'siso_chan1_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '1', + 'rate': 1e6, + 'acceptable-underruns': 50, + 'products': ['B210',], + }, +} + +from rx_samples_to_file_test import rx_samples_to_file_test +rx_samples_to_file_test.tests = { + 'default': { + 'duration': 1, + 'subdev': 'A:A', + 'rate': 5e6, + }, +} + +from tx_bursts_test import uhd_tx_bursts_test +from test_pps_test import uhd_test_pps_test + diff --git a/host/tests/devtest/devtest_x3x0.py b/host/tests/devtest/devtest_x3x0.py new file mode 100755 index 000000000..7ad6b21b6 --- /dev/null +++ b/host/tests/devtest/devtest_x3x0.py @@ -0,0 +1,57 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Run device tests for the X3x0 series. +""" + +from benchmark_rate_test import uhd_benchmark_rate_test +uhd_benchmark_rate_test.tests = { + 'mimo_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0,1', + 'rate': 1e6, + 'acceptable-underruns': 500, + }, + 'mimo_fast': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0,1', + 'rate': 12.5e6, + 'acceptable-underruns': 500, + }, + 'siso_chan0_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0', + 'rate': 1e6, + 'acceptable-underruns': 0, + }, + 'siso_chan1_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '1', + 'rate': 1e6, + 'acceptable-underruns': 0, + }, +} + +#from rx_samples_to_file_test import rx_samples_to_file_test +from tx_bursts_test import uhd_tx_bursts_test +from test_pps_test import uhd_test_pps_test +from gpio_test import gpio_test + diff --git a/host/tests/devtest/gpio_test.py b/host/tests/devtest/gpio_test.py new file mode 100755 index 000000000..d764a8d96 --- /dev/null +++ b/host/tests/devtest/gpio_test.py @@ -0,0 +1,47 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test for test_pps_input. """ + +import re +from uhd_test_base import uhd_example_test_case + +class gpio_test(uhd_example_test_case): + """ Run gpio. """ + tests = {'default': {},} + + def setup_example(self): + """ + Set args. + """ + self.test_params = gpio_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the success message. """ + self.log.info('Running test {n}'.format(n=test_name,)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + (app, run_results) = self.run_example('gpio', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + re.search('All tests passed!', app.stdout) is not None, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/run_testsuite.py b/host/tests/devtest/run_testsuite.py new file mode 100755 index 000000000..30601c8bd --- /dev/null +++ b/host/tests/devtest/run_testsuite.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Device test runner. +""" + +import os +import sys +import subprocess +import argparse +import logging +import time +from threading import Thread +try: + from Queue import Queue, Empty +except ImportError: + from queue import Queue, Empty # Py3k +from usrp_probe import get_usrp_list + +ANI = ('.', 'o', 'O', '0', 'O', 'o') + +def setup_parser(): + """ Set up argparser """ + parser = argparse.ArgumentParser(description="Test utility for UHD/USRP.") + parser.add_argument('--devtest-pattern', '-p', default='*', help='e.g. b2xx') + parser.add_argument('--device-filter', '-f', default=None, required=True, help='b200, x300, ...') + parser.add_argument('--log-dir', '-l', default='.') + parser.add_argument('--src-dir', default='.', help='Directory where the test sources are stored') + parser.add_argument('--build-dir', default=None, help='Build dir (where examples/ and utils/ are)') + parser.add_argument('--build-type', default='Release') + return parser + +def setup_env(args): + def setup_env_win(env, build_dir, build_type): + env['PATH'] = "{build_dir}/lib/{build_type};{build_dir}/examples/{build_type};{build_dir}/utils/{build_type};{path}".format( + build_dir=build_dir, build_type=build_type, path=env.get('PATH', '') + ) + env['LIBPATH'] = "{build_dir}/lib/{build_type};{path}".format( + build_dir=build_dir, build_type=build_type, path=env.get('LIBPATH', '') + ) + env['LIB'] = "{build_dir}/lib/{build_type};{path}".format( + build_dir=build_dir, build_type=build_type, path=env.get('LIB', '') + ) + return env + def setup_env_unix(env, build_dir): + env['PATH'] = "{build_dir}/examples:{build_dir}/utils:{path}".format( + build_dir=build_dir, path=env.get('PATH', '') + ) + env['LD_LIBRARY_PATH'] = "{build_dir}/lib:{path}".format( + build_dir=build_dir, path=env.get('LD_LIBRARY_PATH', '') + ) + return env + def setup_env_osx(env, build_dir): + env['PATH'] = "{build_dir}/examples:{build_dir}/utils:{path}".format( + build_dir=build_dir, path=env.get('PATH', '') + ) + env['DYLD_LIBRARY_PATH'] = "{build_dir}/lib:{path}".format( + build_dir=build_dir, path=env.get('DYLD_LIBRARY_PATH', '') + ) + return env + ### Go + env = os.environ + if sys.platform.startswith('linux'): + env = setup_env_unix(env, args.build_dir) + elif sys.platform.startswith('win'): + env = setup_env_win(env, args.build_dir, args.build_type) + elif sys.platform.startswith('darwin'): + env = setup_env_osx(env, args.build_dir) + else: + print("Devtest not supported on this platform ({0}).".format(sys.platform)) + exit(1) + return env + +def main(): + """ + Go, go, go! + """ + args = setup_parser().parse_args() + devtest_pattern = "devtest_{p}.py".format(p=args.devtest_pattern) + uhd_args_list = get_usrp_list("type=" + args.device_filter) + if len(uhd_args_list) == 0: + print("No devices found. Exiting.") + exit(1) + tests_passed = True + for uhd_idx, uhd_info in enumerate(uhd_args_list): + print('--- Running all tests for device {dev} ({prod}, Serial: {ser}).'.format( + dev=uhd_idx, + prod=uhd_info.get('product', 'USRP'), + ser=uhd_info.get('serial') + )) + print('--- This will take some time. Better grab a cup of tea.') + env = setup_env(args) + args_str = uhd_info['args'] + env['_UHD_TEST_ARGS_STR'] = args_str + logfile_name = "log{}.log".format( + args_str.replace('type=', '_').replace('serial=', '_').replace(',', '') + ) + resultsfile_name = "results{}.log".format( + args_str.replace('type=', '_').replace('serial=', '_').replace(',', '') + ) + env['_UHD_TEST_LOGFILE'] = os.path.join(args.log_dir, logfile_name) + env['_UHD_TEST_RESULTSFILE'] = os.path.join(args.log_dir, resultsfile_name) + env['_UHD_TEST_LOG_LEVEL'] = str(logging.INFO) + env['_UHD_TEST_PRINT_LEVEL'] = str(logging.WARNING) + p = subprocess.Popen( + [ + "python", "-m", "unittest", "discover", "-v", + "-s", args.src_dir, + "-p", devtest_pattern, + ], + env=env, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + ) + print(p.communicate()[0]) + if p.returncode != 0: + tests_passed = False + print('--- Done testing all attached devices.') + return tests_passed + +if __name__ == "__main__": + if not main(): + exit(1) + diff --git a/host/tests/devtest/rx_samples_to_file_test.py b/host/tests/devtest/rx_samples_to_file_test.py new file mode 100755 index 000000000..bac6ac256 --- /dev/null +++ b/host/tests/devtest/rx_samples_to_file_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test the rx_samples_to_file example. """ + +from uhd_test_base import uhd_example_test_case + +class rx_samples_to_file_test(uhd_example_test_case): + """ + Run rx_samples_to_file and check output. + """ + tests = { + 'default': { + 'duration': 1, + 'rate': 5e6, + }, + } + + def setup_example(self): + """ + Set args. + """ + self.test_params = rx_samples_to_file_test.tests + + def run_test(self, test_name, test_args): + """ + Test launcher. Runs the example. + """ + self.log.info('Running test {n}, Subdev = {subdev}, Sample Rate = {rate}'.format( + n=test_name, subdev=test_args.get('subdev'), rate=test_args.get('rate'), + )) + # Run example: + args = [ + self.create_addr_args_str(), + '--null', + '--stats', + '--duration', str(test_args['duration']), + '--rate', str(test_args.get('rate', 1e6)), + '--wirefmt', test_args.get('wirefmt', 'sc16'), + ] + if test_args.has_key('subdev'): + args.append('--subdev') + args.append(test_args['subdev']) + (app, run_results) = self.run_example('rx_samples_to_file', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + not run_results['has_D'], + not run_results['has_S'], + run_results['return_code'] == 0, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/test_messages_test.py b/host/tests/devtest/test_messages_test.py new file mode 100644 index 000000000..496765c75 --- /dev/null +++ b/host/tests/devtest/test_messages_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test the test_messages example. """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_test_messages_test(uhd_example_test_case): + """ + Run test_messages and check output. + """ + tests = {'default': {},} + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_test_messages_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the failure messages. """ + self.log.info('Running test {n}'.format(n=test_name,)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + if test_args.has_key('ntests'): + args.append('--ntests') + args.append(test_args['ntests']) + (app, run_results) = self.run_example('test_messages', args) + # Evaluate pass/fail: + succ_fail_re = re.compile(r'(?P<test>.*)->\s+(?P<succ>\d+) successes,\s+(?P<fail>\d+) +failures') + for mo in succ_fail_re.finditer(app.stdout): + key = mo.group("test").strip().replace(' ', '_').lower() + successes = int(mo.group("succ")) + failures = int(mo.group("fail")) + run_results[key] = "{}/{}".format(successes, successes+failures) + run_results['passed'] = bool(failures) + + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/test_pps_test.py b/host/tests/devtest/test_pps_test.py new file mode 100755 index 000000000..1e5b36e2c --- /dev/null +++ b/host/tests/devtest/test_pps_test.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Test for test_pps_input. """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_test_pps_test(uhd_example_test_case): + """ Run test_pps_input. """ + tests = {'default': {},} + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_test_pps_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the success message. """ + self.log.info('Running test {n}'.format(n=test_name,)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + if test_args.has_key('source'): + args.append('--source') + args.append(test_args['source']) + (app, run_results) = self.run_example('test_pps_input', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + re.search('Success!', app.stdout) is not None, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/tx_bursts_test.py b/host/tests/devtest/tx_bursts_test.py new file mode 100755 index 000000000..863f35fe1 --- /dev/null +++ b/host/tests/devtest/tx_bursts_test.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Run the test for tx_burst """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_tx_bursts_test(uhd_example_test_case): + """ Run test_messages. """ + tests = { + 'default': { + 'nsamps': 10000, + 'rate': 5e6, + 'channels': '0', + }, + } + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_tx_bursts_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the failure messages. """ + self.log.info('Running test {name}, Channel = {channel}, Sample Rate = {rate}'.format( + name=test_name, channel=test_args.get('channel'), rate=test_args.get('rate'), + )) + # Run example: + args = [ + self.create_addr_args_str(), + '--nsamps', str(test_args['nsamps']), + '--channels', str(test_args['channels']), + '--rate', str(test_args.get('rate', 1e6)), + ] + if test_args.has_key('subdev'): + args.append('--subdev') + args.append(test_args['subdev']) + (app, run_results) = self.run_example('tx_bursts', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + not run_results['has_S'], + ]) + run_results['async_burst_ack_found'] = re.search('success', app.stdout) is not None + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/devtest/uhd_test_base.py b/host/tests/devtest/uhd_test_base.py new file mode 100755 index 000000000..07a95ae3d --- /dev/null +++ b/host/tests/devtest/uhd_test_base.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python + +import os +import sys +import yaml +import unittest +import re +import time +import logging +from subprocess import Popen, PIPE, STDOUT +from usrp_probe import get_usrp_list + +#-------------------------------------------------------------------------- +# Application +#-------------------------------------------------------------------------- +class shell_application(object): + """ + Wrapper for applications that are in $PATH. + Note: The CMake infrastructure makes sure all examples and utils are in $PATH. + """ + def __init__(self, name): + self.name = name + self.stdout = '' + self.stderr = '' + self.returncode = None + self.exec_time = None + + def run(self, args = []): + cmd_line = [self.name] + cmd_line.extend(args) + start_time = time.time() + p = Popen(cmd_line, stdout=PIPE, stderr=PIPE, close_fds=True) + self.stdout, self.stderr = p.communicate() + self.returncode = p.returncode + self.exec_time = time.time() - start_time + +#-------------------------------------------------------------------------- +# Test case base +#-------------------------------------------------------------------------- +class uhd_test_case(unittest.TestCase): + """ + Base class for UHD test cases. + """ + test_name = '--TEST--' + + def set_up(self): + """ + Override this to add own setup code per test. + """ + pass + + def setUp(self): + self.name = self.__class__.__name__ + self.test_id = self.id().split('.')[-1] + self.results = {} + self.results_file = os.getenv('_UHD_TEST_RESULTSFILE', "") + if self.results_file and os.path.isfile(self.results_file): + self.results = yaml.safe_load(open(self.results_file).read()) or {} + self.args_str = os.getenv('_UHD_TEST_ARGS_STR', "") + self.usrp_info = get_usrp_list(self.args_str)[0] + if not self.results.has_key(self.usrp_info['serial']): + self.results[self.usrp_info['serial']] = {} + if not self.results[self.usrp_info['serial']].has_key(self.name): + self.results[self.usrp_info['serial']][self.name] = {} + self.setup_logger() + self.set_up() + + def setup_logger(self): + " Add logging infrastructure " + self.log = logging.getLogger("devtest.{name}".format(name=self.name)) + self.log_file = os.getenv('_UHD_TEST_LOGFILE', "devtest.log") + #self.log_level = int(os.getenv('_UHD_TEST_LOG_LEVEL', logging.DEBUG)) + #self.print_level = int(os.getenv('_UHD_TEST_PRINT_LEVEL', logging.WARNING)) + self.log_level = logging.DEBUG + self.print_level = logging.WARNING + file_handler = logging.FileHandler(self.log_file) + file_handler.setLevel(self.log_level) + console_handler = logging.StreamHandler() + console_handler.setLevel(self.print_level) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + self.log.setLevel(logging.DEBUG) + self.log.addHandler(file_handler) + self.log.addHandler(console_handler) + self.log.info("Starting test with device: {dev}".format(dev=self.args_str)) + + def tear_down(self): + pass + + def tearDown(self): + self.tear_down() + if self.results_file: + open(self.results_file, 'w').write(yaml.dump(self.results, default_flow_style=False)) + + def report_result(self, testname, key, value): + """ Store a result as a key/value pair. + After completion, all results for one test are written to the results file. + """ + if not self.results[self.usrp_info['serial']][self.name].has_key(testname): + self.results[self.usrp_info['serial']][self.name][testname] = {} + self.results[self.usrp_info['serial']][self.name][testname][key] = value + + def create_addr_args_str(self, argname="args"): + """ Returns an args string, usually '--args "type=XXX,serial=YYY" """ + if len(self.args_str) == 0: + return '' + return '--{}={}'.format(argname, self.args_str) + + def filter_warnings(self, errstr): + """ Searches errstr for UHD warnings, removes them, and puts them into a separate string. + Returns (errstr, warnstr), where errstr no longer has warning. """ + warn_re = re.compile("UHD Warning:\n(?: .*\n)+") + warnstr = "\n".join(warn_re.findall(errstr)).strip() + errstr = warn_re.sub('', errstr).strip() + return (errstr, warnstr) + + def filter_stderr(self, stderr, run_results={}): + """ Filters the output to stderr. run_results[] is a dictionary. + This function will: + - Remove UUUUU... strings, since they are generally not a problem. + - Remove all DDDD and SSSS strings, and add run_results['has_S'] = True + and run_results['has_D'] = True. + - Remove warnings and put them in run_results['warnings'] + - Put the filtered error string into run_results['errors'] and returns the dictionary + """ + errstr, run_results['warnings'] = self.filter_warnings(stderr) + # Scan for underruns and sequence errors / dropped packets not detected in the counter + errstr = re.sub('UU+', '', errstr) + (errstr, n_subs) = re.subn('SS+', '', errstr) + if n_subs: + run_results['has_S'] = True + (errstr, n_subs) = re.subn('DD+', '', errstr) + if n_subs: + run_results['has_D'] = True + errstr = re.sub("\n\n+", "\n", errstr) + run_results['errors'] = errstr.strip() + return run_results + +class uhd_example_test_case(uhd_test_case): + """ + A test case that runs an example. + """ + + def setup_example(self): + """ + Override this to add specific setup code. + """ + pass + + def set_up(self): + """ + """ + self.setup_example() + + def run_test(self, test_name, test_args): + """ + Override this to run the actual example. + + Needs to return either a boolean or a dict with key 'passed' to determine + pass/fail. + """ + raise NotImplementedError + + def run_example(self, example, args): + """ + Run `example' (which has to be a UHD example or utility) with `args'. + Return results and the app object. + """ + self.log.info("Running example: `{example} {args}'".format(example=example, args=" ".join(args))) + app = shell_application(example) + app.run(args) + run_results = { + 'return_code': app.returncode, + 'passed': False, + 'has_D': False, + 'has_S': False, + } + run_results = self.filter_stderr(app.stderr, run_results) + self.log.info('STDERR Output:') + self.log.info(str(app.stderr)) + return (app, run_results) + + + def report_example_results(self, test_name, run_results): + for key in sorted(run_results): + self.log.info('{key} = {val}'.format(key=key, val=run_results[key])) + self.report_result( + test_name, + key, run_results[key] + ) + if run_results.has_key('passed'): + self.report_result( + test_name, + 'status', + 'Passed' if run_results['passed'] else 'Failed', + ) + if run_results.has_key('errors'): + self.report_result( + test_name, + 'errors', + 'Yes' if run_results['errors'] else 'No', + ) + + def test_all(self): + """ + Hook for test runner. Needs to be a class method that starts with 'test'. + Calls run_test(). + """ + for test_name, test_args in self.test_params.iteritems(): + if not test_args.has_key('products') or (self.usrp_info['product'] in test_args.get('products', [])): + run_results = self.run_test(test_name, test_args) + passed = bool(run_results) + if isinstance(run_results, dict): + passed = run_results['passed'] + self.assertTrue( + passed, + msg="Errors occurred during test `{t}'. Check log file for details.\nRun results:\n{r}".format( + t=test_name, r=yaml.dump(run_results, default_flow_style=False) + ) + ) + diff --git a/host/tests/devtest/usrp_probe.py b/host/tests/devtest/usrp_probe.py new file mode 100644 index 000000000..ba3c645e4 --- /dev/null +++ b/host/tests/devtest/usrp_probe.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Run uhd_find_devices and parse the output. """ + +import re +import subprocess + +def get_usrp_list(device_filter=None): + """ Returns a list of dicts that contain USRP info """ + try: + if device_filter is not None: + output = subprocess.check_output(['uhd_find_devices', '--args', device_filter]) + else: + output = subprocess.check_output('uhd_find_devices') + except subprocess.CalledProcessError: + return [] + split_re = "\n*-+\n-- .*\n-+\n" + uhd_strings = re.split(split_re, output) + result = [] + for uhd_string in uhd_strings: + if not re.match("Device Address", uhd_string): + continue + this_result = {k: v for k, v in re.findall(" ([a-z]+): (.*)", uhd_string)} + args_string = "" + try: + args_string = "type={},serial={}".format(this_result['type'], this_result['serial']) + except KeyError: + continue + this_result['args'] = args_string + result.append(this_result) + return result + +if __name__ == "__main__": + print get_usrp_list() + print get_usrp_list('type=x300') diff --git a/host/tests/devtest/usrp_probe_test.py b/host/tests/devtest/usrp_probe_test.py new file mode 100755 index 000000000..a136a2af7 --- /dev/null +++ b/host/tests/devtest/usrp_probe_test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" Run the test for tx_burst """ + +import re +from uhd_test_base import uhd_example_test_case + +class uhd_usrp_probe_test(uhd_example_test_case): + """ Run uhd_usrp_probe """ + tests = { + 'default': { + 'init-only': False, + }, + } + + def setup_example(self): + """ + Set args. + """ + self.test_params = uhd_usrp_probe_test.tests + + def run_test(self, test_name, test_args): + """ Run the app and scrape for the failure messages. """ + self.log.info('Running test {name}'.format(name=test_name)) + # Run example: + args = [ + self.create_addr_args_str(), + ] + if test_args.get('init-only'): + args.append('--init-only') + (app, run_results) = self.run_example('uhd_usrp_probe', args) + # Evaluate pass/fail: + run_results['passed'] = all([ + app.returncode == 0, + ]) + self.report_example_results(test_name, run_results) + return run_results + diff --git a/host/tests/expert_test.cpp b/host/tests/expert_test.cpp new file mode 100644 index 000000000..016761194 --- /dev/null +++ b/host/tests/expert_test.cpp @@ -0,0 +1,256 @@ +// +// Copyright 2010-2011 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 <boost/test/unit_test.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include "../lib/experts/expert_container.hpp" +#include "../lib/experts/expert_factory.hpp" +#include <uhd/property_tree.hpp> +#include <fstream> + +using namespace uhd::experts; + +class worker1_t : public worker_node_t { +public: + worker1_t(const node_retriever_t& db) + : worker_node_t("A+B=C"), _a(db, "A/desired"), _b(db, "B"), _c(db, "C") + { + bind_accessor(_a); + bind_accessor(_b); + bind_accessor(_c); + } + +private: + void resolve() { + _c = _a + _b; + } + + data_reader_t<int> _a; + data_reader_t<int> _b; + data_writer_t<int> _c; +}; + +//============================================================================= + +class worker2_t : public worker_node_t { +public: + worker2_t(const node_retriever_t& db) + : worker_node_t("C*D=E"), _c(db, "C"), _d(db, "D"), _e(db, "E") + { + bind_accessor(_c); + bind_accessor(_d); + bind_accessor(_e); + } + +private: + void resolve() { + _e.set(_c.get() * _d.get()); + } + + data_reader_t<int> _c; + data_reader_t<int> _d; + data_writer_t<int> _e; +}; + +//============================================================================= + +class worker3_t : public worker_node_t { +public: + worker3_t(const node_retriever_t& db) + : worker_node_t("-B=F"), _b(db, "B"), _f(db, "F") + { + bind_accessor(_b); + bind_accessor(_f); + } + +private: + void resolve() { + _f.set(-_b.get()); + } + + data_reader_t<int> _b; + data_writer_t<int> _f; +}; + +//============================================================================= + +class worker4_t : public worker_node_t { +public: + worker4_t(const node_retriever_t& db) + : worker_node_t("E-F=G"), _e(db, "E"), _f(db, "F"), _g(db, "G") + { + bind_accessor(_e); + bind_accessor(_f); + bind_accessor(_g); + } + +private: + void resolve() { + _g.set(_e.get() - _f.get()); + } + + data_reader_t<int> _e; + data_reader_t<int> _f; + data_writer_t<int> _g; +}; + +//============================================================================= + +class worker5_t : public worker_node_t { +public: + worker5_t(const node_retriever_t& db, boost::shared_ptr<int> output) + : worker_node_t("Consume_G"), _g(db, "G"), _c(db, "C"), _output(output) + { + bind_accessor(_g); +// bind_accessor(_c); + } + +private: + void resolve() { + *_output = _g; + } + + data_reader_t<int> _g; + data_writer_t<int> _c; + + boost::shared_ptr<int> _output; +}; + +class worker6_t : public worker_node_t { +public: + worker6_t() : worker_node_t("null_worker") + { + } + +private: + void resolve() { + } +}; + +//============================================================================= + +#define DUMP_VARS \ + BOOST_TEST_MESSAGE( str(boost::format("### State = {A=%d%s, B=%d%s, C=%d%s, D=%d%s, E=%d%s, F=%d%s, G=%d%s}\n") % \ + nodeA.get() % (nodeA.is_dirty()?"*":"") % \ + nodeB.get() % (nodeB.is_dirty()?"*":"") % \ + nodeC.get() % (nodeC.is_dirty()?"*":"") % \ + nodeD.get() % (nodeD.is_dirty()?"*":"") % \ + nodeE.get() % (nodeE.is_dirty()?"*":"") % \ + nodeF.get() % (nodeF.is_dirty()?"*":"") % \ + nodeG.get() % (nodeG.is_dirty()?"*":"")) ); + +#define VALIDATE_ALL_DEPENDENCIES \ + BOOST_CHECK(!nodeA.is_dirty()); \ + BOOST_CHECK(!nodeB.is_dirty()); \ + BOOST_CHECK(!nodeC.is_dirty()); \ + BOOST_CHECK(!nodeD.is_dirty()); \ + BOOST_CHECK(!nodeE.is_dirty()); \ + BOOST_CHECK(!nodeF.is_dirty()); \ + BOOST_CHECK(!nodeG.is_dirty()); \ + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); \ + BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); \ + BOOST_CHECK(nodeF.get() == - nodeB.get()); \ + BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get()); \ + BOOST_CHECK(nodeG.get() == *final_output); + + +BOOST_AUTO_TEST_CASE(test_experts){ + //Initialize container object + expert_container::sptr container = expert_factory::create_container("example"); + uhd::property_tree::sptr tree = uhd::property_tree::make(); + + //Output of expert tree + boost::shared_ptr<int> final_output = boost::make_shared<int>(); + + //Add data nodes to container + expert_factory::add_dual_prop_node<int>(container, tree, "A", 0, uhd::experts::AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<int>(container, tree, "B", 0); + expert_factory::add_data_node<int>(container, "C", 0); + expert_factory::add_data_node<int>(container, "D", 1); + expert_factory::add_prop_node<int>(container, tree, "E", 0, uhd::experts::AUTO_RESOLVE_ON_READ); + expert_factory::add_data_node<int>(container, "F", 0); + expert_factory::add_data_node<int>(container, "G", 0); + + //Add worker nodes to container + expert_factory::add_worker_node<worker1_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker2_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker3_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker4_t>(container, container->node_retriever()); + expert_factory::add_worker_node<worker5_t>(container, container->node_retriever(), final_output); + expert_factory::add_worker_node<worker6_t>(container); + + //Once initialized, getting modify access to graph nodes is possible (by design) but extremely red-flaggy! + //But we do it here to monitor things + data_node_t<int>& nodeA = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("A/desired")))); + data_node_t<int>& nodeB = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("B")))); + data_node_t<int>& nodeC = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("C")))); + data_node_t<int>& nodeD = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("D")))); + data_node_t<int>& nodeE = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("E")))); + data_node_t<int>& nodeF = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("F")))); + data_node_t<int>& nodeG = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("G")))); + + DUMP_VARS + + //Ensure init behavior + BOOST_CHECK(nodeA.is_dirty()); + BOOST_CHECK(nodeB.is_dirty()); + BOOST_CHECK(nodeC.is_dirty()); + BOOST_CHECK(nodeD.is_dirty()); + BOOST_CHECK(nodeE.is_dirty()); + BOOST_CHECK(nodeF.is_dirty()); + BOOST_CHECK(nodeG.is_dirty()); + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES //Ensure a default resolve + + //Ensure basic node value propagation + tree->access<int>("B").set(3); + BOOST_CHECK(nodeB.get() == 3); //Ensure value propagated + BOOST_CHECK(nodeB.is_dirty()); //Ensure that nothing got resolved... + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES + + nodeD.set(2); //Hack for testing + + //Ensure auto-resolve on write + tree->access<int>("A").set(200); + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); + BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); + BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get()); + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES + + container->resolve_all(); + VALIDATE_ALL_DEPENDENCIES + + //Ensure auto-resolve on read + tree->access<int>("E").get(); + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); + BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); + BOOST_CHECK(!nodeE.is_dirty()); + tree->access<int>("E").set(-10); + container->resolve_all(true); + VALIDATE_ALL_DEPENDENCIES + + //Resolve to/from + tree->access<int>("A").set(-1); + container->resolve_to("C"); + BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); + BOOST_CHECK(!nodeC.is_dirty()); + container->resolve_to("Consume_G"); + VALIDATE_ALL_DEPENDENCIES +} diff --git a/host/tests/fe_conn_test.cpp b/host/tests/fe_conn_test.cpp new file mode 100644 index 000000000..b8e69816a --- /dev/null +++ b/host/tests/fe_conn_test.cpp @@ -0,0 +1,108 @@ +// +// Copyright 2016 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 <uhd/usrp/fe_connection.hpp> +#include <uhd/exception.hpp> +#include <boost/test/unit_test.hpp> + +using namespace uhd::usrp; + +BOOST_AUTO_TEST_CASE(test_quardrature){ + fe_connection_t IQ("IQ"), QI("QI"), IbQ("IbQ"), QbI("QbI"), QbIb("QbIb"); + BOOST_CHECK(IQ.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(QI.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(IbQ.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(QbI.get_sampling_mode()==fe_connection_t::QUADRATURE); + BOOST_CHECK(QbIb.get_sampling_mode()==fe_connection_t::QUADRATURE); + + BOOST_CHECK(not IQ.is_iq_swapped()); + BOOST_CHECK(QI.is_iq_swapped()); + BOOST_CHECK(not IbQ.is_iq_swapped()); + BOOST_CHECK(QbI.is_iq_swapped()); + BOOST_CHECK(QbIb.is_iq_swapped()); + + BOOST_CHECK(not IQ.is_i_inverted()); + BOOST_CHECK(not QI.is_i_inverted()); + BOOST_CHECK(IbQ.is_i_inverted()); + BOOST_CHECK(not QbI.is_i_inverted()); + BOOST_CHECK(QbIb.is_i_inverted()); + + BOOST_CHECK(not IQ.is_q_inverted()); + BOOST_CHECK(not QI.is_q_inverted()); + BOOST_CHECK(not IbQ.is_q_inverted()); + BOOST_CHECK(QbI.is_q_inverted()); + BOOST_CHECK(QbIb.is_q_inverted()); +} + +BOOST_AUTO_TEST_CASE(test_heterodyne){ + fe_connection_t II("II"), QQ("QQ"), IbIb("IbIb"), QbQb("QbQb"); + BOOST_CHECK(II.get_sampling_mode()==fe_connection_t::HETERODYNE); + BOOST_CHECK(QQ.get_sampling_mode()==fe_connection_t::HETERODYNE); + BOOST_CHECK(IbIb.get_sampling_mode()==fe_connection_t::HETERODYNE); + BOOST_CHECK(QbQb.get_sampling_mode()==fe_connection_t::HETERODYNE); + + BOOST_CHECK(not II.is_iq_swapped()); + BOOST_CHECK(QQ.is_iq_swapped()); + BOOST_CHECK(not IbIb.is_iq_swapped()); + BOOST_CHECK(QbQb.is_iq_swapped()); + + BOOST_CHECK(not II.is_i_inverted()); + BOOST_CHECK(not QQ.is_i_inverted()); + BOOST_CHECK(IbIb.is_i_inverted()); + BOOST_CHECK(QbQb.is_i_inverted()); + + BOOST_CHECK(not II.is_q_inverted()); + BOOST_CHECK(not QQ.is_q_inverted()); + BOOST_CHECK(IbIb.is_q_inverted()); + BOOST_CHECK(QbQb.is_q_inverted()); + + BOOST_CHECK_THROW(fe_connection_t dummy("IIb"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("IbI"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("QQb"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("QbQ"), uhd::value_error); +} + +BOOST_AUTO_TEST_CASE(test_real){ + fe_connection_t I("I"), Q("Q"), Ib("Ib"), Qb("Qb"); + BOOST_CHECK(I.get_sampling_mode()==fe_connection_t::REAL); + BOOST_CHECK(Q.get_sampling_mode()==fe_connection_t::REAL); + BOOST_CHECK(Ib.get_sampling_mode()==fe_connection_t::REAL); + BOOST_CHECK(Qb.get_sampling_mode()==fe_connection_t::REAL); + + BOOST_CHECK(not I.is_iq_swapped()); + BOOST_CHECK(Q.is_iq_swapped()); + BOOST_CHECK(not Ib.is_iq_swapped()); + BOOST_CHECK(Qb.is_iq_swapped()); + + BOOST_CHECK(not I.is_i_inverted()); + BOOST_CHECK(not Q.is_i_inverted()); + BOOST_CHECK(Ib.is_i_inverted()); + BOOST_CHECK(Qb.is_i_inverted()); + + BOOST_CHECK(not I.is_q_inverted()); + BOOST_CHECK(not Q.is_q_inverted()); + BOOST_CHECK(not Ib.is_q_inverted()); + BOOST_CHECK(not Qb.is_q_inverted()); +} + +BOOST_AUTO_TEST_CASE(test_invalid){ + BOOST_CHECK_THROW(fe_connection_t dummy("blah"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("123456"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("ii"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("qb"), uhd::value_error); + BOOST_CHECK_THROW(fe_connection_t dummy("IIIQ"), uhd::value_error); +} diff --git a/host/tests/property_test.cpp b/host/tests/property_test.cpp index 00bb3c022..61d1de9a6 100644 --- a/host/tests/property_test.cpp +++ b/host/tests/property_test.cpp @@ -28,18 +28,26 @@ struct coercer_type{ }; struct setter_type{ + setter_type() : _count(0), _x(0) {} + void doit(int x){ + _count++; _x = x; } + int _count; int _x; }; struct getter_type{ + getter_type() : _count(0), _x(0) {} + int doit(void){ + _count++; return _x; } + int _count; int _x; }; @@ -57,29 +65,72 @@ BOOST_AUTO_TEST_CASE(test_prop_simple){ BOOST_CHECK_EQUAL(prop.get(), 34); } -BOOST_AUTO_TEST_CASE(test_prop_with_subscriber){ +BOOST_AUTO_TEST_CASE(test_prop_with_desired_subscriber){ uhd::property_tree::sptr tree = uhd::property_tree::make(); uhd::property<int> &prop = tree->create<int>("/"); setter_type setter; - prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + prop.add_desired_subscriber(boost::bind(&setter_type::doit, &setter, _1)); prop.set(42); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); BOOST_CHECK_EQUAL(prop.get(), 42); BOOST_CHECK_EQUAL(setter._x, 42); prop.set(34); + BOOST_CHECK_EQUAL(prop.get_desired(), 34); BOOST_CHECK_EQUAL(prop.get(), 34); BOOST_CHECK_EQUAL(setter._x, 34); } +BOOST_AUTO_TEST_CASE(test_prop_with_coerced_subscriber){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/"); + + setter_type setter; + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1)); + + prop.set(42); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); + BOOST_CHECK_EQUAL(prop.get(), 42); + BOOST_CHECK_EQUAL(setter._x, 42); + + prop.set(34); + BOOST_CHECK_EQUAL(prop.get_desired(), 34); + BOOST_CHECK_EQUAL(prop.get(), 34); + BOOST_CHECK_EQUAL(setter._x, 34); +} + +BOOST_AUTO_TEST_CASE(test_prop_manual_coercion){ + uhd::property_tree::sptr tree = uhd::property_tree::make(); + uhd::property<int> &prop = tree->create<int>("/", uhd::property_tree::MANUAL_COERCE); + + setter_type dsetter, csetter; + prop.add_desired_subscriber(boost::bind(&setter_type::doit, &dsetter, _1)); + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &csetter, _1)); + + BOOST_CHECK_EQUAL(dsetter._x, 0); + BOOST_CHECK_EQUAL(csetter._x, 0); + + prop.set(42); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); + BOOST_CHECK_EQUAL(dsetter._x, 42); + BOOST_CHECK_EQUAL(csetter._x, 0); + + prop.set_coerced(34); + BOOST_CHECK_EQUAL(prop.get_desired(), 42); + BOOST_CHECK_EQUAL(prop.get(), 34); + BOOST_CHECK_EQUAL(dsetter._x, 42); + BOOST_CHECK_EQUAL(csetter._x, 34); +} + BOOST_AUTO_TEST_CASE(test_prop_with_publisher){ uhd::property_tree::sptr tree = uhd::property_tree::make(); uhd::property<int> &prop = tree->create<int>("/"); BOOST_CHECK(prop.empty()); getter_type getter; - prop.publish(boost::bind(&getter_type::doit, &getter)); + prop.set_publisher(boost::bind(&getter_type::doit, &getter)); BOOST_CHECK(not prop.empty()); getter._x = 42; @@ -96,10 +147,10 @@ BOOST_AUTO_TEST_CASE(test_prop_with_publisher_and_subscriber){ uhd::property<int> &prop = tree->create<int>("/"); getter_type getter; - prop.publish(boost::bind(&getter_type::doit, &getter)); + prop.set_publisher(boost::bind(&getter_type::doit, &getter)); setter_type setter; - prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1)); getter._x = 42; prop.set(0); @@ -117,10 +168,10 @@ BOOST_AUTO_TEST_CASE(test_prop_with_coercion){ uhd::property<int> &prop = tree->create<int>("/"); setter_type setter; - prop.subscribe(boost::bind(&setter_type::doit, &setter, _1)); + prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1)); coercer_type coercer; - prop.coerce(boost::bind(&coercer_type::doit, &coercer, _1)); + prop.set_coercer(boost::bind(&coercer_type::doit, &coercer, _1)); prop.set(42); BOOST_CHECK_EQUAL(prop.get(), 40); diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index 9f0a6afef..6f72c97bc 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -19,6 +19,7 @@ # Utilities that get installed into the runtime path ######################################################################## SET(util_runtime_sources + uhd_config_info.cpp uhd_find_devices.cpp uhd_usrp_probe.cpp uhd_image_loader.cpp @@ -30,7 +31,7 @@ SET(util_runtime_sources SET(x3xx_burner_sources usrp_x3xx_fpga_burner.cpp - cdecode.c + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/usrp/x300/cdecode.c ) find_package(UDev) @@ -48,18 +49,25 @@ FOREACH(util_source ${util_runtime_sources}) UHD_INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) ENDFOREACH(util_source) -ADD_EXECUTABLE(usrp_x3xx_fpga_burner ${x3xx_burner_sources}) -TARGET_LINK_LIBRARIES(usrp_x3xx_fpga_burner uhd ${Boost_LIBRARIES}) -UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +IF(ENABLE_X300) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/usrp/x300) + ADD_EXECUTABLE(usrp_x3xx_fpga_burner ${x3xx_burner_sources}) + TARGET_LINK_LIBRARIES(usrp_x3xx_fpga_burner uhd ${Boost_LIBRARIES}) + UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +ENDIF(ENABLE_X300) ######################################################################## # Utilities that get installed into the share path ######################################################################## SET(util_share_sources + converter_benchmark.cpp query_gpsdo_sensors.cpp usrp_burn_db_eeprom.cpp usrp_burn_mb_eeprom.cpp ) +SET(util_share_sources_py + converter_benchmark.py +) IF(ENABLE_USB) LIST(APPEND util_share_sources fx2_init_eeprom.cpp @@ -108,9 +116,20 @@ FOREACH(util_source ${util_share_sources}) TARGET_LINK_LIBRARIES(${util_name} uhd ${Boost_LIBRARIES}) UHD_INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) ENDFOREACH(util_source) +FOREACH(util_source ${util_share_sources_py}) + UHD_INSTALL(PROGRAMS + ${CMAKE_CURRENT_SOURCE_DIR}/${util_source} + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) +ENDFOREACH(util_source) -UHD_INSTALL(TARGETS usrp_n2xx_simple_net_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) -UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +IF(ENABLE_USRP2) + UHD_INSTALL(TARGETS usrp_n2xx_simple_net_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +ENDIF(ENABLE_USRP2) +IF(ENABLE_X300) + UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +ENDIF(ENABLE_X300) #UHD images downloader configuration CONFIGURE_FILE( diff --git a/host/utils/cdecode.c b/host/utils/cdecode.c deleted file mode 100644 index 1d09cbe22..000000000 --- a/host/utils/cdecode.c +++ /dev/null @@ -1,80 +0,0 @@ -/* -cdecoder.c - c source to a base64 decoding algorithm implementation - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#include "cdecode.h" - -int base64_decode_value(char value_in){ - static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; - static const char decoding_size = sizeof(decoding); - value_in -= 43; - if ((signed char)value_in < 0 || value_in > decoding_size) return -1; - return decoding[(int)value_in]; -} - -void base64_init_decodestate(base64_decodestate* state_in){ - state_in->step = step_a; - state_in->plainchar = 0; -} - -size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in){ - const char* codechar = code_in; - char* plainchar = plaintext_out; - char fragment; - - *plainchar = state_in->plainchar; - - switch (state_in->step){ - while (1){ - case step_a: - do{ - if (codechar == code_in+length_in){ - state_in->step = step_a; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar = (fragment & 0x03f) << 2; - - case step_b: - do{ - if (codechar == code_in+length_in){ - state_in->step = step_b; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar++ |= (fragment & 0x030) >> 4; - *plainchar = (fragment & 0x00f) << 4; - case step_c: - do{ - if (codechar == code_in+length_in) - { - state_in->step = step_c; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar++ |= (fragment & 0x03c) >> 2; - *plainchar = (fragment & 0x003) << 6; - case step_d: - do{ - if (codechar == code_in+length_in){ - state_in->step = step_d; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while ((signed char)fragment < 0); - *plainchar++ |= (fragment & 0x03f); - } - } - /* control should not reach here */ - return plainchar - plaintext_out; -} diff --git a/host/utils/cdecode.h b/host/utils/cdecode.h deleted file mode 100644 index e1eee301f..000000000 --- a/host/utils/cdecode.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -cdecode.h - c header for a base64 decoding algorithm - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifndef BASE64_CDECODE_H -#define BASE64_CDECODE_H - -#include <stddef.h> - -typedef enum{ - step_a, step_b, step_c, step_d -} base64_decodestep; - -typedef struct{ - base64_decodestep step; - char plainchar; -} base64_decodestate; - -void base64_init_decodestate(base64_decodestate* state_in); - -int base64_decode_value(char value_in); - -size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in); - -#endif /* BASE64_CDECODE_H */ diff --git a/host/utils/converter_benchmark.cpp b/host/utils/converter_benchmark.cpp new file mode 100644 index 000000000..ddbf50255 --- /dev/null +++ b/host/utils/converter_benchmark.cpp @@ -0,0 +1,434 @@ +// +// Copyright 2015-2016 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 <uhd/utils/safe_main.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/convert.hpp> +#include <uhd/exception.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/timer.hpp> +#include <boost/algorithm/string.hpp> +#include <iostream> +#include <iomanip> +#include <map> +#include <complex> +#include <stdint.h> + +namespace po = boost::program_options; +using namespace uhd::convert; + +enum buf_init_t { + RANDOM, INC +}; + +// Convert `sc16_item32_le' -> `sc16' +// Finds the first _ in format and returns the string +// until then. Returns the entire string if no _ is found. +std::string format_to_type(const std::string &format) +{ + std::string ret_val = ""; + for (size_t i = 0; i < format.length(); i++) { + if (format[i] == '_') { + return ret_val; + } + ret_val.append(1, format[i]); + } + + return ret_val; +} + +void configure_conv( + converter::sptr conv, + const std::string &in_type, + const std::string &out_type +) { + if (in_type == "sc16") { + if (out_type == "fc32") { + std::cout << "Setting scalar to 32767." << std::endl; + conv->set_scalar(32767.); + return; + } + } + + if (in_type == "fc32") { + if (out_type == "sc16") { + std::cout << "Setting scalar to 32767." << std::endl; + conv->set_scalar(32767.); + return; + } + } + + std::cout << "No configuration required." << std::endl; +} + +template <typename T> +void init_random_vector_complex_float(std::vector<char> &buf_ptr, const size_t n_items) +{ + std::complex<T> * const buf = reinterpret_cast<std::complex<T> * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = std::complex<T>( + T((std::rand()/double(RAND_MAX/2)) - 1), + T((std::rand()/double(RAND_MAX/2)) - 1) + ); + } +} + +template <typename T> +void init_random_vector_complex_int(std::vector<char> &buf_ptr, const size_t n_items) +{ + std::complex<T> * const buf = reinterpret_cast<std::complex<T> * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = std::complex<T>(T(std::rand()), T(std::rand())); + } +} + +template <typename T> +void init_random_vector_real_int(std::vector<char> &buf_ptr, size_t n_items) +{ + T * const buf = reinterpret_cast<T * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = T(std::rand()); + } +} + +// Fill a buffer with increasing numbers +template <typename T> +void init_inc_vector(std::vector<char> &buf_ptr, size_t n_items) +{ + T * const buf = reinterpret_cast<T * const>(&buf_ptr[0]); + for (size_t i = 0; i < n_items; i++) { + buf[i] = T(i); + } +} + +void init_buffers( + std::vector< std::vector<char> > &buf, + const std::string &type, + size_t bytes_per_item, + buf_init_t buf_seed_mode +) { + if (buf.empty()) { + return; + } + size_t n_items = buf[0].size() / bytes_per_item; + + /// Fill with incrementing integers + if (buf_seed_mode == INC) { + for (size_t i = 0; i < buf.size(); i++) { + if (type == "sc8") { + init_inc_vector< std::complex<boost::int8_t> >(buf[i], n_items); + } else if (type == "sc16") { + init_inc_vector< std::complex<boost::int16_t> >(buf[i], n_items); + } else if (type == "sc32") { + init_inc_vector< std::complex<boost::int32_t> >(buf[i], n_items); + } else if (type == "fc32") { + init_inc_vector< std::complex<float> >(buf[i], n_items); + } else if (type == "fc64") { + init_inc_vector< std::complex<double> >(buf[i], n_items); + } else if (type == "s8") { + init_inc_vector< boost::int8_t >(buf[i], n_items); + } else if (type == "s16") { + init_inc_vector< boost::int16_t >(buf[i], n_items); + } else if (type == "item32") { + init_inc_vector< boost::uint32_t >(buf[i], n_items); + init_random_vector_real_int<boost::uint32_t>(buf[i], n_items); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot handle data type: %s") % type + )); + } + } + + return; + } + + assert(buf_seed_mode == RANDOM); + + /// Fill with random data + for (size_t i = 0; i < buf.size(); i++) { + if (type == "sc8") { + init_random_vector_complex_int<boost::int8_t>(buf[i], n_items); + } else if (type == "sc16") { + init_random_vector_complex_int<boost::int16_t>(buf[i], n_items); + } else if (type == "sc32") { + init_random_vector_complex_int<boost::int32_t>(buf[i], n_items); + } else if (type == "fc32") { + init_random_vector_complex_float<float>(buf[i], n_items); + } else if (type == "fc64") { + init_random_vector_complex_float<double>(buf[i], n_items); + } else if (type == "s8") { + init_random_vector_real_int<boost::int8_t>(buf[i], n_items); + } else if (type == "s16") { + init_random_vector_real_int<boost::int16_t>(buf[i], n_items); + } else if (type == "item32") { + init_random_vector_real_int<boost::uint32_t>(buf[i], n_items); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot handle data type: %s") % type + )); + } + } +} + +// Returns time elapsed +double run_benchmark( + converter::sptr conv, + const std::vector<const void *> &input_buf_refs, + const std::vector<void *> &output_buf_refs, + size_t n_items, + size_t iterations +) { + boost::timer benchmark_timer; + for (size_t i = 0; i < iterations; i++) { + conv->conv(input_buf_refs, output_buf_refs, n_items); + } + return benchmark_timer.elapsed(); +} + +template <typename T> +std::string void_ptr_to_hexstring(const void *v_ptr, size_t index) +{ + const T *ptr = reinterpret_cast<const T *>(v_ptr); + return str(boost::format("%X") % ptr[index]); +} + +std::string item_to_hexstring( + const void *v_ptr, + size_t index, + const std::string &type +) { + if (type == "fc32") { + return void_ptr_to_hexstring<uint64_t>(v_ptr, index); + } + else if (type == "sc16" || type == "item32") { + return void_ptr_to_hexstring<uint32_t>(v_ptr, index); + } + else if (type == "sc8" || type == "s16") { + return void_ptr_to_hexstring<uint16_t>(v_ptr, index); + } + else if (type == "u8") { + return void_ptr_to_hexstring<uint8_t>(v_ptr, index); + } + else { + return str(boost::format("<unhandled data type: %s>") % type); + } +} + +std::string item_to_string( + const void *v_ptr, + size_t index, + const std::string &type, + const bool print_hex +) { + if (print_hex) { + return item_to_hexstring(v_ptr, index, type); + } + + if (type == "sc16") { + const std::complex<boost::int16_t> *ptr = reinterpret_cast<const std::complex<boost::int16_t> *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "sc8") { + const std::complex<boost::int8_t> *ptr = reinterpret_cast<const std::complex<boost::int8_t> *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "fc32") { + const std::complex<float> *ptr = reinterpret_cast<const std::complex<float> *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "item32") { + const boost::uint32_t *ptr = reinterpret_cast<const boost::uint32_t *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else if (type == "s16") { + const boost::int16_t *ptr = reinterpret_cast<const boost::int16_t *>(v_ptr); + return boost::lexical_cast<std::string>(ptr[index]); + } + else { + return str(boost::format("<unhandled data type: %s>") % type); + } +} + +int UHD_SAFE_MAIN(int argc, char *argv[]) +{ + std::string in_format, out_format; + std::string priorities; + std::string seed_mode; + priority_type prio = -1, max_prio; + size_t iterations, n_samples; + size_t n_inputs, n_outputs; + buf_init_t buf_seed_mode = RANDOM; + + /// Command line arguments + po::options_description desc("Converter benchmark options:"); + desc.add_options() + ("help", "help message") + ("in", po::value<std::string>(&in_format), "Input format (e.g. 'sc16')") + ("out", po::value<std::string>(&out_format), "Output format (e.g. 'sc16')") + ("samples", po::value<size_t>(&n_samples)->default_value(1000000), "Number of samples per iteration") + ("iterations", po::value<size_t>(&iterations)->default_value(10000), "Number of iterations per benchmark") + ("priorities", po::value<std::string>(&priorities)->default_value("default"), "Converter priorities. Can be 'default', 'all', or a comma-separated list of priorities.") + ("max-prio", po::value<priority_type>(&max_prio)->default_value(4), "Largest available priority (advanced feature)") + ("n-inputs", po::value<size_t>(&n_inputs)->default_value(1), "Number of input vectors") + ("n-outputs", po::value<size_t>(&n_outputs)->default_value(1), "Number of output vectors") + ("debug-converter", "Skip benchmark and print conversion results. Implies iterations==1 and will only run on a single converter.") + ("seed-mode", po::value<std::string>(&seed_mode)->default_value("random"), "How to initialize the data: random, incremental") + ("hex", "When using debug mode, dump memory in hex") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD Converter Benchmark Tool %s") % desc << std::endl << std::endl; + std::cout << " Use this to benchmark or debug converters." << std::endl + << " When using as a benchmark tool, it will output the execution time\n" + " for every conversion run in CSV format to stdout. Every line between\n" + " the output delimiters {{{ }}} is of the format: <PRIO>,<TIME IN MILLISECONDS>\n" + " When using for converter debugging, every line is formatted as\n" + " <INPUT_VALUE>,<OUTPUT_VALUE>\n" << std::endl; + return EXIT_FAILURE; + } + + // Parse more arguments + if (seed_mode == "incremental") { + buf_seed_mode = INC; + } else if (seed_mode == "random") { + buf_seed_mode = RANDOM; + } else { + std::cout << "Invalid argument: --seed-mode must be either 'incremental' or 'random'." << std::endl; + } + + bool debug_mode = bool(vm.count("debug-converter")); + if (debug_mode) { + iterations = 1; + } + + /// Create the converter(s) ////////////////////////////////////////////// + id_type converter_id; + converter_id.input_format = in_format; + converter_id.output_format = out_format; + converter_id.num_inputs = n_inputs; + converter_id.num_outputs = n_outputs; + std::cout << "Requested converter format: " << converter_id.to_string() + << std::endl; + uhd::dict<priority_type, converter::sptr> conv_list; + if (priorities == "default" or priorities.empty()) { + try { + conv_list[prio] = get_converter(converter_id, prio)(); // Can throw a uhd::key_error + } catch(const uhd::key_error &e) { + std::cout << "No converters found." << std::endl; + return EXIT_FAILURE; + } + } else if (priorities == "all") { + for (priority_type i = 0; i < max_prio; i++) { + try { + // get_converter() returns a factory function, execute that immediately: + converter::sptr conv_for_prio = get_converter(converter_id, i)(); // Can throw a uhd::key_error + conv_list[i] = conv_for_prio; + } catch (...) { + continue; + } + } + } else { // Assume that priorities contains a list of prios (e.g. 0,2,3) + std::vector<std::string> prios_in_list; + boost::split( + prios_in_list, + priorities, + boost::is_any_of(","), // Split at , + boost::token_compress_on // Avoid empty results + ); + BOOST_FOREACH(const std::string &this_prio, prios_in_list) { + size_t prio_index = boost::lexical_cast<size_t>(this_prio); + converter::sptr conv_for_prio = get_converter(converter_id, prio_index)(); // Can throw a uhd::key_error + conv_list[prio_index] = conv_for_prio; + } + } + std::cout << "Found " << conv_list.size() << " converter(s)." << std::endl; + + /// Create input and output buffers /////////////////////////////////////// + // First, convert the types to plain types (e.g. sc16_item32_le -> sc16) + const std::string in_type = format_to_type(in_format); + const std::string out_type = format_to_type(out_format); + const size_t in_size = get_bytes_per_item(in_type); + const size_t out_size = get_bytes_per_item(out_type); + // Create the buffers and fill them with random data & zeros, respectively + std::vector< std::vector<char> > input_buffers(n_inputs, std::vector<char>(in_size * n_samples, 0)); + std::vector< std::vector<char> > output_buffers(n_outputs, std::vector<char>(out_size * n_samples, 0)); + init_buffers(input_buffers, in_type, in_size, buf_seed_mode); + // Create ref vectors for the converter: + std::vector<const void *> input_buf_refs(n_inputs); + std::vector<void *> output_buf_refs(n_outputs); + for (size_t i = 0; i < n_inputs; i++) { + input_buf_refs[i] = reinterpret_cast<const void *>(&input_buffers[i][0]); + } + for (size_t i = 0; i < n_outputs; i++) { + output_buf_refs[i] = reinterpret_cast<void *>(&output_buffers[i][0]); + } + + /// Final configurations to the converter: + std::cout << "Configuring converters:" << std::endl; + BOOST_FOREACH(priority_type prio_i, conv_list.keys()) { + std::cout << "* [" << prio_i << "]: "; + configure_conv(conv_list[prio_i], in_type, out_type); + } + + /// Run the benchmark for every converter //////////////////////////////// + std::cout << "{{{" << std::endl; + if (not debug_mode) { + std::cout << "prio,duration_ms,avg_duration_ms,n_samples,iterations" << std::endl; + BOOST_FOREACH(priority_type prio_i, conv_list.keys()) { + double duration = run_benchmark( + conv_list[prio_i], + input_buf_refs, + output_buf_refs, + n_samples, + iterations + ); + std::cout << boost::format("%i,%d,%d,%d,%d") + % prio_i + % (duration * 1000) + % (duration * 1000.0 / iterations) + % n_samples + % iterations + << std::endl; + } + } + + /// Or run debug mode, which runs one conversion and prints the results //// + if (debug_mode) { + // Only run on the first converter: + run_benchmark( + conv_list[conv_list.keys().at(0)], + input_buf_refs, + output_buf_refs, + n_samples, + iterations + ); + for (size_t i = 0; i < n_samples; i++) { + std::cout << item_to_string(input_buf_refs[0], i, in_type, vm.count("hex")) + << ";" + << item_to_string(reinterpret_cast< const void * >(output_buf_refs[0]), i, out_type, vm.count("hex")) + << std::endl; + } + } + std::cout << "}}}" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/host/utils/converter_benchmark.py b/host/utils/converter_benchmark.py new file mode 100644 index 000000000..c3cab8753 --- /dev/null +++ b/host/utils/converter_benchmark.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +""" +Wrap the converter_benchmark tool and produce prettier results. +""" + +from __future__ import print_function +import argparse +import csv +import subprocess + +INTRO_SETUP = { + 'n_samples': { + 'title': 'Samples per iteration', + }, + 'iterations': { + 'title': 'Number of iterations' + }, +} + +TABLE_SETUP = { + 'prio': { + 'title': 'Priority', + }, + 'duration_ms': { + 'title': 'Total Duration (ms)', + }, + 'avg_duration_ms': { + 'title': 'Avg. Duration (ms)', + }, +} + +def run_benchmark(args): + """ Run the tool with the given arguments, return the section in the {{{ }}} brackets """ + call_args = ['./converter_benchmark',] + for k, v in args.__dict__.iteritems(): + k = k.replace('_', '-') + if v is None: + continue + if k in ('debug-converter', 'hex'): + if v: + call_args.append('--{0}'.format(k)) + continue + call_args.append('--{0}'.format(k)) + call_args.append(str(v)) + print(call_args) + try: + output = subprocess.check_output(call_args) + except subprocess.CalledProcessError as ex: + print(ex.output) + exit(ex.returncode) + header_out, csv_output = output.split('{{{', 1) + csv_output = csv_output.split('}}}', 1) + assert len(csv_output) == 2 and csv_output[1].strip() == '' + return header_out, csv_output[0] + +def print_stats_table(args, csv_output): + """ + Print stats. + """ + reader = csv.reader(csv_output.strip().split('\n'), delimiter=',') + title_row = reader.next() + row_widths = [0,] * len(TABLE_SETUP) + for idx, row in enumerate(reader): + if idx == 0: + # Print intro: + for k, v in INTRO_SETUP.iteritems(): + print("{title}: {value}".format( + title=v['title'], + value=row[title_row.index(k)], + )) + print("") + # Print table header + for idx, item in enumerate(TABLE_SETUP): + print(" {title} ".format(title=TABLE_SETUP[item]['title']), end='') + row_widths[idx] = len(TABLE_SETUP[item]['title']) + if idx < len(TABLE_SETUP) - 1: + print("|", end='') + print("") + for idx, item in enumerate(TABLE_SETUP): + print("-" * (row_widths[idx] + 2), end='') + if idx < len(TABLE_SETUP) - 1: + print("+", end='') + print("") + # Print actual row data + for idx, item in enumerate(TABLE_SETUP): + format_str = " {{item:>{n}}} ".format(n=row_widths[idx]) + print(format_str.format(item=row[title_row.index(item)]), end='') + if idx < len(TABLE_SETUP) - 1: + print("|", end='') + print("") + +def print_debug_table(args, csv_output): + """ + Print debug output. + """ + reader = csv.reader(csv_output.strip().split('\n'), delimiter=';') + print_widths_hex = { + 'u8': 2, + 'sc16': 8, + 'fc32': 16, + 's16': 4, + } + if args.hex: + format_str = "{{0[0]:0>{n_in}}} => {{0[1]:0>{n_out}}}".format( + n_in=print_widths_hex[getattr(args, 'in').split('_', 1)[0]], + n_out=print_widths_hex[args.out.split('_', 1)[0]] + ) + else: + format_str = "{0[0]}\t=>\t{0[1]}" + for row in reader: + print(format_str.format(row)) + +def setup_argparse(): + """ Configure arg parser. """ + parser = argparse.ArgumentParser( + description="UHD Converter Benchmark + Debugging Utility.", + ) + parser.add_argument( + "-i", "--in", required=True, + help="Input format (e.g. 'sc16')" + ) + parser.add_argument( + "-o", "--out", required=True, + help="Output format (e.g. 'sc16')" + ) + parser.add_argument( + "-s", "--samples", type=int, + help="Number of samples per iteration" + ) + parser.add_argument( + "-N", "--iterations", type=int, + help="Number of iterations per benchmark", + ) + parser.add_argument( + "-p", "--priorities", + help="Converter priorities. Can be 'default', 'all', or a comma-separated list of priorities.", + ) + parser.add_argument( + "--max-prio", type=int, + help="Largest available priority (advanced feature)", + ) + parser.add_argument( + "--n-inputs", type=int, + help="Number of input vectors", + ) + parser.add_argument( + "--n-outputs", type=int, + help="Number of output vectors", + ) + parser.add_argument( + "--seed-mode", choices=('random', 'incremental'), + help="How to initialize the data: random, incremental", + ) + parser.add_argument( + "--debug-converter", action='store_true', + help="Skip benchmark and print conversion results. Implies iterations==1 and will only run on a single converter.", + ) + parser.add_argument( + "--hex", action='store_true', + help="In debug mode, display data as hex values.", + ) + return parser + +def main(): + """ Go, go, go! """ + args = setup_argparse().parse_args() + print("Running converter benchmark...") + header_out, csv_output = run_benchmark(args) + print(header_out) + if args.debug_converter: + print_debug_table(args, csv_output) + else: + print_stats_table(args, csv_output) + +if __name__ == "__main__": + main() + diff --git a/host/utils/fx2_init_eeprom.cpp b/host/utils/fx2_init_eeprom.cpp index 5711b73e0..cf7fb2de2 100644 --- a/host/utils/fx2_init_eeprom.cpp +++ b/host/utils/fx2_init_eeprom.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010,2014 Ettus Research LLC +// Copyright 2010,2014,2016 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 @@ -20,8 +20,17 @@ #include <uhd/property_tree.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> +#include <boost/algorithm/string/predicate.hpp> #include <iostream> -#include <cstdlib> +//#include <cstdlib> +#ifdef UHD_PLATFORM_LINUX +#include <fstream> +#include <unistd.h> // syscall constants +#include <fcntl.h> // O_NONBLOCK +#include <sys/syscall.h> +#include <cerrno> +#include <cstring> // for std::strerror +#endif //UHD_PLATFORM_LINUX const std::string FX2_VENDOR_ID("0x04b4"); const std::string FX2_PRODUCT_ID("0x8613"); @@ -49,10 +58,22 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ return EXIT_FAILURE; } - //cant find a uninitialized usrp with this mystery module in the way... - if (std::system("/sbin/rmmod usbtest") != 0){ - std::cerr << "Did not rmmod usbtest, this may be ok..." << std::endl; +#ifdef UHD_PLATFORM_LINUX + //can't find an uninitialized usrp with this mystery usbtest in the way... + std::string module("usbtest"); + std::ifstream modules("/proc/modules"); + bool module_found = false; + std::string module_line; + while(std::getline(modules, module_line) && (!module_found)) { + module_found = boost::starts_with(module_line, module); } + if(module_found) { + std::cout << boost::format("Found the '%s' module. Unloading it.\n" ) % module; + int fail = syscall(__NR_delete_module, module.c_str(), O_NONBLOCK); + if(fail) + std::cerr << ( boost::format("Removing the '%s' module failed with error '%s'.\n") % module % std::strerror(errno) ); + } +#endif //UHD_PLATFORM_LINUX //load the options into the address uhd::device_addr_t device_addr; diff --git a/host/utils/uhd_config_info.cpp b/host/utils/uhd_config_info.cpp new file mode 100644 index 000000000..4279c325d --- /dev/null +++ b/host/utils/uhd_config_info.cpp @@ -0,0 +1,89 @@ +// +// Copyright 2015 National Instruments Corp. +// +// 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 <uhd/build_info.hpp> +#include <uhd/version.hpp> +#include <uhd/utils/safe_main.hpp> + +#include <boost/format.hpp> +#include <boost/program_options.hpp> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char* argv[]) { + // Program Options + po::options_description desc("Allowed Options"); + desc.add_options() + ("build-date", "Print build date") + ("c-compiler", "Print C compiler") + ("cxx-compiler", "Print C++ compiler") + ("c-flags", "Print C compiler flags") + ("cxx-flags", "Print C++ compiler flags") + ("enabled-components", "Print built-time enabled components") + ("install-prefix", "Print install prefix") + ("libusb-version", "Print libusb version") + ("print-all", "Print everything") + ("version", "Print this UHD build's version") + ("help", "Print help message") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + // Print the help message + if(vm.count("help") > 0) { + std::cout << boost::format("UHD Config Info - %s") % desc << std::endl; + return EXIT_FAILURE; + } + + bool print_all = (vm.count("print-all") > 0); + + if(vm.count("version") > 0 or print_all) { + std::cout << "UHD " << uhd::get_version_string() << std::endl; + } + if(vm.count("build-date") > 0 or print_all) { + std::cout << "Build date: " << uhd::build_info::build_date() << std::endl; + } + if(vm.count("c-compiler") > 0 or print_all) { + std::cout << "C compiler: " << uhd::build_info::c_compiler() << std::endl; + } + if(vm.count("cxx-compiler") > 0 or print_all) { + std::cout << "C++ compiler: " << uhd::build_info::cxx_compiler() << std::endl; + } + if(vm.count("c-flags") > 0 or print_all) { + std::cout << "C flags: " << uhd::build_info::c_flags() << std::endl; + } + if(vm.count("cxx-flags") > 0 or print_all) { + std::cout << "C++ flags: " << uhd::build_info::cxx_flags() << std::endl; + } + if(vm.count("enabled-components") > 0 or print_all) { + std::cout << "Enabled components: " << uhd::build_info::enabled_components() << std::endl; + } + if(vm.count("install-prefix") > 0 or print_all) { + std::cout << "Install prefix: " << uhd::build_info::install_prefix() << std::endl; + } + if(vm.count("boost-version") > 0 or print_all) { + std::cout << "Boost version: " << uhd::build_info::boost_version() << std::endl; + } + if(vm.count("libusb-version") > 0 or print_all) { + std::string _libusb_version = uhd::build_info::libusb_version(); + std::cout << "Libusb version: " << (_libusb_version.empty() ? "N/A" : _libusb_version) << std::endl; + } + + return EXIT_SUCCESS; +} diff --git a/images/CMakeLists.txt b/images/CMakeLists.txt index 161b5130a..6c3b23239 100644 --- a/images/CMakeLists.txt +++ b/images/CMakeLists.txt @@ -18,7 +18,12 @@ ######################################################################## # Setup Project ######################################################################## -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +#OPTION(UHD_PATCH_OVERRIDE "Use this string to force a patch level version string." OFF) +MESSAGE(STATUS ${UHD_PATCH_OVERRIDE}) +IF(DEFINED UHD_PATCH_OVERRIDE) + SET(UHD_VERSION_PATCH_OVERRIDE ${UHD_PATCH_OVERRIDE}) +ENDIF(DEFINED UHD_PATCH_OVERRIDE) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(UHD-images NONE) LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/../host/cmake/Modules) INCLUDE(UHDVersion) #sets version variables (used below) diff --git a/images/create_imgs_package.py b/images/create_imgs_package.py index bdd4e7644..82b7d65d4 100755 --- a/images/create_imgs_package.py +++ b/images/create_imgs_package.py @@ -47,12 +47,13 @@ def parse_args(): parser = argparse.ArgumentParser(description='Link the current set of images to this commit.') parser.add_argument('--commit', default=None, help='Supply a commit message to the changes to host/CMakeLists.txt.') - parser.add_argument('-r', '--release-mode', default=None, + parser.add_argument('-r', '--release-mode', default="", help='Specify UHD_RELEASE_MODE. Typically "release" or "rc1" or similar.') parser.add_argument('--skip-edit', default=False, action='store_true', help='Do not edit the CMakeLists.txt file.') parser.add_argument('--skip-move', default=False, action='store_true', help='Do not move the archives after creating them.') + parser.add_argument('--patch', help='Override patch version number.') return parser.parse_args() def move_zip_to_repo(base_url, zipfilename): @@ -75,8 +76,9 @@ def main(): clear_img_dir(img_root_dir) print "== Creating archives..." cpack_cmd = ["./make_zip.sh",] - if args.release_mode is not None: - cpack_cmd.append(args.release_mode) + cpack_cmd.append(args.release_mode) + if args.patch is not None: + cpack_cmd.append("-DUHD_PATCH_OVERRIDE={}".format(args.patch)) try: cpack_output = subprocess.check_output(cpack_cmd) except subprocess.CalledProcessError as e: diff --git a/images/make_zip.sh b/images/make_zip.sh index 19695ef3f..e9ef802f7 100755 --- a/images/make_zip.sh +++ b/images/make_zip.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Automatically run the make-zip-file process # Check we're in the right directory and all is set: if [ ! -e 'make_zip.sh' ]; then @@ -28,12 +28,12 @@ mkdir build cd build # Run the CPack process (ZIP file) -cmake .. -DCPACK_GENERATOR=ZIP -DUHD_RELEASE_MODE="$1" .. +cmake .. -DCPACK_GENERATOR=ZIP -DUHD_RELEASE_MODE="$1" $2 .. make package mv uhd-images*.zip .. # Run the CPack process (tarball) -cmake .. -DCPACK_GENERATOR=TGZ -DUHD_RELEASE_MODE="$1" .. +cmake .. -DCPACK_GENERATOR=TGZ -DUHD_RELEASE_MODE="$1" $2 .. make package mv uhd-images*.tar.gz .. diff --git a/tools/chdr-dissector/packet-chdr.c b/tools/chdr-dissector/packet-chdr.c index cce46bb84..079e6bb3b 100644 --- a/tools/chdr-dissector/packet-chdr.c +++ b/tools/chdr-dissector/packet-chdr.c @@ -1,5 +1,5 @@ /* - * Dissector for UHD CHDR packets + * Dissector for UHD CVITA (CHDR) packets * * Copyright 2010-2014 Ettus Research LLC * @@ -37,22 +37,45 @@ const unsigned int CHDR_PORT = X300_VITA_UDP_PORT; static int proto_chdr = -1; static int hf_chdr_hdr = -1; -static int hf_chdr_is_extension = -1; -static int hf_chdr_reserved = -1; +static int hf_chdr_type = -1; static int hf_chdr_has_time = -1; static int hf_chdr_eob = -1; +static int hf_chdr_error = -1; static int hf_chdr_sequence = -1; static int hf_chdr_packet_size = -1; static int hf_chdr_stream_id = -1; static int hf_chdr_src_dev = -1; static int hf_chdr_src_ep = -1; +static int hf_chdr_src_blockport = -1; static int hf_chdr_dst_dev = -1; static int hf_chdr_dst_ep = -1; +static int hf_chdr_dst_blockport = -1; static int hf_chdr_timestamp = -1; static int hf_chdr_payload = -1; static int hf_chdr_ext_response = -1; static int hf_chdr_ext_status_code = -1; static int hf_chdr_ext_seq_num = -1; +static int hf_chdr_cmd = -1; +static int hf_chdr_cmd_address = -1; +static int hf_chdr_cmd_value = -1; + +static const value_string CHDR_PACKET_TYPES[] = { + { 0, "Data" }, + { 1, "Data (End-of-Burst)" }, + { 4, "Flow Control" }, + { 8, "Command" }, + { 12, "Response" }, + { 13, "Error Response" }, +}; + +static const value_string CHDR_PACKET_TYPES_SHORT[] = { + { 0, "data" }, + { 1, "data" }, + { 4, "fc" }, + { 8, "cmd" }, + { 12, "resp" }, + { 13, "resp" }, +}; /* the heuristic dissector is called on every packet with payload. * The warning printed for this should only be printed once. @@ -64,6 +87,7 @@ static gint ett_chdr = -1; static gint ett_chdr_header = -1; static gint ett_chdr_id = -1; static gint ett_chdr_response = -1; +static gint ett_chdr_cmd = -1; /* Forward-declare the dissector functions */ void proto_register_chdr(void); @@ -71,7 +95,7 @@ void proto_reg_handoff_chdr(void); static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree); /* heuristic dissector call. Will always return. */ -static gboolean heur_dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +static gboolean heur_dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* whatislove) { if(heur_warning_printed < 1){ printf(LOG_HEADER"heuristic dissector always returns true!\n"); @@ -132,11 +156,21 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) proto_tree *stream_tree; proto_item *response_item; proto_tree *response_tree; + proto_item *cmd_item; + proto_tree *cmd_tree; gint len; gint flag_offset; guint8 *bytes; - gboolean flag_has_time; + guint8 hdr_bits = 0; + guint8 pkt_type = 0; + gboolean flag_has_time = 0; + gboolean flag_is_data = 0; + gboolean flag_is_fc = 0; + gboolean flag_is_cmd = 0; + gboolean flag_is_resp = 0; + gboolean flag_is_eob = 0; + gboolean flag_is_error = 0; unsigned long long timestamp; gboolean is_network; gint endianness; @@ -169,8 +203,16 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) if (len >= 4){ chdr_size = 8; - bytes = tvb_get_string(tvb, 0, 4); - flag_has_time = bytes[flag_offset] & 0x20; + bytes = tvb_get_string(wmem_packet_scope(), tvb, 0, 4); + hdr_bits = (bytes[flag_offset] & 0xF0) >> 4; + pkt_type = hdr_bits >> 2; + flag_is_data = (pkt_type == 0); + flag_is_fc = (pkt_type == 1); + flag_is_cmd = (pkt_type == 2); + flag_is_resp = (pkt_type == 3); + flag_is_eob = flag_is_data && (hdr_bits & 0x1); + flag_is_error = flag_is_resp && (hdr_bits & 0x1); + flag_has_time = hdr_bits & 0x2; if (flag_has_time) chdr_size += 8; // 64-bit timestamp } @@ -178,17 +220,28 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) /* Start with a top-level item to add everything else to */ item = proto_tree_add_item(tree, proto_chdr, tvb, 0, min(len, chdr_size), ENC_NA); - if (len >= 4){ + if (len >= 4) { chdr_tree = proto_item_add_subtree(item, ett_chdr); + /* Header info. First, a top-level header tree item: */ header_item = proto_tree_add_item(chdr_tree, hf_chdr_hdr, tvb, flag_offset, 1, endianness); header_tree = proto_item_add_subtree(header_item, ett_chdr_header); - - /* These lines add flag info to tree */ - proto_tree_add_item(header_tree, hf_chdr_is_extension, tvb, flag_offset, 1, ENC_NA); - proto_tree_add_item(header_tree, hf_chdr_reserved, tvb, flag_offset, 1, ENC_NA); - proto_tree_add_item(header_tree, hf_chdr_has_time, tvb, flag_offset, 1, ENC_NA); - proto_tree_add_item(header_tree, hf_chdr_eob, tvb, flag_offset, 1, ENC_NA); + proto_item_append_text(header_item, ", Packet type: %s", + val_to_str(hdr_bits & 0xD, CHDR_PACKET_TYPES, "Unknown (0x%x)") + ); + /* Let us query hdr.type */ + proto_tree_add_string( + header_tree, hf_chdr_type, tvb, flag_offset, 1, + val_to_str(hdr_bits & 0xD, CHDR_PACKET_TYPES_SHORT, "invalid") + ); + /* And other flags */ + proto_tree_add_boolean(header_tree, hf_chdr_has_time, tvb, flag_offset, 1, flag_has_time); + if (flag_is_data) { + proto_tree_add_boolean(header_tree, hf_chdr_eob, tvb, flag_offset, 1, flag_is_eob); + } + if (flag_is_resp) { + proto_tree_add_boolean(header_tree, hf_chdr_error, tvb, flag_offset, 1, flag_is_error); + } /* These lines add sequence, packet_size and stream ID */ proto_tree_add_item(chdr_tree, hf_chdr_sequence, tvb, (is_network ? 0:2), 2, endianness); @@ -199,16 +252,33 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) stream_item = proto_tree_add_item(chdr_tree, hf_chdr_stream_id, tvb, 4, 4, endianness); stream_tree = proto_item_add_subtree(stream_item, ett_chdr_id); proto_tree_add_item(stream_tree, hf_chdr_src_dev, tvb, id_pos[0], 1, ENC_NA); - proto_tree_add_item(stream_tree, hf_chdr_src_ep, tvb, id_pos[1], 1, ENC_NA); + proto_tree_add_item(stream_tree, hf_chdr_src_ep, tvb, id_pos[1], 1, ENC_NA); proto_tree_add_item(stream_tree, hf_chdr_dst_dev, tvb, id_pos[2], 1, ENC_NA); - proto_tree_add_item(stream_tree, hf_chdr_dst_ep, tvb, id_pos[3], 1, ENC_NA); + proto_tree_add_item(stream_tree, hf_chdr_dst_ep, tvb, id_pos[3], 1, ENC_NA); + + /* Block ports (only add them if address points to a device) */ + bytes = tvb_get_string(wmem_packet_scope(), tvb, 0, 8); + if (bytes[id_pos[0]] != 0) { + proto_tree_add_item(stream_tree, hf_chdr_src_blockport, tvb, id_pos[1], 1, ENC_NA); + } + if (bytes[id_pos[2]] != 0) { + proto_tree_add_item(stream_tree, hf_chdr_dst_blockport, tvb, id_pos[3], 1, ENC_NA); + } + + /* Append SID in sid_t hex format */ + proto_item_append_text(stream_item, " (%02X:%02X>%02X:%02X)", + bytes[id_pos[0]], + bytes[id_pos[1]], + bytes[id_pos[2]], + bytes[id_pos[3]] + ); /* if has_time flag is present interpret timestamp */ if ((flag_has_time) && (len >= 16)){ if (is_network) item = proto_tree_add_item(chdr_tree, hf_chdr_timestamp, tvb, 8, 8, endianness); else{ - bytes = (guint8*) tvb_get_string(tvb, 8, sizeof(unsigned long long)); + bytes = (guint8*) tvb_get_string(wmem_packet_scope(), tvb, 8, sizeof(unsigned long long)); timestamp = get_timestamp(bytes, sizeof(unsigned long long)); proto_tree_add_uint64(chdr_tree, hf_chdr_timestamp, tvb, 8, 8, timestamp); } @@ -217,19 +287,21 @@ static void dissect_chdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) int remaining_bytes = (len - chdr_size); int show_raw_payload = (remaining_bytes > 0); - if (hf_chdr_is_extension){ - if (remaining_bytes == 8){ // Interpret this as a response packet - response_item = proto_tree_add_item(chdr_tree, hf_chdr_ext_response, tvb, chdr_size, 8, endianness); - response_tree = proto_item_add_subtree(response_item, ett_chdr_response); - - proto_tree_add_item(response_tree, hf_chdr_ext_status_code, tvb, chdr_size, 4, endianness); - /* This will show the 12-bits of sequence ID in the last 2 bytes */ - proto_tree_add_item(response_tree, hf_chdr_ext_seq_num, tvb, (chdr_size + 4 + (is_network ? 2 : 0)), 2, endianness); - } - } - - if (show_raw_payload) + if (flag_is_cmd && remaining_bytes == 8) { + cmd_item = proto_tree_add_item(chdr_tree, hf_chdr_cmd, tvb, chdr_size, 8, endianness); + cmd_tree = proto_item_add_subtree(cmd_item, ett_chdr_cmd); + proto_tree_add_item(cmd_tree, hf_chdr_cmd_address, tvb, chdr_size, 4, endianness); + proto_tree_add_item(cmd_tree, hf_chdr_cmd_value, tvb, chdr_size + 4, 4, endianness); + } else if (flag_is_resp) { + response_item = proto_tree_add_item(chdr_tree, hf_chdr_ext_response, tvb, chdr_size, 8, endianness); + response_tree = proto_item_add_subtree(response_item, ett_chdr_response); + + proto_tree_add_item(response_tree, hf_chdr_ext_status_code, tvb, chdr_size, 4, endianness); + /* This will show the 12-bits of sequence ID in the last 2 bytes */ + proto_tree_add_item(response_tree, hf_chdr_ext_seq_num, tvb, (chdr_size + 4 + (is_network ? 2 : 0)), 2, endianness); + } else if (show_raw_payload) { proto_tree_add_item(chdr_tree, hf_chdr_payload, tvb, chdr_size, -1, ENC_NA); + } } } } @@ -244,17 +316,11 @@ void proto_register_chdr(void) NULL, 0xF0, NULL, HFILL } }, - { &hf_chdr_is_extension, - { "Extension context packet", "chdr.hdr.ext", - FT_BOOLEAN, BASE_NONE, - NULL, 0x80, - NULL, HFILL } - }, - { &hf_chdr_reserved, - { "Reserved bit", "chdr.hdr.reserved", - FT_BOOLEAN, BASE_NONE, - NULL, 0x40, - NULL, HFILL } + { &hf_chdr_type, + { "Packet Type", "chdr.hdr.type", + FT_STRINGZ, BASE_NONE, + NULL, 0x00, + "Packet Type", HFILL } }, { &hf_chdr_has_time, { "Time present", "chdr.hdr.has_time", @@ -268,6 +334,12 @@ void proto_register_chdr(void) NULL, 0x10, NULL, HFILL } }, + { &hf_chdr_error, + { "Error Flag", "chdr.hdr.error", + FT_BOOLEAN, BASE_NONE, + NULL, 0x10, + NULL, HFILL } + }, { &hf_chdr_sequence, { "Sequence ID", "chdr.seq", FT_UINT16, BASE_DEC, @@ -298,6 +370,12 @@ void proto_register_chdr(void) NULL, 0x0, NULL, HFILL } }, + { &hf_chdr_src_blockport, + { "Source block port", "chdr.src_bp", + FT_UINT8, BASE_DEC, + NULL, 0xF, + NULL, HFILL } + }, { &hf_chdr_dst_dev, { "Destination device", "chdr.dst_dev", FT_UINT8, BASE_DEC, @@ -310,6 +388,12 @@ void proto_register_chdr(void) NULL, 0x0, NULL, HFILL } }, + { &hf_chdr_dst_blockport, + { "Destination block port", "chdr.dst_bp", + FT_UINT8, BASE_DEC, + NULL, 0xF, + NULL, HFILL } + }, { &hf_chdr_timestamp, { "Time", "chdr.time", FT_UINT64, BASE_DEC, @@ -341,13 +425,32 @@ void proto_register_chdr(void) NULL, 0x0FFF, NULL, HFILL } }, + { &hf_chdr_cmd, + { "Command", "chdr.cmd", + FT_BYTES, BASE_NONE, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_chdr_cmd_address, + { "Register Address", "chdr.cmd.addr", + FT_UINT32, BASE_DEC, + NULL, 0x0, + NULL, HFILL } + }, + { &hf_chdr_cmd_value, + { "Command Value", "chdr.cmd.val", + FT_UINT32, BASE_HEX, + NULL, 0x0, + NULL, HFILL } + }, }; static gint *ett[] = { &ett_chdr, &ett_chdr_header, &ett_chdr_id, - &ett_chdr_response + &ett_chdr_response, + &ett_chdr_cmd }; proto_chdr = proto_register_protocol("UHD CHDR", "CHDR", "chdr"); |