diff options
author | Martin Braun <martin.braun@ettus.com> | 2014-10-07 11:32:14 +0200 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2014-10-07 12:09:33 +0200 |
commit | 1b149f561370687ad65e3aa644a402f00dbd16ea (patch) | |
tree | ab86042840fa1369d64bca56c5f3a64d1a4f1f72 /host | |
parent | fd3e84941de463fa1a7ebab0a69515b4bf2614cd (diff) | |
download | uhd-1b149f561370687ad65e3aa644a402f00dbd16ea.tar.gz uhd-1b149f561370687ad65e3aa644a402f00dbd16ea.tar.bz2 uhd-1b149f561370687ad65e3aa644a402f00dbd16ea.zip |
Initial commit E300 support.
Diffstat (limited to 'host')
42 files changed, 7558 insertions, 9 deletions
diff --git a/host/docs/mainpage.dox b/host/docs/mainpage.dox index 597938c35..17f01cfc0 100644 --- a/host/docs/mainpage.dox +++ b/host/docs/mainpage.dox @@ -39,6 +39,7 @@ devices and how to use the API to connect to them through your own software. ## USRP E-Series Devices \li \subpage page_usrp_e1x0 +\li \subpage page_usrp_e3x0 \li \subpage page_gpsdo ## USRP X-Series Devices diff --git a/host/docs/usrp_e3x0.dox b/host/docs/usrp_e3x0.dox new file mode 100644 index 000000000..bfa9ebf11 --- /dev/null +++ b/host/docs/usrp_e3x0.dox @@ -0,0 +1,302 @@ +/*! \page page_usrp_e3x0 USRP-E3x0 Series Device Manual + +\tableofcontents + +\section e3x0_feature_list Comparative features list + +- Hardware Capabilities: + Integrated RF frontend (70 MHz - 6 GHz) + - External PPS reference input + - External 10 MHz reference input + - Configurable clock rate + - Internal GPIO connector with UHD API control + - 2 USB 2.0 Host ports + - Internal GPSDO + - Soundcard mono input / stereo output + - USB UART + - Zynq-7020 FPGA +- FPGA Capabilities: + - 2 RX DDC chains in FPGA + - 2 TX DUC chain in FPGA + - Timed commands in FPGA + - Timed sampling in FPGA + - 16-bit and sample modes (sc16) + - Up to 10 MHz of RF bandwidth with 16-bit samples + +\section e3x0_getting_started Getting started + +This will run you through the first steps relevant to get your USRP E300/310 +up and running. + +\subsection e3x0_first_boot First boot + +After unpacking and assembling your USRP E300/E310 insert the micro sd card into the micro sd card slot. + +There are two different methods to connect to the device + +- using the onboard serial to usb connector +- using the gigabit ethernet connector and a ssh client on your host computer + +For the first boot, booting with the serial cable connected to the device +is recommended, as it allows to review and modify the network configuration, +and allows to enter the bootloader in case of issues during the boot. + + +\subsubsection e3x0_first_boot_serial Serial connection + +To use the serial connection together with a Linux or OSX machine (most other UNIX variants come with a version of screen, too) +a terminal emulator such as screen can be used: + + $ sudo screen /dev/ttyUSB0 115200 + +The exact device node /dev/ttyUSB0 depends on your operating system's driver and other USB devices that might be already connected. +It can be usually found by perusing the output of dmesg or journalctl, after connecting the USRP E300/E310 device to your host computer. + +An example of a dmesg output for the serial to usb converter: + + 924.102764] usb 1-1: FTDI USB Serial Device converter now attached to ttyUSB0 + + +On Microsoft Windows the serial connection can be established using a tool such as Putty by selecting a baudrate of 115200 and the corresponding serial port for the serial to usb converter. + +In both cases you should see boot messages fly by and finally end up with a login prompt similar to the following: + +TODO!! + +Note: The username is 'root' and the default password is empty. + +You should be presented with a shell similar to the following + + root@ettus-e300:~# + + +\subsubsection e3x0_first_boot_ssh SSH connection + +The USRP E300/E310 device relies on the DHCP protocol to automatically obtain an IP address. +In case your network setup does not include a DHCP server, refer to the section \ref e3x0_first_boot_serial or configure a DHCP server to hand out IP addresses on your network. + +After the device obtained an IP address you can log in from a Linux or OSX machine by typing: + + $ ssh root@192.168.10.42 + +where the IP address depends on your local network setup. + +On Microsoft Windows again the connection can be established using a tool such as Putty, by selecting a username of root without password. + +You should be presented with a shell similar to the following + + root@ettus-e300:~# + +\section e3x0_hw Hardware Setup + +\section e3x0_load_fpga_imgs Load FPGA Images onto the Device + +The USRP-X Series device ships with a bitstream pre-programmed in the flash, +which is automatically loaded onto the FPGA during device power-up. However, +a new FPGA image can be configured over the PCI Express interface or the +on-board USB-JTAG programmer. This process can be seen as a "one-time load", in +that if you power-cycle the device, it will not retain the FPGA image. + +Please note that this process is *different* than replacing the FPGA image +stored in the flash, which will then be automatically loaded the next time the +device is reset. + +\subsection e3x0_load_fpga_imgs_jtag Use JTAG to load FPGA images + +The USRP-E Series device features an on-board JTAG connector that can be accessed on the PCB +of the device. The iMPACT tool in the <a href="http://www.xilinx.com/support/download/index.htm">Xilinx Programming Tools (ISE, iMPACT)</a> package can be used to load an image over the JTAG interface. This can be useful for unbricking devices. + +If you have iMPACT installed, you can use the `impact_jtag_programmer.sh` tool to install images. Make sure your e3x0 is powered on and connected to your computer using the internal JTAG connector. Then run the tool: + + <path_to_uhd_tools>/impact_jtag_programmer.sh --fpga-path=<fpga_image_path> + +\subsection e3x0_setup_change_ip Change the USRP's IP address + +You may need to change the USRP's IP address for several reasons: +- to satisfy your particular network configuration +- to use multiple USRP-E Series devices with the same host computer +- to set a known IP address into USRP (in case you forgot) + +\section e3x0_addressing Addressing the Device + +\subsection e3x0_addressing_singledev Single device configuration + +In a single-device configuration, +the USRP device must have a unique IPv4 address on the host computer. +The USRP can be identified through its IPv4 address or resolvable hostname. +See the application notes on \ref page_identification. +Use this addressing scheme with the uhd::usrp::multi_usrp interface (not a typo!). + +Example device address string representation for a USRP-E Series device with IPv4 address 192.168.10.2: + + addr=192.168.10.2 + +\subsection e3x0_addressing_multidevcfg Multiple device configuration + +In a multi-device configuration, +each USRP device must have a unique IPv4 address on the host computer. +The device address parameter keys must be suffixed with the device index. +Each parameter key should be of the format \<key\>\<index\>. +Use this addressing scheme with the uhd::usrp::multi_usrp interface. + +- The order in which devices are indexed corresponds to the indexing of the transmit and receive channels. +- The key indexing provides the same granularity of device identification as in the single device case. + +Example device address string representation for 2 USRPs with IPv4 addresses **192.168.10.2** and **192.168.20.2**: + + addr0=192.168.10.2, addr1=192.168.20.2 + + +\section e3x0_comm_problems Communication Problems + +When setting up a development machine for the first time, +you may have various difficulties communicating with the USRP device. +The following tips are designed to help narrow down and diagnose the problem. + +\subsection e3x0_comm_problems_runtimeerr RuntimeError: no control response + +This is a common error that occurs when you have set the subnet of your network +interface to a different subnet than the network interface of the USRP device. For +example, if your network interface is set to **192.168.20.1**, and the USRP device is **192.168.10.2** +(note the difference in the third numbers of the IP addresses), you +will likely see a 'no control response' error message. + +Fixing this is simple - just set the your host PC's IP address to the same +subnet as that of your USRP device. Instructions for setting your IP address are in the +previous section of this documentation. + +\subsection e3x0_comm_problems_firewall Firewall issues + +When the IP address is not specified, +the device discovery broadcasts UDP packets from each Ethernet interface. +Many firewalls will block the replies to these broadcast packets. +If disabling your system's firewall +or specifying the IP address yields a discovered device, +then your firewall may be blocking replies to UDP broadcast packets. +If this is the case, we recommend that you disable the firewall +or create a rule to allow all incoming packets with UDP source port **49152**. + +\subsection e3x0_comm_problems_ping Ping the device +The USRP device will reply to ICMP echo requests ("ping"). +A successful ping response means that the device has booted properly +and that it is using the expected IP address. + + ping 192.168.10.2 + +\subsection e3x0_comm_problems_monitor Monitor the host network traffic +Use Wireshark to monitor packets sent to and received from the device. + +\subsection e3x0_comm_problems_leds Observe Ethernet port LEDs +When there is network traffic arriving at the Ethernet port, LEDs will light up. +You can use this to make sure the network connection is correctly set up, e.g. +by pinging the USRP and making sure the LEDs start to blink. + +\section e3x0_hw Hardware Notes + +\subsection e3x0_hw_fpanel Front Panel + +\image html e3x0_fp_overlay.png "e3x0" + +- **RF A Group** + + **TX/RX LED**: Indicates that data is streaming on the TX/RX channel on daughterboard A + + **RX2 LED**: Indicates that data is streaming on the RX2 channel on daughterboard A +- **REF**: Indicates that the external Reference Clock is locked +- **PPS**: Indicates a valid PPS signal by pulsing once per second +- **AUX I/O**: Front panel GPIO connector. +- **GPS**: Indicates that GPS reference is locked +- **LINK**: Indicates that the host computer is communicating with the device (Activity) + +- **RF B Group** + + **TX/RX LED**: Indicates that data is streaming on the TX/RX channel on daughterboard B + + **RX2 LED**: Indicates that data is streaming on the RX2 channel on daughterboard B +- **PWR**: Power switch + +\subsection e3x0_hw_rear_panel Rear Panel + +\image html e3x0_rp_overlay.png "e3x0 Rear Panel" + +- **PWR**: Connector for the USRP-X Series power supply +- **1G/10G ETH**: SFP+ ports for Ethernet interfaces +- **REF OUT**: Output port for the exported reference clock +- **REF IN**: Reference clock input +- **PCIe x4**: Connector for Cabled PCI Express link +- **PPS/TRIG OUT**: Output port for the PPS signal +- **PPS/TRIG IN**: Input port for the PPS signal +- **GPS**: Connection for the GPS antenna + +\subsection e3x0_hw_e3x0_hw_ref10M Ref Clock - 10 MHz + +Using an external 10 MHz reference clock, a square wave will offer the best phase +noise performance, but a sinusoid is acceptable. The power level of the reference clock cannot exceed +15 dBm. + +\subsection e3x0_hw_pps PPS - Pulse Per Second +Using a PPS signal for timestamp synchronization requires a square wave signal with the following a 5Vpp amplitude. + +To test the PPS input, you can use the following tool from the UHD examples: + +- `<args>` are device address arguments (optional if only one USRP device is on your machine) + + cd <install-path>/lib/uhd/examples + ./test_pps_input --args=\<args\> + +\subsection e3x0_hw_gpsdo Internal GPSDO + +Please see \ref page_gpsdo_e3x0 for information on configuring and using the internal GPSDO. + +\subsection e3x0_hw_gpio Internal GPIO + +### Connector + +\image html e3x0_gpio_conn.png "e3x0 GPIO Connector" + +### Pin Mapping + +- Pin 1: +3.3V +- Pin 2: Data[0] +- Pin 3: Data[1] +- Pin 4: Data[2] +- Pin 5: Data[3] +- Pin 6: Data[4] +- Pin 7: Data[5] +- Pin 8: Data[6] +- Pin 9: Data[7] +- Pin 10: Data[8] +TODO: + + +Please see the \ref page_gpio_api for information on configuring and using the GPIO bus. + +\subsection e3x0_hw_chipscope Debugging custom FPGA designs with Xilinx Chipscope + +Xilinx chipscope allows for debugging custom FPGA designs similar to a logic analyzer. +USRP-E series devices can be used with Xilinx chipscope using the internal JTAG connector. + +Further information on how to use Chipscope can be found in the Xilinx Chipscope Pro Software and Cores User Guide (UG029). + +\section e3x0_misc Miscellaneous + +\subsection e3x0_misc_multirx Multiple RX channels + +There are two complete DDC and DUC DSP chains in the FPGA. In the single channel case, +only one chain is ever used. To receive from both channels, the user must set the **RX** or **TX** +subdevice specification. + +In the following example, a TVRX2 is installed. +Channel 0 is sourced from subdevice **RX1**, +and channel 1 is sourced from subdevice **RX2** (**RX1** and **RX2** are antenna connectors on the TVRX2 daughterboard). + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} +usrp->set_rx_subdev_spec("A:RX1 A:RX2"); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +\subsection e3x0_misc_sensors Available Sensors + +The following sensors are available for the USRP-E Series motherboards; +they can be queried through the API. + +- **fe_locked** - rx / tx frontend pll locked +- **temp** - processor temperature value +- Other sensors are added when the GPSDO is enabled + +*/ +// vim:ft=doxygen: diff --git a/host/lib/convert/CMakeLists.txt b/host/lib/convert/CMakeLists.txt index 363555f45..bec88b520 100644 --- a/host/lib/convert/CMakeLists.txt +++ b/host/lib/convert/CMakeLists.txt @@ -94,8 +94,11 @@ IF(CMAKE_COMPILER_IS_GNUCXX) ENDIF(CMAKE_COMPILER_IS_GNUCXX) IF(HAVE_ARM_NEON_H) + ENABLE_LANGUAGE(ASM) + LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_neon.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_neon.S ) ENDIF() diff --git a/host/lib/convert/convert_neon.S b/host/lib/convert/convert_neon.S new file mode 100644 index 000000000..8cbe82bde --- /dev/null +++ b/host/lib/convert/convert_neon.S @@ -0,0 +1,37 @@ +// +// 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/>. +// + + .arch armv7-a + .fpu neon + .syntax unified + .text + .align 2 + .global neon_item32_sc16_swap_16n + .type neon_item32_sc16_swap_16n, %function +neon_item32_sc16_swap_16n: +.loop_swap: + vld2.16 {q0, q1}, [r0]! + vld2.16 {q2, q3}, [r0]! + vswp q0, q1 + vswp q2, q3 + vst2.16 {q0, q1}, [r1]! + vst2.16 {q2, q3}, [r1]! + subs r2, #1 + bne .loop_swap + bx lr + .size neon_item32_sc16_swap_16n, .-neon_item32_sc16_swap_16n + .section .note.GNU-stack,"",%progbits diff --git a/host/lib/convert/convert_with_neon.cpp b/host/lib/convert/convert_with_neon.cpp index e994d97a6..f1c7773ec 100644 --- a/host/lib/convert/convert_with_neon.cpp +++ b/host/lib/convert/convert_with_neon.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -19,6 +19,12 @@ #include <uhd/utils/byteswap.hpp> #include <arm_neon.h> +extern "C" { +void neon_item32_sc16_swap_16n(void *, void *, int iter); +} + +static const int SIMD_WIDTH = 16; + using namespace uhd::convert; DECLARE_CONVERTER(fc32, 1, sc16_item32_le, 1, PRIORITY_SIMD){ @@ -58,3 +64,31 @@ DECLARE_CONVERTER(sc16_item32_le, 1, fc32, 1, PRIORITY_SIMD){ item32_sc16_to_xx<uhd::htowx>(input+i, output+i, nsamps-i, scale_factor); } + +DECLARE_CONVERTER(sc16, 1, sc16_item32_le, 1, PRIORITY_SIMD){ + const sc16_t *input = reinterpret_cast<const sc16_t *>(inputs[0]); + item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + + size_t i = nsamps / SIMD_WIDTH; + + if (i) + neon_item32_sc16_swap_16n((void *) input, (void *) output, i); + + i *= SIMD_WIDTH; + + xx_to_item32_sc16<uhd::htowx>(input+i, output+i, nsamps-i, scale_factor); +} + +DECLARE_CONVERTER(sc16_item32_le, 1, sc16, 1, PRIORITY_SIMD){ + const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); + sc16_t *output = reinterpret_cast<sc16_t *>(outputs[0]); + + size_t i = nsamps / SIMD_WIDTH; + + if (i) + neon_item32_sc16_swap_16n((void *) input, (void *) output, i); + + i *= SIMD_WIDTH; + + item32_sc16_to_xx<uhd::wtohx>(input+i, output+i, nsamps-i, scale_factor); +} diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index c8c2e6a8d..f6788b5ef 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -37,5 +37,6 @@ INCLUDE_SUBDIRECTORY(usrp1) INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) INCLUDE_SUBDIRECTORY(e100) +INCLUDE_SUBDIRECTORY(e300) INCLUDE_SUBDIRECTORY(x300) INCLUDE_SUBDIRECTORY(b200) diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 9e8653608..3c5bb4fa8 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -37,5 +37,6 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_e3x0.cpp ) diff --git a/host/lib/usrp/dboard/db_e3x0.cpp b/host/lib/usrp/dboard/db_e3x0.cpp new file mode 100644 index 000000000..523927d49 --- /dev/null +++ b/host/lib/usrp/dboard/db_e3x0.cpp @@ -0,0 +1,64 @@ +// +// 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 <uhd/utils/static.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> + +namespace uhd { namespace usrp { + +/*********************************************************************** + * The E310 dboard + * AD9361 Interface, thus two subdevs + **********************************************************************/ +class e310_dboard : public xcvr_dboard_base{ +public: + e310_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} + + ~e310_dboard(void) {} +}; + +/*********************************************************************** + * The E310 dboard + * AD9364 Interface, thus one subdev + **********************************************************************/ +class e300_dboard : public xcvr_dboard_base{ +public: + e300_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} + + ~e300_dboard(void) {} +}; + +/*********************************************************************** + * Register the E310 dboards + **********************************************************************/ +static dboard_base::sptr make_e310_dboard(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new e310_dboard(args)); +} + +static dboard_base::sptr make_e300_dboard(dboard_base::ctor_args_t args){ + return dboard_base::sptr(new e300_dboard(args)); +} + +}} // namespace + +using namespace uhd::usrp; + +UHD_STATIC_BLOCK(reg_e3x0_dboards){ + dboard_manager::register_dboard(0x0110, &make_e310_dboard, "E310 MIMO XCVR"); + dboard_manager::register_dboard(0x0100, &make_e300_dboard, "E300 SISO XCVR"); +} diff --git a/host/lib/usrp/e300/CMakeLists.txt b/host/lib/usrp/e300/CMakeLists.txt new file mode 100644 index 000000000..9ee9b5521 --- /dev/null +++ b/host/lib/usrp/e300/CMakeLists.txt @@ -0,0 +1,55 @@ +# +# 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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP-E300 support +######################################################################## +find_package(UDev) + +LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF) + +IF(ENABLE_E300) + LIST(APPEND E300_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/e300_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_io_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_fifo_config.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_sysfs_hooks.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_network.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_global_regs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_spi.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_sensor_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_i2c.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_eeprom_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_common.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_async_serial.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_ublox_control_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e300_remote_codec_ctrl.cpp + ) + LIBUHD_APPEND_SOURCES(${E300_SOURCES}) + IF(UDEV_FOUND) + INCLUDE_DIRECTORIES(${UDEV_INCLUDE_DIR}) + LIBUHD_APPEND_LIBS(${UDEV_LIBS}) + SET_SOURCE_FILES_PROPERTIES( + ${E300_SOURCES} + PROPERTIES COMPILE_DEFINITIONS "E300_NATIVE=1" + ) + ENDIF(UDEV_FOUND) +ENDIF(ENABLE_E300) diff --git a/host/lib/usrp/e300/e300_async_serial.cpp b/host/lib/usrp/e300/e300_async_serial.cpp new file mode 100644 index 000000000..cdf18f7f7 --- /dev/null +++ b/host/lib/usrp/e300/e300_async_serial.cpp @@ -0,0 +1,245 @@ +// +// 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 "e300_async_serial.hpp" + +namespace uhd { namespace usrp { namespace gps { + +async_serial::async_serial() + : _io(), + _port(_io), + _background_thread(), + _open(false), + _error(false) +{ +} + +async_serial::async_serial( + const std::string &node, + const size_t baud_rate, + boost::asio::serial_port_base::parity opt_parity, + boost::asio::serial_port_base::character_size opt_csize, + boost::asio::serial_port_base::flow_control opt_flow, + boost::asio::serial_port_base::stop_bits opt_stop) + : _io(), + _port(_io), + _background_thread(), + _open(false), + _error(false) +{ + open(node, baud_rate, opt_parity, opt_csize, opt_flow, opt_stop); +} + +void async_serial::open( + const std::string &node, + const size_t baud_rate, + boost::asio::serial_port_base::parity opt_parity, + boost::asio::serial_port_base::character_size opt_csize, + boost::asio::serial_port_base::flow_control opt_flow, + boost::asio::serial_port_base::stop_bits opt_stop) +{ + if(is_open()) + close(); + + _set_error_status(true); + _port.open(node); + _port.set_option( + boost::asio::serial_port_base::baud_rate(baud_rate)); + _port.set_option(opt_parity); + _port.set_option(opt_csize); + _port.set_option(opt_flow); + _port.set_option(opt_stop); + + _io.post(boost::bind(&async_serial::_do_read, this)); + + boost::thread t(boost::bind(&boost::asio::io_service::run, &_io)); + _background_thread.swap(t); + _set_error_status(false); + _open=true; +} + +bool async_serial::is_open() const +{ + return _open; +} + +bool async_serial::error_status() const +{ + boost::lock_guard<boost::mutex> l(_error_mutex); + return _error; +} + +void async_serial::close() +{ + if(!is_open()) + return; + + _open=false; + _io.post(boost::bind(&async_serial::_do_close, this)); + _background_thread.join(); + _io.reset(); + if(error_status()) + throw(boost::system::system_error(boost::system::error_code(), + "Error while closing the device")); +} + +void async_serial::write(const char *data, size_t size) +{ + { + boost::lock_guard<boost::mutex> l(_write_queue_mutex); + _write_queue.insert(_write_queue.end(), data, data+size); + } + _io.post(boost::bind(&async_serial::_do_write, this)); +} + +void async_serial::write(const std::vector<char> &data) +{ + { + boost::lock_guard<boost::mutex> l(_write_queue_mutex); + _write_queue.insert( + _write_queue.end(), + data.begin(), + data.end()); + } + _io.post(boost::bind(&async_serial::_do_write, this)); +} + +void async_serial::write_string(const std::string &s) +{ + { + boost::lock_guard<boost::mutex> l(_write_queue_mutex); + _write_queue.insert( + _write_queue.end(), + s.begin(), + s.end()); + } + _io.post(boost::bind(&async_serial::_do_write, this)); +} + +async_serial::~async_serial() +{ + if(is_open()) { + try { + close(); + } catch(...) { + //Don't throw from a destructor + } + } +} + +void async_serial::_do_read() +{ + _port.async_read_some(boost::asio::buffer( + _read_buffer,READ_BUFFER_SIZE), + boost::bind(&async_serial::_read_end, + this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); +} + +void async_serial::_read_end( + const boost::system::error_code& error, + size_t bytes_transferred) +{ + if(error) { + if(is_open()) { + _do_close(); + _set_error_status(true); + } + } else { + if(_callback) + _callback( + _read_buffer, + bytes_transferred); + _do_read(); + } +} + +void async_serial::_do_write() +{ + // if a write operation is already in progress, do nothing + if(_write_buffer == 0) { + boost::lock_guard<boost::mutex> l(_write_queue_mutex); + _write_buffer_size=_write_queue.size(); + _write_buffer.reset(new char[_write_queue.size()]); + std::copy(_write_queue.begin(),_write_queue.end(), + _write_buffer.get()); + _write_queue.clear(); + async_write( + _port, boost::asio::buffer(_write_buffer.get(), + _write_buffer_size), + boost::bind( + &async_serial::_write_end, + this, + boost::asio::placeholders::error)); + } +} + +void async_serial::_write_end(const boost::system::error_code& error) +{ + if(!error) { + boost::lock_guard<boost::mutex> l(_write_queue_mutex); + if(_write_queue.empty()) { + _write_buffer.reset(); + _write_buffer_size=0; + return; + } + _write_buffer_size = _write_queue.size(); + _write_buffer.reset(new char[_write_queue.size()]); + std::copy(_write_queue.begin(),_write_queue.end(), + _write_buffer.get()); + _write_queue.clear(); + async_write( + _port, + boost::asio::buffer(_write_buffer.get(), + _write_buffer_size), + boost::bind( + &async_serial::_write_end, + this, + boost::asio::placeholders::error)); + } else { + _set_error_status(true); + _do_close(); + } +} + +void async_serial::_do_close() +{ + boost::system::error_code ec; + _port.cancel(ec); + if(ec) + _set_error_status(true); + _port.close(ec); + if(ec) + _set_error_status(true); +} + +void async_serial::_set_error_status(const bool e) +{ + boost::lock_guard<boost::mutex> l(_error_mutex); + _error = e; +} + + +void async_serial::set_read_callback( + const boost::function<void (const char*, size_t)> &callback) +{ + _callback = callback; +} + + +}}} // namespace diff --git a/host/lib/usrp/e300/e300_async_serial.hpp b/host/lib/usrp/e300/e300_async_serial.hpp new file mode 100644 index 000000000..fafc7de3d --- /dev/null +++ b/host/lib/usrp/e300/e300_async_serial.hpp @@ -0,0 +1,113 @@ +// +// 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_ASYNC_SERIAL_HPP +#define INCLUDED_ASYNC_SERIAL_HPP + +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <boost/utility.hpp> +#include <boost/function.hpp> +#include <boost/shared_array.hpp> + +namespace uhd { namespace usrp { namespace gps { + +class async_serial : private boost::noncopyable +{ +public: + async_serial(); + ~async_serial(); + + async_serial(const std::string &node, const size_t baud_rate, + boost::asio::serial_port_base::parity opt_parity= + boost::asio::serial_port_base::parity( + boost::asio::serial_port_base::parity::none), + boost::asio::serial_port_base::character_size opt_csize= + boost::asio::serial_port_base::character_size(8), + boost::asio::serial_port_base::flow_control opt_flow= + boost::asio::serial_port_base::flow_control( + boost::asio::serial_port_base::flow_control::none), + boost::asio::serial_port_base::stop_bits opt_stop= + boost::asio::serial_port_base::stop_bits( + boost::asio::serial_port_base::stop_bits::one)); + + void open(const std::string& node, const size_t baud_rate, + boost::asio::serial_port_base::parity opt_parity= + boost::asio::serial_port_base::parity( + boost::asio::serial_port_base::parity::none), + boost::asio::serial_port_base::character_size opt_csize= + boost::asio::serial_port_base::character_size(8), + boost::asio::serial_port_base::flow_control opt_flow= + boost::asio::serial_port_base::flow_control( + boost::asio::serial_port_base::flow_control::none), + boost::asio::serial_port_base::stop_bits opt_stop= + boost::asio::serial_port_base::stop_bits( + boost::asio::serial_port_base::stop_bits::one)); + + bool is_open(void) const; + + bool error_status(void) const; + + void close(void); + + void write(const char *data, const size_t size); + void write(const std::vector<char> &data); + + void write_string(const std::string &s); + + static const size_t READ_BUFFER_SIZE=512; + + void set_read_callback( + const boost::function<void (const char*, size_t)>& callback); + + void clear_callback(); + +private: // methods + void _do_read(); + + void _read_end( + const boost::system::error_code &error, + size_t bytes_transferred); + + void _do_write(); + + void _write_end(const boost::system::error_code &error); + + void _do_close(); + + void _set_error_status(const bool e); +private: // members + boost::asio::io_service _io; + boost::asio::serial_port _port; + boost::thread _background_thread; + bool _open; + bool _error; + mutable boost::mutex _error_mutex; + + std::vector<char> _write_queue; + boost::shared_array<char> _write_buffer; + size_t _write_buffer_size; + boost::mutex _write_queue_mutex; + char _read_buffer[READ_BUFFER_SIZE]; + + boost::function<void (const char*, size_t)> _callback; +}; + +}}} // namespace + +#endif //INCLUDED_ASYNC_SERIAL_HPP diff --git a/host/lib/usrp/e300/e300_common.cpp b/host/lib/usrp/e300/e300_common.cpp new file mode 100644 index 000000000..97e906be7 --- /dev/null +++ b/host/lib/usrp/e300/e300_common.cpp @@ -0,0 +1,59 @@ +// +// 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 <uhd/utils/msg.hpp> + +#include "e300_fifo_config.hpp" +#include "e300_fifo_config.hpp" + +#include "e300_common.hpp" + +#include <boost/filesystem.hpp> +#include <fstream> + +namespace uhd { namespace usrp { namespace e300 { + +namespace common { + +void load_fpga_image(const std::string &path) +{ + if (not boost::filesystem::exists("/dev/xdevcfg")) + ::system("mknod /dev/xdevcfg c 259 0"); + + UHD_MSG(status) << "Loading FPGA image: " << path << "..." << std::flush; + + std::ifstream fpga_file(path.c_str(), std::ios_base::binary); + UHD_ASSERT_THROW(fpga_file.good()); + + std::FILE *wfile; + wfile = std::fopen("/dev/xdevcfg", "wb"); + UHD_ASSERT_THROW(!(wfile == NULL)); + + char buff[16384]; // devcfg driver can't handle huge writes + do { + fpga_file.read(buff, sizeof(buff)); + std::fwrite(buff, 1, fpga_file.gcount(), wfile); + } while (fpga_file); + + fpga_file.close(); + std::fclose(wfile); + + UHD_MSG(status) << " done" << std::endl; +} + +} + +}}} diff --git a/host/lib/usrp/e300/e300_common.hpp b/host/lib/usrp/e300/e300_common.hpp new file mode 100644 index 000000000..d9a0afd9e --- /dev/null +++ b/host/lib/usrp/e300/e300_common.hpp @@ -0,0 +1,31 @@ +// +// 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_E300_COMMON_HPP +#define INCLUDED_E300_COMMON_HPP + +namespace uhd { namespace usrp { namespace e300 { + +namespace common { + +void load_fpga_image(const std::string &path); + +}; + +}}} + +#endif // INCLUDED_E300_COMMON_HPP diff --git a/host/lib/usrp/e300/e300_defaults.hpp b/host/lib/usrp/e300/e300_defaults.hpp new file mode 100644 index 000000000..9c0e5df1d --- /dev/null +++ b/host/lib/usrp/e300/e300_defaults.hpp @@ -0,0 +1,82 @@ +// +// 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_E300_DEFAULTS_HPP +#define INCLUDED_E300_DEFAULTS_HPP + +#include "ad9361_client.h" + +namespace uhd { namespace usrp { namespace e300 { + +static const double DEFAULT_TICK_RATE = 32e6; +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_RX_DATA_FRAME_SIZE = 4096; +static const size_t DEFAULT_RX_DATA_NUM_FRAMES = 32; + +static const size_t DEFAULT_TX_DATA_FRAME_SIZE = 2048; +static const size_t DEFAULT_TX_DATA_NUM_FRAMES = 32; + +static const size_t DEFAULT_CTRL_FRAME_SIZE = 64; +static const size_t DEFAULT_CTRL_NUM_FRAMES = 32; + +static const size_t MAX_NET_RX_DATA_FRAME_SIZE = 1200; +static const size_t MAX_NET_TX_DATA_FRAME_SIZE = 1200; + +class e300_ad9361_client_t : public ad9361_params { +public: + ~e300_ad9361_client_t() {} + double get_band_edge(frequency_band_t band) { + switch (band) { + case AD9361_RX_BAND0: return 1.2e9; + case AD9361_RX_BAND1: return 2.6e9; + case AD9361_TX_BAND0: return 2940.0e6; + 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_LVCMOS; + } + digital_interface_delays_t get_digital_interface_timing() { + digital_interface_delays_t delays; + delays.rx_clk_delay = 0; + delays.rx_data_delay = 0xF; + delays.tx_clk_delay = 0; + delays.tx_data_delay = 0xF; + return delays; + } +}; + +}}} // namespace + +#endif // INCLUDED_E300_DEFAULTS_HPP diff --git a/host/lib/usrp/e300/e300_eeprom_manager.cpp b/host/lib/usrp/e300/e300_eeprom_manager.cpp new file mode 100644 index 000000000..af1235b6b --- /dev/null +++ b/host/lib/usrp/e300/e300_eeprom_manager.cpp @@ -0,0 +1,236 @@ +// +// 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 "e300_eeprom_manager.hpp" +#include <uhd/types/mac_addr.hpp> +#include <uhd/utils/byteswap.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +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'; +} + +e300_eeprom_manager::e300_eeprom_manager(i2c::sptr i2c) : _i2c(i2c) +{ + read_mb_eeprom(); + read_db_eeprom(); +} + +e300_eeprom_manager::~e300_eeprom_manager(void) +{ +} + +const mboard_eeprom_t& e300_eeprom_manager::read_mb_eeprom(void) +{ + boost::mutex::scoped_lock(_mutex); + + std::vector<boost::uint8_t> bytes; + bytes.resize(sizeof(mb_eeprom_map_t)); + mb_eeprom_map_t *map_ptr = reinterpret_cast<mb_eeprom_map_t*>(&bytes[0]); + memset(map_ptr, 0xff, sizeof(mb_eeprom_map_t)); + + // get the old contents + for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) + bytes[i] = _i2c->get_i2c_reg8(MB_ADDR, i); + + mb_eeprom_map_t &map = *map_ptr; + + _mb_eeprom["product"] = boost::lexical_cast<std::string>( + uhd::ntohx<boost::uint16_t>(map.hw_product)); + _mb_eeprom["revision"] = boost::lexical_cast<std::string>( + uhd::ntohx<boost::uint16_t>(map.hw_revision)); + _mb_eeprom["serial"] = _bytes_to_string( + map.serial, MB_SERIAL_LEN); + + byte_vector_t mac_addr(map.mac_addr, map.mac_addr + 6); + _mb_eeprom["mac-addr"] = mac_addr_t::from_bytes(mac_addr).to_string(); + + _mb_eeprom["name"] = _bytes_to_string( + map.user_name, MB_NAME_LEN); + + return _mb_eeprom; +} + +const dboard_eeprom_t& e300_eeprom_manager::read_db_eeprom(void) +{ + boost::mutex::scoped_lock(_mutex); + + std::vector<boost::uint8_t> bytes; + bytes.resize(sizeof(db_eeprom_map_t)); + db_eeprom_map_t *map_ptr = reinterpret_cast<db_eeprom_map_t*>(&bytes[0]); + memset(map_ptr, 0xff, sizeof(db_eeprom_map_t)); + + // get the old contents + for(size_t i = 0; i < sizeof(db_eeprom_map_t); i++) + bytes[i] = _i2c->get_i2c_reg16(DB_ADDR, i); + + db_eeprom_map_t &map = *map_ptr; + + _db_eeprom.id = uhd::usrp::dboard_id_t::from_uint16( + uhd::ntohx<boost::uint16_t>(map.hw_product)); + + _db_eeprom.revision = boost::lexical_cast<std::string>( + uhd::ntohx<boost::uint16_t>(map.hw_revision)); + _db_eeprom.serial = _bytes_to_string( + map.serial, DB_SERIAL_LEN); + + return _db_eeprom; +} + +void e300_eeprom_manager::write_db_eeprom(const dboard_eeprom_t& eeprom) +{ + boost::mutex::scoped_lock(_mutex); + _db_eeprom = eeprom; + std::vector<boost::uint8_t> bytes; + bytes.resize(sizeof(db_eeprom_map_t)); + + + db_eeprom_map_t *map_ptr = reinterpret_cast<db_eeprom_map_t*>(&bytes[0]); + memset(map_ptr, 0xff, sizeof(db_eeprom_map_t)); + + // get the old contents + for(size_t i = 0; i < sizeof(db_eeprom_map_t); i++) + bytes[i] = _i2c->get_i2c_reg16(DB_ADDR, i); + + db_eeprom_map_t &map = *map_ptr; + + if (_db_eeprom.id != dboard_id_t::none()) { + map.hw_product = uhd::htonx<boost::uint16_t>( + _db_eeprom.id.to_uint16()); + } + + if (not _db_eeprom.revision.empty()) { + map.hw_revision = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_db_eeprom.revision)); + } + + if (not _db_eeprom.serial.empty()) { + _string_to_bytes(_db_eeprom.serial, DB_SERIAL_LEN, map.serial); + } + for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) + _i2c->set_i2c_reg16(DB_ADDR, i, bytes[i]); +} + +void e300_eeprom_manager::write_mb_eeprom(const mboard_eeprom_t& eeprom) +{ + boost::mutex::scoped_lock(_mutex); + _mb_eeprom = eeprom; + std::vector<boost::uint8_t> bytes; + bytes.resize(sizeof(mb_eeprom_map_t)); + + + mb_eeprom_map_t *map_ptr = reinterpret_cast<mb_eeprom_map_t*>(&bytes[0]); + memset(map_ptr, 0xff, sizeof(mb_eeprom_map_t)); + + // get the old contents + for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) + bytes[i] = _i2c->get_i2c_reg8(MB_ADDR, i); + + mb_eeprom_map_t &map = *map_ptr; + + 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("serial")) { + _string_to_bytes(_mb_eeprom["serial"], MB_SERIAL_LEN, map.serial); + } + if (_mb_eeprom.has_key("mac-addr")) { + byte_vector_t mac_addr = mac_addr_t::from_string(_mb_eeprom["mac-addr"]).to_bytes(); + std::copy(mac_addr.begin(), mac_addr.end(), map.mac_addr); + } + + //store the name + if (_mb_eeprom.has_key("name")) { + _string_to_bytes(_mb_eeprom["name"], MB_NAME_LEN, map.user_name); + } + + for(size_t i = 0; i < sizeof(mb_eeprom_map_t); i++) + _i2c->set_i2c_reg8(MB_ADDR, i, bytes[i]); + +} + +e300_eeprom_manager::mboard_t e300_eeprom_manager::get_mb_type(void) const +{ + boost::mutex::scoped_lock(_mutex); + boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( + _mb_eeprom["product"]); + return get_mb_type(pid); +} + +e300_eeprom_manager::mboard_t e300_eeprom_manager::get_mb_type( + boost::uint16_t pid) +{ + switch (pid) { + case E300_MB_PID: + return USRP_E300_MB; + + case E310_MB_PID: + return USRP_E310_MB; + + default: + return UNKNOWN; + }; +} + + +std::string e300_eeprom_manager::get_mb_type_string(void) const +{ + boost::mutex::scoped_lock(_mutex); + boost::uint16_t product = boost::lexical_cast<boost::uint16_t>( + _mb_eeprom["product"]); + switch (product) { + case E300_MB_PID: + return "E300"; + + case E310_MB_PID: + return "E310"; + + default: + return "UNKNOWN"; + }; +} + +i2c::sptr e300_eeprom_manager::get_i2c_sptr(void) +{ + return _i2c; +} + + +}}} // namespace diff --git a/host/lib/usrp/e300/e300_eeprom_manager.hpp b/host/lib/usrp/e300/e300_eeprom_manager.hpp new file mode 100644 index 000000000..e77f25ed5 --- /dev/null +++ b/host/lib/usrp/e300/e300_eeprom_manager.hpp @@ -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/>. +// + +#ifndef INCLUDED_E300_EEPROM_MANAGER_HPP +#define INCLUDED_E300_EEPROM_MANAGER_HPP + +#include <boost/thread/mutex.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> + +#include "e300_i2c.hpp" + +namespace uhd { namespace usrp { namespace e300 { + +static const boost::uint16_t E300_MB_PID = 0x77d1; +static const boost::uint16_t E310_MB_PID = 0x77d2; + +static const boost::uint16_t E300_DB_PID = 0x0100; +static const boost::uint16_t E310_DB_PID = 0x0110; + +class e300_eeprom_manager : boost::noncopyable +{ +public: + typedef boost::shared_ptr<e300_eeprom_manager> sptr; + e300_eeprom_manager(i2c::sptr i2c); + ~e300_eeprom_manager(); + + // mboard + const mboard_eeprom_t& read_mb_eeprom(); + void write_mb_eeprom(const mboard_eeprom_t& eeprom); + + UHD_INLINE const mboard_eeprom_t& get_mb_eeprom() + { + return _mb_eeprom; + } + + // dboard + const dboard_eeprom_t& read_db_eeprom(); + void write_db_eeprom(const dboard_eeprom_t& eeprom); + + UHD_INLINE const dboard_eeprom_t& get_db_eeprom() + { + return _db_eeprom; + } + + + i2c::sptr get_i2c_sptr(void); + + enum mboard_t {USRP_E300_MB, USRP_E310_MB, UNKNOWN}; + + mboard_t get_mb_type(void) const; + static mboard_t get_mb_type(boost::uint16_t pid); + std::string get_mb_type_string(void) const; + +private: // types + const static size_t MB_SERIAL_LEN = 6; + const static size_t MB_NAME_LEN = 32; + const static size_t MB_ADDR = 0x51; + + const static size_t DB_SERIAL_LEN = 6; + const static size_t DB_ADDR = 0x50; + + struct mb_eeprom_map_t + { + // Data format version + boost::uint16_t data_version_major; + boost::uint16_t data_version_minor; + + // NIC mac address + boost::uint8_t mac_addr[6]; + + // HW identification info + boost::uint16_t hw_product; + boost::uint16_t hw_revision; + + // serial + boost::uint8_t serial[MB_SERIAL_LEN]; + boost::uint8_t pad[20 - MB_SERIAL_LEN]; + + //User specific + boost::uint8_t user_name[MB_NAME_LEN]; + }; + + struct db_eeprom_map_t + { + // Data format version + boost::uint16_t data_version_major; + boost::uint16_t data_version_minor; + + // HW identification info + boost::uint16_t hw_product; + boost::uint16_t hw_revision; + + // serial + boost::uint8_t serial[MB_SERIAL_LEN]; + boost::uint8_t pad[20 - MB_SERIAL_LEN]; + }; + +private: // members + mboard_eeprom_t _mb_eeprom; + dboard_eeprom_t _db_eeprom; + i2c::sptr _i2c; + + boost::mutex _mutex; +}; + +}}} //namespace + +#endif // INCLUDED_E300_EEPROM_MANAGER_HPP diff --git a/host/lib/usrp/e300/e300_fifo_config.cpp b/host/lib/usrp/e300/e300_fifo_config.cpp new file mode 100644 index 000000000..ac4ace7f2 --- /dev/null +++ b/host/lib/usrp/e300/e300_fifo_config.cpp @@ -0,0 +1,429 @@ +// +// 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/>. +// + +#ifdef E300_NATIVE + +#include <boost/cstdint.hpp> +#include <uhd/config.hpp> + +// constants coded into the fpga parameters +static const size_t ZF_CONFIG_BASE = 0x40000000; +static const size_t ZF_PAGE_WIDTH = 10; +static const size_t H2S_STREAMS_WIDTH = 4; +static const size_t H2S_CMDFIFO_DEPTH = 5; +static const size_t S2H_STREAMS_WIDTH = 4; +static const size_t S2H_CMDFIFO_DEPTH = 5; + +// calculate more useful constants for this module +static const size_t ZF_PAGE_SIZE(1 << ZF_PAGE_WIDTH); +static const size_t H2S_NUM_STREAMS(1 << H2S_STREAMS_WIDTH); +static const size_t H2S_NUM_CMDS(1 << H2S_CMDFIFO_DEPTH); +static const size_t S2H_NUM_STREAMS(1 << S2H_STREAMS_WIDTH); +static const size_t S2H_NUM_CMDS(1 << S2H_CMDFIFO_DEPTH); + +//offsetsinto the arbiter memory map +static const size_t ARBITER_WR_CLEAR = 0; +static const size_t ARBITER_RD_SIG = 0; +static const size_t ARBITER_WR_ADDR = 4; +static const size_t ARBITER_WR_SIZE = 8; +static const size_t ARBITER_WR_STS_RDY = 12; +static const size_t ARBITER_WR_STS = 16; +static const size_t ARBITER_RB_STATUS = 16; +static const size_t ARBITER_RB_STATUS_OCC = 20; +static const size_t ARBITER_RB_ADDR_SPACE = 24; +static const size_t ARBITER_RB_SIZE_SPACE = 28; + +// registers for the wb32_iface +static const size_t SR_CORE_READBACK = 0; + + +static UHD_INLINE size_t S2H_BASE(const size_t base) +{ + return base + ZF_PAGE_SIZE * 0; +} + +static UHD_INLINE size_t H2S_BASE(const size_t base) +{ + return base + ZF_PAGE_SIZE * 1; +} + +static UHD_INLINE size_t REG_BASE(const size_t base) +{ + return base + ZF_PAGE_SIZE * 2; +} + +static UHD_INLINE size_t DST_BASE(const size_t base) +{ + return base + ZF_PAGE_SIZE * 3; +} + +static UHD_INLINE size_t ZF_STREAM_OFF(const size_t which) +{ + return which * 32; +} + +#include "e300_fifo_config.hpp" +#include <sys/mman.h> //mmap +#include <fcntl.h> //open, close +#include <poll.h> //poll +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <uhd/types/time_spec.hpp> //timeout +#include <uhd/utils/log.hpp> +#include <uhd/utils/atomic.hpp> + +//locking stuff for shared irq +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +struct e300_fifo_poll_waiter +{ + e300_fifo_poll_waiter(const int fd): + fd(fd) + { + //NOP + } + + void wait(const double timeout) + { + if (_poll_claimed.cas(1, 0)) + { + boost::mutex::scoped_lock l(mutex); + cond.wait(l); + } + else + { + struct pollfd fds[1]; + fds[0].fd = fd; + fds[0].events = POLLIN; + ::poll(fds, 1, long(timeout*1000)); + if (fds[0].revents & POLLIN) + ::read(fd, NULL, 0); + + _poll_claimed.write(0); + cond.notify_all(); + } + } + + uhd::atomic_uint32_t _poll_claimed; + boost::condition_variable cond; + boost::mutex mutex; + int fd; +}; + +static const size_t DEFAULT_FRAME_SIZE = 2048; +static const size_t DEFAULT_NUM_FRAMES = 32; + +using namespace uhd; +using namespace uhd::transport; + +struct __mem_addrz_t +{ + size_t which, phys, data, ctrl; +}; + +/*********************************************************************** + * peek n' poke mmapped space + **********************************************************************/ +UHD_INLINE void zf_poke32(const boost::uint32_t addr, const boost::uint32_t data) +{ + volatile boost::uint32_t *p = reinterpret_cast<boost::uint32_t *>(addr); + *p = data; +} + +UHD_INLINE boost::uint32_t zf_peek32(const boost::uint32_t addr) +{ + volatile const boost::uint32_t *p = reinterpret_cast<const boost::uint32_t *>(addr); + return *p; +} + +/*********************************************************************** + * managed buffer + **********************************************************************/ +struct e300_fifo_mb : managed_buffer +{ + e300_fifo_mb(const __mem_addrz_t &addrs, const size_t len): + ctrl_base(addrs.ctrl), phys_mem(addrs.phys), mem((void *)addrs.data), len(len){} + + void release(void) + { + UHD_ASSERT_THROW(zf_peek32(ctrl_base+ARBITER_RB_ADDR_SPACE) > 0); + UHD_ASSERT_THROW(zf_peek32(ctrl_base+ARBITER_RB_SIZE_SPACE) > 0); + zf_poke32(ctrl_base + ARBITER_WR_ADDR, phys_mem); + zf_poke32(ctrl_base + ARBITER_WR_SIZE, this->size()); + } + + template <typename T> + UHD_INLINE typename T::sptr get_new(void) + { + return make(reinterpret_cast<T *>(this), mem, len); + } + + const size_t ctrl_base; + const size_t phys_mem; + void *const mem; + const size_t len; +}; + +/*********************************************************************** + * transport + **********************************************************************/ +class e300_transport : public zero_copy_if +{ +public: + e300_transport( + boost::shared_ptr<void> allocator, + const __mem_addrz_t &addrs, + const size_t num_frames, + const size_t frame_size, + e300_fifo_poll_waiter *waiter, + const bool auto_release + ): + _allocator(allocator), + _addrs(addrs), + _num_frames(num_frames), + _frame_size(frame_size), + _index(0), + _waiter(waiter) + { + //UHD_MSG(status) << boost::format("phys 0x%x") % addrs.phys << std::endl; + //UHD_MSG(status) << boost::format("data 0x%x") % addrs.data << std::endl; + //UHD_MSG(status) << boost::format("ctrl 0x%x") % addrs.ctrl << std::endl; + + const boost::uint32_t sig = zf_peek32(_addrs.ctrl + ARBITER_RD_SIG); + UHD_ASSERT_THROW((sig >> 16) == 0xACE0); + + zf_poke32(_addrs.ctrl + ARBITER_WR_CLEAR, 1); + for (size_t i = 0; i < num_frames; i++) + { + //create a managed buffer at the given offset + __mem_addrz_t mb_addrs = addrs; + mb_addrs.phys += (i*frame_size); + mb_addrs.data += (i*frame_size); + boost::shared_ptr<e300_fifo_mb> mb(new e300_fifo_mb(mb_addrs, frame_size)); + + //setup the buffers so they are "positioned for use" + const size_t sts_good = (1 << 7) | (_addrs.which & 0xf); + if (auto_release) mb->get_new<managed_recv_buffer>(); //release for read + else zf_poke32(_addrs.ctrl + ARBITER_WR_STS, sts_good); //poke an ok into the sts fifo + + _buffs.push_back(mb); + } + } + + ~e300_transport(void) + { + //NOP + } + + template <typename T> + UHD_INLINE typename T::sptr get_buff(const double timeout) + { + const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(timeout); + do + { + if (zf_peek32(_addrs.ctrl + ARBITER_RB_STATUS_OCC)) + { + const boost::uint32_t sts = zf_peek32(_addrs.ctrl + ARBITER_RB_STATUS); + UHD_ASSERT_THROW((sts >> 7) & 0x1); //assert OK + UHD_ASSERT_THROW((sts & 0xf) == _addrs.which); //expected tag + zf_poke32(_addrs.ctrl + ARBITER_WR_STS_RDY, 1); //pop from sts fifo + if (_index == _num_frames) + _index = 0; + return _buffs[_index++]->get_new<T>(); + } + _waiter->wait(timeout); + //boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + } + while (time_spec_t::get_system_time() < exit_time); + + return typename T::sptr(); + } + + managed_recv_buffer::sptr get_recv_buff(const double timeout) + { + return this->get_buff<managed_recv_buffer>(timeout); + } + + size_t get_num_recv_frames(void) const + { + return _num_frames; + } + + size_t get_recv_frame_size(void) const + { + return _frame_size; + } + + managed_send_buffer::sptr get_send_buff(const double timeout) + { + return this->get_buff<managed_send_buffer>(timeout); + } + + size_t get_num_send_frames(void) const + { + return _num_frames; + } + + size_t get_send_frame_size(void) const + { + return _frame_size; + } + +private: + boost::shared_ptr<void> _allocator; + const __mem_addrz_t _addrs; + const size_t _num_frames; + const size_t _frame_size; + size_t _index; + e300_fifo_poll_waiter *_waiter; + std::vector<boost::shared_ptr<e300_fifo_mb> > _buffs; +}; + +/*********************************************************************** + * memory mapping + **********************************************************************/ +class e300_fifo_interface_impl : public virtual e300_fifo_interface +{ +public: + e300_fifo_interface_impl(const e300_fifo_config_t &config): + _config(config), + _bytes_in_use(0), + _recv_entries_in_use(std::vector<size_t>(S2H_NUM_STREAMS, 0)), + _send_entries_in_use(std::vector<size_t>(H2S_NUM_STREAMS, 0)) + { + //open the file descriptor to our kernel module + const std::string dev = "/dev/axi_fpga"; + _fd = ::open(dev.c_str(), O_RDWR|O_SYNC); + if (_fd < 0) + { + throw uhd::runtime_error("e300: failed to open " + dev); + } + + //mmap the control and data regions into virtual space + //UHD_VAR(_config.ctrl_length); + //UHD_VAR(_config.buff_length); + //UHD_VAR(_config.phys_addr); + _buff = ::mmap(NULL, _config.ctrl_length + _config.buff_length, PROT_READ|PROT_WRITE, MAP_SHARED, _fd, 0); + if (_buff == MAP_FAILED) + { + ::close(_fd); + throw uhd::runtime_error("e300: failed to mmap " + dev); + } + + //segment the memory according to zynq fifo arbiter + _ctrl_space = size_t(_buff); + _data_space = size_t(_buff) + _config.ctrl_length; + + //zero out the data region + std::memset((void *)_data_space, 0, _config.buff_length); + + //create a poll _waiter for the transports + _waiter = new e300_fifo_poll_waiter(_fd); + } + + virtual ~e300_fifo_interface_impl(void) + { + delete _waiter; + UHD_LOG << "cleanup: munmap" << std::endl; + ::munmap(_buff, _config.ctrl_length + _config.buff_length); + ::close(_fd); + } + + uhd::transport::zero_copy_if::sptr make_recv_xport( + const size_t which_stream, + const uhd::transport::zero_copy_xport_params ¶ms) + { + return this->_make_xport(which_stream, params, true); + } + + uhd::transport::zero_copy_if::sptr make_send_xport( + const size_t which_stream, + const uhd::transport::zero_copy_xport_params ¶ms) + { + return this->_make_xport(which_stream, params, false); + } + + size_t get_global_regs_base() const + { + return REG_BASE(_ctrl_space); + } + +private: + uhd::transport::zero_copy_if::sptr _make_xport( + const size_t which_stream, + const uhd::transport::zero_copy_xport_params ¶ms, + const bool is_recv) + { + boost::mutex::scoped_lock lock(_setup_mutex); + + const size_t frame_size = is_recv ? params.recv_frame_size : params.send_frame_size; + const size_t num_frames = is_recv ? params.num_recv_frames : params.num_send_frames; + size_t &entries_in_use = (is_recv)? _recv_entries_in_use.at(which_stream) + : _send_entries_in_use.at(which_stream); + + __mem_addrz_t addrs; + addrs.which = which_stream; + addrs.phys = _config.phys_addr + _bytes_in_use; + addrs.data = _data_space + _bytes_in_use; + addrs.ctrl = ((is_recv)? S2H_BASE(_ctrl_space) : H2S_BASE(_ctrl_space)) + ZF_STREAM_OFF(which_stream); + + uhd::transport::zero_copy_if::sptr xport; + if (is_recv) xport.reset(new e300_transport(shared_from_this(), addrs, num_frames, frame_size, _waiter, is_recv)); + else xport.reset(new e300_transport(shared_from_this(), addrs, num_frames, frame_size, _waiter, is_recv)); + + _bytes_in_use += num_frames*frame_size; + entries_in_use += num_frames; + + UHD_ASSERT_THROW(_recv_entries_in_use.at(which_stream) <= S2H_NUM_CMDS); + UHD_ASSERT_THROW(_send_entries_in_use.at(which_stream) <= H2S_NUM_CMDS); + UHD_ASSERT_THROW(_bytes_in_use <= _config.buff_length); + + + return xport; + } + + e300_fifo_config_t _config; + e300_fifo_poll_waiter *_waiter; + size_t _bytes_in_use; + int _fd; + void *_buff; + size_t _ctrl_space; + size_t _data_space; + std::vector<size_t> _recv_entries_in_use; + std::vector<size_t> _send_entries_in_use; + boost::mutex _setup_mutex; +}; + +e300_fifo_interface::sptr e300_fifo_interface::make(const e300_fifo_config_t &config) +{ + return e300_fifo_interface::sptr(new e300_fifo_interface_impl(config)); +} + +#else //E300_NATIVE + +#include "e300_fifo_config.hpp" +#include <uhd/exception.hpp> + +e300_fifo_interface::sptr e300_fifo_interface::make(const e300_fifo_config_t &) +{ + throw uhd::assertion_error("e300_fifo_interface::make() !E300_NATIVE"); +} + +#endif //E300_NATIVE diff --git a/host/lib/usrp/e300/e300_fifo_config.hpp b/host/lib/usrp/e300/e300_fifo_config.hpp new file mode 100644 index 000000000..967f451ca --- /dev/null +++ b/host/lib/usrp/e300/e300_fifo_config.hpp @@ -0,0 +1,52 @@ +// +// 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_E300_FIFO_CONFIG_HPP +#define INCLUDED_E300_FIFO_CONFIG_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/enable_shared_from_this.hpp> + +struct e300_fifo_config_t +{ + size_t ctrl_length; + size_t buff_length; + size_t phys_addr; +}; + +e300_fifo_config_t e300_read_sysfs(void); +std::string e300_get_sysfs_attr(const std::string &node, const std::string &attr); + +struct e300_fifo_interface : boost::enable_shared_from_this<e300_fifo_interface> +{ + typedef boost::shared_ptr<e300_fifo_interface> sptr; + static sptr make(const e300_fifo_config_t &config); + + virtual uhd::transport::zero_copy_if::sptr make_recv_xport( + const size_t which_stream, + const uhd::transport::zero_copy_xport_params ¶ms) = 0; + + virtual uhd::transport::zero_copy_if::sptr make_send_xport( + const size_t which_stream, + const uhd::transport::zero_copy_xport_params &parms) = 0; + + virtual size_t get_global_regs_base(void) const = 0; +}; + +#endif /* INCLUDED_E300_FIFO_CONFIG_HPP */ diff --git a/host/lib/usrp/e300/e300_fpga_defs.hpp b/host/lib/usrp/e300/e300_fpga_defs.hpp new file mode 100644 index 000000000..d58fd63a9 --- /dev/null +++ b/host/lib/usrp/e300/e300_fpga_defs.hpp @@ -0,0 +1,29 @@ +// +// 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_E300_FPGA_DEFS_HPP +#define INCLUDED_E300_FPGA_DEFS_HPP +namespace uhd { namespace usrp { namespace e300 { namespace fpga { + +static const size_t NUM_RADIOS = 2; + +static const boost::uint32_t COMPAT_MAJOR = 4; +static const boost::uint32_t COMPAT_MINOR = 0; + +}}}} // namespace + +#endif // INCLUDED_E300_FPGA_DEFS_HPP diff --git a/host/lib/usrp/e300/e300_global_regs.cpp b/host/lib/usrp/e300/e300_global_regs.cpp new file mode 100644 index 000000000..3ba895826 --- /dev/null +++ b/host/lib/usrp/e300/e300_global_regs.cpp @@ -0,0 +1,131 @@ +// +// 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 "e300_global_regs.hpp" + +#include <boost/cstdint.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <cstring> +#include <iostream> + +namespace uhd { namespace usrp { namespace e300 { + +class global_regs_local_impl : public global_regs +{ +public: + global_regs_local_impl(const size_t ctrl_base) : _ctrl_base(ctrl_base) + { + } + + virtual ~global_regs_local_impl(void) + { + } + + boost::uint32_t peek32(const uhd::wb_iface::wb_addr_type addr) + { + // setup readback register + _poke32(_ctrl_base + global_regs::SR_CORE_READBACK, addr); + return _peek32(_ctrl_base); + } + + void poke32(const uhd::wb_iface::wb_addr_type addr, const boost::uint32_t data) + { + _poke32(_ctrl_base + static_cast<size_t>(addr), data); + } + + +private: + const size_t _ctrl_base; + + UHD_INLINE void _poke32(const boost::uint32_t addr, const boost::uint32_t data) + { + volatile boost::uint32_t *p = reinterpret_cast<boost::uint32_t *>(addr); + *p = data; + } + + UHD_INLINE boost::uint32_t _peek32(const boost::uint32_t addr) + { + volatile const boost::uint32_t *p = reinterpret_cast<const boost::uint32_t *>(addr); + return *p; + } +}; + +global_regs::sptr global_regs::make(const size_t ctrl_base) +{ + return sptr(new global_regs_local_impl(ctrl_base)); +} + +class global_regs_zc_impl : public global_regs +{ +public: + global_regs_zc_impl(uhd::transport::zero_copy_if::sptr xport) : _xport(xport) + { + } + + virtual ~global_regs_zc_impl(void) + { + } + + boost::uint32_t peek32(const uhd::wb_iface::wb_addr_type addr) + { + global_regs_transaction_t transaction; + transaction.is_poke = uhd::htonx<boost::uint32_t>(0); + transaction.addr = uhd::htonx<boost::uint32_t>( + static_cast<boost::uint32_t>(addr)); + { + uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw std::runtime_error("global_regs_zc_impl send timeout"); + std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + { + uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw std::runtime_error("global_regs_zc_impl recv timeout"); + std::memcpy(&transaction, buff->cast<const void *>(), sizeof(transaction)); + } + return uhd::ntohx<boost::uint32_t>(transaction.data); + } + + void poke32(const uhd::wb_iface::wb_addr_type addr, const boost::uint32_t data) + { + global_regs_transaction_t transaction; + transaction.is_poke = uhd::htonx<boost::uint32_t>(1); + transaction.addr = uhd::htonx<boost::uint32_t>( + static_cast<boost::uint32_t>(addr)); + transaction.data = uhd::htonx<boost::uint32_t>(data); + { + uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw uhd::runtime_error("global_regs_zc_impl send timeout"); + std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + } + +private: + uhd::transport::zero_copy_if::sptr _xport; +}; + +global_regs::sptr global_regs::make(uhd::transport::zero_copy_if::sptr xport) +{ + return sptr(new global_regs_zc_impl(xport)); +} + +}}}; diff --git a/host/lib/usrp/e300/e300_global_regs.hpp b/host/lib/usrp/e300/e300_global_regs.hpp new file mode 100644 index 000000000..12693da79 --- /dev/null +++ b/host/lib/usrp/e300/e300_global_regs.hpp @@ -0,0 +1,78 @@ +// +// 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_E300_GLOBAL_REGS_HPP +#define INCLUDED_E300_GLOBAL_REGS_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +struct global_regs_transaction_t { + boost::uint32_t is_poke; + boost::uint32_t addr; + boost::uint32_t data; + boost::uint32_t pad; +}; + +class global_regs : boost::noncopyable, public virtual uhd::wb_iface +{ +public: + typedef boost::shared_ptr<global_regs> sptr; + + static sptr make(const size_t ctrl_base); + static sptr make(uhd::transport::zero_copy_if::sptr xport); + + static const size_t SR_CORE_READBACK = 0; + static const size_t SR_CORE_MISC = 4; + static const size_t SR_CORE_TEST = 28; + static const size_t SR_CORE_XB_LOCAL = 32; + + // leave some room for registers, + // xbar starts with an offset of one + // 1K page. A part of which is used for + // DST_LOOKUP for DST_LOOKUP + + static const size_t SR_CORE_DST = 1024; + static const size_t SR_CORE_XBAR = 2048; + + static const size_t RB32_CORE_MISC = 1; + static const size_t RB32_CORE_COMPAT = 2; + static const size_t RB32_CORE_GITHASH = 3; + static const size_t RB32_CORE_PLL = 4; + static const size_t RB32_CORE_TEST = 24; + + // PPS selection + static const size_t PPS_GPS = 0; + static const size_t PPS_INT = 2; + static const size_t PPS_EXT = 3; +}; + +UHD_INLINE boost::uint32_t XB_ADDR(const boost::uint32_t addr) +{ + return global_regs::SR_CORE_XBAR + (addr << 2); +} + +UHD_INLINE boost::uint32_t DST_ADDR(const boost::uint32_t addr) +{ + return global_regs::SR_CORE_DST + (addr << 2); +} + +}}}; + +#endif /* INCLUDED_E300_GLOBAL_REGS_HPP */ diff --git a/host/lib/usrp/e300/e300_i2c.cpp b/host/lib/usrp/e300/e300_i2c.cpp new file mode 100644 index 000000000..d8f535a98 --- /dev/null +++ b/host/lib/usrp/e300/e300_i2c.cpp @@ -0,0 +1,409 @@ +// +// 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 <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/transport/udp_simple.hpp> + + +#include "e300_i2c.hpp" +#include <cstring> + +namespace uhd { namespace usrp { namespace e300 { + +class zc_impl : public i2c +{ +public: + zc_impl(uhd::transport::zero_copy_if::sptr xport) : _xport(xport) + { + } + + virtual ~zc_impl(void) + { + } + + void set_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg, + const boost::uint8_t value) + { + i2c_transaction_t transaction; + transaction.type = WRITE | ONEBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + transaction.data = value; + { + uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw uhd::runtime_error("i2c_zc_impl send timeout"); + std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + } + + boost::uint8_t get_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg) + { + i2c_transaction_t transaction; + transaction.type = READ | ONEBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + { + uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw std::runtime_error("i2c_zc_impl send timeout"); + std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + { + uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw std::runtime_error("i2c_zc_impl recv timeout"); + std::memcpy(&transaction, buff->cast<const void *>(), sizeof(transaction)); + } + return transaction.data; + } + + void set_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg, + const boost::uint8_t value) + { + i2c_transaction_t transaction; + transaction.type = WRITE | TWOBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + transaction.data = value; + { + uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw uhd::runtime_error("i2c_zc_impl send timeout"); + std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + } + + boost::uint8_t get_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg) + { + i2c_transaction_t transaction; + transaction.type = READ | TWOBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + { + uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw std::runtime_error("i2c_zc_impl send timeout"); + std::memcpy(buff->cast<void *>(), &transaction, sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + { + uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); + if (not buff or buff->size() < sizeof(transaction)) + throw std::runtime_error("i2c_zc_impl recv timeout"); + std::memcpy(&transaction, buff->cast<const void *>(), sizeof(transaction)); + } + return transaction.data; + } + + +private: + uhd::transport::zero_copy_if::sptr _xport; +}; + +i2c::sptr i2c::make_zc(uhd::transport::zero_copy_if::sptr xport) +{ + return sptr(new zc_impl(xport)); +} + +class simple_udp_impl : public i2c +{ +public: + simple_udp_impl(const std::string &ip_addr, const std::string &port) + { + _xport = uhd::transport::udp_simple::make_connected(ip_addr, port); + } + + virtual ~simple_udp_impl(void) + { + } + + void set_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg, + const boost::uint8_t value) + { + i2c_transaction_t transaction; + transaction.type = i2c::WRITE | ONEBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + transaction.data = value; + + _xport->send( + boost::asio::buffer( + &transaction, + sizeof(transaction))); + } + + boost::uint8_t get_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg) + { + i2c_transaction_t transaction; + transaction.type = i2c::READ | ONEBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + transaction.data = 0; + + _xport->send( + boost::asio::buffer( + &transaction, + sizeof(transaction))); + + boost::uint8_t buff[sizeof(i2c_transaction_t)] = {}; + const size_t nbytes = _xport->recv( + boost::asio::buffer(buff), 0.100); + if (not (nbytes == sizeof(transaction))) + throw std::runtime_error("i2c_simple_udp_impl recv timeout"); + i2c_transaction_t *reply = reinterpret_cast<i2c_transaction_t*>(buff); + return reply->data; + } + + void set_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg, + const boost::uint8_t value) + { + i2c_transaction_t transaction; + transaction.type = i2c::WRITE | TWOBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + transaction.data = value; + + _xport->send( + boost::asio::buffer( + &transaction, + sizeof(transaction))); + } + + boost::uint8_t get_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg) + { + i2c_transaction_t transaction; + transaction.type = i2c::READ | TWOBYTE; + transaction.addr = addr; + transaction.reg = uhd::htonx<boost::uint16_t>(reg); + transaction.data = 0; + + _xport->send( + boost::asio::buffer( + &transaction, + sizeof(transaction))); + + boost::uint8_t buff[sizeof(i2c_transaction_t)] = {}; + const size_t nbytes = _xport->recv( + boost::asio::buffer(buff), 0.100); + if (not (nbytes == sizeof(transaction))) + throw std::runtime_error("i2c_simple_udp_impl recv timeout"); + i2c_transaction_t *reply = reinterpret_cast<i2c_transaction_t*>(buff); + return reply->data; + } + +private: + uhd::transport::udp_simple::sptr _xport; +}; + +i2c::sptr i2c::make_simple_udp( + const std::string &ip_addr, + const std::string &port) +{ + return sptr(new simple_udp_impl(ip_addr,port)); +} + +}}} // namespace + +#ifdef E300_NATIVE + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#include <boost/thread.hpp> +#include <boost/cstdint.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class i2cdev_impl : public i2c +{ +public: + i2cdev_impl(const std::string &device) + { + _fd = ::open(device.c_str(), O_RDWR); + if (_fd < 0) + throw uhd::system_error("open failed."); + } + + virtual ~i2cdev_impl(void) + { + close(_fd); + } + + void set_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg, + const boost::uint8_t value) + { + boost::uint8_t outbuf[2]; + i2c_rdwr_ioctl_data packets; + i2c_msg messages[1]; + + messages[0].addr = addr; + messages[0].flags = 0; + messages[0].len = sizeof(outbuf); + messages[0].buf = outbuf; + + outbuf[0] = reg; + outbuf[1] = value; + + packets.msgs = messages; + packets.nmsgs = 1; + + if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { + throw std::runtime_error("ioctl failed"); + } + // this is ugly + boost::this_thread::sleep(boost::posix_time::milliseconds(5)); + } + + boost::uint8_t get_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg) + { + i2c_rdwr_ioctl_data packets; + i2c_msg messages[2]; + + boost::uint8_t outbuf = reg; + messages[0].addr = addr; + messages[0].flags = 0; + messages[0].len = sizeof(outbuf); + messages[0].buf = &outbuf; + + boost::uint8_t inbuf; + messages[1].addr = addr; + messages[1].flags = I2C_M_RD; + messages[1].len = sizeof(inbuf); + messages[1].buf = &inbuf; + + packets.msgs = messages; + packets.nmsgs = 2; + + if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { + throw std::runtime_error("ioctl failed."); + } + + return inbuf; + } + + // the daughterboard uses 16 bit addresses + void set_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg, + const boost::uint8_t value) + { + boost::uint8_t outbuf[3]; + i2c_rdwr_ioctl_data packets; + i2c_msg messages[1]; + + messages[0].addr = addr; + messages[0].flags = 0; + messages[0].len = sizeof(outbuf); + messages[0].buf = outbuf; + + outbuf[0] = (reg >> 8) & 0xff; + outbuf[1] = reg & 0xff; + outbuf[2] = value; + + packets.msgs = messages; + packets.nmsgs = 1; + + if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { + throw std::runtime_error("ioctl failed"); + } + // this is ugly + boost::this_thread::sleep(boost::posix_time::milliseconds(5)); + } + + + // the daughterboard eeprom uses 16 bit addresses + boost::uint8_t get_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg) + { + i2c_rdwr_ioctl_data packets; + i2c_msg messages[2]; + + // always little endian + boost::uint8_t outbuf[2]; + outbuf[0] = (reg >> 8) & 0xff; + outbuf[1] = reg & 0xff; + + messages[0].addr = addr; + messages[0].flags = 0; + messages[0].len = sizeof(outbuf); + messages[0].buf = outbuf; + + boost::uint8_t inbuf; + messages[1].addr = addr; + messages[1].flags = I2C_M_RD; + messages[1].len = sizeof(inbuf); + messages[1].buf = &inbuf; + + packets.msgs = messages; + packets.nmsgs = 2; + + if(::ioctl(_fd, I2C_RDWR, &packets) < 0) { + throw std::runtime_error("ioctl failed."); + } + + return inbuf; + } + +private: + int _fd; +}; + +}}} // namespace + +using namespace uhd::usrp::e300; + +i2c::sptr i2c::make_i2cdev(const std::string &device) +{ + return sptr(new i2cdev_impl(device)); +} +#else +using namespace uhd::usrp::e300; + +i2c::sptr i2c::make_i2cdev(const std::string &) +{ + throw uhd::assertion_error("i2c::make() !E300_NATIVE"); +} +#endif // E300_NATIVE diff --git a/host/lib/usrp/e300/e300_i2c.hpp b/host/lib/usrp/e300/e300_i2c.hpp new file mode 100644 index 000000000..6cca7ab70 --- /dev/null +++ b/host/lib/usrp/e300/e300_i2c.hpp @@ -0,0 +1,77 @@ +// +// 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_E300_I2C_HPP +#define INCLUDED_E300_I2C_HPP + +#include <boost/noncopyable.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> + +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +struct i2c_transaction_t { + boost::uint16_t reg; + boost::uint8_t addr; + boost::uint8_t data; + boost::uint8_t type; +}; + +class i2c : public boost::noncopyable +{ +public: + typedef boost::shared_ptr<i2c> sptr; + + static sptr make_i2cdev(const std::string &device); + static sptr make_zc(uhd::transport::zero_copy_if::sptr xport); + static sptr make_simple_udp( + const std::string &ip_addr, + const std::string &port); + + virtual boost::uint8_t get_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg) = 0; + + virtual boost::uint8_t get_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg) = 0; + + virtual void set_i2c_reg8( + const boost::uint8_t addr, + const boost::uint8_t reg, + const boost::uint8_t value) = 0; + + virtual void set_i2c_reg16( + const boost::uint8_t addr, + const boost::uint16_t reg, + const boost::uint8_t value) = 0; + + + static const boost::uint8_t DB_EEPROM_ADDR = 0x50; + static const boost::uint8_t MB_EEPROM_ADDR = 0x51; + + static const boost::uint8_t WRITE = 0x1; + static const boost::uint8_t READ = 0x0; + static const boost::uint8_t TWOBYTE = 0x4; + static const boost::uint8_t ONEBYTE = 0x2; +}; + +}}}; + +#endif // INCLUDED_E300_I2C_HPP diff --git a/host/lib/usrp/e300/e300_impl.cpp b/host/lib/usrp/e300/e300_impl.cpp new file mode 100644 index 000000000..0f4cb5852 --- /dev/null +++ b/host/lib/usrp/e300/e300_impl.cpp @@ -0,0 +1,1347 @@ +// +// 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 "e300_impl.hpp" +#include "e300_defaults.hpp" +#include "e300_fpga_defs.hpp" +#include "e300_spi.hpp" +#include "e300_regs.hpp" +#include "e300_eeprom_manager.hpp" +#include "e300_sensor_manager.hpp" +#include "e300_common.hpp" +#include "e300_remote_codec_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/sensors.hpp> +#include <boost/make_shared.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/functional/hash.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <boost/asio.hpp> +#include <fstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace fs = boost::filesystem; +namespace asio = boost::asio; + +//! mapping of frontend to radio perif index +static const size_t FE0 = 1; +static const size_t FE1 = 0; + +namespace uhd { namespace usrp { namespace e300 { + +/*********************************************************************** + * Discovery + **********************************************************************/ + +static std::vector<std::string> discover_ip_addrs( + const std::string& addr_hint, const std::string& port) +{ + 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; + } catch(...) { + UHD_MSG(error) << "E300 Network discovery unknown error" << std::endl; + return addrs; + } + + // TODO: Do not abuse the I2C transport here ... + // we send a read request to i2c address 0x51, + // to read register 0 + i2c_transaction_t req; + req.type = i2c::READ | i2c::ONEBYTE; + req.addr = 0x51; // mboard's eeprom address, we don't really care + req.reg = 4; + + // send dummy request + try { + udp_bcast_xport->send(boost::asio::buffer(&req, sizeof(req))); + } catch (const std::exception &ex) { + UHD_MSG(error) << "E300 Network discovery error " << ex.what() << std::endl; + return addrs; + } catch(...) { + UHD_MSG(error) << "E300 Network discovery unknown error" << std::endl; + return addrs; + } + + // loop for replies until timeout + while (true) { + boost::uint8_t buff[sizeof(i2c_transaction_t)] = {}; + const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050); + if (nbytes == 0) + break; //No more responses + + const i2c_transaction_t *reply = reinterpret_cast<const i2c_transaction_t*>(buff); + if (req.addr == reply->addr) + addrs.push_back(udp_bcast_xport->get_recv_addr()); + } + + return addrs; +} + +static bool is_loopback(const if_addrs_t &if_addrs) +{ + return if_addrs.inet == asio::ip::address_v4::loopback().to_string(); +} + +static device_addrs_t e300_find(const device_addr_t &multi_dev_hint) +{ + // handle multi device discovery + device_addrs_t hints = separate_device_addr(multi_dev_hint); + + if (hints.size() > 1) { + device_addrs_t found_devices; + std::string err_msg; + BOOST_FOREACH(const device_addr_t &hint_i, hints) + { + device_addrs_t found_devices_i = e300_find(hint_i); + if(found_devices_i.size() != 1) + err_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 err_msg.empty()) + throw uhd::value_error(err_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 e300_addrs; + + // return an empty list of addresses when type is set to non-e300 + if (hint.has_key("type") and hint["type"] != "e3x0") + return e300_addrs; + + const bool loopback_only = + get_if_addrs().size() == 1 and is_loopback(get_if_addrs().at(0)); + + // if we don't have connectivity, we might as well skip the network part + if (not loopback_only) { + // if no address or node has been specified, send a broadcast + if ((not hint.has_key("addr")) and (not hint.has_key("node"))) { + BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()) + { + // avoid the loopback device + if (is_loopback(if_addrs)) + 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 ad append results + device_addrs_t new_e300_addrs = e300_find(new_hint); + e300_addrs.insert(e300_addrs.begin(), + new_e300_addrs.begin(), new_e300_addrs.end()); + + } + return e300_addrs; + } + + std::vector<std::string> ip_addrs = discover_ip_addrs( + hint["addr"], E300_SERVER_I2C_PORT); + + BOOST_FOREACH(const std::string &ip_addr, ip_addrs) + { + device_addr_t new_addr; + new_addr["type"] = "e3x0"; + new_addr["addr"] = ip_addr; + + // see if we can read the eeprom + try { + e300_eeprom_manager eeprom_manager( + i2c::make_simple_udp(new_addr["addr"], E300_SERVER_I2C_PORT)); + const mboard_eeprom_t eeprom = eeprom_manager.get_mb_eeprom(); + new_addr["name"] = eeprom["name"]; + new_addr["serial"] = eeprom["serial"]; + new_addr["product"] = eeprom["product"]; + } catch (...) { + // set these values as empty string, so the device may still be found + // and the filters 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"])) + { + e300_addrs.push_back(new_addr); + } + } + } + + // finally search locally + // if device node is not provided, + // use the default one + if (not hint.has_key("node")) { + device_addr_t new_addr = hint; + new_addr["node"] = "/dev/axi_fpga"; + return e300_find(new_addr); + } + + // use the given node + if (fs::exists(hint["node"])) { + device_addr_t new_addr; + new_addr["type"] = "e3x0"; + new_addr["node"] = fs::system_complete(fs::path(hint["node"])).string(); + + try { + e300_eeprom_manager eeprom_manager(i2c::make_i2cdev(E300_I2CDEV_DEVICE)); + const mboard_eeprom_t eeprom = eeprom_manager.get_mb_eeprom(); + new_addr["name"] = eeprom["name"]; + new_addr["serial"] = eeprom["serial"]; + new_addr["product"] = eeprom["product"]; + } catch (...) { + // set these values as empty string, so the device may still be found + // and the filters 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"])) + { + e300_addrs.push_back(new_addr); + } + } + + return e300_addrs; +} + + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr e300_make(const device_addr_t &device_addr) +{ + UHD_LOG << "e300_make with args " << device_addr.to_pp_string() << std::endl; + if(device_addr.has_key("server")) + throw uhd::runtime_error( + str(boost::format("Please run the server executable \"%s\"") + % "usrp_e3x0_network_mode")); + else + return device::sptr(new e300_impl(device_addr)); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +e300_impl::e300_impl(const uhd::device_addr_t &device_addr) + : _device_addr(device_addr) + , _xport_path(device_addr.has_key("addr") ? ETH : AXI) + , _sid_framer(0) +{ + _type = uhd::device::USRP; + + _async_md.reset(new async_md_type(1000/*messages deep*/)); + + //////////////////////////////////////////////////////////////////// + // load the fpga image + //////////////////////////////////////////////////////////////////// + if (_xport_path == AXI) { + if (not device_addr.has_key("no_reload_fpga")) { + // Load FPGA image if provided via args + if (device_addr.has_key("fpga")) { + common::load_fpga_image(device_addr["fpga"]); + // Else load the FPGA image based on the product ID + } else { + //extract the FPGA path for the e300 + const boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( + device_addr["product"]); + std::string fpga_image; + switch(e300_eeprom_manager::get_mb_type(pid)) { + case e300_eeprom_manager::USRP_E310_MB: + fpga_image = find_image_path(E310_FPGA_FILE_NAME); + break; + case e300_eeprom_manager::USRP_E300_MB: + fpga_image = find_image_path(E300_FPGA_FILE_NAME); + break; + case e300_eeprom_manager::UNKNOWN: + default: + UHD_MSG(warning) << "Unknown motherboard type, loading e300 image." + << std::endl; + fpga_image = find_image_path(E300_FPGA_FILE_NAME); + break; + } + common::load_fpga_image(fpga_image); + } + } + } + + //////////////////////////////////////////////////////////////////// + // setup fifo xports + //////////////////////////////////////////////////////////////////// + _ctrl_xport_params.recv_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; + _ctrl_xport_params.num_recv_frames = e300::DEFAULT_CTRL_NUM_FRAMES; + _ctrl_xport_params.send_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; + _ctrl_xport_params.num_send_frames = e300::DEFAULT_CTRL_NUM_FRAMES; + + _data_xport_params.recv_frame_size = e300::DEFAULT_RX_DATA_FRAME_SIZE; + _data_xport_params.num_recv_frames = e300::DEFAULT_RX_DATA_NUM_FRAMES; + _data_xport_params.send_frame_size = e300::DEFAULT_TX_DATA_FRAME_SIZE; + _data_xport_params.num_send_frames = e300::DEFAULT_TX_DATA_NUM_FRAMES; + + // until we figure out why this goes wrong we'll keep this hack around + if (_xport_path == ETH) { + _data_xport_params.recv_frame_size = + std::min(e300::MAX_NET_RX_DATA_FRAME_SIZE, _data_xport_params.recv_frame_size); + _data_xport_params.send_frame_size = + std::min(e300::MAX_NET_TX_DATA_FRAME_SIZE, _data_xport_params.send_frame_size); + } + udp_zero_copy::buff_params dummy_buff_params_out; + + if (_xport_path == ETH) { + zero_copy_if::sptr codec_xport = + udp_zero_copy::make(device_addr["addr"], E300_SERVER_CODEC_PORT, _ctrl_xport_params, dummy_buff_params_out, device_addr); + _codec_ctrl = e300_remote_codec_ctrl::make(codec_xport); + zero_copy_if::sptr gregs_xport = + udp_zero_copy::make(device_addr["addr"], E300_SERVER_GREGS_PORT, _ctrl_xport_params, dummy_buff_params_out, device_addr); + _global_regs = global_regs::make(gregs_xport); + + zero_copy_if::sptr i2c_xport; + i2c_xport = udp_zero_copy::make(device_addr["addr"], E300_SERVER_I2C_PORT, _ctrl_xport_params, dummy_buff_params_out, device_addr); + _eeprom_manager = boost::make_shared<e300_eeprom_manager>(i2c::make_zc(i2c_xport)); + + uhd::transport::zero_copy_xport_params sensor_xport_params; + sensor_xport_params.recv_frame_size = 128; + sensor_xport_params.num_recv_frames = 10; + sensor_xport_params.send_frame_size = 128; + sensor_xport_params.num_send_frames = 10; + + zero_copy_if::sptr sensors_xport; + sensors_xport = udp_zero_copy::make(device_addr["addr"], E300_SERVER_SENSOR_PORT, sensor_xport_params, dummy_buff_params_out, device_addr); + _sensor_manager = e300_sensor_manager::make_proxy(sensors_xport); + + } else { + e300_fifo_config_t fifo_cfg; + try { + fifo_cfg = e300_read_sysfs(); + } catch (uhd::lookup_error &e) { + throw uhd::runtime_error("Failed to get driver parameters from sysfs."); + } + _fifo_iface = e300_fifo_interface::make(fifo_cfg); + _global_regs = global_regs::make(_fifo_iface->get_global_regs_base()); + + ad9361_params::sptr client_settings = boost::make_shared<e300_ad9361_client_t>(); + _codec_ctrl = ad9361_ctrl::make_spi(client_settings, spi::make(E300_SPIDEV_DEVICE), 1); + // This is horrible ... why do I have to sleep here? + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + _eeprom_manager = boost::make_shared<e300_eeprom_manager>(i2c::make_i2cdev(E300_I2CDEV_DEVICE)); + } + + UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush; + if (_xport_path == AXI) { + try { + _gps = gps::ublox::ubx::control::make("/dev/ttyPS1", 9600); + } catch (std::exception &e) { + UHD_MSG(error) << "An error occured making GPSDO control: " << e.what() << std::endl; + } + _sensor_manager = e300_sensor_manager::make_local(_gps); + } + UHD_MSG(status) << (_sensor_manager->get_gps_found() ? "found" : "not found") << std::endl; + + // Verify we can talk to the e300 core control registers ... + UHD_MSG(status) << "Initializing core control..." << std::endl; + this->_register_loopback_self_test(_global_regs); + + // Verify fpga compatibility version matches at least for the major + if (_get_version(FPGA_MAJOR) != fpga::COMPAT_MAJOR) { + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number %lu.x, but got %lu.%lu:\n" + "The FPGA build is not compatible with the host code build.\n" + "%s" + ) % fpga::COMPAT_MAJOR + % _get_version(FPGA_MAJOR) % _get_version(FPGA_MINOR) + % print_images_error())); + } + + //////////////////////////////////////////////////////////////////// + // Initialize the properties tree + //////////////////////////////////////////////////////////////////// + _tree = property_tree::make(); + _tree->create<std::string>("/name").set("E-Series Device"); + const fs_path mb_path = "/mboards/0"; + _tree->create<std::string>(mb_path / "name") + .set(_eeprom_manager->get_mb_type_string()); + + _tree->create<std::string>(mb_path / "codename").set("Troll"); + + _tree->create<std::string>(mb_path / "fpga_version").set( + str(boost::format("%u.%u") + % _get_version(FPGA_MAJOR) + % _get_version(FPGA_MINOR))); + + _tree->create<std::string>(mb_path / "fpga_version_hash").set( + _get_version_hash()); + + //////////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////////// + _tree->create<int>(mb_path / "sensors"); + 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)); + } + + //////////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////////// + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(_eeprom_manager->get_mb_eeprom()) // set first... + .subscribe(boost::bind( + &e300_eeprom_manager::write_mb_eeprom, + _eeprom_manager, _1)); + + //////////////////////////////////////////////////////////////////// + // 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)); + + //default some chains on -- needed for setup purposes + _codec_ctrl->set_active_chains(true, false, true, false); + _codec_ctrl->set_clock_rate(50e6); + + //////////////////////////////////////////////////////////////////// + // setup radios + //////////////////////////////////////////////////////////////////// + for(size_t instance = 0; instance < fpga::NUM_RADIOS; instance++) + this->_setup_radio(instance); + + _codec_ctrl->data_port_loopback(true); + + // Radio 0 loopback through AD9361 + this->_codec_loopback_self_test(_radio_perifs[0].ctrl); + // Radio 1 loopback through AD9361 + this->_codec_loopback_self_test(_radio_perifs[1].ctrl); + + _codec_ctrl->data_port_loopback(false); + + //////////////////////////////////////////////////////////////////// + // internal gpios + //////////////////////////////////////////////////////////////////// + gpio_core_200::sptr fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); + const std::vector<std::string> gpio_attrs = boost::assign::list_of("CTRL")("DDR")("OUT")("ATR_0X")("ATR_RX")("ATR_TX")("ATR_XX"); + BOOST_FOREACH(const std::string &attr, gpio_attrs) + { + _tree->create<boost::uint32_t>(mb_path / "gpio" / "INT0" / attr) + .subscribe(boost::bind(&e300_impl::_set_internal_gpio, this, fp_gpio, attr, _1)) + .set(0); + } + _tree->create<boost::uint8_t>(mb_path / "gpio" / "INT0" / "READBACK") + .publish(boost::bind(&e300_impl::_get_internal_gpio, this, fp_gpio, "READBACK")); + + + //////////////////////////////////////////////////////////////////// + // 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(&time_core_3000::set_time_now, _radio_perifs[0].time64, _1)) + .subscribe(boost::bind(&time_core_3000::set_time_now, _radio_perifs[1].time64, _1)); + _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)); + //setup time source props + _tree->create<std::string>(mb_path / "time_source" / "value") + .subscribe(boost::bind(&e300_impl::_update_time_source, this, _1)); + static const std::vector<std::string> time_sources = boost::assign::list_of("none")("external")("gpsdo"); + _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); + //setup reference source props + _tree->create<std::string>(mb_path / "clock_source" / "value") + .subscribe(boost::bind(&e300_impl::_update_clock_source, this, _1)); + static const std::vector<std::string> clock_sources = boost::assign::list_of("internal"); + // not implemented ("external")("gpsdo"); + _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources); + + //////////////////////////////////////////////////////////////////// + // dboard eeproms but not really + //////////////////////////////////////////////////////////////////// + dboard_eeprom_t db_eeprom; + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom") + .set(_eeprom_manager->get_db_eeprom()) + .subscribe(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( + &e300_eeprom_manager::write_db_eeprom, + _eeprom_manager, _1)); + + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom").set(db_eeprom); + + //////////////////////////////////////////////////////////////////// + // create RF frontend interfacing + //////////////////////////////////////////////////////////////////// + { + const fs_path codec_path = mb_path / ("rx_codecs") / "A"; + _tree->create<std::string>(codec_path / "name").set("E3x0 RX dual ADC"); + _tree->create<int>(codec_path / "gains"); //empty cuz gains are in frontend + } + { + const fs_path codec_path = mb_path / ("tx_codecs") / "A"; + _tree->create<std::string>(codec_path / "name").set("E3x0 TX dual DAC"); + _tree->create<int>(codec_path / "gains"); //empty cuz gains are in frontend + } + + //////////////////////////////////////////////////////////////////// + // create frontend mapping + //////////////////////////////////////////////////////////////////// + + std::vector<size_t> default_map(2, 0); + default_map[0] = 0; // set A->0 + default_map[1] = 1; // set B->1, even if there's only A + + _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") + .set(subdev_spec_t()) + .subscribe(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)); + + //////////////////////////////////////////////////////////////////// + // do some post-init tasks + //////////////////////////////////////////////////////////////////// + + // init the clock rate to something reasonable + _tree->access<double>(mb_path / "tick_rate").set( + device_addr.cast<double>("master_clock_rate", e300::DEFAULT_TICK_RATE)); + + // subdev spec contains full width of selections + subdev_spec_t rx_spec, tx_spec; + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) + { + rx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) + { + tx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec); + _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); + + if (_sensor_manager->get_gps_found()) { + _tree->access<std::string>(mb_path / "clock_source" / "value").set("gpsdo"); + _tree->access<std::string>(mb_path / "time_source" / "value").set("gpsdo"); + UHD_MSG(status) << "References initialized to GPSDO sources" << std::endl; + const time_t tp = time_t(_sensor_manager->get_gps_time().to_int()); + _tree->access<time_spec_t>(mb_path / "time" / "pps").set(time_spec_t(tp)); + //wait for time to be set (timeout after 1 second) + for (int i = 0; i < 10; i++) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + if(tp == (_tree->access<time_spec_t>(mb_path / "time" / "pps").get()).get_full_secs()) + break; + } + } else { + // init to default time and clock source + _tree->access<std::string>(mb_path / "clock_source" / "value").set( + e300::DEFAULT_CLOCK_SRC); + _tree->access<std::string>(mb_path / "time_source" / "value").set( + e300::DEFAULT_TIME_SRC); + + UHD_MSG(status) << "References initialized to internal sources" << std::endl; + } +} + +boost::uint8_t e300_impl::_get_internal_gpio( + gpio_core_200::sptr gpio, + const std::string &) +{ + return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); +} + +void e300_impl::_set_internal_gpio( + gpio_core_200::sptr gpio, + const std::string &attr, + const boost::uint32_t value) +{ + if (attr == "CTRL") + return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); + else if (attr == "DDR") + return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); + else if (attr == "OUT") + return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); + else if (attr == "ATR_0X") + return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); + else if (attr == "ATR_RX") + return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); + else if (attr == "ATR_TX") + return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); + else if (attr == "ATR_XX") + return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); +} + +uhd::sensor_value_t e300_impl::_get_fe_pll_lock(const bool is_tx) +{ + const boost::uint32_t st = + _global_regs->peek32(global_regs::RB32_CORE_PLL); + const bool locked = is_tx ? st & 0x1 : st & 0x2; + return sensor_value_t("LO", locked, "locked", "unlocked"); +} + +e300_impl::~e300_impl(void) +{ + /* NOP */ +} + +void e300_impl::_enforce_tick_rate_limits( + const size_t chan_count, + const double tick_rate, + const std::string &direction) +{ + const size_t max_chans = 2; + if (chan_count > max_chans) { + throw uhd::value_error(boost::str( + boost::format("cannot not setup %d %s channels (maximum is %d)") + % chan_count + % direction + % max_chans + )); + } else { + const double max_tick_rate = ad9361_device_t::AD9361_MAX_CLOCK_RATE / ((chan_count <= 1) ? 1 : 2); + if (tick_rate - max_tick_rate >= 1.0) + { + throw uhd::value_error(boost::str( + boost::format("current master clock rate (%.6f MHz) exceeds maximum possible master clock rate (%.6f MHz) when using %d %s channels") + % (tick_rate/1e6) + % (max_tick_rate/1e6) + % chan_count + % direction + )); + } + } +} + +double e300_impl::_set_tick_rate(const double rate) +{ + UHD_MSG(status) << "Asking for clock rate " << rate/1e6 << " MHz\n"; + _tick_rate = _codec_ctrl->set_clock_rate(rate); + UHD_MSG(status) << "Actually got clock rate " << _tick_rate/1e6 << " MHz\n"; + + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + perif.time64->set_tick_rate(_tick_rate); + perif.time64->self_test(); + } + return _tick_rate; +} + +void e300_impl::_load_fpga_image(const std::string &path) +{ + if (not fs::exists("/dev/xdevcfg")) + { + ::system("mknod /dev/xdevcfg c 259 0"); + //throw uhd::runtime_error("no xdevcfg, please run: mknod /dev/xdevcfg c 259 0"); + } + + UHD_MSG(status) << "Loading FPGA image: " << path << "..." << std::flush; + + std::ifstream fpga_file(path.c_str(), std::ios_base::binary); + UHD_ASSERT_THROW(fpga_file.good()); + + std::FILE *wfile; + wfile = std::fopen("/dev/xdevcfg", "wb"); + UHD_ASSERT_THROW(!(wfile == NULL)); + + char buff[16384]; // devcfg driver can't handle huge writes + do { + fpga_file.read(buff, sizeof(buff)); + std::fwrite(buff, 1, fpga_file.gcount(), wfile); + } while (fpga_file); + + fpga_file.close(); + std::fclose(wfile); + + UHD_MSG(status) << " done" << std::endl; +} + +void e300_impl::_register_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + UHD_MSG(status) << "Performing register loopback test... " << std::flush; + size_t hash = time(NULL); + for (size_t i = 0; i < 100; i++) + { + boost::hash_combine(hash, i); + iface->poke32(TOREG(SR_TEST), boost::uint32_t(hash)); + test_fail = iface->peek32(RB32_TEST) != boost::uint32_t(hash); + if (test_fail) break; //exit loop on any failure + } + UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; +} + +boost::uint32_t e300_impl::_get_version(compat_t which) +{ + const boost::uint16_t compat_num + = _global_regs->peek32(global_regs::RB32_CORE_COMPAT); + + switch(which) { + case FPGA_MINOR: + return compat_num & 0xff; + case FPGA_MAJOR: + return (compat_num & 0xff00) >> 8; + default: + throw uhd::value_error("Requested unknown version."); + }; +} + +std::string e300_impl::_get_version_hash(void) +{ + const boost::uint32_t git_hash + = _global_regs->peek32(global_regs::RB32_CORE_GITHASH); + return str(boost::format("%7x%s") + % (git_hash & 0x0FFFFFFF) + % ((git_hash & 0xF000000) ? "-dirty" : "")); +} + +void e300_impl::_codec_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + UHD_ASSERT_THROW(bool(iface)); + UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush; + size_t hash = time(NULL); + for (size_t i = 0; i < 100; i++) + { + boost::hash_combine(hash, i); + const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0; + iface->poke32(TOREG(SR_CODEC_IDLE), word32); + iface->peek64(RB64_CODEC_READBACK); //enough idleness for loopback to propagate + const boost::uint64_t rb_word64 = iface->peek64(RB64_CODEC_READBACK); + const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); + const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); + test_fail = word32 != rb_tx or word32 != rb_rx; + if (test_fail) break; //exit loop on any failure + } + UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; + + /* Zero out the idle data. */ + iface->poke32(TOREG(SR_CODEC_IDLE), 0); +} + +boost::uint32_t e300_impl::_allocate_sid(const sid_config_t &config) +{ + const boost::uint32_t stream = (config.dst_prefix | (config.router_dst_there << 2)) & 0xff; + + const boost::uint32_t sid = 0 + | (E300_DEVICE_HERE << 24) + | (_sid_framer << 16) + | (config.router_addr_there << 8) + | (stream << 0) + ; + UHD_LOG << std::hex + << " sid 0x" << sid + << " framer 0x" << _sid_framer + << " stream 0x" << stream + << " router_dst_there 0x" << int(config.router_dst_there) + << " router_addr_there 0x" << int(config.router_addr_there) + << std::dec << std::endl; + + // Program the E300 to recognize it's own local address. + _global_regs->poke32(global_regs::SR_CORE_XB_LOCAL, config.router_addr_there); + + // Program CAM entry for outgoing packets matching a E300 resource (e.g. Radio). + // This type of packet matches the XB_LOCAL address and is looked up in the upper + // half of the CAM + _global_regs->poke32(XB_ADDR(256 + stream), + config.router_dst_there); + + // Program CAM entry for returning packets to us (for example GR host via zynq_fifo) + // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM + _global_regs->poke32(XB_ADDR(E300_DEVICE_HERE), + config.router_dst_here); + + UHD_LOG << std::hex + << "done router config for sid 0x" << sid + << std::dec << std::endl; + + //increment for next setup + _sid_framer++; + + return sid; +} + +void e300_impl::_setup_dest_mapping(const boost::uint32_t sid, const size_t which_stream) +{ + UHD_LOG << boost::format("Setting up dest map for 0x%lx to be stream %d") + % (sid & 0xff) % which_stream << std::endl; + _global_regs->poke32(DST_ADDR(sid & 0xff), which_stream); +} + +void e300_impl::_update_time_source(const std::string &source) +{ + UHD_MSG(status) << boost::format("Setting time source to %s") % source << std::endl; + if (source == "none" or source == "internal") { + _misc.pps_sel = global_regs::PPS_INT; + } else if (source == "gpsdo") { + _misc.pps_sel = global_regs::PPS_GPS; + } else if (source == "external") { + _misc.pps_sel = global_regs::PPS_EXT; + } else { + throw uhd::key_error("update_time_source: unknown source: " + source); + } + _update_gpio_state(); +} + +size_t e300_impl::_get_axi_dma_channel( + boost::uint8_t destination, + boost::uint8_t prefix) +{ + static const boost::uint32_t RADIO_GRP_SIZE = 4; + static const boost::uint32_t RADIO0_GRP = 0; + static const boost::uint32_t RADIO1_GRP = 1; + + boost::uint32_t radio_grp = (destination == E300_XB_DST_R0) ? RADIO0_GRP : RADIO1_GRP; + return ((radio_grp * RADIO_GRP_SIZE) + prefix); +} + +boost::uint16_t e300_impl::_get_udp_port( + boost::uint8_t destination, + boost::uint8_t prefix) +{ + if (destination == E300_XB_DST_R0) { + if (prefix == E300_RADIO_DEST_PREFIX_CTRL) + return boost::lexical_cast<boost::uint16_t>(E300_SERVER_CTRL_PORT0); + else if (prefix == E300_RADIO_DEST_PREFIX_TX) + return boost::lexical_cast<boost::uint16_t>(E300_SERVER_TX_PORT0); + else if (prefix == E300_RADIO_DEST_PREFIX_RX) + return boost::lexical_cast<boost::uint16_t>(E300_SERVER_RX_PORT0); + } else if (destination == E300_XB_DST_R1) { + if (prefix == E300_RADIO_DEST_PREFIX_CTRL) + return boost::lexical_cast<boost::uint16_t>(E300_SERVER_CTRL_PORT1); + else if (prefix == E300_RADIO_DEST_PREFIX_TX) + return boost::lexical_cast<boost::uint16_t>(E300_SERVER_TX_PORT1); + else if (prefix == E300_RADIO_DEST_PREFIX_RX) + return boost::lexical_cast<boost::uint16_t>(E300_SERVER_RX_PORT1); + } + throw uhd::value_error(str(boost::format("No UDP port defined for combination: %u %u") % destination % prefix)); +} + +e300_impl::both_xports_t e300_impl::_make_transport( + const boost::uint8_t &destination, + const boost::uint8_t &prefix, + const uhd::transport::zero_copy_xport_params ¶ms, + boost::uint32_t &sid) +{ + both_xports_t xports; + + sid_config_t config; + config.router_addr_there = E300_DEVICE_THERE; + config.dst_prefix = prefix; + config.router_dst_there = destination; + config.router_dst_here = E300_XB_DST_AXI; + sid = this->_allocate_sid(config); + + // in local mode + if (_xport_path == AXI) { + // lookup which dma channel we need + // to use to create our transport + const size_t stream = _get_axi_dma_channel( + destination, + prefix); + + xports.send = + _fifo_iface->make_send_xport(stream, params); + xports.recv = + _fifo_iface->make_recv_xport(stream, params); + + // in network mode + } else if (_xport_path == ETH) { + // lookup which udp port we need + // to use to create our transport + const boost::uint16_t port = _get_udp_port( + destination, + prefix); + + udp_zero_copy::buff_params dummy_buff_params_out; + xports.send = udp_zero_copy::make( + _device_addr["addr"], + str(boost::format("%u") % port), params, + dummy_buff_params_out, + _device_addr); + + // use the same xport in both directions + xports.recv = xports.send; + } + + // configure the return path + _setup_dest_mapping(sid, _get_axi_dma_channel(destination, prefix)); + + return xports; +} + +void e300_impl::_update_clock_source(const std::string &) +{ +} + +void e300_impl::_update_antenna_sel(const size_t &which, const std::string &ant) +{ + if (ant != "TX/RX" and ant != "RX2") + throw uhd::value_error("e300: unknown RX antenna option: " + ant); + _radio_perifs[which].ant_rx2 = (ant == "RX2"); + this->_update_atrs(); +} + +void e300_impl::_update_fe_lo_freq(const std::string &fe, const double freq) +{ + if (fe[0] == 'R') + _settings.rx_freq = freq; + if (fe[0] == 'T') + _settings.tx_freq = freq; + this->_update_atrs(); + _update_bandsel(fe, freq); +} + +void e300_impl::_setup_radio(const size_t dspno) +{ + radio_perifs_t &perif = _radio_perifs[dspno]; + const fs_path mb_path = "/mboards/0"; + + //////////////////////////////////////////////////////////////////// + // crossbar config for ctrl xports + //////////////////////////////////////////////////////////////////// + + // make a transport, grab a sid + boost::uint32_t ctrl_sid; + both_xports_t ctrl_xports = _make_transport( + dspno ? E300_XB_DST_R1 : E300_XB_DST_R0, + E300_RADIO_DEST_PREFIX_CTRL, + _ctrl_xport_params, + ctrl_sid); + + this->_setup_dest_mapping( + ctrl_sid, + dspno ? E300_R1_CTRL_STREAM + : E300_R0_CTRL_STREAM); + + //////////////////////////////////////////////////////////////////// + // radio control + //////////////////////////////////////////////////////////////////// + perif.ctrl = radio_ctrl_core_3000::make( + false/*lilE*/, + ctrl_xports.send, + ctrl_xports.recv, + ctrl_sid, + dspno ? "1" : "0"); + this->_register_loopback_self_test(perif.ctrl); + perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_GPIO)); + + //////////////////////////////////////////////////////////////////// + // front end corrections + //////////////////////////////////////////////////////////////////// + std::string slot_name = (dspno == 0) ? "A" : "B"; + perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, TOREG(SR_RX_FRONT)); + const fs_path rx_fe_path = mb_path / "rx_frontends" / slot_name; + _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, perif.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, perif.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, perif.rx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + + perif.tx_fe = tx_frontend_core_200::make(perif.ctrl, TOREG(SR_TX_FRONT)); + const fs_path tx_fe_path = mb_path / "tx_frontends" / slot_name; + _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, perif.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, perif.tx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + + //////////////////////////////////////////////////////////////////// + // create rx dsp control objects + //////////////////////////////////////////////////////////////////// + perif.framer = rx_vita_core_3000::make(perif.ctrl, TOREG(SR_RX_CTRL)); + perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP)); + perif.ddc->set_link_rate(10e9/8); //whatever + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .subscribe(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); + const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % dspno); + _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); + _tree->create<double>(rx_dsp_path / "rate" / "value") + .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) + .subscribe(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) + .set(e300::DEFAULT_RX_SAMP_RATE); + _tree->create<double>(rx_dsp_path / "freq" / "value") + .coerce(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) + .set(e300::DEFAULT_DDC_FREQ); + _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + + //////////////////////////////////////////////////////////////////// + // create tx dsp control objects + //////////////////////////////////////////////////////////////////// + perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL)); + perif.duc = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP)); + perif.duc->set_link_rate(10e9/8); //whatever + _tree->access<double>(mb_path / "tick_rate") + .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1)) + .subscribe(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); + const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % dspno); + _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") + .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); + _tree->create<double>(tx_dsp_path / "rate" / "value") + .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) + .subscribe(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) + .set(e300::DEFAULT_TX_SAMP_RATE); + _tree->create<double>(tx_dsp_path / "freq" / "value") + .coerce(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) + .set(e300::DEFAULT_DUC_FREQ); + _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") + .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + + //////////////////////////////////////////////////////////////////// + // create time control objects + //////////////////////////////////////////////////////////////////// + time_core_3000::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_now = RB64_TIME_NOW; + time64_rb_bases.rb_pps = RB64_TIME_PPS; + perif.time64 = time_core_3000::make(perif.ctrl, TOREG(SR_TIME), time64_rb_bases); + + //////////////////////////////////////////////////////////////////// + // create RF frontend interfacing + //////////////////////////////////////////////////////////////////// + static const std::vector<std::string> data_directions = boost::assign::list_of("rx")("tx"); + BOOST_FOREACH(const std::string& direction, data_directions) + { + const std::string key = boost::to_upper_copy(direction) + std::string(((dspno == FE0)? "1" : "2")); + const fs_path rf_fe_path + = mb_path / "dboards" / "A" / (direction + "_frontends") / ((dspno == 0) ? "A" : "B"); + + _tree->create<std::string>(rf_fe_path / "name").set("FE-"+key); + _tree->create<int>(rf_fe_path / "sensors"); //empty TODO + _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked") + .publish(boost::bind(&e300_impl::_get_fe_pll_lock, this, direction == "tx")); + BOOST_FOREACH(const std::string &name, ad9361_ctrl::get_gain_names(key)) + { + _tree->create<meta_range_t>(rf_fe_path / "gains" / name / "range") + .set(ad9361_ctrl::get_gain_range(key)); + + _tree->create<double>(rf_fe_path / "gains" / name / "value") + .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) + .set(e300::DEFAULT_FE_GAIN); + } + _tree->create<std::string>(rf_fe_path / "connection").set("IQ"); + _tree->create<bool>(rf_fe_path / "enabled").set(true); + _tree->create<bool>(rf_fe_path / "use_lo_offset").set(false); + _tree->create<double>(rf_fe_path / "bandwidth" / "value") + .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) + .set(e300::DEFAULT_FE_BW); + _tree->create<meta_range_t>(rf_fe_path / "bandwidth" / "range") + .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)); + _tree->create<double>(rf_fe_path / "freq" / "value") + .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) + .subscribe(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) + .set(e300::DEFAULT_FE_FREQ); + _tree->create<meta_range_t>(rf_fe_path / "freq" / "range") + .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)); + + //setup antenna stuff + if (key[0] == 'R') { + static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2"); + _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); + _tree->create<std::string>(rf_fe_path / "antenna" / "value") + .subscribe(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1)) + .set("RX2"); + + } + if (key[0] == 'T') { + static const std::vector<std::string> ants(1, "TX/RX"); + _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants); + _tree->create<std::string>(rf_fe_path / "antenna" / "value").set("TX/RX"); + } + } +} + +void e300_impl::_update_enables(void) +{ + //extract settings from state variables + const bool enb_tx1 = bool(_radio_perifs[FE0].tx_streamer.lock()); + const bool enb_rx1 = bool(_radio_perifs[FE0].rx_streamer.lock()); + const bool enb_tx2 = bool(_radio_perifs[FE1].tx_streamer.lock()); + const bool enb_rx2 = bool(_radio_perifs[FE1].rx_streamer.lock()); + const size_t num_rx = (enb_rx1 ? 1 : 0) + (enb_rx2 ? 1:0); + const size_t num_tx = (enb_tx1 ? 1 : 0) + (enb_tx2 ? 1:0); + const bool mimo = num_rx == 2 or num_tx == 2; + + //setup the active chains in the codec + _codec_ctrl->set_active_chains(enb_tx1, enb_tx2, enb_rx1, enb_rx2); + if ((num_rx + num_tx) == 0) + _codec_ctrl->set_active_chains( + true, false, true, false); // enable something + + //set_active_chains could cause a clock rate change - reset dcm + _reset_codec_mmcm(); + + //figure out if mimo is enabled based on new state + _misc.mimo = (mimo)? 1 : 0; + _update_gpio_state(); + + //atrs change based on enables + _update_atrs(); +} + +void e300_impl::_update_gpio_state(void) +{ + boost::uint32_t misc_reg = 0 + | (_misc.pps_sel << gpio_t::PPS_SEL) + | (_misc.mimo << gpio_t::MIMO) + | (_misc.codec_arst << gpio_t::CODEC_ARST) + | (_misc.tx_bandsels << gpio_t::TX_BANDSEL) + | (_misc.rx_bandsel_a << gpio_t::RX_BANDSELA) + | (_misc.rx_bandsel_b << gpio_t::RX_BANDSELB) + | (_misc.rx_bandsel_c << gpio_t::RX_BANDSELC); + _global_regs->poke32(global_regs::SR_CORE_MISC, misc_reg); +} + +void e300_impl::_reset_codec_mmcm(void) +{ + _misc.codec_arst = 1; + _update_gpio_state(); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + _misc.codec_arst = 0; + _update_gpio_state(); +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +//////////////// ATR SETUP FOR FRONTEND CONTROL VIA GPIO /////////////// +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// + +void e300_impl::_update_bandsel(const std::string& which, double freq) +{ + if(which[0] == 'R') { + if (freq < 450e6) { + _misc.rx_bandsel_a = 44; // 4 | (5 << 3) + _misc.rx_bandsel_b = 0; // 0 | (0 << 2) + _misc.rx_bandsel_c = 6; // 2 | (1 << 2) + } else if (freq < 700e6) { + _misc.rx_bandsel_a = 26; // 2 | (3 << 3) + _misc.rx_bandsel_b = 0; // 0 | (0 << 2) + _misc.rx_bandsel_c = 15; // 3 | (3 << 2) + } else if (freq < 1200e6) { + _misc.rx_bandsel_a = 8; // 0 | (1 << 3) + _misc.rx_bandsel_b = 0; // 0 | (0 << 2) + _misc.rx_bandsel_c = 9; // 1 | (2 << 2) + } else if (freq < 1800e6) { + _misc.rx_bandsel_a = 1; // 1 | (0 << 3) + _misc.rx_bandsel_b = 6; // 2 | (1 << 2) + _misc.rx_bandsel_c = 0; // 0 | (0 << 2) + } else if (freq < 2350e6){ + _misc.rx_bandsel_a = 19; // 3 | (2 << 3) + _misc.rx_bandsel_b = 15; // 3 | (3 << 2) + _misc.rx_bandsel_c = 0; // 0 | (0 << 2) + } else if (freq < 2600e6){ + _misc.rx_bandsel_a = 37; // 5 | (4 << 3) + _misc.rx_bandsel_b = 9; // 1 | (2 << 2) + _misc.rx_bandsel_c = 0; // 0 | (0 << 2) + } else { + _misc.rx_bandsel_a = 0; + _misc.rx_bandsel_b = 0; + _misc.rx_bandsel_c = 0; + } + _update_gpio_state(); + } else if(which[0] == 'T') { + if (freq < 117.7e6) + _misc.tx_bandsels = 7; + else if (freq < 178.2e6) + _misc.tx_bandsels = 6; + else if (freq < 284.3e6) + _misc.tx_bandsels = 5; + else if (freq < 453.7e6) + _misc.tx_bandsels = 4; + else if (freq < 723.8e6) + _misc.tx_bandsels = 3; + else if (freq < 1154.9e6) + _misc.tx_bandsels = 2; + else if (freq < 1842.6e6) + _misc.tx_bandsels = 1; + else if (freq < 2940.0e6) + _misc.tx_bandsels = 0; + else + _misc.tx_bandsels = 7; + _update_gpio_state(); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + + +void e300_impl::_update_atrs(void) +{ + for (size_t instance = 0; instance < fpga::NUM_RADIOS; instance++) + { + // if we're not ready, no point ... + if (not _radio_perifs[instance].atr) + return; + + radio_perifs_t &perif = _radio_perifs[instance]; + const bool enb_rx = bool(perif.rx_streamer.lock()); + const bool enb_tx = bool(perif.tx_streamer.lock()); + const bool rx_ant_rx2 = perif.ant_rx2; + + const bool rx_low_band = _settings.rx_freq < 2.6e9; + const bool tx_low_band = _settings.tx_freq < 2940.0e6; + + // VCRX + int vcrx_v1_rxing = 1; + int vcrx_v2_rxing = 0; + int vcrx_v1_txing = 1; + int vcrx_v2_txing = 0; + + if (rx_low_band) { + vcrx_v1_rxing = rx_ant_rx2 ? 0 : 1; + vcrx_v2_rxing = rx_ant_rx2 ? 1 : 0; + vcrx_v1_txing = 0; + vcrx_v2_txing = 1; + } else { + vcrx_v1_rxing = rx_ant_rx2 ? 1 : 0; + vcrx_v2_rxing = rx_ant_rx2 ? 0 : 1; + vcrx_v1_txing = 1; + vcrx_v2_txing = 0; + } + + // VCTX + int vctxrx_v1_rxing = 0; + int vctxrx_v2_rxing = 1; + int vctxrx_v1_txing = 0; + int vctxrx_v2_txing = 1; + + if (tx_low_band) { + vctxrx_v1_rxing = rx_ant_rx2 ? 0 : 0; + vctxrx_v2_rxing = rx_ant_rx2 ? 0 : 1; + vctxrx_v1_txing = 1; + vctxrx_v2_txing = 0; + } else { + vctxrx_v1_rxing = rx_ant_rx2 ? 0 : 0; + vctxrx_v2_rxing = rx_ant_rx2 ? 0 : 1; + vctxrx_v1_txing = 1; + vctxrx_v2_txing = 1; + } + //swapped for routing reasons, reswap it here + if (instance == 1) { + std::swap(vctxrx_v1_rxing, vctxrx_v2_rxing); + std::swap(vctxrx_v1_txing, vctxrx_v2_txing); + } + + int tx_enable_a = (!tx_low_band and enb_tx) ? 1 : 0; + int tx_enable_b = (tx_low_band and enb_tx) ? 1 : 0; + + //----------------- LEDS ----------------------------// + const int led_rx2 = rx_ant_rx2 ? 1 : 0; + const int led_txrx = !rx_ant_rx2 ? 1 : 0; + const int led_tx = 1; + + const int rx_leds = (led_rx2 << LED_RX_RX) | (led_txrx << LED_TXRX_RX); + const int tx_leds = (led_tx << LED_TXRX_TX); + const int xx_leds = tx_leds | (1 << LED_RX_RX); //forced to rx2 + + const int rx_selects = 0 + | (vcrx_v1_rxing << VCRX_V1) + | (vcrx_v2_rxing << VCRX_V2) + | (vctxrx_v1_rxing << VCTXRX_V1) + | (vctxrx_v2_rxing << VCTXRX_V2) + ; + const int tx_selects = 0 + | (vcrx_v1_txing << VCRX_V1) + | (vcrx_v2_txing << VCRX_V2) + | (vctxrx_v1_txing << VCTXRX_V1) + | (vctxrx_v2_txing << VCTXRX_V2) + ; + const int tx_enables = 0 + | (tx_enable_a << TX_ENABLEA) + | (tx_enable_b << TX_ENABLEB) + ; + + //default selects + int oo_reg = rx_selects; + int rx_reg = rx_selects; + int tx_reg = tx_selects; + int fd_reg = tx_selects; //tx selects dominate in fd mode + + //add in leds and tx enables based on fe enable + if (enb_rx) + rx_reg |= rx_leds; + if (enb_rx) + fd_reg |= xx_leds; + if (enb_tx) + tx_reg |= tx_enables | tx_leds; + 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); + } +} + +}}} // namespace + +UHD_STATIC_BLOCK(register_e300_device) +{ + device::register_device(&uhd::usrp::e300::e300_find, &uhd::usrp::e300::e300_make, uhd::device::USRP); +} diff --git a/host/lib/usrp/e300/e300_impl.hpp b/host/lib/usrp/e300/e300_impl.hpp new file mode 100644 index 000000000..9cfd80afd --- /dev/null +++ b/host/lib/usrp/e300/e300_impl.hpp @@ -0,0 +1,296 @@ +// +// 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_E300_IMPL_HPP +#define INCLUDED_E300_IMPL_HPP + +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/types/sensors.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/thread/mutex.hpp> +#include "e300_fifo_config.hpp" +#include "radio_ctrl_core_3000.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.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 "ad9361_ctrl.hpp" +#include "gpio_core_200.hpp" + +#include "e300_global_regs.hpp" +#include "e300_i2c.hpp" +#include "e300_eeprom_manager.hpp" +#include "e300_sensor_manager.hpp" +#include "e300_ublox_control.hpp" + +namespace uhd { namespace usrp { namespace e300 { + +static const std::string E300_FPGA_FILE_NAME = "usrp_e300_fpga.bit"; +static const std::string E310_FPGA_FILE_NAME = "usrp_e310_fpga.bit"; + +static const std::string E300_TEMP_SYSFS = "iio:device0"; +static const std::string E300_SPIDEV_DEVICE = "/dev/spidev0.1"; +static const std::string E300_I2CDEV_DEVICE = "/dev/i2c-0"; + +static std::string E300_SERVER_RX_PORT0 = "21756"; +static std::string E300_SERVER_TX_PORT0 = "21757"; +static std::string E300_SERVER_CTRL_PORT0 = "21758"; + +static std::string E300_SERVER_RX_PORT1 = "21856"; +static std::string E300_SERVER_TX_PORT1 = "21857"; +static std::string E300_SERVER_CTRL_PORT1 = "21858"; + + +static std::string E300_SERVER_CODEC_PORT = "21759"; +static std::string E300_SERVER_GREGS_PORT = "21760"; +static std::string E300_SERVER_I2C_PORT = "21761"; +static std::string E300_SERVER_SENSOR_PORT = "21762"; + +static const double E300_RX_SW_BUFF_FULLNESS = 0.9; //Buffer should be half full + +// crossbar settings +static const boost::uint8_t E300_RADIO_DEST_PREFIX_TX = 0; +static const boost::uint8_t E300_RADIO_DEST_PREFIX_CTRL = 1; +static const boost::uint8_t E300_RADIO_DEST_PREFIX_RX = 2; + +static const boost::uint8_t E300_XB_DST_AXI = 0; +static const boost::uint8_t E300_XB_DST_R0 = 1; +static const boost::uint8_t E300_XB_DST_R1 = 2; +static const boost::uint8_t E300_XB_DST_CE0 = 3; +static const boost::uint8_t E300_XB_DST_CE1 = 4; + +static const boost::uint8_t E300_DEVICE_THERE = 2; +static const boost::uint8_t E300_DEVICE_HERE = 0; + +static const size_t E300_R0_CTRL_STREAM = (0 << 2) | E300_RADIO_DEST_PREFIX_CTRL; +static const size_t E300_R0_TX_DATA_STREAM = (0 << 2) | E300_RADIO_DEST_PREFIX_TX; +static const size_t E300_R0_RX_DATA_STREAM = (0 << 2) | E300_RADIO_DEST_PREFIX_RX; + +static const size_t E300_R1_CTRL_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_CTRL; +static const size_t E300_R1_TX_DATA_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_TX; +static const size_t E300_R1_RX_DATA_STREAM = (1 << 2) | E300_RADIO_DEST_PREFIX_RX; + + +/*! + * USRP-E300 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class e300_impl : public uhd::device +{ +public: + //structors + e300_impl(const uhd::device_addr_t &); + virtual ~e300_impl(void); + + //the io interface + boost::mutex _stream_spawn_mutex; + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); + + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + boost::shared_ptr<async_md_type> _async_md; + + bool recv_async_msg(uhd::async_metadata_t &, double); + +private: // types + // sid convenience struct + struct sid_config_t + { + boost::uint8_t router_addr_there; + boost::uint8_t dst_prefix; //2bits + boost::uint8_t router_dst_there; + boost::uint8_t router_dst_here; + }; + + // perifs in the radio core + struct radio_perifs_t + { + radio_ctrl_core_3000::sptr ctrl; + gpio_core_200_32wo::sptr atr; + time_core_3000::sptr time64; + rx_vita_core_3000::sptr framer; + rx_dsp_core_3000::sptr ddc; + tx_vita_core_3000::sptr deframer; + tx_dsp_core_3000::sptr duc; + rx_frontend_core_200::sptr rx_fe; + tx_frontend_core_200::sptr tx_fe; + + boost::weak_ptr<uhd::rx_streamer> rx_streamer; + boost::weak_ptr<uhd::tx_streamer> tx_streamer; + + bool ant_rx2; + }; + + //frontend cache so we can update gpios + struct fe_control_settings_t + { + fe_control_settings_t(void) + { + rx_freq = 1e9; + tx_freq = 1e9; + } + double rx_freq; + double tx_freq; + }; + + // convenience struct + struct both_xports_t + { + uhd::transport::zero_copy_if::sptr recv; + uhd::transport::zero_copy_if::sptr send; + }; + + enum xport_t {AXI, ETH}; + + enum compat_t {FPGA_MAJOR, FPGA_MINOR}; + + struct gpio_t + { + gpio_t() : pps_sel(global_regs::PPS_INT), + mimo(0), codec_arst(0), tx_bandsels(0), + rx_bandsel_a(0), rx_bandsel_b(0), rx_bandsel_c(0) + {} + + boost::uint32_t pps_sel; + boost::uint32_t mimo; + boost::uint32_t codec_arst; + + boost::uint32_t tx_bandsels; + boost::uint32_t rx_bandsel_a; + boost::uint32_t rx_bandsel_b; + boost::uint32_t rx_bandsel_c; + + static const size_t PPS_SEL = 0; + static const size_t MIMO = 2; + static const size_t CODEC_ARST = 3; + static const size_t TX_BANDSEL = 4; + static const size_t RX_BANDSELA = 7; + static const size_t RX_BANDSELB = 13; + static const size_t RX_BANDSELC = 17; + }; + +private: // methods + void _load_fpga_image(const std::string &path); + + void _register_loopback_self_test(uhd::wb_iface::sptr iface); + + boost::uint32_t _get_version(compat_t which); + std::string _get_version_hash(void); + + void _setup_radio(const size_t which_radio); + + boost::uint32_t _allocate_sid(const sid_config_t &config); + + void _setup_dest_mapping( + const boost::uint32_t sid, + const size_t which_stream); + + size_t _get_axi_dma_channel( + boost::uint8_t destination, + boost::uint8_t prefix); + + boost::uint16_t _get_udp_port( + boost::uint8_t destination, + boost::uint8_t prefix); + + both_xports_t _make_transport( + const boost::uint8_t &destination, + const boost::uint8_t &prefix, + const uhd::transport::zero_copy_xport_params ¶ms, + boost::uint32_t &sid); + + double _get_tick_rate(void){return _tick_rate;} + double _set_tick_rate(const double rate); + + void _update_gpio_state(void); + void _update_enables(void); + void _reset_codec_mmcm(void); + void _update_bandsel(const std::string& which, double freq); + + void _check_tick_rate_with_current_streamers(const double rate); + void _enforce_tick_rate_limits( + const size_t change, + const double tick_rate, + const std::string &direction); + + void _update_tick_rate(const double); + void _update_rx_samp_rate(const size_t, const double); + void _update_tx_samp_rate(const size_t, const double); + + void _update_time_source(const std::string &source); + void _update_clock_source(const std::string &); + + void _update_subdev_spec( + const std::string &txrx, + const uhd::usrp::subdev_spec_t &spec); + + void _codec_loopback_self_test(uhd::wb_iface::sptr iface); + + void _update_atrs(void); + void _update_antenna_sel(const size_t &fe, const std::string &ant); + void _update_fe_lo_freq(const std::string &fe, const double freq); + + // overflow handling is special for MIMO case + void _handle_overflow( + radio_perifs_t &perif, + boost::weak_ptr<uhd::rx_streamer> streamer); + + + // 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, + const std::string &); + + void _set_internal_gpio( + gpio_core_200::sptr gpio, + const std::string &attr, + const boost::uint32_t value); + +private: // members + uhd::device_addr_t _device_addr; + xport_t _xport_path; + e300_fifo_interface::sptr _fifo_iface; + size_t _sid_framer; + radio_perifs_t _radio_perifs[2]; + double _tick_rate; + ad9361_ctrl::sptr _codec_ctrl; + fe_control_settings_t _settings; + global_regs::sptr _global_regs; + e300_sensor_manager::sptr _sensor_manager; + e300_eeprom_manager::sptr _eeprom_manager; + uhd::transport::zero_copy_xport_params _data_xport_params; + uhd::transport::zero_copy_xport_params _ctrl_xport_params; + gpio_t _misc; + gps::ublox::ubx::control::sptr _gps; +}; + +}}} // namespace + +#endif /* INCLUDED_E300_IMPL_HPP */ diff --git a/host/lib/usrp/e300/e300_io_impl.cpp b/host/lib/usrp/e300/e300_io_impl.cpp new file mode 100644 index 000000000..dcb6f2afe --- /dev/null +++ b/host/lib/usrp/e300/e300_io_impl.cpp @@ -0,0 +1,599 @@ +// +// 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 "e300_regs.hpp" +#include "e300_impl.hpp" +#include "e300_fpga_defs.hpp" +#include "validate_subdev_spec.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "async_packet_handler.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <uhd/utils/tasks.hpp> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +namespace uhd { namespace usrp { namespace e300 { + +static const boost::uint32_t HW_SEQ_NUM_MASK = 0xfff; + +/*********************************************************************** + * update streamer rates + **********************************************************************/ +void e300_impl::_check_tick_rate_with_current_streamers(const double rate) +{ + size_t max_tx_chan_count = 0, max_rx_chan_count = 0; + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + { + boost::shared_ptr<sph::recv_packet_streamer> rx_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>( + perif.rx_streamer.lock()); + if (rx_streamer) + max_rx_chan_count = std::max( + max_rx_chan_count, + rx_streamer->get_num_channels()); + } + + { + boost::shared_ptr<sph::send_packet_streamer> tx_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>( + perif.tx_streamer.lock()); + if (tx_streamer) + max_tx_chan_count = std::max( + max_tx_chan_count, + tx_streamer->get_num_channels()); + } + } + _enforce_tick_rate_limits(max_rx_chan_count, rate, "RX"); + _enforce_tick_rate_limits(max_tx_chan_count, rate, "TX"); +} + +void e300_impl::_update_tick_rate(const double rate) +{ + _check_tick_rate_with_current_streamers(rate); + + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(perif.rx_streamer.lock()); + if (my_streamer) + my_streamer->set_tick_rate(rate); + perif.framer->set_tick_rate(_tick_rate); + } + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock()); + if (my_streamer) + my_streamer->set_tick_rate(rate); + perif.deframer->set_tick_rate(_tick_rate); + } +} + +void e300_impl::_update_rx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_radio_perifs[dspno].rx_streamer.lock()); + if (my_streamer) + my_streamer->set_samp_rate(rate); +} + +void e300_impl::_update_tx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_radio_perifs[dspno].tx_streamer.lock()); + if (my_streamer) + my_streamer->set_samp_rate(rate); +} + +/*********************************************************************** + * frontend selection + **********************************************************************/ +void e300_impl::_update_subdev_spec( + const std::string &txrx, + 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() >= 1) + { + UHD_ASSERT_THROW(spec[0].db_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A" or spec[0].sd_name == "B"); + } + if (spec.size() == 2) + { + UHD_ASSERT_THROW(spec[1].db_name == "A"); + UHD_ASSERT_THROW( + (spec[0].sd_name == "A" and spec[1].sd_name == "B") or + (spec[0].sd_name == "B" and spec[1].sd_name == "A") + ); + } + + std::vector<size_t> chan_to_dsp_map(spec.size(), 0); + for (size_t i = 0; i < spec.size(); i++) + chan_to_dsp_map[i] = (spec[i].sd_name == "A") ? 0 : 1; + _tree->access<std::vector<size_t> >("/mboards/0" / (txrx + "_chan_dsp_mapping")).set(chan_to_dsp_map); + + const fs_path mb_path = "/mboards/0"; + + if (txrx == "tx") { + for (size_t i = 0; i < spec.size(); i++) + { + const std::string conn = _tree->access<std::string>( + mb_path / "dboards" / spec[i].db_name / + ("tx_frontends") / spec[i].sd_name / "connection").get(); + _radio_perifs[i].tx_fe->set_mux(conn); + } + + } else { + for (size_t i = 0; i < spec.size(); i++) + { + 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); + } + } + + this->_update_enables(); +} + +/*********************************************************************** + * VITA stuff + **********************************************************************/ +static void e300_if_hdr_unpack_le( + const boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info +){ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_unpack_le(packet_buff, if_packet_info); +} + +static void e300_if_hdr_pack_le( + boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info +){ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_pack_le(packet_buff, if_packet_info); +} + +/*********************************************************************** + * RX flow control handler + **********************************************************************/ +struct e300_rx_fc_cache_t +{ + e300_rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; +}; + +void e300_impl::_handle_overflow( + radio_perifs_t &perif, + boost::weak_ptr<uhd::rx_streamer> streamer) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(streamer.lock()); + + //If the rx_streamer has expired then overflow handling makes no sense. + if (not my_streamer) + return; + + if (my_streamer->get_num_channels() == 1) { + perif.framer->handle_overflow(); + return; + } + + // MIMO overflow recovery time + // find out if we were in continuous mode before stopping + const bool in_continuous_streaming_mode = perif.framer->in_continuous_streaming_mode(); + // stop streaming + my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + // flush transports + my_streamer->flush_all(0.001); + // restart streaming + if (in_continuous_streaming_mode) { + stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = false; + stream_cmd.time_spec = perif.time64->get_time_now() + time_spec_t(0.01); + my_streamer->issue_stream_cmd(stream_cmd); + } +} + + +static void handle_rx_flowctrl( + const boost::uint32_t sid, + zero_copy_if::sptr xport, + boost::shared_ptr<e300_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(1.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; + 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 + e300_if_hdr_pack_le(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htowx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htowx<boost::uint32_t>(seq_sw); + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + + +/*********************************************************************** + * TX flow control handler + **********************************************************************/ +struct e300_tx_fc_cache_t +{ + e300_tx_fc_cache_t(void): + 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; + bounded_buffer<size_t> seq_queue; + boost::shared_ptr<e300_impl::async_md_type> async_queue; + boost::shared_ptr<e300_impl::async_md_type> old_async_queue; +}; + +#define E300_ASYNC_EVENT_CODE_FLOW_CTRL 0 + +typedef boost::function<double(void)> tick_rate_retriever_t; + + +static void handle_tx_async_msgs(boost::shared_ptr<e300_tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + boost::function<double(void)> 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 + try + { + e300_if_hdr_unpack_le(packet_buff, if_packet_info); + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + //catch the flow control packets and react + if (uhd::wtohx(packet_buff[if_packet_info.num_header_words32+0]) == 0) + { + const size_t seq = uhd::wtohx(packet_buff[if_packet_info.num_header_words32+1]); + fc_cache->seq_queue.push_with_haste(seq); + return; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff(uhd::wtohx<boost::uint32_t>, + 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 == E300_ASYNC_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 != E300_ASYNC_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); + } +} + +static managed_send_buffer::sptr get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<e300_tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + const size_t fc_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_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; +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool e300_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +) +{ + return _async_md->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr e300_impl::get_rx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_spawn_mutex); + 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("e300_impl::get_rx_stream only supports otw_format sc16"); + } + args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + + const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/0/rx_chan_dsp_mapping") + .get().at(args.channels[stream_i]); + + radio_perifs_t &perif = _radio_perifs[radio_index]; + + // make a transport, grab a sid + boost::uint32_t data_sid; + both_xports_t data_xports = _make_transport( + radio_index ? E300_XB_DST_R1 : E300_XB_DST_R0, + E300_RADIO_DEST_PREFIX_RX, + _data_xport_params, + data_sid); + + //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 = data_xports.recv->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + + //init some streamer stuff + my_streamer->set_vrt_unpacker(&e300_if_hdr_unpack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_le"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.framer->set_nsamps_per_packet(spp); //seems to be a good place to set this + perif.framer->set_sid((data_sid << 16) | (data_sid >> 16)); + perif.framer->setup(args); + perif.ddc->setup(args); + my_streamer->set_xport_chan_get_buff(stream_i, boost::bind( + &zero_copy_if::get_recv_buff, data_xports.recv, _1 + ), true /*flush*/); + my_streamer->set_overflow_handler(stream_i, + boost::bind(&rx_vita_core_3000::handle_overflow, perif.framer) + ); + + //setup flow control + const size_t fc_window = data_xports.recv->get_num_recv_frames(); + perif.framer->configure_flow_control(fc_window); + boost::shared_ptr<e300_rx_fc_cache_t> fc_cache(new e300_rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl(stream_i, + boost::bind(&handle_rx_flowctrl, data_sid, data_xports.send, fc_cache, _1), + static_cast<size_t>(static_cast<double>(fc_window) * E300_RX_SW_BUFF_FULLNESS), + true/*init*/); + + my_streamer->set_issue_stream_cmd(stream_i, + boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1) + ); + perif.rx_streamer = my_streamer; //store weak pointer + + //sets all tick and samp rates on this streamer + this->_update_tick_rate(this->_get_tick_rate()); + _tree->access<double>(str(boost::format("/mboards/0/rx_dsps/%u/rate/value") % radio_index)).update(); + + } + _update_enables(); + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr e300_impl::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_spawn_mutex); + 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("e300_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_type> async_md(new async_md_type(1000/*messages deep*/)); + + 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 radio_index = _tree->access<std::vector<size_t> >("/mboards/0/tx_chan_dsp_mapping") + .get().at(args.channels[stream_i]); + + + radio_perifs_t &perif = _radio_perifs[radio_index]; + + + // make a transport, grab a sid + boost::uint32_t data_sid; + both_xports_t data_xports = _make_transport( + radio_index ? E300_XB_DST_R1 : E300_XB_DST_R0, + E300_RADIO_DEST_PREFIX_TX, + _data_xport_params, + data_sid); + + //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 = data_xports.send->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()); + + //init some streamer stuff + my_streamer->set_vrt_packer(&e300_if_hdr_pack_le); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_le"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.deframer->clear(); + perif.deframer->setup(args); + perif.duc->setup(args); + + //flow control setup + const size_t fc_window = data_xports.send->get_num_send_frames(); + perif.deframer->configure_flow_control(0/*cycs off*/, fc_window/8/*pkts*/); + boost::shared_ptr<e300_tx_fc_cache_t> fc_cache(new e300_tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = args.channels[stream_i]; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md; + + tick_rate_retriever_t get_tick_rate_fn = boost::bind(&e300_impl::_get_tick_rate, this); + + task::sptr task = task::make(boost::bind(&handle_tx_async_msgs, + fc_cache, data_xports.recv, + get_tick_rate_fn)); + + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&get_tx_buff_with_flowctrl, task, fc_cache, data_xports.send, fc_window, _1) + ); + + my_streamer->set_async_receiver( + boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, data_sid); + my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet + perif.tx_streamer = my_streamer; //store weak pointer + + //sets all tick and samp rates on this streamer + this->_update_tick_rate(this->_get_tick_rate()); + _tree->access<double>(str(boost::format("/mboards/0/tx_dsps/%u/rate/value") % radio_index)).update(); + } + _update_enables(); + return my_streamer; +} +}}} // namespace diff --git a/host/lib/usrp/e300/e300_network.cpp b/host/lib/usrp/e300/e300_network.cpp new file mode 100644 index 000000000..883ff0c4f --- /dev/null +++ b/host/lib/usrp/e300/e300_network.cpp @@ -0,0 +1,642 @@ +// +// 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 "e300_network.hpp" + +#ifdef E300_NATIVE + +#include "e300_impl.hpp" + +#include "ad9361_ctrl.hpp" + +#include "e300_sensor_manager.hpp" +#include "e300_fifo_config.hpp" +#include "e300_spi.hpp" +#include "e300_i2c.hpp" +#include "e300_defaults.hpp" +#include "e300_common.hpp" +#include "e300_remote_codec_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/images.hpp> + +#include <boost/asio.hpp> +#include <boost/thread.hpp> +#include <boost/filesystem.hpp> +#include <boost/make_shared.hpp> + +#include <fstream> + +using namespace uhd; +using namespace uhd::transport; +namespace asio = boost::asio; +namespace fs = boost::filesystem; + +namespace uhd { namespace usrp { namespace e300 { + +static const size_t E300_NETWORK_DEBUG = false; + +static inline bool wait_for_recv_ready(int sock_fd, const size_t timeout_ms) +{ + //setup timeval for timeout + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = timeout_ms*1000; + + //setup rset for timeout + fd_set rset; + FD_ZERO(&rset); + FD_SET(sock_fd, &rset); + + //call select with timeout on receive socket + return ::select(sock_fd+1, &rset, NULL, NULL, &tv) > 0; +} + +static boost::mutex endpoint_mutex; + +/*********************************************************************** + * Receive tunnel - forwards recv interface to send socket + **********************************************************************/ +static void e300_recv_tunnel( + const std::string &name, + uhd::transport::zero_copy_if::sptr recver, + boost::shared_ptr<asio::ip::udp::socket> sender, + asio::ip::udp::endpoint *endpoint, + bool *running +) +{ + asio::ip::udp::endpoint _tx_endpoint; + try + { + while (*running) + { + //step 1 - get the buffer + managed_recv_buffer::sptr buff = recver->get_recv_buff(); + if (not buff) continue; + if (E300_NETWORK_DEBUG) UHD_MSG(status) << name << " got " << buff->size() << std::endl; + + //step 1.5 -- update endpoint + { + boost::mutex::scoped_lock l(endpoint_mutex); + _tx_endpoint = *endpoint; + } + + //step 2 - send to the socket + sender->send_to(asio::buffer(buff->cast<const void *>(), buff->size()), _tx_endpoint); + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "e300_recv_tunnel exit " << name << " " << ex.what() << std::endl; + } + catch(...) + { + UHD_MSG(error) << "e300_recv_tunnel exit " << name << std::endl; + } + UHD_MSG(status) << "e300_recv_tunnel exit " << name << std::endl; + *running = false; +} + +/*********************************************************************** + * Send tunnel - forwards recv socket to send interface + **********************************************************************/ +static void e300_send_tunnel( + const std::string &name, + boost::shared_ptr<asio::ip::udp::socket> recver, + uhd::transport::zero_copy_if::sptr sender, + asio::ip::udp::endpoint *endpoint, + bool *running +) +{ + asio::ip::udp::endpoint _rx_endpoint; + try + { + while (*running) + { + //step 1 - get the buffer + managed_send_buffer::sptr buff = sender->get_send_buff(); + if (not buff) continue; + + //step 2 - recv from socket + while (not wait_for_recv_ready(recver->native(), 100) and *running){} + if (not *running) break; + const size_t num_bytes = recver->receive_from(asio::buffer(buff->cast<void *>(), buff->size()), _rx_endpoint); + if (E300_NETWORK_DEBUG) UHD_MSG(status) << name << " got " << num_bytes << std::endl; + + //step 2.5 -- update endpoint + { + boost::mutex::scoped_lock l(endpoint_mutex); + *endpoint = _rx_endpoint; + } + + //step 3 - commit the buffer + buff->commit(num_bytes); + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "e300_send_tunnel exit " << name << " " << ex.what() << std::endl; + } + catch(...) + { + UHD_MSG(error) << "e300_send_tunnel exit " << name << std::endl; + } + UHD_MSG(status) << "e300_send_tunnel exit " << name << std::endl; + *running = false; +} + +static void e300_codec_ctrl_tunnel( + const std::string &name, + boost::shared_ptr<asio::ip::udp::socket> socket, + ad9361_ctrl::sptr _codec_ctrl, + asio::ip::udp::endpoint *endpoint, + bool *running +) +{ + asio::ip::udp::endpoint _endpoint; + try + { + while (*running) + { + uint8_t in_buff[64] = {}; + uint8_t out_buff[64] = {}; + + const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + + typedef e300_remote_codec_ctrl::transaction_t codec_xact_t; + + if (num_bytes < sizeof(codec_xact_t)) { + std::cout << "Received short packet of " << num_bytes << std::endl; + continue; + } + + codec_xact_t *in = reinterpret_cast<codec_xact_t*>(in_buff); + codec_xact_t *out = reinterpret_cast<codec_xact_t*>(out_buff); + std::memcpy(out, in, sizeof(codec_xact_t)); + + std::string which_str; + switch (uhd::ntohx<boost::uint32_t>(in->which)) { + case codec_xact_t::CHAIN_TX1: + which_str = "TX1"; break; + case codec_xact_t::CHAIN_TX2: + which_str = "TX2"; break; + case codec_xact_t::CHAIN_RX1: + which_str = "RX1"; break; + case codec_xact_t::CHAIN_RX2: + which_str = "RX2"; break; + default: + which_str = ""; break; + } + + switch (uhd::ntohx<boost::uint32_t>(in->action)) { + case codec_xact_t::ACTION_SET_GAIN: + out->gain = _codec_ctrl->set_gain(which_str, in->gain); + break; + case codec_xact_t::ACTION_SET_CLOCK_RATE: + out->rate = _codec_ctrl->set_clock_rate(in->rate); + break; + case codec_xact_t::ACTION_SET_ACTIVE_CHANS: + _codec_ctrl->set_active_chains( + uhd::ntohx<boost::uint32_t>(in->bits) & (1<<0), + uhd::ntohx<boost::uint32_t>(in->bits) & (1<<1), + uhd::ntohx<boost::uint32_t>(in->bits) & (1<<2), + uhd::ntohx<boost::uint32_t>(in->bits) & (1<<3)); + break; + case codec_xact_t::ACTION_TUNE: + out->freq = _codec_ctrl->tune(which_str, in->freq); + break; + case codec_xact_t::ACTION_SET_LOOPBACK: + _codec_ctrl->data_port_loopback( + uhd::ntohx<boost::uint32_t>(in->bits) & 1); + break; + default: + UHD_MSG(status) << "Got unknown request?!" << std::endl; + //Zero out actions to fail this request on client + out->action = uhd::htonx<boost::uint32_t>(0); + } + + socket->send_to(asio::buffer(out_buff, 64), *endpoint); + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "e300_ctrl_tunnel exit " << name << " " << ex.what() << std::endl; + } + catch(...) + { + UHD_MSG(error) << "e300_ctrl_tunnel exit " << name << std::endl; + } + UHD_MSG(status) << "e300_ctrl_tunnel exit " << name << std::endl; + *running = false; +} + +static void e300_global_regs_tunnel( + const std::string &name, + boost::shared_ptr<asio::ip::udp::socket> socket, + global_regs::sptr regs, + asio::ip::udp::endpoint *endpoint, + bool *running +) +{ + UHD_ASSERT_THROW(regs); + asio::ip::udp::endpoint _endpoint; + try + { + while (*running) + { + uint8_t in_buff[16] = {}; + + const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + + if (num_bytes < 16) { + std::cout << "Received short packet: " << num_bytes << std::endl; + continue; + } + + global_regs_transaction_t *in = + reinterpret_cast<global_regs_transaction_t *>(in_buff); + + if(uhd::ntohx<boost::uint32_t>(in->is_poke)) { + regs->poke32(uhd::ntohx<boost::uint32_t>(in->addr), uhd::ntohx<boost::uint32_t>(in->data)); + } + else { + in->data = uhd::htonx<boost::uint32_t>(regs->peek32(uhd::ntohx<boost::uint32_t>(in->addr))); + socket->send_to(asio::buffer(in_buff, 16), *endpoint); + } + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "e300_gregs_tunnel exit " << name << " " << ex.what() << std::endl; + } + catch(...) + { + UHD_MSG(error) << "e300_gregs_tunnel exit " << name << std::endl; + } + UHD_MSG(status) << "e300_gregs_tunnel exit " << name << std::endl; + *running = false; +} + +static void e300_sensor_tunnel( + const std::string &name, + boost::shared_ptr<asio::ip::udp::socket> socket, + e300_sensor_manager::sptr sensor_manager, + asio::ip::udp::endpoint *endpoint, + bool *running +) +{ + asio::ip::udp::endpoint _endpoint; + try + { + while (*running) + { + uint8_t in_buff[128] = {}; + + const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + + if (num_bytes < sizeof(sensor_transaction_t)) { + std::cout << "Received short packet: " << num_bytes << std::endl; + continue; + } + + uhd::usrp::e300::sensor_transaction_t *in = + reinterpret_cast<uhd::usrp::e300::sensor_transaction_t *>(in_buff); + + if (uhd::ntohx(in->which) == ZYNQ_TEMP) { + sensor_value_t temp = sensor_manager->get_mb_temp(); + // TODO: This is ugly ... use proper serialization + in->value = uhd::htonx<boost::uint32_t>( + e300_sensor_manager::pack_float_in_uint32_t(temp.to_real())); + } else if (uhd::ntohx(in->which) == GPS_FOUND) { + in->value = uhd::htonx<boost::uint32_t>( + sensor_manager->get_gps_found() ? 1 : 0); + + } else if (uhd::ntohx(in->which) == GPS_LOCK) { + in->value = uhd::htonx<boost::uint32_t>( + sensor_manager->get_gps_lock().to_bool() ? 1 : 0); + } else if (uhd::ntohx(in->which) == GPS_TIME) { + in->value = uhd::htonx<boost::uint32_t>( + sensor_manager->get_gps_time().to_int()); + } else + UHD_MSG(status) << "Got unknown request?!" << std::endl; + + socket->send_to(asio::buffer(in_buff, sizeof(sensor_transaction_t)), *endpoint); + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "e300_sensor_tunnel exit " << name << " " << ex.what() << std::endl; + } + catch(...) + { + UHD_MSG(error) << "e300_sensor_tunnel exit " << name << std::endl; + } + UHD_MSG(status) << "e300_sensor_tunnel exit " << name << std::endl; + *running = false; +} + +static void e300_i2c_tunnel( + const std::string &name, + boost::shared_ptr<asio::ip::udp::socket> socket, + uhd::usrp::e300::i2c::sptr i2c, + asio::ip::udp::endpoint *endpoint, + bool *running +) +{ + UHD_ASSERT_THROW(i2c); + asio::ip::udp::endpoint _endpoint; + try + { + while (*running) + { + uint8_t in_buff[sizeof(uhd::usrp::e300::i2c_transaction_t)]; + + const size_t num_bytes = socket->receive_from(asio::buffer(in_buff), *endpoint); + + if (num_bytes < sizeof(uhd::usrp::e300::i2c_transaction_t)) { + std::cout << "Received short packet: " << num_bytes << std::endl; + continue; + } + + uhd::usrp::e300::i2c_transaction_t *in = + reinterpret_cast<uhd::usrp::e300::i2c_transaction_t *>(in_buff); + + // byte addressed accesses go through here + if(in->type & i2c::ONEBYTE) { + if(in->type & i2c::WRITE) { + i2c->set_i2c_reg8( + in->addr, + uhd::ntohx<boost::uint16_t>(in->reg), in->data); + } else { + in->data = i2c->get_i2c_reg8(in->addr, uhd::ntohx<boost::uint16_t>(in->reg)); + socket->send_to(asio::buffer(in_buff, sizeof(in_buff)), *endpoint); + } + + // 2 byte addressed accesses go through here + } else if (in->type & i2c::TWOBYTE) { + if(in->type & i2c::WRITE) { + i2c->set_i2c_reg16( + in->addr, + uhd::ntohx<boost::uint16_t>(in->reg), in->data); + } else { + in->data = i2c->get_i2c_reg16(in->addr, uhd::ntohx<boost::uint16_t>(in->reg)); + socket->send_to(asio::buffer(in_buff, sizeof(in_buff)), *endpoint); + } + + } else { + UHD_MSG(error) << "e300_i2c_tunnel could not handle message." << std::endl; + } + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "e300_i2c_tunnel exit " << name << " " << ex.what() << std::endl; + } + catch(...) + { + UHD_MSG(error) << "e300_i2c_tunnel exit " << name << std::endl; + } + UHD_MSG(status) << "e300_i2c_tunnel exit " << name << std::endl; + *running = false; +} + + + + +class network_server_impl : public network_server +{ +public: + network_server_impl(const uhd::device_addr_t &device_addr); + virtual ~network_server_impl(void); + void run(void); + +private: + struct xports_t + { + uhd::transport::zero_copy_if::sptr send_ctrl_xport; + uhd::transport::zero_copy_if::sptr recv_ctrl_xport; + uhd::transport::zero_copy_if::sptr tx_data_xport; + uhd::transport::zero_copy_if::sptr tx_flow_xport; + uhd::transport::zero_copy_if::sptr rx_data_xport; + uhd::transport::zero_copy_if::sptr rx_flow_xport; + }; + +private: + void _run_server( + const std::string &port, + const std::string &what, + const size_t fe); + +private: + boost::shared_ptr<e300_fifo_interface> _fifo_iface; + xports_t _xports[2]; + boost::shared_ptr<ad9361_ctrl> _codec_ctrl; + boost::shared_ptr<global_regs> _global_regs; + boost::shared_ptr<e300_sensor_manager> _sensor_manager; + boost::shared_ptr<e300_eeprom_manager> _eeprom_manager; +}; + +network_server_impl::~network_server_impl(void) +{ +} + +/*********************************************************************** + * The UDP server itself + **********************************************************************/ +void network_server_impl::_run_server( + const std::string &port, + const std::string &what, + const size_t fe) +{ + asio::io_service io_service; + asio::ip::udp::resolver resolver(io_service); + asio::ip::udp::resolver::query query(asio::ip::udp::v4(), "0.0.0.0", port); + asio::ip::udp::endpoint endpoint = *resolver.resolve(query); + + //boost::shared_ptr<asio::ip::udp::acceptor> acceptor(new asio::ip::udp::acceptor(io_service, endpoint)); + while (not boost::this_thread::interruption_requested()) + { + UHD_MSG(status) << "e300 run server on port " << port << " for " << what << std::endl; + try + { + //while (not wait_for_recv_ready(acceptor->native(), 100)) + //{ + // if (boost::this_thread::interruption_requested()) return; + //} + boost::shared_ptr<asio::ip::udp::socket> socket; + socket.reset(new asio::ip::udp::socket(io_service, endpoint)); + //acceptor->accept(*socket); + UHD_MSG(status) << "e300 socket accept on port " << port << " for " << what << std::endl; + //asio::ip::udp::no_delay option(true); + //socket->set_option(option); + boost::thread_group tg; + bool running = true; + xports_t &perif = _xports[fe]; + if (what == "RX") { + tg.create_thread(boost::bind(&e300_recv_tunnel, "RX data tunnel", perif.rx_data_xport, socket, &endpoint, &running)); + tg.create_thread(boost::bind(&e300_send_tunnel, "RX flow tunnel", socket, perif.rx_flow_xport, &endpoint, &running)); + } + if (what == "TX") { + tg.create_thread(boost::bind(&e300_recv_tunnel, "TX flow tunnel", perif.tx_flow_xport, socket, &endpoint, &running)); + tg.create_thread(boost::bind(&e300_send_tunnel, "TX data tunnel", socket, perif.tx_data_xport, &endpoint, &running)); + } + if (what == "CTRL") { + tg.create_thread(boost::bind(&e300_recv_tunnel, "response tunnel", perif.recv_ctrl_xport, socket, &endpoint, &running)); + tg.create_thread(boost::bind(&e300_send_tunnel, "control tunnel", socket, perif.send_ctrl_xport, &endpoint, &running)); + } + if (what == "CODEC") { + tg.create_thread(boost::bind(&e300_codec_ctrl_tunnel, "CODEC tunnel", socket, _codec_ctrl, &endpoint, &running)); + } + if (what == "I2C") { + tg.create_thread(boost::bind(&e300_i2c_tunnel, "I2C tunnel", socket, _eeprom_manager->get_i2c_sptr(), &endpoint, &running)); + } + if (what == "GREGS") { + tg.create_thread(boost::bind(&e300_global_regs_tunnel, "GREGS tunnel", socket, _global_regs, &endpoint, &running)); + } + if (what == "SENSOR") { + tg.create_thread(boost::bind(&e300_sensor_tunnel, "SENSOR tunnel", socket, _sensor_manager, &endpoint, &running)); + } + + tg.join_all(); + socket->close(); + socket.reset(); + } + catch(...){} + } +} + +void network_server_impl::run() +{ + for(;;) + { + boost::thread_group tg; + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_RX_PORT0, "RX",0)); + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_TX_PORT0, "TX",0)); + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CTRL_PORT0, "CTRL",0)); + + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_RX_PORT1, "RX",1)); + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_TX_PORT1, "TX",1)); + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CTRL_PORT1, "CTRL",1)); + + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_SENSOR_PORT, "SENSOR", 0 /*don't care */)); + + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_CODEC_PORT, "CODEC", 0 /*don't care */)); + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_GREGS_PORT, "GREGS", 0 /*don't care */)); + tg.create_thread(boost::bind(&network_server_impl::_run_server, this, E300_SERVER_I2C_PORT, "I2C", 0 /*don't care */)); + tg.join_all(); + } +} +network_server_impl::network_server_impl(const uhd::device_addr_t &device_addr) +{ + _eeprom_manager = boost::make_shared<e300_eeprom_manager>(i2c::make_i2cdev(E300_I2CDEV_DEVICE)); + if (not device_addr.has_key("no_reload_fpga")) { + // Load FPGA image if provided via args + if (device_addr.has_key("fpga")) { + common::load_fpga_image(device_addr["fpga"]); + // Else load the FPGA image based on the product ID + } else { + //extract the FPGA path for the e300 + const boost::uint16_t pid = boost::lexical_cast<boost::uint16_t>( + _eeprom_manager->get_mb_eeprom()["product"]); + std::string fpga_image; + switch(e300_eeprom_manager::get_mb_type(pid)) { + case e300_eeprom_manager::USRP_E310_MB: + fpga_image = find_image_path(E310_FPGA_FILE_NAME); + break; + case e300_eeprom_manager::USRP_E300_MB: + fpga_image = find_image_path(E300_FPGA_FILE_NAME); + break; + case e300_eeprom_manager::UNKNOWN: + default: + UHD_MSG(warning) << "Unknown motherboard type, loading e300 image." + << std::endl; + fpga_image = find_image_path(E300_FPGA_FILE_NAME); + break; + } + common::load_fpga_image(fpga_image); + } + } + + uhd::transport::zero_copy_xport_params ctrl_xport_params; + ctrl_xport_params.recv_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; + ctrl_xport_params.num_recv_frames = e300::DEFAULT_CTRL_NUM_FRAMES; + ctrl_xport_params.send_frame_size = e300::DEFAULT_CTRL_FRAME_SIZE; + ctrl_xport_params.num_send_frames = e300::DEFAULT_CTRL_NUM_FRAMES; + + uhd::transport::zero_copy_xport_params data_xport_params; + data_xport_params.recv_frame_size = device_addr.cast<size_t>("recv_frame_size", e300::DEFAULT_RX_DATA_FRAME_SIZE); + data_xport_params.num_recv_frames = device_addr.cast<size_t>("num_recv_frames", e300::DEFAULT_RX_DATA_NUM_FRAMES); + data_xport_params.send_frame_size = device_addr.cast<size_t>("send_frame_size", e300::DEFAULT_TX_DATA_FRAME_SIZE); + data_xport_params.num_send_frames = device_addr.cast<size_t>("num_send_frames", e300::DEFAULT_TX_DATA_NUM_FRAMES); + // until we figure out why this goes wrong we'll keep this hack around + data_xport_params.recv_frame_size = + std::min(e300::MAX_NET_RX_DATA_FRAME_SIZE, data_xport_params.recv_frame_size); + data_xport_params.send_frame_size = + std::min(e300::MAX_NET_TX_DATA_FRAME_SIZE, data_xport_params.send_frame_size); + + + e300_fifo_config_t fifo_cfg; + try { + fifo_cfg = e300_read_sysfs(); + } catch (uhd::lookup_error &e) { + throw uhd::runtime_error("Failed to get driver parameters from sysfs."); + } + _fifo_iface = e300_fifo_interface::make(fifo_cfg); + _global_regs = global_regs::make(_fifo_iface->get_global_regs_base()); + + // static mapping, boooohhhhhh + _xports[0].send_ctrl_xport = _fifo_iface->make_send_xport(E300_R0_CTRL_STREAM, ctrl_xport_params); + _xports[0].recv_ctrl_xport = _fifo_iface->make_recv_xport(E300_R0_CTRL_STREAM, ctrl_xport_params); + _xports[0].tx_data_xport = _fifo_iface->make_send_xport(E300_R0_TX_DATA_STREAM, data_xport_params); + _xports[0].tx_flow_xport = _fifo_iface->make_recv_xport(E300_R0_TX_DATA_STREAM, ctrl_xport_params); + _xports[0].rx_data_xport = _fifo_iface->make_recv_xport(E300_R0_RX_DATA_STREAM, data_xport_params); + _xports[0].rx_flow_xport = _fifo_iface->make_send_xport(E300_R0_RX_DATA_STREAM, ctrl_xport_params); + + _xports[1].send_ctrl_xport = _fifo_iface->make_send_xport(E300_R1_CTRL_STREAM, ctrl_xport_params); + _xports[1].recv_ctrl_xport = _fifo_iface->make_recv_xport(E300_R1_CTRL_STREAM, ctrl_xport_params); + _xports[1].tx_data_xport = _fifo_iface->make_send_xport(E300_R1_TX_DATA_STREAM, data_xport_params); + _xports[1].tx_flow_xport = _fifo_iface->make_recv_xport(E300_R1_TX_DATA_STREAM, ctrl_xport_params); + _xports[1].rx_data_xport = _fifo_iface->make_recv_xport(E300_R1_RX_DATA_STREAM, data_xport_params); + _xports[1].rx_flow_xport = _fifo_iface->make_send_xport(E300_R1_RX_DATA_STREAM, ctrl_xport_params); + + ad9361_params::sptr client_settings = boost::make_shared<e300_ad9361_client_t>(); + _codec_ctrl = ad9361_ctrl::make_spi(client_settings, spi::make(E300_SPIDEV_DEVICE), 1); + // This is horrible ... why do I have to sleep here? + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + _sensor_manager = e300_sensor_manager::make_local( + gps::ublox::ubx::control::make("/dev/ttyPS1", 9600)); +} + +}}} // namespace + +using namespace uhd::usrp::e300; + +network_server::sptr network_server::make(const uhd::device_addr_t &device_addr) +{ + return sptr(new network_server_impl(device_addr)); +} + +#else + +using namespace uhd::usrp::e300; + +network_server::sptr network_server::make(const uhd::device_addr_t &) +{ + throw uhd::assertion_error("network_server::make() !E300_NATIVE"); +} +#endif diff --git a/host/lib/usrp/e300/e300_network.hpp b/host/lib/usrp/e300/e300_network.hpp new file mode 100644 index 000000000..5a9df0ca7 --- /dev/null +++ b/host/lib/usrp/e300/e300_network.hpp @@ -0,0 +1,43 @@ +// +// 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_E300_NETWORK_HPP +#define INCLUDED_E300_NETWORK_HPP + +#include <string> +#include <boost/noncopyable.hpp> + +#include <uhd/device.hpp> + + +static const std::string E310_FPGA_FILE_NAME = "usrp_e310_fpga.bit"; +static const std::string E300_FPGA_FILE_NAME = "usrp_e300_fpga.bit"; + +namespace uhd { namespace usrp { namespace e300 { + +class UHD_API network_server : boost::noncopyable +{ +public: + typedef boost::shared_ptr<network_server> sptr; + virtual void run(void) = 0; + + static sptr make(const uhd::device_addr_t &device_addr); +}; + + +}}} +#endif // INCLUDED_E300_NETWORK_HPP diff --git a/host/lib/usrp/e300/e300_regs.hpp b/host/lib/usrp/e300/e300_regs.hpp new file mode 100644 index 000000000..f99a19b8e --- /dev/null +++ b/host/lib/usrp/e300/e300_regs.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2012-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_E300_REGS_HPP +#define INCLUDED_E300_REGS_HPP + +#include <boost/cstdint.hpp> + +#define TOREG(x) ((x)*4) + +#define localparam static const int + +localparam SR_TEST = 7; +localparam SR_SPI = 8; +localparam SR_GPIO = 16; +localparam SR_MISC_OUTS = 24; +localparam SR_READBACK = 32; +localparam SR_TX_CTRL = 64; +localparam SR_RX_CTRL = 96; +localparam SR_TIME = 128; +localparam SR_RX_DSP = 144; +localparam SR_TX_DSP = 184; +localparam SR_LEDS = 196; +localparam SR_FP_GPIO = 200; +localparam SR_RX_FRONT = 208; +localparam SR_TX_FRONT = 216; +localparam SR_CODEC_IDLE = 250; + + +localparam RB32_SPI = 4; +localparam RB64_TIME_NOW = 8; +localparam RB64_TIME_PPS = 16; +localparam RB32_TEST = 24; +localparam RB32_FP_GPIO = 32; +localparam RB64_CODEC_READBACK = 40; +localparam RB32_RADIO_NUM = 48; + +localparam ST_RX_ENABLE = 20; +localparam ST_TX_ENABLE = 19; + +localparam LED_TXRX_TX = 18; +localparam LED_TXRX_RX = 17; +localparam LED_RX_RX = 16; +localparam VCRX_V2 = 15; +localparam VCRX_V1 = 14; +localparam VCTXRX_V2 = 13; +localparam VCTXRX_V1 = 12; +localparam TX_ENABLEB = 11; +localparam TX_ENABLEA = 10; +localparam RXC_BANDSEL = 8; +localparam RXB_BANDSEL = 6; +localparam RX_BANDSEL = 3; +localparam TX_BANDSEL = 0; + +#endif /* INCLUDED_E300_REGS_HPP */ diff --git a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp new file mode 100644 index 000000000..bcc8ee4cf --- /dev/null +++ b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp @@ -0,0 +1,148 @@ +// +// 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 "e300_remote_codec_ctrl.hpp" + +#include <boost/cstdint.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <cstring> +#include <iostream> + +namespace uhd { namespace usrp { namespace e300 { + +class e300_remote_codec_ctrl_impl : public e300_remote_codec_ctrl +{ +public: + e300_remote_codec_ctrl_impl(uhd::transport::zero_copy_if::sptr xport) : _xport(xport) + { + } + + virtual ~e300_remote_codec_ctrl_impl(void) + { + } + + double set_gain(const std::string &which, const double value) + { + _clear(); + _args.action = uhd::htonx<boost::uint32_t>(transaction_t::ACTION_SET_GAIN); + if (which == "TX1") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX1); + else if (which == "TX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX2); + else if (which == "RX1") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX1); + else if (which == "RX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX2); + else throw std::runtime_error("e300_remote_codec_ctrl_impl incorrect chain string."); + _args.gain = value; + + _transact(); + return _retval.gain; + } + + double set_clock_rate(const double rate) + { + _clear(); + _args.action = uhd::htonx<boost::uint32_t>( + transaction_t::ACTION_SET_CLOCK_RATE); + _args.which = uhd::htonx<boost::uint32_t>( + transaction_t::CHAIN_NONE); /*Unused*/ + _args.rate = rate; + + _transact(); + return _retval.gain; + } + + void set_active_chains(bool tx1, bool tx2, bool rx1, bool rx2) + { + _clear(); + _args.action = uhd::htonx<boost::uint32_t>( + transaction_t::ACTION_SET_ACTIVE_CHANS); + /*Unused*/ + _args.which = uhd::htonx<boost::uint32_t>( + transaction_t::CHAIN_NONE); + _args.bits = uhd::htonx<boost::uint32_t>( + (tx1 ? (1<<0) : 0) | + (tx2 ? (1<<1) : 0) | + (rx1 ? (1<<2) : 0) | + (rx2 ? (1<<3) : 0)); + + _transact(); + } + + double tune(const std::string &which, const double value) + { + _clear(); + _args.action = uhd::htonx<boost::uint32_t>(transaction_t::ACTION_TUNE); + if (which == "TX1") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX1); + else if (which == "TX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_TX2); + else if (which == "RX1") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX1); + else if (which == "RX2") _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_RX2); + else throw std::runtime_error("e300_remote_codec_ctrl_impl incorrect chain string."); + _args.freq = value; + + _transact(); + return _retval.freq; + } + + void data_port_loopback(const bool on) + { + _clear(); + _args.action = uhd::htonx<boost::uint32_t>(transaction_t::ACTION_SET_LOOPBACK); + _args.which = uhd::htonx<boost::uint32_t>(transaction_t::CHAIN_NONE); /*Unused*/ + _args.bits = uhd::htonx<boost::uint32_t>(on ? 1 : 0); + + _transact(); + } + +private: + void _transact() { + { + uhd::transport::managed_send_buffer::sptr buff = _xport->get_send_buff(10.0); + if (not buff or buff->size() < sizeof(_args)) + throw std::runtime_error("e300_remote_codec_ctrl_impl send timeout"); + std::memcpy(buff->cast<void *>(), &_args, sizeof(_args)); + buff->commit(sizeof(_args)); + } + { + uhd::transport::managed_recv_buffer::sptr buff = _xport->get_recv_buff(10.0); + if (not buff or buff->size() < sizeof(_retval)) + throw std::runtime_error("e300_remote_codec_ctrl_impl recv timeout"); + std::memcpy(&_retval, buff->cast<const void *>(), sizeof(_retval)); + } + + if (_args.action != _retval.action) + throw std::runtime_error("e300_remote_codec_ctrl_impl trancation failed."); + } + + void _clear() { + _args.action = 0; + _args.which = 0; + _args.bits = 0; + _retval.action = 0; + _retval.which = 0; + _retval.bits = 0; + } + + uhd::transport::zero_copy_if::sptr _xport; + transaction_t _args; + transaction_t _retval; +}; + +ad9361_ctrl::sptr e300_remote_codec_ctrl::make(uhd::transport::zero_copy_if::sptr xport) +{ + return sptr(new e300_remote_codec_ctrl_impl(xport)); +} + +}}}; diff --git a/host/lib/usrp/e300/e300_remote_codec_ctrl.hpp b/host/lib/usrp/e300/e300_remote_codec_ctrl.hpp new file mode 100644 index 000000000..015ad8323 --- /dev/null +++ b/host/lib/usrp/e300/e300_remote_codec_ctrl.hpp @@ -0,0 +1,59 @@ +// +// 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_E300_REMOTE_CODEC_CTRL_HPP +#define INCLUDED_E300_REMOTE_CODEC_CTRL_HPP + +#include "ad9361_ctrl.hpp" +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class e300_remote_codec_ctrl : public uhd::usrp::ad9361_ctrl +{ +public: + struct transaction_t { + boost::uint32_t action; + boost::uint32_t which; + union { + double rate; + double gain; + double freq; + boost::uint64_t bits; + }; + + //Actions + static const boost::uint32_t ACTION_SET_GAIN = 10; + static const boost::uint32_t ACTION_SET_CLOCK_RATE = 11; + static const boost::uint32_t ACTION_SET_ACTIVE_CHANS = 12; + static const boost::uint32_t ACTION_TUNE = 13; + static const boost::uint32_t ACTION_SET_LOOPBACK = 14; + + //Values for "which" + static const boost::uint32_t CHAIN_NONE = 0; + static const boost::uint32_t CHAIN_TX1 = 1; + static const boost::uint32_t CHAIN_TX2 = 2; + static const boost::uint32_t CHAIN_RX1 = 3; + static const boost::uint32_t CHAIN_RX2 = 4; + }; + + static sptr make(uhd::transport::zero_copy_if::sptr xport); +}; + +}}}; + +#endif /* INCLUDED_E300_REMOTE_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/e300/e300_sensor_manager.cpp b/host/lib/usrp/e300/e300_sensor_manager.cpp new file mode 100644 index 000000000..5e65b8fd3 --- /dev/null +++ b/host/lib/usrp/e300/e300_sensor_manager.cpp @@ -0,0 +1,289 @@ +// +// 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 "e300_sensor_manager.hpp" + +#include <boost/thread.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> + +#include <cstring> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class e300_sensor_proxy : public e300_sensor_manager +{ +public: + e300_sensor_proxy( + uhd::transport::zero_copy_if::sptr xport) : _xport(xport) + { + } + + std::vector<std::string> get_sensors() + { + return boost::assign::list_of("temp")("gps_locked")("gps_time"); + } + + uhd::sensor_value_t get_sensor(const std::string &key) + { + if (key == "temp") + return get_mb_temp(); + else if (key == "gps_locked") + return get_gps_lock(); + else if (key == "gps_time") + return get_gps_time(); + else + throw uhd::lookup_error( + str(boost::format("Invalid sensor %s requested.") % key)); + } + + uhd::sensor_value_t get_mb_temp(void) + { + boost::mutex::scoped_lock(_mutex); + sensor_transaction_t transaction; + transaction.which = uhd::htonx<boost::uint32_t>(ZYNQ_TEMP); + { + uhd::transport::managed_send_buffer::sptr buff + = _xport->get_send_buff(1.0); + if (not buff or buff->size() < sizeof(transaction)) { + throw uhd::runtime_error("sensor proxy send timeout"); + } + std::memcpy( + buff->cast<void *>(), + &transaction, + sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + { + uhd::transport::managed_recv_buffer::sptr buff + = _xport->get_recv_buff(1.0); + + if (not buff or buff->size() < sizeof(transaction)) + throw uhd::runtime_error("sensor proxy recv timeout"); + + std::memcpy( + &transaction, + buff->cast<const void *>(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == ZYNQ_TEMP); + // TODO: Use proper serialization here ... + return sensor_value_t( + "temp", + e300_sensor_manager::unpack_float_from_uint32_t( + uhd::ntohx(transaction.value)), + "C"); + } + + uhd::sensor_value_t get_gps_time(void) + { + boost::mutex::scoped_lock(_mutex); + sensor_transaction_t transaction; + transaction.which = uhd::htonx<boost::uint32_t>(GPS_TIME); + { + uhd::transport::managed_send_buffer::sptr buff + = _xport->get_send_buff(1.0); + if (not buff or buff->size() < sizeof(transaction)) { + throw uhd::runtime_error("sensor proxy send timeout"); + } + std::memcpy( + buff->cast<void *>(), + &transaction, + sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + { + uhd::transport::managed_recv_buffer::sptr buff + = _xport->get_recv_buff(1.0); + + if (not buff or buff->size() < sizeof(transaction)) + throw uhd::runtime_error("sensor proxy recv timeout"); + + std::memcpy( + &transaction, + buff->cast<const void *>(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == GPS_TIME); + // TODO: Use proper serialization here ... + return sensor_value_t("GPS epoch time", int(uhd::ntohx<boost::uint32_t>(transaction.value)), "seconds"); + } + + bool get_gps_found(void) + { + boost::mutex::scoped_lock(_mutex); + sensor_transaction_t transaction; + transaction.which = uhd::htonx<boost::uint32_t>(GPS_FOUND); + { + uhd::transport::managed_send_buffer::sptr buff + = _xport->get_send_buff(1.0); + if (not buff or buff->size() < sizeof(transaction)) { + throw uhd::runtime_error("sensor proxy send timeout"); + } + std::memcpy( + buff->cast<void *>(), + &transaction, + sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + { + uhd::transport::managed_recv_buffer::sptr buff + = _xport->get_recv_buff(1.0); + + if (not buff or buff->size() < sizeof(transaction)) + throw uhd::runtime_error("sensor proxy recv timeout"); + + std::memcpy( + &transaction, + buff->cast<const void *>(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == GPS_FOUND); + // TODO: Use proper serialization here ... + return static_cast<bool>(uhd::ntohx(transaction.value)); + } + + uhd::sensor_value_t get_gps_lock(void) + { + boost::mutex::scoped_lock(_mutex); + sensor_transaction_t transaction; + transaction.which = uhd::htonx<boost::uint32_t>(GPS_LOCK); + { + uhd::transport::managed_send_buffer::sptr buff + = _xport->get_send_buff(1.0); + if (not buff or buff->size() < sizeof(transaction)) { + throw uhd::runtime_error("sensor proxy send timeout"); + } + std::memcpy( + buff->cast<void *>(), + &transaction, + sizeof(transaction)); + buff->commit(sizeof(transaction)); + } + { + uhd::transport::managed_recv_buffer::sptr buff + = _xport->get_recv_buff(1.0); + + if (not buff or buff->size() < sizeof(transaction)) + throw uhd::runtime_error("sensor proxy recv timeout"); + + std::memcpy( + &transaction, + buff->cast<const void *>(), + sizeof(transaction)); + } + UHD_ASSERT_THROW(uhd::ntohx<boost::uint32_t>(transaction.which) == GPS_LOCK); + // TODO: Use proper serialization here ... + return sensor_value_t("GPS lock status", static_cast<bool>(uhd::ntohx(transaction.value)), "locked", "unlocked"); + } + +private: + uhd::transport::zero_copy_if::sptr _xport; + boost::mutex _mutex; +}; + +}}} // namespace + +using namespace uhd::usrp::e300; + +e300_sensor_manager::sptr e300_sensor_manager::make_proxy( + uhd::transport::zero_copy_if::sptr xport) +{ + return sptr(new e300_sensor_proxy(xport)); +} + +#ifdef E300_NATIVE +#include "e300_fifo_config.hpp" + +namespace uhd { namespace usrp { namespace e300 { + +static const std::string E300_TEMP_SYSFS = "iio:device0"; + +class e300_sensor_local : public e300_sensor_manager +{ +public: + e300_sensor_local(uhd::gps_ctrl::sptr gps_ctrl) : _gps_ctrl(gps_ctrl) + { + } + + std::vector<std::string> get_sensors() + { + return boost::assign::list_of("temp")("gps_locked")("gps_time"); + } + + uhd::sensor_value_t get_sensor(const std::string &key) + { + if (key == "temp") + return get_mb_temp(); + else if (key == "gps_locked") + return get_gps_lock(); + else if (key == "gps_time") + return get_gps_time(); + else + throw uhd::lookup_error( + str(boost::format("Invalid sensor %s requested.") % key)); + } + + uhd::sensor_value_t get_mb_temp(void) + { + double scale = boost::lexical_cast<double>( + e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_scale")); + unsigned long raw = boost::lexical_cast<unsigned long>( + e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_raw")); + unsigned long offset = boost::lexical_cast<unsigned long>( + e300_get_sysfs_attr(E300_TEMP_SYSFS, "in_temp0_offset")); + return sensor_value_t("temp", (raw + offset) * scale / 1000, "C"); + } + + bool get_gps_found(void) + { + return _gps_ctrl->gps_detected(); + } + + uhd::sensor_value_t get_gps_lock(void) + { + return _gps_ctrl->get_sensor("gps_locked"); + } + + uhd::sensor_value_t get_gps_time(void) + { + return _gps_ctrl->get_sensor("gps_time"); + } + +private: + gps_ctrl::sptr _gps_ctrl; +}; +}}} + +using namespace uhd::usrp::e300; +e300_sensor_manager::sptr e300_sensor_manager::make_local( + uhd::gps_ctrl::sptr gps_ctrl) +{ + return sptr(new e300_sensor_local(gps_ctrl)); +} + +#else +using namespace uhd::usrp::e300; +e300_sensor_manager::sptr e300_sensor_manager::make_local( + uhd::gps_ctrl::sptr gps_ctrl) +{ + throw uhd::assertion_error("e300_sensor_manager::make_local() !E300_NATIVE"); +} +#endif diff --git a/host/lib/usrp/e300/e300_sensor_manager.hpp b/host/lib/usrp/e300/e300_sensor_manager.hpp new file mode 100644 index 000000000..503a7bb63 --- /dev/null +++ b/host/lib/usrp/e300/e300_sensor_manager.hpp @@ -0,0 +1,77 @@ +// +// 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 <boost/noncopyable.hpp> +#include <boost/cstdint.hpp> + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +#ifndef INCLUDED_E300_SENSOR_MANAGER_HPP +#define INCLUDED_E300_SENSOR_MANAGER_HPP + +namespace uhd { namespace usrp { namespace e300 { + +struct sensor_transaction_t { + boost::uint32_t which; + union { + boost::uint32_t value; + boost::uint32_t value64; + }; +}; + + + +enum sensor {ZYNQ_TEMP=0, GPS_FOUND=1, GPS_TIME=2, + GPS_LOCK=3}; + +class e300_sensor_manager : boost::noncopyable +{ +public: + typedef boost::shared_ptr<e300_sensor_manager> sptr; + virtual bool get_gps_found(void) = 0; + + virtual uhd::sensor_value_t get_sensor(const std::string &key) = 0; + virtual std::vector<std::string> get_sensors(void) = 0; + + virtual uhd::sensor_value_t get_mb_temp(void) = 0; + virtual uhd::sensor_value_t get_gps_lock(void) = 0; + virtual uhd::sensor_value_t get_gps_time(void) = 0; + + static sptr make_proxy(uhd::transport::zero_copy_if::sptr xport); + static sptr make_local(uhd::gps_ctrl::sptr gps_ctrl); + + // Note: This is a hack + static boost::uint32_t pack_float_in_uint32_t(const float &v) + { + const boost::uint32_t *cast = reinterpret_cast<const uint32_t*>(&v); + return *cast; + } + + static float unpack_float_from_uint32_t(const boost::uint32_t &v) + { + const float *cast = reinterpret_cast<const float*>(&v); + return *cast; + } +}; + + +}}} // namespace + +#endif // INCLUDED_E300_SENSOR_MANAGER_HPP diff --git a/host/lib/usrp/e300/e300_spi.cpp b/host/lib/usrp/e300/e300_spi.cpp new file mode 100644 index 000000000..9a2daf4a7 --- /dev/null +++ b/host/lib/usrp/e300/e300_spi.cpp @@ -0,0 +1,127 @@ +// +// 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 <uhd/config.hpp> +#include <uhd/exception.hpp> +#include "e300_spi.hpp" + +#ifdef E300_NATIVE +#include <boost/thread.hpp> +#include <boost/format.hpp> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/types.h> +#include <linux/spi/spidev.h> + +namespace uhd { namespace usrp { namespace e300 { + +class spidev_impl : public spi +{ +public: + + spidev_impl(const std::string &device) + : _mode(SPI_CPHA), + _speed(2000000), + _bits(8), + _delay(0) + { + int ret; + _fd = open(device.c_str(), O_RDWR); + if (_fd < 0) + throw uhd::runtime_error(str(boost::format("Could not open spidev device %s") % device)); + + ret = ioctl(_fd, SPI_IOC_WR_MODE, &_mode); + if (ret == -1) + throw uhd::runtime_error("Could not set spidev mode"); + + ret = ioctl(_fd, SPI_IOC_RD_MODE, &_mode); + if (ret == -1) + throw uhd::runtime_error("Could not get spidev mode"); + + ret = ioctl(_fd, SPI_IOC_WR_BITS_PER_WORD, &_bits); + if (ret == -1) + throw uhd::runtime_error("Could not set spidev bits per word"); + + ret = ioctl(_fd, SPI_IOC_RD_BITS_PER_WORD, &_bits); + if (ret == -1) + throw uhd::runtime_error("Could not get spidev bits per word"); + + ret = ioctl(_fd, SPI_IOC_WR_MAX_SPEED_HZ, &_speed); + if (ret == -1) + throw uhd::runtime_error("Could not set spidev max speed"); + + ret = ioctl(_fd, SPI_IOC_RD_MAX_SPEED_HZ, &_speed); + if (ret == -1) + throw uhd::runtime_error("Could not get spidev max speed"); + } + + virtual ~spidev_impl() + { + close(_fd); + } + + boost::uint32_t transact_spi(int, const uhd::spi_config_t &, + boost::uint32_t data, size_t num_bits, + bool) + { + int ret(0); + struct spi_ioc_transfer tr; + + uint8_t *tx_data = reinterpret_cast<uint8_t *>(&data); + + + UHD_ASSERT_THROW(num_bits == 24); + uint8_t tx[] = {tx_data[2], tx_data[1], tx_data[0]}; + + uint8_t rx[3]; + tr.tx_buf = (unsigned long) &tx[0]; + tr.rx_buf = (unsigned long) &rx[0]; + tr.len = num_bits >> 3; + tr.bits_per_word = _bits; + tr.speed_hz = _speed; + tr.delay_usecs = _delay; + + ret = ioctl(_fd, SPI_IOC_MESSAGE(1), &tr); + if (ret < 1) + throw uhd::runtime_error("Could not send spidev message"); + + return rx[2]; + } + +private: + int _fd; + boost::uint8_t _mode; + boost::uint32_t _speed; + boost::uint8_t _bits; + boost::uint16_t _delay; +}; + +spi::sptr spi::make(const std::string &device) +{ + return spi::sptr(new spidev_impl(device)); +} +}}}; +#else +namespace uhd { namespace usrp { namespace e300 { + +spi::sptr spi::make(const std::string &) +{ + throw uhd::assertion_error("spi::make() !E300_NATIVE"); +} +}}}; +#endif //E300_NATIVE diff --git a/host/lib/usrp/e300/e300_spi.hpp b/host/lib/usrp/e300/e300_spi.hpp new file mode 100644 index 000000000..67e990aaa --- /dev/null +++ b/host/lib/usrp/e300/e300_spi.hpp @@ -0,0 +1,34 @@ +// +// 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_E300_SPI_HPP +#define INCLUDED_E300_SPI_HPP + +#include <uhd/types/serial.hpp> + +namespace uhd { namespace usrp { namespace e300 { + +class spi : public virtual uhd::spi_iface +{ +public: + typedef boost::shared_ptr<spi> sptr; + static sptr make(const std::string &device); +}; + +}}}; + +#endif /* INCLUDED_E300_SPI_HPP */ diff --git a/host/lib/usrp/e300/e300_sysfs_hooks.cpp b/host/lib/usrp/e300/e300_sysfs_hooks.cpp new file mode 100644 index 000000000..fdeaf0858 --- /dev/null +++ b/host/lib/usrp/e300/e300_sysfs_hooks.cpp @@ -0,0 +1,121 @@ +// +// 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/>. +// + +#ifdef E300_NATIVE + +#include <cstdio> +#include <cstdlib> +#include <string> +#include <fcntl.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include <libudev.h> + +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> + +static const std::string E300_AXI_FPGA_SYSFS = "40000000.axi-fpga"; +static const std::string E300_XDEV_SYSFS = "f8007000.ps7-dev-cfg"; + +std::string e300_get_sysfs_attr(const std::string &node, const std::string &attr) +{ + udev *udev; + udev_enumerate *enumerate; + udev_list_entry *devices, *dev_list_entry; + udev_device *dev; + std::string retstring; + + udev = udev_new(); + + if (!udev) { + throw uhd::lookup_error("Failed to get udev handle."); + } + + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_sysname(enumerate, node.c_str()); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + + udev_list_entry_foreach(dev_list_entry, devices) + { + const char *path; + + path = udev_list_entry_get_name(dev_list_entry); + dev = udev_device_new_from_syspath(udev, path); + + retstring = udev_device_get_sysattr_value(dev, attr.c_str()); + if (retstring.size()) + break; + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return retstring; +} + +static bool e300_fpga_loaded_successfully(void) +{ + return boost::lexical_cast<bool>(e300_get_sysfs_attr(E300_XDEV_SYSFS, "prog_done")); +} + +#include "e300_fifo_config.hpp" +#include <uhd/exception.hpp> + +e300_fifo_config_t e300_read_sysfs(void) +{ + + if (not e300_fpga_loaded_successfully()) + { + throw uhd::runtime_error("E300 FPGA load failed!"); + } + + e300_fifo_config_t config; + + config.buff_length = boost::lexical_cast<unsigned long>( + e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "buffer_length")); + config.ctrl_length = boost::lexical_cast<unsigned long>( + e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "control_length")); + config.phys_addr = boost::lexical_cast<unsigned long>( + e300_get_sysfs_attr(E300_AXI_FPGA_SYSFS, "phys_addr")); + + return config; +} + +#else //E300_NATIVE + +#include "e300_fifo_config.hpp" +#include <uhd/exception.hpp> + +e300_fifo_config_t e300_read_sysfs(void) +{ + throw uhd::assertion_error("e300_read_sysfs() !E300_NATIVE"); +} + +std::string e300_get_sysfs_attr(const std::string &, const std::string &) +{ + throw uhd::assertion_error("e300_sysfs_attr() !E300_NATIVE"); +} + +#endif //E300_NATIVE diff --git a/host/lib/usrp/e300/e300_ublox_control.hpp b/host/lib/usrp/e300/e300_ublox_control.hpp new file mode 100644 index 000000000..8705d6c52 --- /dev/null +++ b/host/lib/usrp/e300/e300_ublox_control.hpp @@ -0,0 +1,50 @@ +#ifndef INCLUDED_UHD_USRP_UBLOX_CONTROL_HPP +#define INCLUDED_UHD_USRP_UBLOX_CONTROL_HPP + +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/asio.hpp> +#include <uhd/config.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/types/sensors.hpp> + +#include "e300_async_serial.hpp" + +namespace uhd { namespace usrp { namespace gps { + +namespace ublox { namespace ubx { + +class control : public virtual uhd::gps_ctrl +{ +public: + typedef boost::shared_ptr<control> sptr; + + static sptr make(const std::string &node, const size_t baud_rate); + + virtual void configure_message_rate( + const boost::uint16_t msg, + const boost::uint8_t rate) = 0; + + virtual void configure_antenna( + const boost::uint16_t flags, + const boost::uint16_t pins) = 0; + + virtual void configure_pps( + const boost::uint32_t interval, + const boost::uint32_t length, + const boost::int8_t status, + const boost::uint8_t time_ref, + const boost::uint8_t flags, + const boost::int16_t antenna_delay, + const boost::int16_t rf_group_delay, + const boost::int32_t user_delay) = 0; + + virtual void configure_rates( + boost::uint16_t meas_rate, + boost::uint16_t nav_rate, + boost::uint16_t time_ref) = 0; +}; +}} // namespace ublox::ubx + +}}} // namespace +#endif // INCLUDED_UHD_USRP_UBLOX_CONTROL_HPP diff --git a/host/lib/usrp/e300/e300_ublox_control_impl.cpp b/host/lib/usrp/e300/e300_ublox_control_impl.cpp new file mode 100644 index 000000000..a0ec10271 --- /dev/null +++ b/host/lib/usrp/e300/e300_ublox_control_impl.cpp @@ -0,0 +1,505 @@ +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/assign/list_of.hpp> +#include "boost/date_time/posix_time/posix_time.hpp" + +#include <iostream> + + +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> + +#include "e300_ublox_control.hpp" + +#ifdef E300_NATIVE +#include "e300_ublox_control_impl.hpp" + + +namespace uhd { namespace usrp { namespace gps { + +namespace ublox { namespace ubx { + +control_impl::control_impl(const std::string &node, const size_t baud_rate) +{ + _decode_init(); + _serial = boost::make_shared<async_serial>(node, baud_rate); + _serial->set_read_callback(boost::bind(&control_impl::_rx_callback, this, _1, _2)); + + _detect(); + + configure_message_rate(MSG_GLL, 0); + configure_message_rate(MSG_GSV, 0); + configure_message_rate(MSG_GGA, 0); + configure_message_rate(MSG_GSA, 0); + configure_message_rate(MSG_RMC, 0); + configure_message_rate(MSG_VTG, 0); + configure_message_rate(MSG_NAV_TIMEUTC, 1); + configure_message_rate(MSG_NAV_SOL, 1); + + configure_antenna(0x001b, 0x8251); + + configure_pps(0xf4240, 0x3d090, 1, 0 /* utc */, 1, 0, 0, 0); + + _sensors = boost::assign::list_of("gps_locked")("gps_time"); +} + +bool control_impl::gps_detected(void) +{ + return _detected; +} + +void control_impl::_detect(void) +{ + _send_message(MSG_MON_VER, NULL, 0); +} + +std::vector<std::string> control_impl::get_sensors(void) +{ + return _sensors; +} + +uhd::sensor_value_t control_impl::get_sensor(std::string key) +{ + if (key == "gps_time") { + return sensor_value_t("GPS epoch time", int(_get_epoch_time()), "seconds"); + } else if (key == "gps_locked") { + bool lock; + _locked.wait_and_see(lock); + return sensor_value_t("GPS lock status", lock, "locked", "unlocked"); + } else + throw uhd::key_error(str(boost::format("sensor %s unknown.") % key)); +} + +std::time_t control_impl::_get_epoch_time(void) +{ + boost::posix_time::ptime ptime; + _ptime.wait_and_see(ptime); + return (ptime - boost::posix_time::from_time_t(0)).total_seconds(); +} + +control_impl::~control_impl(void) +{ + // turn it all off again + configure_antenna(0x001a, 0x8251); + configure_pps(0xf4240, 0x3d090, 1, 1, 0, 0, 0, 0); +} + +void control_impl::_decode_init(void) +{ + _decode_state = DECODE_SYNC1; + _rx_ck_a = 0; + _rx_ck_b = 0; + _rx_payload_length = 0; + _rx_payload_index = 0; +} + +void control_impl::_add_byte_to_checksum(const boost::uint8_t b) +{ + _rx_ck_a = _rx_ck_a + b; + _rx_ck_b = _rx_ck_b + _rx_ck_a; +} + +void control_impl::_calc_checksum( + const boost::uint8_t *buffer, + const boost::uint16_t length, + checksum_t &checksum) +{ + for (size_t i = 0; i < length; i++) + { + checksum.ck_a = checksum.ck_a + buffer[i]; + checksum.ck_b = checksum.ck_b + checksum.ck_a; + } +} + +void control_impl::configure_rates( + boost::uint16_t meas_rate, + boost::uint16_t nav_rate, + boost::uint16_t time_ref) +{ + payload_tx_cfg_rate_t cfg_rate; + cfg_rate.meas_rate = uhd::htowx<boost::uint16_t>(meas_rate); + cfg_rate.nav_rate = uhd::htowx<boost::uint16_t>(nav_rate); + cfg_rate.time_ref = uhd::htowx<boost::uint16_t>(time_ref); + + _send_message( + MSG_CFG_RATE, + reinterpret_cast<const uint8_t*>(&cfg_rate), + sizeof(cfg_rate)); + + _wait_for_ack(MSG_CFG_RATE, 1.0); +} + +void control_impl::configure_message_rate( + const boost::uint16_t msg, + const uint8_t rate) +{ + payload_tx_cfg_msg_t cfg_msg; + cfg_msg.msg = uhd::htowx<boost::uint16_t>(msg); + cfg_msg.rate[0] = 0;//rate; + cfg_msg.rate[1] = rate; + cfg_msg.rate[2] = 0;//rate; + cfg_msg.rate[3] = 0;//rate; + cfg_msg.rate[4] = 0;//rate; + cfg_msg.rate[5] = 0;//rate; + _send_message( + MSG_CFG_MSG, + reinterpret_cast<const uint8_t*>(&cfg_msg), + sizeof(cfg_msg)); + + _wait_for_ack(MSG_CFG_MSG, 1.0); +} + +void control_impl::configure_antenna( + const boost::uint16_t flags, + const boost::uint16_t pins) +{ + payload_tx_cfg_ant_t cfg_ant; + cfg_ant.pins = uhd::htowx<boost::uint16_t>(pins); + cfg_ant.flags = uhd::htowx<boost::uint16_t>(flags); + _send_message( + MSG_CFG_ANT, + reinterpret_cast<const uint8_t*>(&cfg_ant), + sizeof(cfg_ant)); + if (_wait_for_ack(MSG_CFG_ANT, 1.0) < 0) { + throw uhd::runtime_error("Didn't get an ACK for antenna configuration."); + } + +} + +void control_impl::configure_pps( + const boost::uint32_t interval, + const boost::uint32_t length, + const boost::int8_t status, + const boost::uint8_t time_ref, + const boost::uint8_t flags, + const boost::int16_t antenna_delay, + const boost::int16_t rf_group_delay, + const boost::int32_t user_delay) +{ + payload_tx_cfg_tp_t cfg_tp; + cfg_tp.interval = uhd::htowx<boost::uint32_t>(interval); + cfg_tp.length = uhd::htowx<boost::uint32_t>(length); + cfg_tp.status = status; + cfg_tp.time_ref = time_ref; + cfg_tp.flags = flags; + cfg_tp.antenna_delay = uhd::htowx<boost::int16_t>(antenna_delay); + cfg_tp.rf_group_delay = uhd::htowx<boost::int16_t>(rf_group_delay); + cfg_tp.user_delay = uhd::htowx<boost::int32_t>(user_delay); + _send_message( + MSG_CFG_TP, + reinterpret_cast<const uint8_t*>(&cfg_tp), + sizeof(cfg_tp)); + if (_wait_for_ack(MSG_CFG_TP, 1.0) < 0) { + throw uhd::runtime_error("Didn't get an ACK for PPS configuration."); + } +} + + +void control_impl::_rx_callback(const char *data, unsigned int len) +{ + //std::cout << "IN RX CALLBACK" << std::flush << std::endl; + std::vector<char> v(data, data+len); + BOOST_FOREACH(const char &c, v) + { + _parse_char(c); + } +} + +void control_impl::_parse_char(const boost::uint8_t b) +{ + int ret = 0; + + switch (_decode_state) { + + // we're expecting the first sync byte + case DECODE_SYNC1: + if (b == SYNC1) { // sync1 found goto next step + _decode_state = DECODE_SYNC2; + } // else stay around + break; + + // we're expecting the second sync byte + case DECODE_SYNC2: + if (b == SYNC2) { // sync2 found goto next step + _decode_state = DECODE_CLASS; + } else { + // failed, reset + _decode_init(); + } + break; + + // we're expecting the class byte + case DECODE_CLASS: + _add_byte_to_checksum(b); + _rx_msg = b; + _decode_state = DECODE_ID; + break; + + // we're expecting the id byte + case DECODE_ID: + _add_byte_to_checksum(b); + _rx_msg |= (b << 8); + _decode_state = DECODE_LENGTH1; + break; + + // we're expecting the first length byte + case DECODE_LENGTH1: + _add_byte_to_checksum(b); + _rx_payload_length = b; + _decode_state = DECODE_LENGTH2; + break; + + // we're expecting the second length byte + case DECODE_LENGTH2: + _add_byte_to_checksum(b); + _rx_payload_length |= (b << 8); + if(_payload_rx_init()) { + _decode_init(); // we failed, give up for this one + } else { + _decode_state = _rx_payload_length ? + DECODE_PAYLOAD : DECODE_CHKSUM1; + } + break; + + // we're expecting payload + case DECODE_PAYLOAD: + _add_byte_to_checksum(b); + switch(_rx_msg) { + default: + ret = _payload_rx_add(b); + break; + }; + if (ret < 0) { + // we couldn't deal with the payload, discard the whole thing + _decode_init(); + } else if (ret > 0) { + // payload was complete, let's check the checksum; + _decode_state = DECODE_CHKSUM1; + } else { + // more payload expected, don't move + } + ret = 0; + break; + + case DECODE_CHKSUM1: + if (_rx_ck_a != b) { + // checksum didn't match, barf + std::cout << boost::format("Failed checksum byte1 %lx != %lx") + % int(_rx_ck_a) % int(b) << std::endl; + _decode_init(); + } else { + _decode_state = DECODE_CHKSUM2; + } + break; + + case DECODE_CHKSUM2: + if (_rx_ck_b != b) { + // checksum didn't match, barf + std::cout << boost::format("Failed checksum byte2 %lx != %lx") + % int(_rx_ck_b) % int(b) << std::endl; + + } else { + ret = _payload_rx_done(); // payload done + } + _decode_init(); + break; + + default: + break; + }; +} + +int control_impl::_payload_rx_init(void) +{ + int ret = 0; + + _rx_state = RXMSG_HANDLE; // by default handle + switch(_rx_msg) { + + case MSG_NAV_SOL: + if (not (_rx_payload_length == sizeof(payload_rx_nav_sol_t))) + _rx_state = RXMSG_ERROR_LENGTH; + break; + + case MSG_NAV_TIMEUTC: + if (not (_rx_payload_length == sizeof(payload_rx_nav_timeutc_t))) + _rx_state = RXMSG_ERROR_LENGTH; + break; + + case MSG_MON_VER: + break; // always take this one + + case MSG_ACK_ACK: + if (not (_rx_payload_length == sizeof(payload_rx_ack_ack_t))) + _rx_state = RXMSG_ERROR_LENGTH; + break; + + case MSG_ACK_NAK: + if (not (_rx_payload_length == sizeof(payload_rx_ack_nak_t))) + _rx_state = RXMSG_ERROR_LENGTH; + break; + + default: + _rx_state = RXMSG_DISABLE; + break; + }; + + switch (_rx_state) { + case RXMSG_HANDLE: // handle message + case RXMSG_IGNORE: // ignore message but don't report error + ret = 0; + break; + case RXMSG_DISABLE: // ignore message but don't report error + case RXMSG_ERROR_LENGTH: // the length doesn't match + ret = -1; + break; + default: // invalid, error + ret = -1; + break; + }; + + return ret; +} + +int control_impl::_payload_rx_add(const boost::uint8_t b) +{ + int ret = 0; + _buf.raw[_rx_payload_index] = b; + if (++_rx_payload_index >= _rx_payload_length) + ret = 1; + return ret; +} + +int control_impl::_payload_rx_done(void) +{ + int ret = 0; + if (_rx_state != RXMSG_HANDLE) { + return 0; + } + + switch (_rx_msg) { + case MSG_MON_VER: + _detected = true; + break; + + case MSG_MON_HW: + std::cout << "MON-HW" << std::endl; + break; + + case MSG_ACK_ACK: + if ((_ack_state == ACK_WAITING) and (_buf.payload_rx_ack_ack.msg == _ack_waiting_msg)) + _ack_state = ACK_GOT_ACK; + break; + + case MSG_ACK_NAK: + if ((_ack_state == ACK_WAITING) and (_buf.payload_rx_ack_nak.msg == _ack_waiting_msg)) + _ack_state = ACK_GOT_NAK; + + break; + + case MSG_CFG_ANT: + break; + + case MSG_NAV_TIMEUTC: + _ptime.update(boost::posix_time::ptime( + boost::gregorian::date( + boost::gregorian::greg_year(uhd::wtohx<boost::uint16_t>( + _buf.payload_rx_nav_timeutc.year)), + boost::gregorian::greg_month(_buf.payload_rx_nav_timeutc.month), + boost::gregorian::greg_day(_buf.payload_rx_nav_timeutc.day)), + (boost::posix_time::hours(_buf.payload_rx_nav_timeutc.hour) + + boost::posix_time::minutes(_buf.payload_rx_nav_timeutc.min) + + boost::posix_time::seconds(_buf.payload_rx_nav_timeutc.sec)))); + break; + + case MSG_NAV_SOL: + _locked.update(_buf.payload_rx_nav_sol.gps_fix > 0); + break; + + default: + std::cout << boost::format("Got unknown message %lx , with good checksum [") % int(_rx_msg); + for(size_t i = 0; i < _rx_payload_length; i++) + std::cout << boost::format("%lx, ") % int(_buf.raw[i]); + std::cout << "]"<< std::endl; + break; + }; + return ret; +} + +void control_impl::_send_message( + const boost::uint16_t msg, + const boost::uint8_t *payload, + const boost::uint16_t len) +{ + header_t header = {SYNC1, SYNC2, msg, len}; + checksum_t checksum = {0, 0}; + + // calculate checksums, first header without sync + // then payload + _calc_checksum( + reinterpret_cast<boost::uint8_t*>(&header) + 2, + sizeof(header) - 2, checksum); + if (payload) + _calc_checksum(payload, len, checksum); + + _serial->write( + reinterpret_cast<const char*>(&header), + sizeof(header)); + + if (payload) + _serial->write((const char *) payload, len); + + _serial->write( + reinterpret_cast<const char*>(&checksum), + sizeof(checksum)); +} + +int control_impl::_wait_for_ack( + const boost::uint16_t msg, + const double timeout) +{ + int ret = -1; + + _ack_state = ACK_WAITING; + _ack_waiting_msg = msg; + + boost::system_time timeout_time = + boost::get_system_time() + + boost::posix_time::milliseconds(timeout * 1000.0); + + do { + if(_ack_state == ACK_GOT_ACK) + return 0; + else if (_ack_state == ACK_GOT_NAK) { + return -1; + } + boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + } while (boost::get_system_time() < timeout_time); + + // we get here ... it's a timeout + _ack_state = ACK_IDLE; + return ret; +} + + +}} // namespace ublox::ubx +}}} // namespace + +using namespace uhd::usrp::gps::ublox::ubx; + +control::sptr control::make(const std::string &node, const size_t baud_rate) +{ + return control::sptr(new control_impl(node, baud_rate)); +} +#else +using namespace uhd::usrp::gps::ublox::ubx; + +control::sptr control::make(const std::string &node, const size_t baud_rate) +{ + throw uhd::assertion_error("control::sptr::make: !E300_NATIVE"); +} +#endif // E300_NATIVE diff --git a/host/lib/usrp/e300/e300_ublox_control_impl.hpp b/host/lib/usrp/e300/e300_ublox_control_impl.hpp new file mode 100644 index 000000000..a1dcbfe6c --- /dev/null +++ b/host/lib/usrp/e300/e300_ublox_control_impl.hpp @@ -0,0 +1,457 @@ +#ifndef INCLUDED_UHD_USRP_UBLOX_CONTROL_IMPL_HPP +#define INCLUDED_UHD_USRP_UBLOX_CONTROL_IMPL_HPP + +#include <boost/cstdint.hpp> +#include <boost/noncopyable.hpp> +#include <boost/asio.hpp> +#include <uhd/config.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/types/sensors.hpp> + +#include "e300_async_serial.hpp" + +namespace uhd { namespace usrp { namespace gps { + +namespace ublox { namespace ubx { +// ublox binary sync words +static const boost::uint8_t SYNC1 = 0xB5; +static const boost::uint8_t SYNC2 = 0x62; + +// message classes +static const boost::uint8_t CLASS_NAV = 0x01; +static const boost::uint8_t CLASS_ACK = 0x05; +static const boost::uint8_t CLASS_CFG = 0x06; +static const boost::uint8_t CLASS_MON = 0x0a; +static const boost::uint8_t CLASS_NMEA = 0xf0; + +// Message IDs +static const boost::uint8_t ID_NAV_POSLLH = 0x02; +static const boost::uint8_t ID_NAV_SOL = 0x06; +static const boost::uint8_t ID_NAV_PVT = 0x07; +static const boost::uint8_t ID_NAV_VELNED = 0x12; +static const boost::uint8_t ID_NAV_TIMEUTC = 0x21; +static const boost::uint8_t ID_NAV_SVINFO = 0x30; +static const boost::uint8_t ID_ACK_NAK = 0x00; +static const boost::uint8_t ID_ACK_ACK = 0x01; +static const boost::uint8_t ID_CFG_PRT = 0x00; +static const boost::uint8_t ID_CFG_ANT = 0x13; +static const boost::uint8_t ID_CFG_TP = 0x07; +static const boost::uint8_t ID_CFG_MSG = 0x01; +static const boost::uint8_t ID_CFG_RATE = 0x08; +static const boost::uint8_t ID_CFG_NAV5 = 0x24; +static const boost::uint8_t ID_MON_VER = 0x04; +static const boost::uint8_t ID_MON_HW = 0x09; +static const boost::uint8_t ID_GGA = 0x00; +static const boost::uint8_t ID_GLL = 0x01; +static const boost::uint8_t ID_GSA = 0x02; +static const boost::uint8_t ID_GSV = 0x03; +static const boost::uint8_t ID_RMC = 0x04; +static const boost::uint8_t ID_VTG = 0x05; +static const boost::uint8_t ID_GST = 0x07; + +// Message Classes & IDs // +static const boost::uint16_t MSG_NAV_POSLLH + = CLASS_NAV | (ID_NAV_POSLLH << 8); +static const boost::uint16_t MSG_NAV_SOL + = CLASS_NAV | (ID_NAV_SOL << 8); +static const boost::uint16_t MSG_NAV_PVT + = CLASS_NAV | (ID_NAV_PVT << 8); +static const boost::uint16_t MSG_NAV_VELNED + = CLASS_NAV | (ID_NAV_VELNED << 8); +static const boost::uint16_t MSG_NAV_TIMEUTC + = CLASS_NAV | (ID_NAV_TIMEUTC << 8); +static const boost::uint16_t MSG_NAV_SVINFO + = CLASS_NAV | (ID_NAV_SVINFO << 8); +static const boost::uint16_t MSG_ACK_NAK + = CLASS_ACK | (ID_ACK_NAK << 8); +static const boost::uint16_t MSG_ACK_ACK + = CLASS_ACK | (ID_ACK_ACK << 8); +static const boost::uint16_t MSG_CFG_PRT + = CLASS_CFG | (ID_CFG_PRT << 8); +static const boost::uint16_t MSG_CFG_ANT + = CLASS_CFG | (ID_CFG_ANT << 8); +static const boost::uint16_t MSG_CFG_TP + = CLASS_CFG | (ID_CFG_TP << 8); +static const boost::uint16_t MSG_CFG_MSG + = CLASS_CFG | (ID_CFG_MSG << 8); +static const boost::uint16_t MSG_CFG_RATE + = CLASS_CFG | (ID_CFG_RATE << 8); +static const boost::uint16_t MSG_CFG_NAV5 + = CLASS_CFG | (ID_CFG_NAV5 << 8); +static const boost::uint16_t MSG_MON_HW + = CLASS_MON | (ID_MON_HW << 8); +static const boost::uint16_t MSG_MON_VER + = CLASS_MON | (ID_MON_VER << 8); + +// NMEA ones +static const boost::uint16_t MSG_GGA + = CLASS_NMEA | (ID_GGA << 8); +static const boost::uint16_t MSG_GLL + = CLASS_NMEA | (ID_GLL << 8); +static const boost::uint16_t MSG_GSA + = CLASS_NMEA | (ID_GSA << 8); +static const boost::uint16_t MSG_GSV + = CLASS_NMEA | (ID_GSV << 8); +static const boost::uint16_t MSG_RMC + = CLASS_NMEA | (ID_RMC << 8); +static const boost::uint16_t MSG_VTG + = CLASS_NMEA | (ID_VTG << 8); + +// header +struct header_t +{ + boost::uint8_t sync1; + boost::uint8_t sync2; + boost::uint16_t msg; + boost::uint16_t length; +}; + +// checksum +struct checksum_t +{ + boost::uint8_t ck_a; + boost::uint8_t ck_b; +}; + +// rx rx mon-hw (ubx6) +struct payload_rx_mon_hw_t +{ + boost::uint32_t pin_sel; + boost::uint32_t pin_bank; + boost::uint32_t pin_dir; + boost::uint32_t pin_val; + boost::uint16_t noise_per_ms; + boost::uint16_t agc_cnt; + boost::uint8_t a_status; + boost::uint8_t a_power; + boost::uint8_t flags; + boost::uint8_t reserved1; + boost::uint32_t used_mask; + boost::uint8_t vp[25]; + boost::uint8_t jam_ind; + boost::uint16_t reserved3; + boost::uint32_t pin_irq; + boost::uint32_t pullh; + boost::uint32_t pulll; +}; + +// rx mon-ver +struct payload_rx_mon_ver_part1_t +{ + char sw_version[30]; + char hw_version[10]; +}; + +struct payload_rx_mon_ver_part2_t +{ + boost::uint8_t extension[30]; +}; + +// rx ack-ack +typedef union { + boost::uint16_t msg; + struct { + boost::uint8_t cls_id; + boost::uint8_t msg_id; + }; +} payload_rx_ack_ack_t; + +// rx ack-nak +typedef union { + boost::uint16_t msg; + struct { + boost::uint8_t cls_id; + boost::uint8_t msg_id; + }; +} payload_rx_ack_nak_t; + +// tx cfg-prt (uart) +struct payload_tx_cfg_prt_t +{ + boost::uint8_t port_id; + boost::uint8_t reserved0; + boost::uint16_t tx_ready; + boost::uint32_t mode; + boost::uint32_t baud_rate; + boost::uint16_t in_proto_mask; + boost::uint16_t out_proto_mask; + boost::uint16_t flags; + boost::uint16_t reserved5; +}; + +// tx cfg-rate +struct payload_tx_cfg_rate_t +{ + boost::uint16_t meas_rate; + boost::uint16_t nav_rate; + boost::uint16_t time_ref; +}; + +// tx cfg-msg +struct payload_tx_cfg_msg_t +{ + boost::uint16_t msg; + boost::uint8_t rate[6]; +}; + + +// tx cfg-ant +struct payload_tx_cfg_ant_t +{ + boost::uint16_t flags; + boost::uint16_t pins; +}; + +// tx cfg-tp +struct payload_tx_cfg_tp_t +{ + boost::uint32_t interval; + boost::uint32_t length; + boost::int8_t status; + boost::uint8_t time_ref; + boost::uint8_t flags; + boost::uint8_t reserved1; + boost::int16_t antenna_delay; + boost::int16_t rf_group_delay; + boost::int32_t user_delay; +}; + +struct payload_rx_nav_sol_t +{ + boost::uint32_t i_tow; + boost::int32_t f_tow; + boost::int16_t week; + boost::uint8_t gps_fix; + boost::uint8_t flags; + boost::int32_t ecef_x; + boost::int32_t ecef_y; + boost::int32_t ecef_z; + boost::uint32_t p_acc; + boost::int32_t ecef_vx; + boost::int32_t ecef_vy; + boost::int32_t ecef_vz; + boost::uint32_t s_acc; + boost::uint16_t p_dop; + boost::uint8_t reserved1; + boost::uint8_t num_sv; + boost::uint32_t reserved2; +}; + +struct payload_rx_nav_timeutc_t +{ + boost::uint32_t i_tow; + boost::uint32_t t_acc; + boost::int32_t nano; + boost::uint16_t year; + boost::uint8_t month; + boost::uint8_t day; + boost::uint8_t hour; + boost::uint8_t min; + boost::uint8_t sec; + boost::uint8_t valid; +}; + +typedef union { + payload_rx_mon_hw_t payload_rx_mon_hw; + + payload_rx_mon_ver_part1_t payload_rx_mon_ver_part1; + payload_rx_mon_ver_part2_t payload_rx_mon_ver_part2; + + payload_rx_ack_ack_t payload_rx_ack_ack; + payload_rx_ack_nak_t payload_rx_ack_nak; + + payload_tx_cfg_prt_t payload_tx_cfg_prt; + payload_tx_cfg_ant_t payload_tx_cfg_ant; + payload_tx_cfg_rate_t payload_tx_cfg_rate; + + payload_tx_cfg_msg_t payload_tx_cfg_msg; + + payload_rx_nav_timeutc_t payload_rx_nav_timeutc; + payload_rx_nav_sol_t payload_rx_nav_sol; + boost::uint8_t raw[]; +} buf_t; + + +template <typename T> +class sensor_entry +{ +public: + sensor_entry() : _seen(false) + { + } + + void update(const T &val) + { + boost::mutex::scoped_lock l(_mutex); + _value = val; + _seen = false; + l.unlock(); + _cond.notify_one(); + } + + bool seen() const + { + boost::mutex::scoped_lock l(_mutex); + return _seen; + } + + bool try_and_see(T &val) + { + boost::mutex::scoped_lock l(_mutex); + if (_seen) + return false; + + val = _value; + _seen = true; + return true; + } + + void wait_and_see(T &val) + { + boost::mutex::scoped_lock l(_mutex); + while(_seen) + { + _cond.wait(l); + //std::cout << "Already seen ... " << std::endl; + } + val = _value; + _seen = true; + } + +private: // members + T _value; + boost::mutex _mutex; + boost::condition_variable _cond; + bool _seen; +}; + +class control_impl : public control +{ +public: + control_impl(const std::string &node, const size_t baud_rate); + + virtual ~control_impl(void); + + void configure_message_rate( + const boost::uint16_t msg, + const boost::uint8_t rate); + + void configure_antenna( + const boost::uint16_t flags, + const boost::uint16_t pins); + + void configure_pps( + const boost::uint32_t interval, + const boost::uint32_t length, + const boost::int8_t status, + const boost::uint8_t time_ref, + const boost::uint8_t flags, + const boost::int16_t antenna_delay, + const boost::int16_t rf_group_delay, + const boost::int32_t user_delay); + + void configure_rates( + boost::uint16_t meas_rate, + boost::uint16_t nav_rate, + boost::uint16_t time_ref); + + // gps_ctrl interface + bool gps_detected(void); + std::vector<std::string> get_sensors(void); + uhd::sensor_value_t get_sensor(std::string key); + +private: // types + enum decoder_state_t { + DECODE_SYNC1 = 0, + DECODE_SYNC2, + DECODE_CLASS, + DECODE_ID, + DECODE_LENGTH1, + DECODE_LENGTH2, + DECODE_PAYLOAD, + DECODE_CHKSUM1, + DECODE_CHKSUM2, + }; + + enum rxmsg_state_t { + RXMSG_IGNORE = 0, + RXMSG_HANDLE, + RXMSG_DISABLE, + RXMSG_ERROR_LENGTH + }; + + enum ack_state_t { + ACK_IDLE = 0, + ACK_WAITING, + ACK_GOT_ACK, + ACK_GOT_NAK + }; + +private: // methods + std::time_t _get_epoch_time(void); + + void _decode_init(void); + + void _add_byte_to_checksum(const boost::uint8_t b); + + void _detect(void); + + void _send_message( + const boost::uint16_t msg, + const boost::uint8_t *payload, + const boost::uint16_t len); + + int _wait_for_ack( + const boost::uint16_t msg, + const double timeout); + + void _calc_checksum( + const boost::uint8_t *buffer, + const boost::uint16_t length, + checksum_t &checksum); + + void _rx_callback(const char *data, unsigned len); + + void _parse_char(const boost::uint8_t b); + + int _payload_rx_init(void); + + int _payload_rx_add(const boost::uint8_t b); + + int _payload_rx_done(void); + +private: // members + // gps_ctrl stuff + bool _detected; + std::vector<std::string> _sensors; + + sensor_entry<bool> _locked; + sensor_entry<boost::posix_time::ptime> _ptime; + + // decoder state + decoder_state_t _decode_state; + rxmsg_state_t _rxmsg_state; + + ack_state_t _ack_state; + boost::uint16_t _ack_waiting_msg; + + boost::uint8_t _rx_ck_a; + boost::uint8_t _rx_ck_b; + + boost::uint16_t _rx_payload_length; + size_t _rx_payload_index; + boost::uint16_t _rx_msg; + + rxmsg_state_t _rx_state; + + boost::shared_ptr<async_serial> _serial; + + // this has to be at the end of the + // class to be valid C++ + buf_t _buf; +}; + +}} // namespace ublox::ubx + +}}} // namespace +#endif // INCLUDED_UHD_USRP_UBLOX_CONTROL_IMPL_HPP diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index 11bb2488a..f693ee7a6 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -35,6 +35,13 @@ SET(x3xx_burner_sources cdecode.c ) +find_package(UDev) +IF(ENABLE_E300) + IF(UDEV_FOUND) + LIST(APPEND util_runtime_sources usrp_e3x0_network_mode.cpp) + ENDIF(UDEV_FOUND) +ENDIF(ENABLE_E300) + #for each source: build an executable and install FOREACH(util_source ${util_runtime_sources}) GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) diff --git a/host/utils/query_gpsdo_sensors.cpp b/host/utils/query_gpsdo_sensors.cpp index 9a1556650..05f918eb4 100644 --- a/host/utils/query_gpsdo_sensors.cpp +++ b/host/utils/query_gpsdo_sensors.cpp @@ -107,15 +107,20 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ const time_t pc_clock_time = time(NULL); const uhd::time_spec_t last_pps_time = usrp->get_time_last_pps(); if (last_pps_time.to_ticks(1.0) == gps_time.to_int()) { - std::cout << boost::format("GPS and UHD Device time are aligned.\n"); - } else - std::cout << boost::format("\nGPS and UHD Device time are NOT aligned. Try re-running the program. Double check 1 PPS connection from GPSDO.\n\n"); - + std::cout << boost::format("GPS and UHD Device time are aligned.\n"); + } else { + std::cout << boost::format("\nGPS and UHD Device time are NOT aligned last_pps: %ld vs gps: %ld. Try re-running the program. Double check 1 PPS connection from GPSDO.\n\n") % last_pps_time.to_ticks(1.0) % gps_time.to_int() << std::endl; + } + //print NMEA strings - std::cout << boost::format("Printing available NMEA strings:\n"); - uhd::sensor_value_t gga_string = usrp->get_mboard_sensor("gps_gpgga"); - uhd::sensor_value_t rmc_string = usrp->get_mboard_sensor("gps_gprmc"); - std::cout << boost::format("%s\n%s\n%s\n") % gga_string.to_pp_string() % rmc_string.to_pp_string() % gps_time.to_pp_string(); + try { + uhd::sensor_value_t gga_string = usrp->get_mboard_sensor("gps_gpgga"); + uhd::sensor_value_t rmc_string = usrp->get_mboard_sensor("gps_gprmc"); + std::cout << boost::format("Printing available NMEA strings:\n"); + std::cout << boost::format("%s\n%s\n%s\n") % gga_string.to_pp_string() % rmc_string.to_pp_string() % gps_time.to_pp_string(); + } catch (std::exception &e) { + std::cout << "NMEA strings not implemented for this device." << std::endl; + } std::cout << boost::format("UHD Device time: %.0f seconds\n") % (last_pps_time.get_real_secs()); std::cout << boost::format("PC Clock time: %.0f seconds\n") % pc_clock_time; diff --git a/host/utils/usrp_e3x0_network_mode.cpp b/host/utils/usrp_e3x0_network_mode.cpp new file mode 100644 index 000000000..dae4b6ff7 --- /dev/null +++ b/host/utils/usrp_e3x0_network_mode.cpp @@ -0,0 +1,80 @@ +// +// 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 "../lib/usrp/e300/e300_network.hpp" +#include <uhd/device.hpp> +#include <uhd/exception.hpp> + +#include <uhd/utils/msg.hpp> +#include <uhd/transport/if_addrs.hpp> + +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> + +#include <iostream> + +namespace po = boost::program_options; + +static void check_network_ok(void) +{ + using namespace uhd::transport; + using namespace boost::asio::ip; + std::vector<if_addrs_t> addrs = get_if_addrs(); + + if(addrs.size() == 1 and addrs.at(0).inet == address_v4::loopback().to_string()) + throw uhd::runtime_error( + "No network address except for loopback found.\n" + "Make sure your DHCP server is working or configure a static IP"); +} + +int main(int argc, char *argv[]) +{ + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("fpga", po::value<std::string>(), "fpga image to load") + ; + + 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 E3x0 Network Mode %s") % desc << std::endl; + return EXIT_FAILURE; + } + uhd::device_addr_t args; + if(vm.count("fpga")) { + args["fpga"] = vm["fpga"].as<std::string>(); + } + + try { + check_network_ok(); + uhd::usrp::e300::network_server::sptr server = uhd::usrp::e300::network_server::make(args); + server->run(); + } catch (uhd::assertion_error &e) { + UHD_MSG(error) << "This executable is supposed to run on the device, not on the host." << std::endl + << "Please refer to the manual section on operating your e3x0 device in network mode." << std::endl; + return EXIT_FAILURE; + } catch (uhd::runtime_error &e) { + UHD_MSG(error) << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} |