diff options
Diffstat (limited to 'host')
33 files changed, 5168 insertions, 1 deletions
diff --git a/host/AUTHORS b/host/AUTHORS index 137eba0e6..7292da8f9 100644 --- a/host/AUTHORS +++ b/host/AUTHORS @@ -1,5 +1,6 @@ Matt Ettus - matt@ettus.com - USRP1/USRP2 FPGA code + USRP1 FPGA code + USRP2 FPGA code Josh Blum - josh@ettus.com driver framework @@ -14,4 +15,11 @@ Jason Abele - jason@ettus.com WBX host code Eric Blossom - eb@comsec.com + USRP1 firmware USRP2 firmware + +Tom Tsou - ttsou@vt.edu + UHD-USB framework + LIBUSB host code + USRP1 host code + USRP1 firmware diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt index b4383f88d..bbb8812b0 100644 --- a/host/docs/CMakeLists.txt +++ b/host/docs/CMakeLists.txt @@ -25,6 +25,7 @@ SET(manual_sources dboards.rst general.rst images.rst + usrp1.rst usrp2.rst ) diff --git a/host/docs/index.rst b/host/docs/index.rst index 6973ede19..bd55edc0b 100644 --- a/host/docs/index.rst +++ b/host/docs/index.rst @@ -22,6 +22,7 @@ Application Notes ^^^^^^^^^^^^^^^^^^^^^ * `General App Notes <./general.html>`_ * `Firmware and FPGA Image Notes <./images.html>`_ +* `USRP1 App Notes <./usrp1.html>`_ * `USRP2 App Notes <./usrp2.html>`_ * `Daughterboard App Notes <./dboards.html>`_ diff --git a/host/docs/usrp1.rst b/host/docs/usrp1.rst new file mode 100644 index 000000000..ebc33cbfa --- /dev/null +++ b/host/docs/usrp1.rst @@ -0,0 +1,41 @@ +======================================================================== +UHD - USRP1 Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Addressing the device +------------------------------------------------------------------------ +A USRP1 can be identified though its serial number, +designated by the "serial" key in the device address. + +The device address string representation for a USRP1 with serial 1234 + +:: + + serial=1234 + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Change the USRP1's serial number +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TODO + +------------------------------------------------------------------------ +OS Specific Notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Setup Udev on Linux +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +On Linux, Udev handles USB plug and unplug events. +The following command creates a Udev rule for the USRP1 +so that non-root users may access the device: + +:: + + echo 'ACTION=="add", BUS=="usb", SYSFS{idVendor}=="fffe", SYSFS{idProduct}=="0002", MODE:="0666"' > tmpfile + sudo chown root.root tmpfile + sudo mv tmpfile /etc/udev/rules.d/10-usrp.rules + sudo udevadm control --reload-rules + diff --git a/host/include/uhd/transport/CMakeLists.txt b/host/include/uhd/transport/CMakeLists.txt index 93e9a6485..0f1cbf2a2 100644 --- a/host/include/uhd/transport/CMakeLists.txt +++ b/host/include/uhd/transport/CMakeLists.txt @@ -25,6 +25,9 @@ INSTALL(FILES if_addrs.hpp udp_simple.hpp udp_zero_copy.hpp + usb_control.hpp + usb_zero_copy.hpp + usb_device_handle.hpp vrt_if_packet.hpp zero_copy.hpp DESTINATION ${INCLUDE_DIR}/uhd/transport diff --git a/host/include/uhd/transport/usb_control.hpp b/host/include/uhd/transport/usb_control.hpp new file mode 100644 index 000000000..6137ecf84 --- /dev/null +++ b/host/include/uhd/transport/usb_control.hpp @@ -0,0 +1,65 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_USB_CONTROL_HPP +#define INCLUDED_UHD_TRANSPORT_USB_CONTROL_HPP + +#include "usb_device_handle.hpp" + +namespace uhd { namespace transport { + +class UHD_API usb_control : boost::noncopyable { +public: + typedef boost::shared_ptr<usb_control> sptr; + + /*! + * Create a new usb control transport: + * This transport is for sending and receiving control information from + * the host to device using the Default Control Pipe. + * + * \param handle a device handle that uniquely identifies a USB device + */ + static sptr make(usb_device_handle::sptr handle); + + /*! + * Submit a USB device request: + * Blocks until the request returns + * + * For format and corresponding USB request fields + * see USB Specification Revision 2.0 - 9.3 USB Device Requests + * + * Usage is device specific + * + * \param request_type 1-byte bitmask (bmRequestType) + * \param request 1-byte (bRequest) + * \param value 2-byte (wValue) + * \param index 2-byte (wIndex) + * \param buff buffer to hold send or receive data + * \param length 2-byte (wLength) + * \return number of bytes submitted + */ + virtual size_t submit(boost::uint8_t request_type, + boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) = 0; +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_USB_CONTROL_HPP */ diff --git a/host/include/uhd/transport/usb_device_handle.hpp b/host/include/uhd/transport/usb_device_handle.hpp new file mode 100644 index 000000000..78c78f6b5 --- /dev/null +++ b/host/include/uhd/transport/usb_device_handle.hpp @@ -0,0 +1,79 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_USB_DEVICE_HANDLE_HPP +#define INCLUDED_UHD_TRANSPORT_USB_DEVICE_HANDLE_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> +#include <vector> + +namespace uhd { namespace transport { + +/*! + * Device handle class that represents a USB device + * Used for identifying devices on the USB bus and selecting which device is + * used when creating a USB transport. A minimal subset of USB descriptor + * fields are used. Fields can be found in the USB 2.0 specification Table + * 9-8 (Standard Device Descriptor). In addition to fields of the device + * descriptor, the interface returns the device's USB device address. + * + * Note: The USB 2.0 Standard Device Descriptor contains an index rather then + * a true descriptor serial number string. This interface returns the + * actual string descriptor. + */ +class UHD_API usb_device_handle : boost::noncopyable { +public: + typedef boost::shared_ptr<usb_device_handle> sptr; + + /*! + * Return the device's serial number + * \return a string describing the device's serial number + */ + virtual UHD_API std::string get_serial() const = 0; + + /*! + * Return the device's Vendor ID (usually assigned by the USB-IF) + * \return a Vendor ID + */ + virtual UHD_API boost::uint16_t get_vendor_id() const = 0; + + /*! + * Return the device's Product ID (usually assigned by manufacturer) + * \return a Product ID + */ + virtual UHD_API boost::uint16_t get_product_id() const = 0; + + /*! + * Return the device's USB address + * \return a Product ID + */ + virtual UHD_API boost::uint16_t get_device_addr() const = 0; + + /*! + * Return a vector of USB devices on this host + * \return a vector of USB device handles + */ + static UHD_API std::vector<usb_device_handle::sptr> get_device_list(); + +}; //namespace usb + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_USB_DEVICE_HANDLE_HPP */ diff --git a/host/include/uhd/transport/usb_zero_copy.hpp b/host/include/uhd/transport/usb_zero_copy.hpp new file mode 100644 index 000000000..2edd6d91d --- /dev/null +++ b/host/include/uhd/transport/usb_zero_copy.hpp @@ -0,0 +1,62 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_USB_ZERO_COPY_HPP +#define INCLUDED_UHD_TRANSPORT_USB_ZERO_COPY_HPP + +#include "usb_device_handle.hpp" +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { namespace transport { + +/*! + * A zero copy usb transport provides an efficient way to handle data. + * by avoiding the extra copy when recv() or send() is called on the handle. + * Rather, the zero copy transport gives the caller memory references. + * The caller informs the transport when it is finished with the reference. + * + * On linux systems, the zero copy transport can use a kernel packet ring. + * If no platform specific solution is available, make returns a boost asio + * implementation that wraps functionality around standard send/recv calls. + */ +class UHD_API usb_zero_copy : public virtual zero_copy_if { +public: + typedef boost::shared_ptr<usb_zero_copy> sptr; + + /*! + * Make a new zero copy usb transport: + * This transport is for sending and receiving between the host + * and a pair of USB bulk transfer endpoints. + * The primary usage for this transport is data transactions. + * The underlying implementation may be platform specific. + * + * \param handle a device handle that uniquely identifying the device + * \param rx_endpoint an integer specifiying an IN endpoint number + * \param tx_endpoint an integer specifiying an OUT endpoint number + * \param buff_size total number of bytes of buffer space to allocate + * \param block_size number of bytes allocated for each I/O transaction + */ + static sptr make(usb_device_handle::sptr handle, + unsigned int rx_endpoint, + unsigned int tx_endpoint, + size_t buff_size = 0, + size_t block_size = 0); +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_USB_ZERO_COPY_HPP */ diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt index bde2b72b9..753fd5e85 100644 --- a/host/lib/transport/CMakeLists.txt +++ b/host/lib/transport/CMakeLists.txt @@ -18,6 +18,24 @@ #This file will be included by cmake, use absolute paths! ######################################################################## +# Setup libusb +######################################################################## +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/lib/transport) +FIND_PACKAGE(USB1 REQUIRED) + +IF(LIBUSB_FOUND) + INCLUDE_DIRECTORIES(${LIBUSB_INCLUDE_DIR}) + LIBUHD_APPEND_LIBS(${LIBUSB_LIBRARIES}) + LIBUHD_APPEND_SOURCES( + ${CMAKE_SOURCE_DIR}/lib/transport/libusb1_control.cpp + ${CMAKE_SOURCE_DIR}/lib/transport/libusb1_zero_copy.cpp + ${CMAKE_SOURCE_DIR}/lib/transport/libusb1_base.cpp + ${CMAKE_SOURCE_DIR}/lib/transport/libusb1_device_handle.cpp + ) + SET(HAVE_USB_SUPPORT TRUE) +ENDIF(LIBUSB_FOUND) + +######################################################################## # Check for SIMD headers ######################################################################## INCLUDE(CheckIncludeFileCXX) diff --git a/host/lib/transport/FindUSB1.cmake b/host/lib/transport/FindUSB1.cmake new file mode 100644 index 000000000..ebcac99eb --- /dev/null +++ b/host/lib/transport/FindUSB1.cmake @@ -0,0 +1,38 @@ +# - Try to find the freetype library +# Once done this defines +# +# LIBUSB_FOUND - system has libusb +# LIBUSB_INCLUDE_DIR - the libusb include directory +# LIBUSB_LIBRARIES - Link these to use libusb + +# Copyright (c) 2006, 2008 Laurent Montel, <montel@kde.org> +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) + + # in cache already + set(LIBUSB_FOUND TRUE) + +else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) + IF (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + pkg_check_modules(PC_LIBUSB libusb-1.0) + ENDIF(NOT WIN32) + + FIND_PATH(LIBUSB_INCLUDE_DIR libusb.h + PATHS ${PC_LIBUSB_INCLUDEDIR} ${PC_LIBUSB_INCLUDE_DIRS}) + + FIND_LIBRARY(LIBUSB_LIBRARIES NAMES usb-1.0 + PATHS ${PC_LIBUSB_LIBDIR} ${PC_LIBUSB_LIBRARY_DIRS}) + + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR) + + MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES) + +endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp new file mode 100644 index 000000000..92dcd969f --- /dev/null +++ b/host/lib/transport/libusb1_base.cpp @@ -0,0 +1,162 @@ +// +// Copyright 2010 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 "libusb1_base.hpp" +#include <uhd/utils/assert.hpp> +#include <iostream> + +using namespace uhd::transport; + +/********************************************************** + * Helper Methods + **********************************************************/ +/* + * Check for FSF device + * Compare the device's descriptor Vendor ID + * \param dev pointer to libusb_device + * \return true if Vendor ID matches 0xfffe + */ +bool check_fsf_device(libusb_device *dev) +{ + libusb_device_descriptor desc; + + if (libusb_get_device_descriptor(dev, &desc) < 0) { + UHD_ASSERT_THROW("USB: failed to get device descriptor"); + } + + return desc.idVendor == 0xfffe; +} + +/********************************************************** + * libusb namespace + **********************************************************/ +void libusb::init(libusb_context **ctx, int debug_level) +{ + if (libusb_init(ctx) < 0) + std::cerr << "error: libusb_init" << std::endl; + + libusb_set_debug(*ctx, debug_level); +} + +std::vector<libusb_device *> libusb::get_fsf_device_list(libusb_context *ctx) +{ + libusb_device **libusb_dev_list; + std::vector<libusb_device *> fsf_dev_list; + + ssize_t dev_cnt = libusb_get_device_list(ctx, &libusb_dev_list); + + //find the FSF devices + for (ssize_t i = 0; i < dev_cnt; i++) { + libusb_device *dev = libusb_dev_list[i]; + + if (check_fsf_device(dev)) + fsf_dev_list.push_back(dev); + else + libusb_unref_device(dev); + } + + libusb_free_device_list(libusb_dev_list, 0); + + return fsf_dev_list; +} + +libusb_device_handle *libusb::open_device(libusb_context *ctx, + usb_device_handle::sptr handle) +{ + libusb_device_handle *dev_handle = NULL; + std::vector<libusb_device *> fsf_dev_list = get_fsf_device_list(ctx); + + //find and open the USB device + for (size_t i = 0; i < fsf_dev_list.size(); i++) { + libusb_device *dev = fsf_dev_list[i]; + + if (compare_device(dev, handle)) { + libusb_open(dev, &dev_handle); + libusb_unref_device(dev); + break; + } + + libusb_unref_device(dev); + } + + return dev_handle; +} + + +bool libusb::compare_device(libusb_device *dev, + usb_device_handle::sptr handle) +{ + std::string serial = handle->get_serial(); + boost::uint16_t vendor_id = handle->get_vendor_id(); + boost::uint16_t product_id = handle->get_product_id(); + boost::uint8_t device_addr = handle->get_device_addr(); + + libusb_device_descriptor libusb_desc; + if (libusb_get_device_descriptor(dev, &libusb_desc) < 0) + return false; + if (serial != get_serial(dev)) + return false; + if (vendor_id != libusb_desc.idVendor) + return false; + if (product_id != libusb_desc.idProduct) + return false; + if (device_addr != libusb_get_device_address(dev)) + return false; + + return true; +} + + +bool libusb::open_interface(libusb_device_handle *dev_handle, + int interface) +{ + int ret = libusb_claim_interface(dev_handle, interface); + if (ret < 0) { + std::cerr << "error: libusb_claim_interface() " << ret << std::endl; + return false; + } + else { + return true; + } +} + + +std::string libusb::get_serial(libusb_device *dev) +{ + unsigned char buff[32]; + + libusb_device_descriptor desc; + if (libusb_get_device_descriptor(dev, &desc) < 0) + return ""; + + if (desc.iSerialNumber == 0) + return ""; + + //open the device because we have to + libusb_device_handle *dev_handle; + if (libusb_open(dev, &dev_handle) < 0) + return ""; + + if (libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, + buff, sizeof(buff)) < 0) { + return ""; + } + + libusb_close(dev_handle); + + return (char*) buff; +} diff --git a/host/lib/transport/libusb1_base.hpp b/host/lib/transport/libusb1_base.hpp new file mode 100644 index 000000000..442f89ded --- /dev/null +++ b/host/lib/transport/libusb1_base.hpp @@ -0,0 +1,104 @@ +// +// Copyright 2010 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_TRANSPORT_LIBUSB_HPP +#define INCLUDED_TRANSPORT_LIBUSB_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/usb_device_handle.hpp> +#include <libusb-1.0/libusb.h> + +namespace uhd { namespace transport { + +namespace libusb { + /* + * Initialize libusb and set debug level + * Takes a pointer to context pointer because that's + * how libusb rolls. Debug levels. + * + * Level 0: no messages ever printed by the library (default) + * Level 1: error messages are printed to stderr + * Level 2: warning and error messages are printed to stderr + * Level 3: informational messages are printed to stdout, warning + * and error messages are printed to stderr + * + * \param ctx pointer to context pointer + * \param debug_level + */ + void init(libusb_context **ctx, int debug_level); + + /* + * Get a list of Free Software Foundation devices (Vendor ID 0xfffe) + * As opposed to the public USB device handle interface, which returns + * generic identifiers, this call returns device pointers speficic + * to libusb. + * \param ctx the libusb context used for init + * \return a vector of libusb devices + */ + std::vector<libusb_device *> get_fsf_device_list(libusb_context *ctx); + + /* + * Open the device specified by a generic handle + * Find the libusb_device cooresponding to the generic handle + * and open it for I/O, which returns a libusb_device_handle + * ready for an interface + * \param ctx the libusb context used for init + * \return a libusb_device_handle ready for action + */ + libusb_device_handle *open_device(libusb_context *ctx, + usb_device_handle::sptr handle); + + /* + * Compare a libusb device with a generic handle + * Check the descriptors and open the device to check the + * serial number string. Compare values against the given + * handle. The libusb context is already implied in the + * libusb_device. + * \param dev a libusb_device pointer + * \param handle a generic handle specifier + * \return true if handle and device match, false otherwise + */ + bool compare_device(libusb_device *dev, usb_device_handle::sptr handle); + + /* + * Open an interface to the device + * This is a logical operation for operating system housekeeping as + * nothing is sent over the bus. The interface much correspond + * to the USB device descriptors. + * \param dev_handle libusb handle to an opened device + * \param interface integer of the interface to use + * \return true on success, false on error + */ + bool open_interface(libusb_device_handle *dev_handle, int interface); + + /* + * Get serial number + * The standard USB device descriptor contains an index to an + * actual serial number string descriptor. The index is readily + * readble, but the string descriptor requires probing the device. + * Because this call attempts to open the device, it may not + * succeed because not all USB devices are readily opened. + * The default language is used for the request (English). + * \param dev a libusb_device pointer + * \return string serial number or 0 on error or unavailablity + */ + std::string get_serial(libusb_device *dev); +} + +}} //namespace + +#endif /* INCLUDED_TRANSPORT_LIBUSB_HPP */ diff --git a/host/lib/transport/libusb1_control.cpp b/host/lib/transport/libusb1_control.cpp new file mode 100644 index 000000000..3531128b2 --- /dev/null +++ b/host/lib/transport/libusb1_control.cpp @@ -0,0 +1,95 @@ +// +// Copyright 2010 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 "libusb1_base.hpp" +#include <uhd/transport/usb_control.hpp> + +using namespace uhd::transport; + +const int libusb_debug_level = 0; +const int libusb_timeout = 0; + +/*********************************************************************** + * libusb-1.0 implementation of USB control transport + **********************************************************************/ +class libusb_control_impl : public usb_control { +public: + libusb_control_impl(usb_device_handle::sptr handle); + ~libusb_control_impl(); + + size_t submit(boost::uint8_t request_type, + boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length); + +private: + libusb_context *_ctx; + libusb_device_handle *_dev_handle; +}; + + +libusb_control_impl::libusb_control_impl(usb_device_handle::sptr handle) +{ + libusb::init(&_ctx, libusb_debug_level); + + // Find and open the libusb_device corresponding to the + // given handle and return the libusb_device_handle + // that can be used for I/O purposes. + _dev_handle = libusb::open_device(_ctx, handle); + + // Open USB interfaces for control using magic value + // IN interface: 2 + // OUT interface: 1 + // Control interface: 0 + libusb::open_interface(_dev_handle, 0); +} + + +libusb_control_impl::~libusb_control_impl() +{ + libusb_close(_dev_handle); + libusb_exit(_ctx); +} + + +size_t libusb_control_impl::submit(boost::uint8_t request_type, + boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) +{ + return libusb_control_transfer(_dev_handle, + request_type, + request, + value, + index, + buff, + length, + libusb_timeout); +} + + +/*********************************************************************** + * USB control public make functions + **********************************************************************/ +usb_control::sptr usb_control::make(usb_device_handle::sptr handle) +{ + return sptr(new libusb_control_impl(handle)); +} diff --git a/host/lib/transport/libusb1_device_handle.cpp b/host/lib/transport/libusb1_device_handle.cpp new file mode 100644 index 000000000..5d9d8faf0 --- /dev/null +++ b/host/lib/transport/libusb1_device_handle.cpp @@ -0,0 +1,111 @@ +// +// Copyright 2010 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 "libusb1_base.hpp" +#include <uhd/utils/assert.hpp> + +using namespace uhd::transport; + +const int libusb_debug_level = 0; + +/**************************************************************** + * libusb USB device handle implementation class + ***************************************************************/ +class libusb1_device_handle_impl : public usb_device_handle { +public: + libusb1_device_handle_impl(std::string serial, + boost::uint32_t product_id, + boost::uint32_t vendor_id, + boost::uint32_t device_addr) + : _serial(serial), _product_id(product_id), + _vendor_id(vendor_id), _device_addr(device_addr) + { + /* NOP */ + } + + ~libusb1_device_handle_impl() + { + /* NOP */ + } + + std::string get_serial() const + { + return _serial; + } + + boost::uint16_t get_vendor_id() const + { + return _vendor_id; + } + + + boost::uint16_t get_product_id() const + { + return _product_id; + } + + boost::uint16_t get_device_addr() const + { + return _device_addr; + } + +private: + std::string _serial; + boost::uint32_t _product_id; + boost::uint32_t _vendor_id; + boost::uint32_t _device_addr; +}; + + +usb_device_handle::sptr make_usb_device_handle(libusb_device *dev) +{ + libusb_device_descriptor desc; + + if (libusb_get_device_descriptor(dev, &desc) < 0) { + UHD_ASSERT_THROW("USB: failed to get device descriptor"); + } + + std::string serial = libusb::get_serial(dev); + boost::uint32_t product_id = desc.idProduct; + boost::uint32_t vendor_id = desc.idVendor; + boost::uint32_t device_addr = libusb_get_device_address(dev); + + return usb_device_handle::sptr(new libusb1_device_handle_impl( + serial, + product_id, + vendor_id, + device_addr)); +} + +std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list() +{ + libusb_context *ctx = NULL; + std::vector<libusb_device *> libusb_device_list; + std::vector<usb_device_handle::sptr> device_handle_list; + + libusb::init(&ctx, libusb_debug_level); + + libusb_device_list = libusb::get_fsf_device_list(ctx); + + for (size_t i = 0; i < libusb_device_list.size(); i++) { + libusb_device *dev = libusb_device_list[i]; + device_handle_list.push_back(make_usb_device_handle(dev)); + } + + libusb_exit(ctx); + return device_handle_list; +} diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp new file mode 100644 index 000000000..b890a87f9 --- /dev/null +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -0,0 +1,767 @@ +// +// Copyright 2010 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 "libusb1_base.hpp" +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/asio.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <iomanip> + +using namespace uhd::transport; + +const int libusb_debug_level = 0; +const int libusb_timeout = 0; + +/*********************************************************************** + * Helper functions + ***********************************************************************/ +/* + * Print the values of a libusb_transfer struct + * http://libusb.sourceforge.net/api-1.0/structlibusb__transfer.html + */ +void pp_transfer(libusb_transfer *lut) +{ + std::cout << "Libusb transfer" << std::endl; + std::cout << " flags: 0x" << std::hex << (unsigned int) lut->flags << std::endl; + std::cout << " endpoint: 0x" << std::hex << (unsigned int) lut->endpoint << std::endl; + std::cout << " type: 0x" << std::hex << (unsigned int) lut->type << std::endl; + std::cout << " timeout: " << std::dec << lut->timeout << std::endl; + std::cout << " status: 0x" << std::hex << lut->status << std::endl; + std::cout << " length: " << std::dec << lut->length << std::endl; + std::cout << " actual_length: " << std::dec << lut->actual_length << std::endl; +} + +/*********************************************************************** + * USB asynchronous zero_copy endpoint + * This endpoint implementation provides asynchronous I/O to libusb-1.0 + * devices. Each endpoint is directional and two can be combined to + * create a bidirectional interface. It is a zero copy implementation + * with respect to libusb, however, each send and recv requires a copy + * operation from kernel to userspace; this is due to the usbfs + * interface provided by the kernel. + **********************************************************************/ +class usb_endpoint { +private: + libusb_device_handle *_dev_handle; + libusb_context *_ctx; + int _endpoint; + bool _input; + + size_t _transfer_size; + size_t _num_transfers; + + // Transfer state lists (transfers are free, pending, or completed) + std::list<libusb_transfer *> _free_list; + std::list<libusb_transfer *> _pending_list; + std::list<libusb_transfer *> _completed_list; + + // Calls for processing asynchronous I/O + libusb_transfer *allocate_transfer(int buff_len); + bool cancel(libusb_transfer *lut); + bool cancel_all(); + bool reap_pending_list(); + bool reap_pending_list_timeout(); + bool reap_completed_list(); + + // Transfer state manipulators + void free_list_add(libusb_transfer *lut); + void pending_list_add(libusb_transfer *lut); + void completed_list_add(libusb_transfer *lut); + libusb_transfer *free_list_get(); + libusb_transfer *completed_list_get(); + bool pending_list_remove(libusb_transfer *lut); + + // Debug use + void print_transfer_status(libusb_transfer *lut); + +public: + usb_endpoint(libusb_device_handle *dev_handle, + libusb_context *ctx, int endpoint, bool input, + size_t transfer_size, size_t num_transfers); + + ~usb_endpoint(); + + // Exposed interface for submitting / retrieving transfer buffers + bool submit(libusb_transfer *lut); + libusb_transfer *get_completed_transfer(); + libusb_transfer *get_free_transfer(); + + //Callback use only + void callback_handle_transfer(libusb_transfer *lut); +}; + + +/* + * Callback function called when submitted transfers complete. + * The endpoint upon which the transfer is part of is recovered + * and the transfer moved from pending to completed state. + * Callbacks occur during the reaping calls where libusb_handle_events() + * is used. The callback only modifies the transfer state by moving + * it from the pending to completed status list. + * \param lut pointer to libusb_transfer + */ +static void callback(libusb_transfer *lut) +{ + usb_endpoint *endpoint = (usb_endpoint *) lut->user_data; + endpoint->callback_handle_transfer(lut); +} + + +/* + * Accessor call to allow list access from callback space + * \param pointer to libusb_transfer + */ +void usb_endpoint::callback_handle_transfer(libusb_transfer *lut) +{ + if (!pending_list_remove(lut)) { + std::cerr << "USB: pending remove failed" << std::endl; + return; + } + + completed_list_add(lut); +} + + +/* + * Constructor + * Allocate libusb transfers and mark as free. For IN endpoints, + * submit the transfers so that they're ready to return when + * data is available. + */ +usb_endpoint::usb_endpoint(libusb_device_handle *dev_handle, + libusb_context *ctx, int endpoint, bool input, + size_t transfer_size, size_t num_transfers) + : _dev_handle(dev_handle), + _ctx(ctx), _endpoint(endpoint), _input(input), + _transfer_size(transfer_size), _num_transfers(num_transfers) +{ + unsigned int i; + for (i = 0; i < _num_transfers; i++) { + free_list_add(allocate_transfer(_transfer_size)); + + if (_input) + submit(free_list_get()); + } +} + + +/* + * Destructor + * Make sure all the memory is freed. Cancel any pending transfers. + * When all completed transfers are moved to the free list, release + * the transfers. Libusb will deallocate the data buffer held by + * each transfer. + */ +usb_endpoint::~usb_endpoint() +{ + cancel_all(); + + while (!_pending_list.empty()) { + if (!reap_pending_list()) + std::cerr << "error: destructor failed to reap" << std::endl; + } + + while (!_completed_list.empty()) { + if (!reap_completed_list()) + std::cerr << "error: destructor failed to reap" << std::endl; + } + + while (!_free_list.empty()) { + libusb_free_transfer(free_list_get()); + } +} + + +/* + * Allocate a libusb transfer + * The allocated transfer - and buffer it contains - is repeatedly + * submitted, reaped, and reused and should not be freed until shutdown. + * \param buff_len size of the individual buffer held by each transfer + * \return pointer to an allocated libusb_transfer + */ +libusb_transfer *usb_endpoint::allocate_transfer(int buff_len) +{ + libusb_transfer *lut = libusb_alloc_transfer(0); + + unsigned char *buff = new unsigned char[buff_len]; + + unsigned int endpoint = ((_endpoint & 0x7f) | (_input ? 0x80 : 0)); + + libusb_fill_bulk_transfer(lut, // transfer + _dev_handle, // dev_handle + endpoint, // endpoint + buff, // buffer + buff_len, // length + callback, // callback + this, // user_data + 0); // timeout + return lut; +} + + +/* + * Asynchonous transfer submission + * Submit a libusb transfer to libusb add pending status + * \param lut pointer to libusb_transfer + * \return true on success or false on error + */ +bool usb_endpoint::submit(libusb_transfer *lut) +{ + int retval; + if ((retval = libusb_submit_transfer(lut)) < 0) { + std::cerr << "error: libusb_submit_transfer: " << retval << std::endl; + return false; + } + + pending_list_add(lut); + return true; +} + + +/* + * Cancel a pending transfer + * Search the pending list for the transfer and cancel if found. + * \param lut pointer to libusb_transfer to cancel + * \return true on success or false if transfer is not found + * + * Note: success only indicates submission of cancelation request. + * Sucessful cancelation is not known until the callback occurs. + */ +bool usb_endpoint::cancel(libusb_transfer *lut) +{ + std::list<libusb_transfer*>::iterator iter; + for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) { + if (*iter == lut) { + libusb_cancel_transfer(lut); + return true; + } + } + return false; +} + + +/* + * Cancel all pending transfers + * \return bool true if cancelation request is submitted + * + * Note: success only indicates submission of cancelation request. + * Sucessful cancelation is not known until the callback occurs. + */ +bool usb_endpoint::cancel_all() +{ + std::list<libusb_transfer*>::iterator iter; + + for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) { + if (libusb_cancel_transfer(*iter) < 0) { + std::cerr << "error: libusb_cancal_transfer() failed" << std::endl; + return false; + } + } + + return true; +} + + +/* + * Reap completed transfers + * return true if at least one transfer was reaped, false otherwise. + * Check completed transfers for errors and mark as free. This is a + * blocking call. + * \return bool true if a libusb transfer is reaped, false otherwise + */ +bool usb_endpoint::reap_completed_list() +{ + libusb_transfer *lut; + + if (_completed_list.empty()) { + if (!reap_pending_list_timeout()) + return false; + } + + while (!_completed_list.empty()) { + lut = completed_list_get(); + print_transfer_status(lut); + free_list_add(lut); + } + + return true; +} + + +/* + * Print status errors of a completed transfer + * \param lut pointer to an libusb_transfer + */ +void usb_endpoint::print_transfer_status(libusb_transfer *lut) +{ + switch (lut->status) { + case LIBUSB_TRANSFER_COMPLETED: + if (lut->actual_length < lut->length) { + std::cerr << "USB: transfer completed with short write," + << " length = " << lut->length + << " actual = " << lut->actual_length << std::endl; + } + + if ((lut->actual_length < 0) || (lut->length < 0)) { + std::cerr << "USB: transfer completed with invalid response" + << std::endl; + } + break; + case LIBUSB_TRANSFER_CANCELLED: + break; + case LIBUSB_TRANSFER_NO_DEVICE: + std::cerr << "USB: device was disconnected" << std::endl; + break; + case LIBUSB_TRANSFER_OVERFLOW: + std::cerr << "USB: device sent more data than requested" << std::endl; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + std::cerr << "USB: transfer timed out" << std::endl; + break; + case LIBUSB_TRANSFER_STALL: + std::cerr << "USB: halt condition detected (stalled)" << std::endl; + break; + case LIBUSB_TRANSFER_ERROR: + std::cerr << "USB: transfer failed" << std::endl; + break; + default: + std::cerr << "USB: received unknown transfer status" << std::endl; + } +} + + +/* + * Reap pending transfers without timeout + * This is a blocking call. Reaping submitted transfers is + * handled by libusb and the assigned callback function. + * Block until at least one transfer is reaped. + * \return true true if a transfer was reaped or false otherwise + */ +bool usb_endpoint::reap_pending_list() +{ + int retval; + + if ((retval = libusb_handle_events(_ctx)) < 0) { + std::cerr << "error: libusb_handle_events: " << retval << std::endl; + return false; + } + + return true; +} + + +/* + * Reap pending transfers with timeout + * This call blocks until a transfer is reaped or timeout. + * Reaping submitted transfers is handled by libusb and the + * assigned callback function. Block until at least one + * transfer is reaped or timeout occurs. + * \return true if a transfer was reaped or false otherwise + */ +bool usb_endpoint::reap_pending_list_timeout() +{ + int retval; + timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 100000; //100ms + + size_t pending_list_size = _pending_list.size(); + + if ((retval = libusb_handle_events_timeout(_ctx, &tv)) < 0) { + std::cerr << "error: libusb_handle_events: " << retval << std::endl; + return false; + } + + if (_pending_list.size() < pending_list_size) { + return true; + } + else { + return false; + } +} + + +/* + * Get a free transfer + * The transfer has an empty data bufer for OUT requests + * \return pointer to a libusb_transfer + */ +libusb_transfer *usb_endpoint::get_free_transfer() +{ + if (_free_list.empty()) { + if (!reap_completed_list()) + return NULL; + } + + return free_list_get(); +} + + +/* + * Get a completed transfer + * The transfer has a full data buffer for IN requests + * \return pointer to libusb_transfer + */ +libusb_transfer *usb_endpoint::get_completed_transfer() +{ + if (_completed_list.empty()) { + if (!reap_pending_list_timeout()) + return NULL; + } + + return completed_list_get(); +} + +/* + * List operations + */ +void usb_endpoint::free_list_add(libusb_transfer *lut) +{ + _free_list.push_back(lut); +} + +void usb_endpoint::pending_list_add(libusb_transfer *lut) +{ + _pending_list.push_back(lut); +} + +void usb_endpoint::completed_list_add(libusb_transfer *lut) +{ + _completed_list.push_back(lut); +} + + +/* + * Free and completed lists don't have ordered content + * Pop transfers from the front as needed + */ +libusb_transfer *usb_endpoint::free_list_get() +{ + libusb_transfer *lut; + + if (_free_list.size() == 0) { + return NULL; + } + else { + lut = _free_list.front(); + _free_list.pop_front(); + return lut; + } +} + + +/* + * Free and completed lists don't have ordered content + * Pop transfers from the front as needed + */ +libusb_transfer *usb_endpoint::completed_list_get() +{ + libusb_transfer *lut; + + if (_completed_list.empty()) { + return NULL; + } + else { + lut = _completed_list.front(); + _completed_list.pop_front(); + return lut; + } +} + + +/* + * Search and remove transfer from pending list + * Assuming that the callbacks occur in order, the front element + * should yield the correct transfer. If not, then something else + * is going on. If no transfers match, then something went wrong. + */ +bool usb_endpoint::pending_list_remove(libusb_transfer *lut) +{ + std::list<libusb_transfer*>::iterator iter; + for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) { + if (*iter == lut) { + _pending_list.erase(iter); + return true; + } + } + return false; +} + + +/*********************************************************************** + * Managed buffers + **********************************************************************/ +/* + * Libusb managed receive buffer + * Construct a recv buffer from a libusb transfer. The memory held by + * the libusb transfer is exposed through the managed buffer interface. + * Upon destruction, the transfer and buffer are resubmitted to the + * endpoint for further use. + */ +class libusb_managed_recv_buffer_impl : public managed_recv_buffer { +public: + libusb_managed_recv_buffer_impl(libusb_transfer *lut, + usb_endpoint *endpoint) + : _buff(lut->buffer, lut->length) + { + _lut = lut; + _endpoint = endpoint; + } + + ~libusb_managed_recv_buffer_impl() + { + if (!_endpoint->submit(_lut)) + std::cerr << "USB: failed to submit IN transfer" << std::endl; + } + +private: + const boost::asio::const_buffer &get() const + { + return _buff; + } + + libusb_transfer *_lut; + usb_endpoint *_endpoint; + const boost::asio::const_buffer _buff; +}; + +/* + * Libusb managed send buffer + * Construct a send buffer from a libusb transfer. The memory held by + * the libusb transfer is exposed through the managed buffer interface. + * Committing the buffer will set the data length and submit the buffer + * to the endpoint. Submitting a buffer multiple times or destroying + * the buffer before committing is an error. For the latter, the transfer + * is returned to the endpoint with no data for reuse. + */ +class libusb_managed_send_buffer_impl : public managed_send_buffer { +public: + libusb_managed_send_buffer_impl(libusb_transfer *lut, + usb_endpoint *endpoint, + size_t buff_size) + : _buff(lut->buffer, buff_size), _committed(false) + { + _lut = lut; + _endpoint = endpoint; + } + + ~libusb_managed_send_buffer_impl() + { + if (!_committed) { + _lut->length = 0; + _lut->actual_length = 0; + _endpoint->submit(_lut); + } + } + + ssize_t commit(size_t num_bytes) + { + if (_committed) { + std::cerr << "UHD: send buffer already committed" << std::endl; + return 0; + } + + UHD_ASSERT_THROW(num_bytes <= boost::asio::buffer_size(_buff)); + + _lut->length = num_bytes; + _lut->actual_length = 0; + + if (_endpoint->submit(_lut)) { + _committed = true; + return num_bytes; + } + else { + return 0; + } + } + +private: + const boost::asio::mutable_buffer &get() const + { + return _buff; + } + + libusb_transfer *_lut; + usb_endpoint *_endpoint; + const boost::asio::mutable_buffer _buff; + bool _committed; +}; + + +/*********************************************************************** + * USB zero_copy device class + **********************************************************************/ +class libusb_zero_copy_impl : public usb_zero_copy +{ +private: + usb_endpoint *_rx_ep; + usb_endpoint *_tx_ep; + + // Maintain libusb values + libusb_context *_rx_ctx; + libusb_context *_tx_ctx; + libusb_device_handle *_rx_dev_handle; + libusb_device_handle *_tx_dev_handle; + + size_t _recv_buff_size; + size_t _send_buff_size; + size_t _num_frames; + +public: + typedef boost::shared_ptr<libusb_zero_copy_impl> sptr; + + libusb_zero_copy_impl(usb_device_handle::sptr handle, + unsigned int rx_endpoint, + unsigned int tx_endpoint, + size_t recv_buff_size, + size_t send_buff_size); + + ~libusb_zero_copy_impl(); + + managed_recv_buffer::sptr get_recv_buff(void); + managed_send_buffer::sptr get_send_buff(void); + + size_t get_num_recv_frames(void) const { return _num_frames; } + size_t get_num_send_frames(void) const { return _num_frames; } +}; + +/* + * Constructor + * Initializes libusb, opens devices, and sets up interfaces for I/O. + * Finally, creates endpoints for asynchronous I/O. + */ +libusb_zero_copy_impl::libusb_zero_copy_impl(usb_device_handle::sptr handle, + unsigned int rx_endpoint, + unsigned int tx_endpoint, + size_t buff_size, + size_t block_size) + : _rx_ctx(NULL), _tx_ctx(NULL), _rx_dev_handle(NULL), _tx_dev_handle(NULL), + _recv_buff_size(block_size), _send_buff_size(block_size), + _num_frames(buff_size / block_size) +{ + // Initialize libusb with separate contexts to allow + // thread safe operation of transmit and receive + libusb::init(&_rx_ctx, libusb_debug_level); + libusb::init(&_tx_ctx, libusb_debug_level); + + UHD_ASSERT_THROW((_rx_ctx != NULL) && (_tx_ctx != NULL)); + + // Find and open the libusb_device corresponding to the + // given handle and return the libusb_device_handle + // that can be used for I/O purposes. + _rx_dev_handle = libusb::open_device(_rx_ctx, handle); + _tx_dev_handle = libusb::open_device(_tx_ctx, handle); + + // Open USB interfaces for tx/rx using magic values. + // IN interface: 2 + // OUT interface: 1 + // Control interface: 0 + libusb::open_interface(_rx_dev_handle, 2); + libusb::open_interface(_tx_dev_handle, 1); + + _rx_ep = new usb_endpoint(_rx_dev_handle, // libusb device_handle + _rx_ctx, // libusb context + rx_endpoint, // USB endpoint number + true, // IN endpoint + _recv_buff_size, // buffer size per transfer + _num_frames); // number of libusb transfers + + _tx_ep = new usb_endpoint(_tx_dev_handle, // libusb device_handle + _tx_ctx, // libusb context + tx_endpoint, // USB endpoint number + false, // OUT endpoint + _send_buff_size, // buffer size per transfer + _num_frames); // number of libusb transfers +} + + +libusb_zero_copy_impl::~libusb_zero_copy_impl() +{ + delete _rx_ep; + delete _tx_ep; + + libusb_close(_rx_dev_handle); + libusb_close(_tx_dev_handle); + + libusb_exit(_rx_ctx); + libusb_exit(_tx_ctx); +} + + +/* + * Construct a managed receive buffer from a completed libusb transfer + * (happy with buffer full of data) obtained from the receive endpoint. + * Return empty pointer if no transfer is available (timeout or error). + * \return pointer to a managed receive buffer + */ +managed_recv_buffer::sptr libusb_zero_copy_impl::get_recv_buff() +{ + libusb_transfer *lut = _rx_ep->get_completed_transfer(); + if (lut == NULL) { + return managed_recv_buffer::sptr(); + } + else { + return managed_recv_buffer::sptr( + new libusb_managed_recv_buffer_impl(lut, + _rx_ep)); + } +} + + +/* + * Construct a managed send buffer from a free libusb transfer (with + * empty buffer). Return empty pointer of no transfer is available + * (timeout or error). + * \return pointer to a managed send buffer + */ +managed_send_buffer::sptr libusb_zero_copy_impl::get_send_buff() +{ + libusb_transfer *lut = _tx_ep->get_free_transfer(); + if (lut == NULL) { + return managed_send_buffer::sptr(); + } + else { + return managed_send_buffer::sptr( + new libusb_managed_send_buffer_impl(lut, + _tx_ep, + _send_buff_size)); + } +} + + +/*********************************************************************** + * USB zero_copy make functions + **********************************************************************/ +usb_zero_copy::sptr usb_zero_copy::make(usb_device_handle::sptr handle, + unsigned int rx_endpoint, + unsigned int tx_endpoint, + size_t buff_size, + size_t block_size) + +{ + return sptr(new libusb_zero_copy_impl(handle, + rx_endpoint, + tx_endpoint, + buff_size, + block_size)); +} + + + diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index 2b408e479..b5c545988 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -31,4 +31,5 @@ LIBUHD_APPEND_SOURCES( ) INCLUDE(${CMAKE_SOURCE_DIR}/lib/usrp/dboard/CMakeLists.txt) +INCLUDE(${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/CMakeLists.txt) INCLUDE(${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/CMakeLists.txt) diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt new file mode 100644 index 000000000..229a4ce63 --- /dev/null +++ b/host/lib/usrp/usrp1/CMakeLists.txt @@ -0,0 +1,57 @@ +# +# Copyright 2010 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 will be included by cmake, use absolute paths! + +######################################################################## +# Conditionally configure the USRP1 support +######################################################################## +MESSAGE(STATUS "Configuring usrp1 support...") + +IF(HAVE_USB_SUPPORT) + MESSAGE(STATUS "Has USB support - found") +ELSE(HAVE_USB_SUPPORT) + MESSAGE(STATUS "Has USB support - not found") +ENDIF(HAVE_USB_SUPPORT) + +#TODO check for usrp1 enable/disable option flag + +IF(HAVE_USB_SUPPORT) + MESSAGE(STATUS " Building usrp1 support.") + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/../firmware/fx2/include) + + LIBUHD_APPEND_SOURCES( + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/clock_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/clock_ctrl.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/codec_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/codec_ctrl.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/codec_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/dboard_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/dboard_iface.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/dsp_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/io_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/mboard_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_iface.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_iface.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_impl.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_ctrl.hpp + ) +ELSE(HAVE_USB_SUPPORT) + MESSAGE(STATUS " Skipping usrp1 support.") +ENDIF(HAVE_USB_SUPPORT) diff --git a/host/lib/usrp/usrp1/clock_ctrl.cpp b/host/lib/usrp/usrp1/clock_ctrl.cpp new file mode 100644 index 000000000..68c5f5320 --- /dev/null +++ b/host/lib/usrp/usrp1/clock_ctrl.cpp @@ -0,0 +1,60 @@ +// +// Copyright 2010 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 "clock_ctrl.hpp" +#include "fpga_regs_standard.h" +#include <uhd/utils/assert.hpp> +#include <boost/cstdint.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <utility> +#include <iostream> + +using namespace uhd; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const double master_clock_rate = 64e6; + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class usrp1_clock_ctrl_impl : public usrp1_clock_ctrl { +public: + usrp1_clock_ctrl_impl(usrp1_iface::sptr iface) + { + _iface = iface; + } + + double get_master_clock_freq(void) + { + return master_clock_rate; + } + +private: + usrp1_iface::sptr _iface; + +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +usrp1_clock_ctrl::sptr usrp1_clock_ctrl::make(usrp1_iface::sptr iface) +{ + return sptr(new usrp1_clock_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp1/clock_ctrl.hpp b/host/lib/usrp/usrp1/clock_ctrl.hpp new file mode 100644 index 000000000..366869dab --- /dev/null +++ b/host/lib/usrp/usrp1/clock_ctrl.hpp @@ -0,0 +1,50 @@ +// +// Copyright 2010 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_USRP1_CLOCK_CTRL_HPP +#define INCLUDED_USRP1_CLOCK_CTRL_HPP + +#include "usrp1_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp1 clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class usrp1_clock_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp1_clock_ctrl> sptr; + + /*! + * Make a new clock control object. + * \param iface the usrp1 iface object + * \return the clock control object + */ + static sptr make(usrp1_iface::sptr iface); + + /*! + * Get the rate of the fpga clock line. + * \return the fpga clock rate in Hz + */ + virtual double get_master_clock_freq(void) = 0; + +}; + +#endif /* INCLUDED_USRP1_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/codec_ctrl.cpp b/host/lib/usrp/usrp1/codec_ctrl.cpp new file mode 100644 index 000000000..08f2d2a8e --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.cpp @@ -0,0 +1,429 @@ +// +// Copyright 2010 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 "codec_ctrl.hpp" +#include "usrp_commands.h" +#include "clock_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> +#include <iomanip> + +using namespace uhd; + +static const bool codec_debug = false; + +const gain_range_t usrp1_codec_ctrl::tx_pga_gain_range(-20, 0, float(0.1)); +const gain_range_t usrp1_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class usrp1_codec_ctrl_impl : public usrp1_codec_ctrl { +public: + //structors + usrp1_codec_ctrl_impl(usrp1_iface::sptr iface, + usrp1_clock_ctrl::sptr clock, + int spi_slave); + ~usrp1_codec_ctrl_impl(void); + + //aux adc and dac control + float read_aux_adc(aux_adc_t which); + void write_aux_dac(aux_dac_t which, float volts); + + //duc control + void set_duc_freq(double freq); + + //pga gain control + void set_tx_pga_gain(float); + float get_tx_pga_gain(void); + void set_rx_pga_gain(float, char); + float get_rx_pga_gain(char); + +private: + usrp1_iface::sptr _iface; + usrp1_clock_ctrl::sptr _clock_ctrl; + int _spi_slave; + ad9862_regs_t _ad9862_regs; + aux_adc_t _last_aux_adc_a, _last_aux_adc_b; + void send_reg(boost::uint8_t addr); + void recv_reg(boost::uint8_t addr); + + double coarse_tune(double codec_rate, double freq); + double fine_tune(double codec_rate, double freq); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +usrp1_codec_ctrl_impl::usrp1_codec_ctrl_impl(usrp1_iface::sptr iface, + usrp1_clock_ctrl::sptr clock, + int spi_slave) +{ + _iface = iface; + _clock_ctrl = clock; + _spi_slave = spi_slave; + + //soft reset + _ad9862_regs.soft_reset = 1; + this->send_reg(0); + + //initialize the codec register settings + _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; + _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; + _ad9862_regs.soft_reset = 0; + + //setup rx side of codec + _ad9862_regs.byp_buffer_a = 1; + _ad9862_regs.byp_buffer_b = 1; + _ad9862_regs.buffer_a_pd = 1; + _ad9862_regs.buffer_b_pd = 1; + _ad9862_regs.rx_pga_a = 0; + _ad9862_regs.rx_pga_b = 0; + _ad9862_regs.rx_twos_comp = 1; + _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + + //setup tx side of codec + _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; + _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; + _ad9862_regs.tx_pga_gain = 199; + _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; + _ad9862_regs.interp = ad9862_regs_t::INTERP_4; + _ad9862_regs.tx_twos_comp = 1; + _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; + _ad9862_regs.dac_a_coarse_gain = 0x3; + _ad9862_regs.dac_b_coarse_gain = 0x3; + + //setup the dll + _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; + _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; + _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + + //setup clockout + _ad9862_regs.clkout2_div_factor = ad9862_regs_t::CLKOUT2_DIV_FACTOR_2; + + //write the register settings to the codec + for (uint8_t addr = 0; addr <= 25; addr++) { + this->send_reg(addr); + } + + //aux adc clock + _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; + this->send_reg(34); +} + +usrp1_codec_ctrl_impl::~usrp1_codec_ctrl_impl(void) +{ + //set aux dacs to zero + this->write_aux_dac(AUX_DAC_A, 0); + this->write_aux_dac(AUX_DAC_B, 0); + this->write_aux_dac(AUX_DAC_C, 0); + this->write_aux_dac(AUX_DAC_D, 0); + + //power down + _ad9862_regs.all_rx_pd = 1; + this->send_reg(1); + _ad9862_regs.tx_digital_pd = 1; + _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; + this->send_reg(8); +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void usrp1_codec_ctrl_impl::set_tx_pga_gain(float gain){ + int gain_word = int(mtpgw*(gain - tx_pga_gain_range.min)/(tx_pga_gain_range.max - tx_pga_gain_range.min)); + _ad9862_regs.tx_pga_gain = std::clip(gain_word, 0, mtpgw); + this->send_reg(16); +} + +float usrp1_codec_ctrl_impl::get_tx_pga_gain(void){ + return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.max - tx_pga_gain_range.min)/mtpgw) + tx_pga_gain_range.min; +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void usrp1_codec_ctrl_impl::set_rx_pga_gain(float gain, char which){ + int gain_word = int(mrpgw*(gain - rx_pga_gain_range.min)/(rx_pga_gain_range.max - rx_pga_gain_range.min)); + gain_word = std::clip(gain_word, 0, mrpgw); + switch(which){ + case 'A': + _ad9862_regs.rx_pga_a = gain_word; + this->send_reg(2); + return; + case 'B': + _ad9862_regs.rx_pga_b = gain_word; + this->send_reg(3); + return; + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +float usrp1_codec_ctrl_impl::get_rx_pga_gain(char which){ + int gain_word; + switch(which){ + case 'A': gain_word = _ad9862_regs.rx_pga_a; break; + case 'B': gain_word = _ad9862_regs.rx_pga_b; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + return (gain_word*(rx_pga_gain_range.max - rx_pga_gain_range.min)/mrpgw) + rx_pga_gain_range.min; +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static float aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low) +{ + return float((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +float usrp1_codec_ctrl_impl::read_aux_adc(aux_adc_t which) +{ + //check to see if the switch needs to be set + bool write_switch = false; + switch(which) { + + case AUX_ADC_A1: + case AUX_ADC_A2: + if (which != _last_aux_adc_a) { + _ad9862_regs.select_a = (which == AUX_ADC_A1)? + ad9862_regs_t::SELECT_A_AUX_ADC1: ad9862_regs_t::SELECT_A_AUX_ADC2; + _last_aux_adc_a = which; + write_switch = true; + } + break; + + case AUX_ADC_B1: + case AUX_ADC_B2: + if (which != _last_aux_adc_b) { + _ad9862_regs.select_b = (which == AUX_ADC_B1)? + ad9862_regs_t::SELECT_B_AUX_ADC1: ad9862_regs_t::SELECT_B_AUX_ADC2; + _last_aux_adc_b = which; + write_switch = true; + } + break; + + } + + //write the switch if it changed + if(write_switch) this->send_reg(34); + + //map aux adcs to register values to read + static const uhd::dict<aux_adc_t, boost::uint8_t> aux_dac_to_addr = boost::assign::map_list_of + (AUX_ADC_A2, 26) (AUX_ADC_A1, 28) + (AUX_ADC_B2, 30) (AUX_ADC_B1, 32) + ; + + //read the value + this->recv_reg(aux_dac_to_addr[which]+0); + this->recv_reg(aux_dac_to_addr[which]+1); + + //return the value scaled to volts + switch(which) { + case AUX_ADC_A1: return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); + case AUX_ADC_A2: return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + case AUX_ADC_B1: return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); + case AUX_ADC_B2: return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); + } + UHD_ASSERT_THROW(false); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void usrp1_codec_ctrl_impl::write_aux_dac(aux_dac_t which, float volts) +{ + //special case for aux dac d (aka sigma delta word) + if (which == AUX_DAC_D) { + boost::uint16_t dac_word = std::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); + _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); + _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); + this->send_reg(42); + this->send_reg(43); + return; + } + + //calculate the dac word for aux dac a, b, c + boost::uint8_t dac_word = std::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + + //setup a lookup table for the aux dac params (reg ref, reg addr) + typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; + uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of + (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) + (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) + (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) + ; + + //set the aux dac register + UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); + boost::uint8_t *reg_ref, reg_addr; + boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; + *reg_ref = dac_word; + this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void usrp1_codec_ctrl_impl::send_reg(boost::uint8_t addr) +{ + boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); + + if (codec_debug) { + std::cout.fill('0'); + std::cout << "codec control write reg: 0x"; + std::cout << std::setw(8) << std::hex << reg << std::endl; + } + _iface->transact_spi(_spi_slave, + spi_config_t::EDGE_RISE, reg, 16, false); +} + +void usrp1_codec_ctrl_impl::recv_reg(boost::uint8_t addr) +{ + boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); + + if (codec_debug) { + std::cout.fill('0'); + std::cout << "codec control read reg: 0x"; + std::cout << std::setw(8) << std::hex << reg << std::endl; + } + + boost::uint32_t ret = _iface->transact_spi(_spi_slave, + spi_config_t::EDGE_RISE, reg, 16, true); + + if (codec_debug) { + std::cout.fill('0'); + std::cout << "codec control read ret: 0x"; + std::cout << std::setw(8) << std::hex << ret << std::endl; + } + + _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * DUC tuning + **********************************************************************/ +double usrp1_codec_ctrl_impl::coarse_tune(double codec_rate, double freq) +{ + double coarse_freq; + + double coarse_freq_1 = codec_rate / 8; + double coarse_freq_2 = codec_rate / 4; + double coarse_limit_1 = coarse_freq_1 / 2; + double coarse_limit_2 = (coarse_freq_1 + coarse_freq_2) / 2; + double max_freq = coarse_freq_2 + .09375 * codec_rate; + + if (freq < -max_freq) { + return false; + } + else if (freq < -coarse_limit_2) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; + coarse_freq = -coarse_freq_2; + } + else if (freq < -coarse_limit_1) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; + coarse_freq = -coarse_freq_1; + } + else if (freq < coarse_limit_1) { + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; + coarse_freq = 0; + } + else if (freq < coarse_limit_2) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; + coarse_freq = coarse_freq_1; + } + else if (freq <= max_freq) { + _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; + coarse_freq = coarse_freq_2; + } + else { + return 0; + } + + return coarse_freq; +} + +double usrp1_codec_ctrl_impl::fine_tune(double codec_rate, double target_freq) +{ + static const double scale_factor = std::pow(2.0, 24); + + boost::uint32_t freq_word = boost::uint32_t( + boost::math::round(abs((target_freq / codec_rate) * scale_factor))); + + double actual_freq = freq_word * codec_rate / scale_factor; + + if (target_freq < 0) { + _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_NEG_SHIFT; + actual_freq = -actual_freq; + } + else { + _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_POS_SHIFT; + } + + _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; + _ad9862_regs.ftw_23_16 = (freq_word >> 16) & 0xff; + _ad9862_regs.ftw_15_8 = (freq_word >> 8) & 0xff; + _ad9862_regs.ftw_7_0 = (freq_word >> 0) & 0xff; + + return actual_freq; +} + +void usrp1_codec_ctrl_impl::set_duc_freq(double freq) +{ + double codec_rate = _clock_ctrl->get_master_clock_freq() * 2; + double coarse_freq = coarse_tune(codec_rate, freq); + double fine_freq = fine_tune(codec_rate / 4, freq - coarse_freq); + + if (codec_debug) { + std::cout << "ad9862 tuning result:" << std::endl; + std::cout << " requested: " << freq << std::endl; + std::cout << " actual: " << coarse_freq + fine_freq << std::endl; + std::cout << " coarse freq: " << coarse_freq << std::endl; + std::cout << " fine freq: " << fine_freq << std::endl; + std::cout << " codec rate: " << codec_rate << std::endl; + } + + this->send_reg(20); + this->send_reg(21); + this->send_reg(22); + this->send_reg(23); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +usrp1_codec_ctrl::sptr usrp1_codec_ctrl::make(usrp1_iface::sptr iface, + usrp1_clock_ctrl::sptr clock, + int spi_slave) +{ + return sptr(new usrp1_codec_ctrl_impl(iface, clock, spi_slave)); +} diff --git a/host/lib/usrp/usrp1/codec_ctrl.hpp b/host/lib/usrp/usrp1/codec_ctrl.hpp new file mode 100644 index 000000000..259d10ef4 --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.hpp @@ -0,0 +1,97 @@ +// +// Copyright 2010 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_USRP1_CODEC_CTRL_HPP +#define INCLUDED_USRP1_CODEC_CTRL_HPP + +#include "usrp1_iface.hpp" +#include "clock_ctrl.hpp" +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp1 codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class usrp1_codec_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp1_codec_ctrl> sptr; + + static const uhd::gain_range_t tx_pga_gain_range; + static const uhd::gain_range_t rx_pga_gain_range; + + /*! + * Make a new clock control object. + * \param iface the usrp1 iface object + * \param spi_slave which spi device + * \return the clock control object + */ + static sptr make(usrp1_iface::sptr iface, + usrp1_clock_ctrl::sptr clock, int spi_slave + ); + + //! aux adc identifier constants + enum aux_adc_t{ + AUX_ADC_A2 = 0xA2, + AUX_ADC_A1 = 0xA1, + AUX_ADC_B2 = 0xB2, + AUX_ADC_B1 = 0xB1 + }; + + /*! + * Read an auxiliary adc: + * The internals remember which aux adc was read last. + * Therefore, the aux adc switch is only changed as needed. + * \param which which of the 4 adcs + * \return a value in volts + */ + virtual float read_aux_adc(aux_adc_t which) = 0; + + //! aux dac identifier constants + enum aux_dac_t{ + AUX_DAC_A = 0xA, + AUX_DAC_B = 0xB, + AUX_DAC_C = 0xC, + AUX_DAC_D = 0xD + }; + + /*! + * Write an auxiliary dac. + * \param which which of the 4 dacs + * \param volts the level in in volts + */ + virtual void write_aux_dac(aux_dac_t which, float volts) = 0; + + //! Set the TX PGA gain + virtual void set_tx_pga_gain(float gain) = 0; + + //! Get the TX PGA gain + virtual float get_tx_pga_gain(void) = 0; + + //! Set the RX PGA gain ('A' or 'B') + virtual void set_rx_pga_gain(float gain, char which) = 0; + + //! Get the RX PGA gain ('A' or 'B') + virtual float get_rx_pga_gain(char which) = 0; + + //! Set the TX modulator frequency + virtual void set_duc_freq(double freq) = 0; +}; + +#endif /* INCLUDED_USRP1_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/codec_impl.cpp b/host/lib/usrp/usrp1/codec_impl.cpp new file mode 100644 index 000000000..1756c1ed4 --- /dev/null +++ b/host/lib/usrp/usrp1/codec_impl.cpp @@ -0,0 +1,157 @@ +// +// Copyright 2010 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 "usrp1_impl.hpp" +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/codec_props.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void usrp1_impl::codec_init(void) +{ + //make proxies + BOOST_FOREACH(dboard_slot_t dboard_slot, _dboard_slots){ + _rx_codec_proxies[dboard_slot] = wax_obj_proxy::make( + boost::bind(&usrp1_impl::rx_codec_get, this, _1, _2, dboard_slot), + boost::bind(&usrp1_impl::rx_codec_set, this, _1, _2, dboard_slot)); + + _tx_codec_proxies[dboard_slot] = wax_obj_proxy::make( + boost::bind(&usrp1_impl::tx_codec_get, this, _1, _2, dboard_slot), + boost::bind(&usrp1_impl::tx_codec_set, this, _1, _2, dboard_slot)); + } +} + +/*********************************************************************** + * RX Codec Properties + **********************************************************************/ +static const std::string ad9862_pga_gain_name = "ad9862 pga"; + +void usrp1_impl::rx_codec_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<codec_prop_t>()) { + case CODEC_PROP_NAME: + val = str(boost::format("usrp1 adc - ad9862 - slot %c") % char(dboard_slot)); + return; + + case CODEC_PROP_OTHERS: + val = prop_names_t(); + return; + + case CODEC_PROP_GAIN_NAMES: + val = prop_names_t(1, ad9862_pga_gain_name); + return; + + case CODEC_PROP_GAIN_RANGE: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = usrp1_codec_ctrl::rx_pga_gain_range; + return; + + case CODEC_PROP_GAIN_I: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = _codec_ctrls[dboard_slot]->get_rx_pga_gain('A'); + return; + + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = _codec_ctrls[dboard_slot]->get_rx_pga_gain('B'); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +void usrp1_impl::rx_codec_set(const wax::obj &key_, const wax::obj &val, dboard_slot_t dboard_slot) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the set request conditioned on the key + switch(key.as<codec_prop_t>()) { + case CODEC_PROP_GAIN_I: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + _codec_ctrls[dboard_slot]->set_rx_pga_gain(val.as<float>(), 'A'); + return; + + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + _codec_ctrls[dboard_slot]->set_rx_pga_gain(val.as<float>(), 'B'); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} + +/*********************************************************************** + * TX Codec Properties + **********************************************************************/ +void usrp1_impl::tx_codec_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<codec_prop_t>()) { + case CODEC_PROP_NAME: + val = str(boost::format("usrp1 dac - ad9862 - slot %c") % char(dboard_slot)); + return; + + case CODEC_PROP_OTHERS: + val = prop_names_t(); + return; + + case CODEC_PROP_GAIN_NAMES: + val = prop_names_t(1, ad9862_pga_gain_name); + return; + + case CODEC_PROP_GAIN_RANGE: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = usrp1_codec_ctrl::tx_pga_gain_range; + return; + + case CODEC_PROP_GAIN_I: //only one gain for I and Q + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = _codec_ctrls[dboard_slot]->get_tx_pga_gain(); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +void usrp1_impl::tx_codec_set(const wax::obj &key_, const wax::obj &val, dboard_slot_t dboard_slot) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the set request conditioned on the key + switch(key.as<codec_prop_t>()){ + case CODEC_PROP_GAIN_I: //only one gain for I and Q + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + _codec_ctrls[dboard_slot]->set_tx_pga_gain(val.as<float>()); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp new file mode 100644 index 000000000..b2221e221 --- /dev/null +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -0,0 +1,371 @@ +// +// Copyright 2010 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 "usrp1_iface.hpp" +#include "usrp1_impl.hpp" +#include "fpga_regs_common.h" +#include "usrp_spi_defs.h" +#include "fpga_regs_standard.h" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class usrp1_dboard_iface : public dboard_iface { +public: + + usrp1_dboard_iface(usrp1_iface::sptr iface, + usrp1_clock_ctrl::sptr clock, + usrp1_codec_ctrl::sptr codec, + usrp1_impl::dboard_slot_t dboard_slot, + const dboard_id_t &rx_dboard_id + ): + _dboard_slot(dboard_slot), + _rx_dboard_id(rx_dboard_id) + { + _iface = iface; + _clock = clock; + _codec = codec; + + //init the clock rate shadows + this->set_clock_rate(UNIT_RX, _clock->get_master_clock_freq()); + this->set_clock_rate(UNIT_TX, _clock->get_master_clock_freq()); + } + + ~usrp1_dboard_iface() + { + /* NOP */ + } + + special_props_t get_special_props() + { + special_props_t props; + props.soft_clock_divider = true; + props.mangle_i2c_addrs = (_dboard_slot == usrp1_impl::DBOARD_SLOT_B); + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, float); + float read_aux_adc(unit_t, aux_adc_t); + + void set_pin_ctrl(unit_t, boost::uint16_t); + void set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); + void set_gpio_ddr(unit_t, boost::uint16_t); + void write_gpio(unit_t, boost::uint16_t); + void set_gpio_debug(unit_t, int); + boost::uint16_t read_gpio(unit_t); + + void write_i2c(boost::uint8_t, const byte_vector_t &); + byte_vector_t read_i2c(boost::uint8_t, size_t); + + void write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits); + + boost::uint32_t read_write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits); + + void set_clock_rate(unit_t, double); + std::vector<double> get_clock_rates(unit_t); + double get_clock_rate(unit_t); + void set_clock_enabled(unit_t, bool); + +private: + usrp1_iface::sptr _iface; + usrp1_clock_ctrl::sptr _clock; + usrp1_codec_ctrl::sptr _codec; + uhd::dict<unit_t, double> _clock_rates; + const usrp1_impl::dboard_slot_t _dboard_slot; + const dboard_id_t &_rx_dboard_id; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr usrp1_impl::make_dboard_iface(usrp1_iface::sptr iface, + usrp1_clock_ctrl::sptr clock, + usrp1_codec_ctrl::sptr codec, + dboard_slot_t dboard_slot, + const dboard_id_t &rx_dboard_id +){ + return dboard_iface::sptr(new usrp1_dboard_iface( + iface, clock, codec, dboard_slot, rx_dboard_id + )); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +static const dboard_id_t dbsrx_classic_id(0x0002); + +/* + * Daughterboard reference clock register + * + * Bit 7 - 1 turns on refclk, 0 allows IO use + * Bits 6:0 - Divider value + */ +void usrp1_dboard_iface::set_clock_rate(unit_t unit, double rate) +{ + assert_has(this->get_clock_rates(unit), rate, "dboard clock rate"); + _clock_rates[unit] = rate; + + if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ + size_t divider = size_t(rate/_clock->get_master_clock_freq()); + switch(_dboard_slot){ + case usrp1_impl::DBOARD_SLOT_A: + _iface->poke32(FR_RX_A_REFCLK, (divider & 0x7f) | 0x80); + break; + + case usrp1_impl::DBOARD_SLOT_B: + _iface->poke32(FR_RX_B_REFCLK, (divider & 0x7f) | 0x80); + break; + } + } +} + +std::vector<double> usrp1_dboard_iface::get_clock_rates(unit_t unit) +{ + std::vector<double> rates; + if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ + for (size_t div = 1; div <= 127; div++) + rates.push_back(_clock->get_master_clock_freq() / div); + } + else{ + rates.push_back(_clock->get_master_clock_freq()); + } + return rates; +} + +double usrp1_dboard_iface::get_clock_rate(unit_t unit) +{ + return _clock_rates[unit]; +} + +void usrp1_dboard_iface::set_clock_enabled(unit_t, bool) +{ + //TODO we can only enable for special case anyway... +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp1_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint16_t value) +{ + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_MASK_1, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_MASK_3, value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_MASK_0, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_MASK_2, value); + break; + } +} + +void usrp1_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint16_t value) +{ + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_OE_1, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_OE_3, 0xffff0000 | value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_OE_0, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_OE_2, 0xffff0000 | value); + break; + } +} + +void usrp1_dboard_iface::write_gpio(unit_t unit, boost::uint16_t value) +{ + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_IO_1, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_IO_3, 0xffff0000 | value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_IO_0, 0xffff0000 | value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_IO_2, 0xffff0000 | value); + break; + } +} + +void usrp1_dboard_iface::set_gpio_debug(unit_t, int) +{ + /* NOP */ +} + +boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit) +{ + boost::uint32_t out_value; + + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + out_value = _iface->peek32(1); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + out_value = _iface->peek32(2); + else + UHD_THROW_INVALID_CODE_PATH(); + + switch(unit) { + case UNIT_RX: + return (boost::uint16_t)((out_value >> 16) & 0x0000ffff); + case UNIT_TX: + return (boost::uint16_t)((out_value >> 0) & 0x0000ffff); + } + UHD_ASSERT_THROW(false); +} + +void usrp1_dboard_iface::set_atr_reg(unit_t unit, + atr_reg_t atr, boost::uint16_t value) +{ + // Ignore unsupported states + if ((atr == ATR_REG_IDLE) || (atr == ATR_REG_FULL_DUPLEX)) + return; + + switch(unit) { + case UNIT_RX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_RXVAL_1, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_RXVAL_3, value); + break; + case UNIT_TX: + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + _iface->poke32(FR_ATR_TXVAL_0, value); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + _iface->poke32(FR_ATR_TXVAL_2, value); + break; + } +} +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \param slot the side (A or B) the dboard is attached + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit, + usrp1_impl::dboard_slot_t slot) +{ + switch(unit) { + case dboard_iface::UNIT_TX: + if (slot == usrp1_impl::DBOARD_SLOT_A) + return SPI_ENABLE_TX_A; + else if (slot == usrp1_impl::DBOARD_SLOT_B) + return SPI_ENABLE_TX_B; + else + break; + case dboard_iface::UNIT_RX: + if (slot == usrp1_impl::DBOARD_SLOT_A) + return SPI_ENABLE_RX_A; + else if (slot == usrp1_impl::DBOARD_SLOT_B) + return SPI_ENABLE_RX_B; + else + break; + } + throw std::invalid_argument("unknown unit type"); +} + +void usrp1_dboard_iface::write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits) +{ + _iface->transact_spi(unit_to_otw_spi_dev(unit, _dboard_slot), + config, data, num_bits, false); +} + +boost::uint32_t usrp1_dboard_iface::read_write_spi(unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits) +{ + return _iface->transact_spi(unit_to_otw_spi_dev(unit, _dboard_slot), + config, data, num_bits, true); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp1_dboard_iface::write_i2c(boost::uint8_t addr, + const byte_vector_t &bytes) +{ + return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp1_dboard_iface::read_i2c(boost::uint8_t addr, + size_t num_bytes) +{ + return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp1_dboard_iface::write_aux_dac(dboard_iface::unit_t, + aux_dac_t which, float value) +{ + //same aux dacs for each unit + static const uhd::dict<aux_dac_t, usrp1_codec_ctrl::aux_dac_t> + which_to_aux_dac = map_list_of + (AUX_DAC_A, usrp1_codec_ctrl::AUX_DAC_A) + (AUX_DAC_B, usrp1_codec_ctrl::AUX_DAC_B) + (AUX_DAC_C, usrp1_codec_ctrl::AUX_DAC_C) + (AUX_DAC_D, usrp1_codec_ctrl::AUX_DAC_D); + + _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +float usrp1_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, + aux_adc_t which) +{ + static const + uhd::dict<unit_t, uhd::dict<aux_adc_t, usrp1_codec_ctrl::aux_adc_t> > + unit_to_which_to_aux_adc = map_list_of(UNIT_RX, map_list_of + (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A1) + (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B1)) + (UNIT_TX, map_list_of + (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A2) + (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B2)); + + return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/usrp1/dboard_impl.cpp b/host/lib/usrp/usrp1/dboard_impl.cpp new file mode 100644 index 000000000..3a8480e1b --- /dev/null +++ b/host/lib/usrp/usrp1/dboard_impl.cpp @@ -0,0 +1,217 @@ +// +// Copyright 2010 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 "usrp1_impl.hpp" +#include "usrp_i2c_addr.h" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +static boost::uint8_t get_rx_ee_addr(usrp1_impl::dboard_slot_t dboard_slot){ + switch(dboard_slot){ + case usrp1_impl::DBOARD_SLOT_A: return I2C_ADDR_RX_A; + case usrp1_impl::DBOARD_SLOT_B: return I2C_ADDR_RX_B; + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +static boost::uint8_t get_tx_ee_addr(usrp1_impl::dboard_slot_t dboard_slot){ + switch(dboard_slot){ + case usrp1_impl::DBOARD_SLOT_A: return I2C_ADDR_TX_A; + case usrp1_impl::DBOARD_SLOT_B: return I2C_ADDR_TX_B; + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +/*********************************************************************** + * Dboard Initialization + **********************************************************************/ +void usrp1_impl::dboard_init(void) +{ + BOOST_FOREACH(dboard_slot_t dboard_slot, _dboard_slots){ + + //read the tx and rx dboard eeproms + _rx_db_eeproms[dboard_slot] = dboard_eeprom_t(_iface->read_eeprom( + get_rx_ee_addr(dboard_slot), 0, dboard_eeprom_t::num_bytes() + )); + + _tx_db_eeproms[dboard_slot] = dboard_eeprom_t(_iface->read_eeprom( + get_tx_ee_addr(dboard_slot), 0, dboard_eeprom_t::num_bytes() + )); + + //create a new dboard interface and manager + _dboard_ifaces[dboard_slot] = make_dboard_iface( + _iface, _clock_ctrl, _codec_ctrls[dboard_slot], + dboard_slot, _rx_db_eeproms[dboard_slot].id + ); + + _dboard_managers[dboard_slot] = dboard_manager::make( + _rx_db_eeproms[dboard_slot].id, + _tx_db_eeproms[dboard_slot].id, + _dboard_ifaces[dboard_slot] + ); + + //setup the dboard proxies + _rx_dboard_proxies[dboard_slot] = wax_obj_proxy::make( + boost::bind(&usrp1_impl::rx_dboard_get, this, _1, _2, dboard_slot), + boost::bind(&usrp1_impl::rx_dboard_set, this, _1, _2, dboard_slot)); + + _tx_dboard_proxies[dboard_slot] = wax_obj_proxy::make( + boost::bind(&usrp1_impl::tx_dboard_get, this, _1, _2, dboard_slot), + boost::bind(&usrp1_impl::tx_dboard_set, this, _1, _2, dboard_slot)); + } + +} + +/*********************************************************************** + * RX Dboard Get + **********************************************************************/ +void usrp1_impl::rx_dboard_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<dboard_prop_t>()){ + case DBOARD_PROP_NAME: + val = str(boost::format("usrp1 dboard (rx unit) - %c") % char(dboard_slot)); + return; + + case DBOARD_PROP_SUBDEV: + val = _dboard_managers[dboard_slot]->get_rx_subdev(key.name); + return; + + case DBOARD_PROP_SUBDEV_NAMES: + val = _dboard_managers[dboard_slot]->get_rx_subdev_names(); + return; + + case DBOARD_PROP_DBOARD_ID: + val = _rx_db_eeproms[dboard_slot].id; + return; + + case DBOARD_PROP_DBOARD_IFACE: + val = _dboard_ifaces[dboard_slot]; + return; + + case DBOARD_PROP_CODEC: + val = _rx_codec_proxies[dboard_slot]->get_link(); + return; + + case DBOARD_PROP_GAIN_GROUP: + val = make_gain_group( + _dboard_managers[dboard_slot]->get_rx_subdev(key.name), + _rx_codec_proxies[dboard_slot]->get_link(), + GAIN_GROUP_POLICY_RX + ); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * RX Dboard Set + **********************************************************************/ +void usrp1_impl::rx_dboard_set(const wax::obj &key, const wax::obj &val, dboard_slot_t dboard_slot) +{ + switch(key.as<dboard_prop_t>()) { + case DBOARD_PROP_DBOARD_ID: + _rx_db_eeproms[dboard_slot].id = val.as<dboard_id_t>(); + _iface->write_eeprom( + get_rx_ee_addr(dboard_slot), 0, + _rx_db_eeproms[dboard_slot].get_eeprom_bytes() + ); + return; + + default: + UHD_THROW_PROP_SET_ERROR(); + } +} + +/*********************************************************************** + * TX Dboard Get + **********************************************************************/ +void usrp1_impl::tx_dboard_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<dboard_prop_t>()){ + case DBOARD_PROP_NAME: + val = str(boost::format("usrp1 dboard (tx unit) - %c") % char(dboard_slot)); + return; + + case DBOARD_PROP_SUBDEV: + val = _dboard_managers[dboard_slot]->get_tx_subdev(key.name); + return; + + case DBOARD_PROP_SUBDEV_NAMES: + val = _dboard_managers[dboard_slot]->get_tx_subdev_names(); + return; + + case DBOARD_PROP_DBOARD_ID: + val = _tx_db_eeproms[dboard_slot].id; + return; + + case DBOARD_PROP_DBOARD_IFACE: + val = _dboard_ifaces[dboard_slot]; + return; + + case DBOARD_PROP_CODEC: + val = _tx_codec_proxies[dboard_slot]->get_link(); + return; + + case DBOARD_PROP_GAIN_GROUP: + val = make_gain_group( + _dboard_managers[dboard_slot]->get_tx_subdev(key.name), + _tx_codec_proxies[dboard_slot]->get_link(), + GAIN_GROUP_POLICY_TX + ); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * TX Dboard Set + **********************************************************************/ +void usrp1_impl::tx_dboard_set(const wax::obj &key, const wax::obj &val, dboard_slot_t dboard_slot) +{ + switch(key.as<dboard_prop_t>()) { + case DBOARD_PROP_DBOARD_ID: + _tx_db_eeproms[dboard_slot].id = val.as<dboard_id_t>(); + _iface->write_eeprom( + get_tx_ee_addr(dboard_slot), 0, + _tx_db_eeproms[dboard_slot].get_eeprom_bytes() + ); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} diff --git a/host/lib/usrp/usrp1/dsp_impl.cpp b/host/lib/usrp/usrp1/dsp_impl.cpp new file mode 100644 index 000000000..d5a88fa2d --- /dev/null +++ b/host/lib/usrp/usrp1/dsp_impl.cpp @@ -0,0 +1,205 @@ +// +// Copyright 2010 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 "usrp1_impl.hpp" +#include "fpga_regs_standard.h" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * RX DDC Initialization + **********************************************************************/ +void usrp1_impl::rx_dsp_init(void) +{ + _rx_dsp_proxy = wax_obj_proxy::make( + boost::bind(&usrp1_impl::rx_dsp_get, this, _1, _2), + boost::bind(&usrp1_impl::rx_dsp_set, this, _1, _2)); + + rx_dsp_set(DSP_PROP_HOST_RATE, _clock_ctrl->get_master_clock_freq() / 16); +} + +/*********************************************************************** + * RX DDC Get + **********************************************************************/ +void usrp1_impl::rx_dsp_get(const wax::obj &key, wax::obj &val) +{ + switch(key.as<dsp_prop_t>()){ + case DSP_PROP_NAME: + val = std::string("usrp1 ddc0"); + return; + + case DSP_PROP_OTHERS: + val = prop_names_t(); + return; + + case DSP_PROP_FREQ_SHIFT: + val = _rx_dsp_freq; + return; + + case DSP_PROP_CODEC_RATE: + val = _clock_ctrl->get_master_clock_freq(); + return; + + case DSP_PROP_HOST_RATE: + val = _clock_ctrl->get_master_clock_freq()/_rx_dsp_decim; + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } + +} + +/*********************************************************************** + * RX DDC Set + **********************************************************************/ +void usrp1_impl::rx_dsp_set(const wax::obj &key, const wax::obj &val) +{ + switch(key.as<dsp_prop_t>()) { + case DSP_PROP_FREQ_SHIFT: { + double new_freq = val.as<double>(); + boost::uint32_t reg_word = dsp_type1::calc_cordic_word_and_update( + new_freq, _clock_ctrl->get_master_clock_freq()); + + //TODO TODO TODO TODO TODO TODO TODO TODO TODO + // + // Handle multiple receive channels / DDC's + // + //TODO TODO TODO TODO TODO TODO TODO TODO TODO + _iface->poke32(FR_RX_FREQ_0, reg_word); + _iface->poke32(FR_RX_FREQ_1, reg_word); + _iface->poke32(FR_RX_FREQ_2, reg_word); + _iface->poke32(FR_RX_FREQ_3, reg_word); + + _rx_dsp_freq = new_freq; + return; + } + case DSP_PROP_HOST_RATE: { + unsigned int rate = + _clock_ctrl->get_master_clock_freq() / val.as<double>(); + + if ((rate & 0x01) || (rate < 4) || (rate > 256)) { + std::cerr << "Decimation must be even and between 4 and 256" + << std::endl; + return; + } + + _rx_dsp_decim = rate; + //TODO Poll every 100ms. Make it selectable? + _rx_samps_per_poll_interval = 0.1 * _clock_ctrl->get_master_clock_freq() / rate; + + _iface->poke32(FR_DECIM_RATE, _rx_dsp_decim/2 - 1); + } + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } + +} + +/*********************************************************************** + * TX DUC Initialization + **********************************************************************/ +void usrp1_impl::tx_dsp_init(void) +{ + _tx_dsp_proxy = wax_obj_proxy::make( + boost::bind(&usrp1_impl::tx_dsp_get, this, _1, _2), + boost::bind(&usrp1_impl::tx_dsp_set, this, _1, _2)); + + //initial config and update + tx_dsp_set(DSP_PROP_HOST_RATE, _clock_ctrl->get_master_clock_freq() * 2 / 16); +} + +/*********************************************************************** + * TX DUC Get + **********************************************************************/ +void usrp1_impl::tx_dsp_get(const wax::obj &key, wax::obj &val) +{ + switch(key.as<dsp_prop_t>()) { + case DSP_PROP_NAME: + val = std::string("usrp1 duc0"); + return; + + case DSP_PROP_OTHERS: + val = prop_names_t(); //empty + return; + + case DSP_PROP_FREQ_SHIFT: + val = _tx_dsp_freq; + return; + + case DSP_PROP_CODEC_RATE: + val = _clock_ctrl->get_master_clock_freq() * 2; + return; + + case DSP_PROP_HOST_RATE: + val = _clock_ctrl->get_master_clock_freq() * 2 / _tx_dsp_interp; + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } + +} + +/*********************************************************************** + * TX DUC Set + **********************************************************************/ +void usrp1_impl::tx_dsp_set(const wax::obj &key, const wax::obj &val) +{ + switch(key.as<dsp_prop_t>()) { + + //TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO + // + // Set both codec frequencies until we have duality properties + // + //TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO + case DSP_PROP_FREQ_SHIFT: { + double new_freq = val.as<double>(); + _codec_ctrls[DBOARD_SLOT_A]->set_duc_freq(new_freq); + _codec_ctrls[DBOARD_SLOT_B]->set_duc_freq(new_freq); + _tx_dsp_freq = new_freq; + return; + } + + case DSP_PROP_HOST_RATE: { + unsigned int rate = + _clock_ctrl->get_master_clock_freq() * 2 / val.as<double>(); + + if ((rate & 0x01) || (rate < 8) || (rate > 512)) { + std::cerr << "Interpolation rate must be even and between 8 and 512" + << std::endl; + return; + } + + _tx_dsp_interp = rate; + + //TODO Poll every 100ms. Make it selectable? + _tx_samps_per_poll_interval = 0.1 * _clock_ctrl->get_master_clock_freq() * 2 / rate; + + _iface->poke32(FR_INTERP_RATE, _tx_dsp_interp / 4 - 1); + return; + } + default: UHD_THROW_PROP_SET_ERROR(); + } + +} diff --git a/host/lib/usrp/usrp1/io_impl.cpp b/host/lib/usrp/usrp1/io_impl.cpp new file mode 100644 index 000000000..920c47b30 --- /dev/null +++ b/host/lib/usrp/usrp1/io_impl.cpp @@ -0,0 +1,326 @@ +// +// Copyright 2010 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 "../../transport/vrt_packet_handler.hpp" +#include "usrp_commands.h" +#include "usrp1_impl.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/convert_types.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +struct usrp1_send_state { + uhd::transport::managed_send_buffer::sptr send_buff; + size_t bytes_written; + size_t underrun_poll_samp_count; + + size_t bytes_free() + { + if (send_buff != NULL) + return send_buff->size() - bytes_written; + else + return 0; + } +}; + +struct usrp1_recv_state { + uhd::transport::managed_recv_buffer::sptr recv_buff; + size_t bytes_read; + size_t overrun_poll_samp_count; + + size_t bytes_avail() + { + if (recv_buff != NULL) + return recv_buff->size() - bytes_read; + else + return 0; + } +}; + +/*********************************************************************** + * IO Implementation Details + **********************************************************************/ +struct usrp1_impl::io_impl { + io_impl(); + ~io_impl(void); + + //state handling for buffer management + usrp1_recv_state recv_state; + usrp1_send_state send_state; + + //send transport management + bool get_send_buffer(zero_copy_if::sptr zc_if); + size_t copy_convert_send_samps(const void *buff, size_t num_samps, + size_t sample_offset, const io_type_t io_type, + otw_type_t otw_type); + bool conditional_buff_commit(bool force); + bool check_underrun(usrp_ctrl::sptr ctrl_if, + size_t poll_interval, bool force); + + //recv transport management + bool get_recv_buffer(zero_copy_if::sptr zc_if); + size_t copy_convert_recv_samps(void *buff, size_t num_samps, + size_t sample_offset, const io_type_t io_type, + otw_type_t otw_type); + bool check_overrun(usrp_ctrl::sptr ctrl_if, + size_t poll_interval, bool force); +}; + +usrp1_impl::io_impl::io_impl() +{ + send_state.send_buff = uhd::transport::managed_send_buffer::sptr(); + recv_state.recv_buff = uhd::transport::managed_recv_buffer::sptr(); +} + +usrp1_impl::io_impl::~io_impl(void) +{ + /* NOP */ +} + +void usrp1_impl::io_init(void) +{ + _rx_otw_type.width = 16; + _rx_otw_type.shift = 0; + _rx_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + + _tx_otw_type.width = 16; + _tx_otw_type.shift = 0; + _tx_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + + _io_impl = UHD_PIMPL_MAKE(io_impl, ()); +} + +/*********************************************************************** + * Data Send + **********************************************************************/ +bool usrp1_impl::io_impl::get_send_buffer(zero_copy_if::sptr zc_if) +{ + if (send_state.send_buff == NULL) { + + send_state.send_buff = zc_if->get_send_buff(); + if (send_state.send_buff == NULL) + return false; + + send_state.bytes_written = 0; + } + + return true; +} + +size_t usrp1_impl::io_impl::copy_convert_send_samps(const void *buff, + size_t num_samps, + size_t sample_offset, + const io_type_t io_type, + otw_type_t otw_type) +{ + UHD_ASSERT_THROW(send_state.bytes_free() % otw_type.get_sample_size() == 0); + + size_t samps_free = send_state.bytes_free() / otw_type.get_sample_size(); + size_t copy_samps = std::min(num_samps - sample_offset, samps_free); + + const boost::uint8_t *io_mem = + reinterpret_cast<const boost::uint8_t *>(buff); + + boost::uint8_t *otw_mem = send_state.send_buff->cast<boost::uint8_t *>(); + + convert_io_type_to_otw_type(io_mem + sample_offset * io_type.size, + io_type, + otw_mem + send_state.bytes_written, + otw_type, + copy_samps); + + send_state.bytes_written += copy_samps * otw_type.get_sample_size(); + send_state.underrun_poll_samp_count += copy_samps; + + return copy_samps; +} + +bool usrp1_impl::io_impl::conditional_buff_commit(bool force) +{ + if (send_state.bytes_written % 512) + return false; + + if (force || send_state.bytes_free() == 0) { + send_state.send_buff->commit(send_state.bytes_written); + send_state.send_buff = uhd::transport::managed_send_buffer::sptr(); + return true; + } + + return false; +} + +bool usrp1_impl::io_impl::check_underrun(usrp_ctrl::sptr ctrl_if, + size_t poll_interval, + bool force) +{ + unsigned char underrun = 0; + + bool ready_to_poll = send_state.underrun_poll_samp_count > poll_interval; + + if (force || ready_to_poll) { + int ret = ctrl_if->usrp_control_read(VRQ_GET_STATUS, + 0, + GS_TX_UNDERRUN, + &underrun, sizeof(char)); + if (ret < 0) + std::cerr << "USRP: underrun check failed" << std::endl; + if (underrun) + std::cerr << "Uu"; + + send_state.underrun_poll_samp_count = 0; + } + + return (bool) underrun; +} + +size_t usrp1_impl::send(const std::vector<const void *> &buffs, + size_t num_samps, + const tx_metadata_t &, + const io_type_t &io_type, + send_mode_t) +{ + UHD_ASSERT_THROW(buffs.size() == 1); + + size_t total_samps_sent = 0; + + while (total_samps_sent < num_samps) { + if (!_io_impl->get_send_buffer(_data_transport)) + return 0; + + total_samps_sent += _io_impl->copy_convert_send_samps(buffs[0], + num_samps, + total_samps_sent, + io_type, + _tx_otw_type); + if (total_samps_sent == num_samps) + _io_impl->conditional_buff_commit(true); + else + _io_impl->conditional_buff_commit(false); + + _io_impl->check_underrun(_ctrl_transport, + _tx_samps_per_poll_interval, false); + } + + return total_samps_sent; +} + +/*********************************************************************** + * Data Recv + **********************************************************************/ +bool usrp1_impl::io_impl::get_recv_buffer(zero_copy_if::sptr zc_if) +{ + if ((recv_state.recv_buff == NULL) || (recv_state.bytes_avail() == 0)) { + + recv_state.recv_buff = zc_if->get_recv_buff(); + if (recv_state.recv_buff == NULL) + return false; + + recv_state.bytes_read = 0; + } + + return true; +} + +size_t usrp1_impl::io_impl::copy_convert_recv_samps(void *buff, + size_t num_samps, + size_t sample_offset, + const io_type_t io_type, + otw_type_t otw_type) +{ + UHD_ASSERT_THROW(recv_state.bytes_avail() % otw_type.get_sample_size() == 0); + + size_t samps_avail = recv_state.bytes_avail() / otw_type.get_sample_size(); + size_t copy_samps = std::min(num_samps - sample_offset, samps_avail); + + const boost::uint8_t *otw_mem = + recv_state.recv_buff->cast<const boost::uint8_t *>(); + + boost::uint8_t *io_mem = reinterpret_cast<boost::uint8_t *>(buff); + + convert_otw_type_to_io_type(otw_mem + recv_state.bytes_read, + otw_type, + io_mem + sample_offset * io_type.size, + io_type, + copy_samps); + + recv_state.bytes_read += copy_samps * otw_type.get_sample_size(); + recv_state.overrun_poll_samp_count += copy_samps; + + return copy_samps; +} + +bool usrp1_impl::io_impl::check_overrun(usrp_ctrl::sptr ctrl_if, + size_t poll_interval, + bool force) +{ + unsigned char overrun = 0; + + bool ready_to_poll = recv_state.overrun_poll_samp_count > poll_interval; + + if (force || ready_to_poll) { + int ret = ctrl_if->usrp_control_read(VRQ_GET_STATUS, + 0, + GS_RX_OVERRUN, + &overrun, sizeof(char)); + if (ret < 0) + std::cerr << "USRP: overrrun check failed" << std::endl; + if (overrun) + std::cerr << "Oo"; + + recv_state.overrun_poll_samp_count = 0; + } + + return (bool) overrun; +} + +size_t usrp1_impl::recv(const std::vector<void *> &buffs, + size_t num_samps, + rx_metadata_t &, + const io_type_t &io_type, + recv_mode_t, + size_t) +{ + UHD_ASSERT_THROW(buffs.size() == 1); + + size_t total_samps_recv = 0; + + while (total_samps_recv < num_samps) { + + if (!_io_impl->get_recv_buffer(_data_transport)) + return 0; + + total_samps_recv += _io_impl->copy_convert_recv_samps(buffs[0], + num_samps, + total_samps_recv, + io_type, + _rx_otw_type); + _io_impl->check_overrun(_ctrl_transport, + _rx_samps_per_poll_interval, false); + } + + return total_samps_recv; +} diff --git a/host/lib/usrp/usrp1/mboard_impl.cpp b/host/lib/usrp/usrp1/mboard_impl.cpp new file mode 100644 index 000000000..75129c32f --- /dev/null +++ b/host/lib/usrp/usrp1/mboard_impl.cpp @@ -0,0 +1,348 @@ +// +// Copyright 2010 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 "usrp1_impl.hpp" +#include "usrp_commands.h" +#include "fpga_regs_standard.h" +#include "fpga_regs_common.h" +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Calculate the RX mux value: + * The I and Q mux values are intentionally reversed to flip I and Q + * to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_rx_mux_pair(int adc_for_i, int adc_for_q){ + return (adc_for_i << 2) | (adc_for_q << 0); //shift reversal here +} + +/*! + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * | must be zero | Q3| I3| Q2| I2| Q1| I1| Q0| I0|Z| NCH | + * +-----------------------+-------+-------+-------+-------+-+-----+ + */ +static boost::uint32_t calc_rx_mux( + const subdev_spec_t &subdev_spec, wax::obj mboard +){ + //create look-up-table for mapping dboard name and connection type to ADC flags + static const int ADC0 = 0, ADC1 = 1, ADC2 = 2, ADC3 = 3; + static const uhd::dict<std::string, uhd::dict<subdev_conn_t, int> > name_to_conn_to_flag = boost::assign::map_list_of + ("A", boost::assign::map_list_of + (SUBDEV_CONN_COMPLEX_IQ, calc_rx_mux_pair(ADC0, ADC1)) //I and Q + (SUBDEV_CONN_COMPLEX_QI, calc_rx_mux_pair(ADC1, ADC0)) //I and Q + (SUBDEV_CONN_REAL_I, calc_rx_mux_pair(ADC0, ADC0)) //I and Q (Q identical but ignored Z=1) + (SUBDEV_CONN_REAL_Q, calc_rx_mux_pair(ADC1, ADC1)) //I and Q (Q identical but ignored Z=1) + ) + ("B", boost::assign::map_list_of + (SUBDEV_CONN_COMPLEX_IQ, calc_rx_mux_pair(ADC2, ADC3)) //I and Q + (SUBDEV_CONN_COMPLEX_QI, calc_rx_mux_pair(ADC3, ADC2)) //I and Q + (SUBDEV_CONN_REAL_I, calc_rx_mux_pair(ADC2, ADC2)) //I and Q (Q identical but ignored Z=1) + (SUBDEV_CONN_REAL_Q, calc_rx_mux_pair(ADC3, ADC3)) //I and Q (Q identical but ignored Z=1) + ) + ; + + //extract the number of channels + size_t nchan = subdev_spec.size(); + + //calculate the channel flags + int channel_flags = 0; + size_t num_reals = 0, num_quads = 0; + BOOST_FOREACH(const subdev_spec_pair_t &pair, subdev_spec){ + wax::obj dboard = mboard[named_prop_t(MBOARD_PROP_RX_DBOARD, pair.db_name)]; + wax::obj subdev = dboard[named_prop_t(DBOARD_PROP_SUBDEV, pair.sd_name)]; + subdev_conn_t conn = subdev[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>(); + switch(conn){ + case SUBDEV_CONN_COMPLEX_IQ: + case SUBDEV_CONN_COMPLEX_QI: num_quads++; break; + case SUBDEV_CONN_REAL_I: + case SUBDEV_CONN_REAL_Q: num_reals++; break; + } + channel_flags = (channel_flags << 4) | name_to_conn_to_flag[pair.db_name][conn]; + } + + //calculate Z: + // for all real sources: Z = 1 + // for all quadrature sources: Z = 0 + // for mixed sources: warning + Z = 0 + int Z = (num_quads > 0)? 0 : 1; + if (num_quads != 0 and num_reals != 0) uhd::print_warning( + "Mixing real and quadrature rx subdevices is not supported.\n" + "The Q input to the real source(s) will be non-zero.\n" + ); + + //calculate the rx mux value + return ((channel_flags & 0xffff) << 4) | ((Z & 0x1) << 3) | ((nchan & 0x7) << 0); +} + +/*********************************************************************** + * Calculate the TX mux value: + * The I and Q mux values are intentionally reversed to flip I and Q + * to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_tx_mux_pair(int chn_for_i, int chn_for_q){ + return (chn_for_i << 4) | (chn_for_q << 0); //shift reversal here +} + +/*! + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * | | DAC1Q | DAC1I | DAC0Q | DAC0I |0| NCH | + * +-----------------------------------------------+-------+-+-----+ + */ +static boost::uint32_t calc_tx_mux( + const subdev_spec_t &subdev_spec, wax::obj mboard +){ + //create look-up-table for mapping channel number and connection type to flags + static const int ENB = 1 << 3, CHAN_I0 = 0, CHAN_Q0 = 1, CHAN_I1 = 2, CHAN_Q1 = 3; + static const uhd::dict<size_t, uhd::dict<subdev_conn_t, int> > chan_to_conn_to_flag = boost::assign::map_list_of + (0, boost::assign::map_list_of + (SUBDEV_CONN_COMPLEX_IQ, calc_tx_mux_pair(CHAN_I0 | ENB, CHAN_Q0 | ENB)) + (SUBDEV_CONN_COMPLEX_QI, calc_tx_mux_pair(CHAN_Q0 | ENB, CHAN_I0 | ENB)) + (SUBDEV_CONN_REAL_I, calc_tx_mux_pair(CHAN_I0 | ENB, 0 )) + (SUBDEV_CONN_REAL_Q, calc_tx_mux_pair(0, CHAN_I0 | ENB)) + ) + (1, boost::assign::map_list_of + (SUBDEV_CONN_COMPLEX_IQ, calc_tx_mux_pair(CHAN_I1 | ENB, CHAN_Q1 | ENB)) + (SUBDEV_CONN_COMPLEX_QI, calc_tx_mux_pair(CHAN_Q1 | ENB, CHAN_I1 | ENB)) + (SUBDEV_CONN_REAL_I, calc_tx_mux_pair(CHAN_I1 | ENB, 0 )) + (SUBDEV_CONN_REAL_Q, calc_tx_mux_pair(0, CHAN_I1 | ENB)) + ) + ; + + //extract the number of channels + size_t nchan = subdev_spec.size(); + + //calculate the channel flags + int channel_flags = 0, chan = 0; + BOOST_FOREACH(const subdev_spec_pair_t &pair, subdev_spec){ + wax::obj dboard = mboard[named_prop_t(MBOARD_PROP_TX_DBOARD, pair.db_name)]; + wax::obj subdev = dboard[named_prop_t(DBOARD_PROP_SUBDEV, pair.sd_name)]; + subdev_conn_t conn = subdev[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>(); + + //combine the channel flags: shift for slot A vs B + if (pair.db_name == "A") channel_flags |= chan_to_conn_to_flag[chan][conn] << 0; + if (pair.db_name == "B") channel_flags |= chan_to_conn_to_flag[chan][conn] << 8; + + //increment for the next channel + chan++; + } + + //calculate the tx mux value + return ((channel_flags & 0xffff) << 4) | ((nchan & 0x7) << 0); +} + +/*! + * Capabilities Register + * + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------------------------------+-+-----+-+-----+ + * | Reserved |T|DUCs |R|DDCs | + * +-----------------------------------------------+-+-----+-+-----+ + */ +static int num_ddcs(boost::uint32_t regval) +{ + return (regval >> 0) & 0x0007; +} + +static int num_ducs(boost::uint32_t regval) +{ + return (regval >> 4) & 0x0007; +} + +static bool has_rx_halfband(boost::uint32_t regval) +{ + return (regval >> 3) & 0x0001; +} + +static bool has_tx_halfband(boost::uint32_t regval) +{ + return (regval >> 7) & 0x0001; +} + +/*********************************************************************** + * Mboard Initialization + **********************************************************************/ +void usrp1_impl::mboard_init(void) +{ + _mboard_proxy = wax_obj_proxy::make( + boost::bind(&usrp1_impl::mboard_get, this, _1, _2), + boost::bind(&usrp1_impl::mboard_set, this, _1, _2)); + + // Normal mode with no loopback or Rx counting + _iface->poke32(FR_MODE, 0x00000000); + _iface->poke32(FR_DEBUG_EN, 0x00000000); + _iface->poke32(FR_RX_SAMPLE_RATE_DIV, 0x00000001); + _iface->poke32(FR_TX_SAMPLE_RATE_DIV, 0x00000003); + _iface->poke32(FR_DC_OFFSET_CL_EN, 0x0000000f); + + // Reset offset correction registers + _iface->poke32(FR_ADC_OFFSET_0, 0x00000000); + _iface->poke32(FR_ADC_OFFSET_1, 0x00000000); + _iface->poke32(FR_ADC_OFFSET_2, 0x00000000); + _iface->poke32(FR_ADC_OFFSET_3, 0x00000000); + + // Set default for RX format to 16-bit I&Q and no half-band filter bypass + _iface->poke32(FR_RX_FORMAT, 0x00000300); + + // Set default for TX format to 16-bit I&Q + _iface->poke32(FR_TX_FORMAT, 0x00000000); + + // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO + // + // Do something useful with the capabilities register + // + boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); + std::cout << "USRP1 Capabilities" << std::endl; + std::cout << " number of duc's: " << num_ddcs(regval) << std::endl; + std::cout << " number of ddc's: " << num_ducs(regval) << std::endl; + std::cout << " rx halfband: " << has_rx_halfband(regval) << std::endl; + std::cout << " tx halfband: " << has_tx_halfband(regval) << std::endl; +} + +void usrp1_impl::issue_stream_cmd(const stream_cmd_t &stream_cmd) +{ + if (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS) { + _iface->write_firmware_cmd(VRQ_FPGA_SET_RX_ENABLE, true, 0, 0, 0); + } + + if (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS) { + _iface->write_firmware_cmd(VRQ_FPGA_SET_RX_ENABLE, false, 0, 0, 0); + } +} + +/*********************************************************************** + * Mboard Get + **********************************************************************/ +static prop_names_t dboard_names = boost::assign::list_of("A")("B"); + +void usrp1_impl::mboard_get(const wax::obj &key_, wax::obj &val) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<mboard_prop_t>()){ + case MBOARD_PROP_NAME: + val = std::string("usrp1 mboard"); + return; + + case MBOARD_PROP_OTHERS: + val = prop_names_t(); + return; + + case MBOARD_PROP_RX_DBOARD: + uhd::assert_has(dboard_names, key.name, "dboard name"); + if (key.name == "A") val = _rx_dboard_proxies[DBOARD_SLOT_A]->get_link(); + if (key.name == "B") val = _rx_dboard_proxies[DBOARD_SLOT_B]->get_link(); + return; + + case MBOARD_PROP_RX_DBOARD_NAMES: + val = dboard_names; + return; + + case MBOARD_PROP_TX_DBOARD: + uhd::assert_has(dboard_names, key.name, "dboard name"); + if (key.name == "A") val = _tx_dboard_proxies[DBOARD_SLOT_A]->get_link(); + if (key.name == "B") val = _tx_dboard_proxies[DBOARD_SLOT_B]->get_link(); + return; + + case MBOARD_PROP_TX_DBOARD_NAMES: + val = dboard_names; + return; + + case MBOARD_PROP_RX_DSP: + UHD_ASSERT_THROW(key.name == ""); + val = _rx_dsp_proxy->get_link(); + return; + + case MBOARD_PROP_RX_DSP_NAMES: + val = prop_names_t(1, ""); + return; + + case MBOARD_PROP_TX_DSP: + UHD_ASSERT_THROW(key.name == ""); + val = _tx_dsp_proxy->get_link(); + return; + + case MBOARD_PROP_TX_DSP_NAMES: + val = prop_names_t(1, ""); + return; + + case MBOARD_PROP_CLOCK_CONFIG: + val = _clock_config; + return; + + case MBOARD_PROP_RX_SUBDEV_SPEC: + val = _rx_subdev_spec; + return; + + case MBOARD_PROP_TX_SUBDEV_SPEC: + val = _tx_subdev_spec; + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * Mboard Set + **********************************************************************/ +void usrp1_impl::mboard_set(const wax::obj &key, const wax::obj &val) +{ + //handle the get request conditioned on the key + switch(key.as<mboard_prop_t>()){ + + case MBOARD_PROP_STREAM_CMD: + issue_stream_cmd(val.as<stream_cmd_t>()); + return; + + case MBOARD_PROP_RX_SUBDEV_SPEC: + _rx_subdev_spec = val.as<subdev_spec_t>(); + verify_rx_subdev_spec(_rx_subdev_spec, _mboard_proxy->get_link()); + //sanity check + UHD_ASSERT_THROW(_rx_subdev_spec.size() <= 2); + //set the mux and set the number of rx channels + _iface->poke32(FR_RX_MUX, calc_rx_mux(_rx_subdev_spec, _mboard_proxy->get_link())); + return; + + case MBOARD_PROP_TX_SUBDEV_SPEC: + _tx_subdev_spec = val.as<subdev_spec_t>(); + verify_tx_subdev_spec(_tx_subdev_spec, _mboard_proxy->get_link()); + //sanity check + UHD_ASSERT_THROW(_tx_subdev_spec.size() <= 2); + //set the mux and set the number of tx channels + _iface->poke32(FR_TX_MUX, calc_tx_mux(_tx_subdev_spec, _mboard_proxy->get_link())); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} diff --git a/host/lib/usrp/usrp1/usrp1_ctrl.cpp b/host/lib/usrp/usrp1/usrp1_ctrl.cpp new file mode 100644 index 000000000..98226b738 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_ctrl.cpp @@ -0,0 +1,385 @@ +// +// Copyright 2010 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 "usrp1_ctrl.hpp" +#include "usrp_commands.h" +#include <uhd/transport/usb_control.hpp> +#include <boost/functional/hash.hpp> +#include <boost/thread/thread.hpp> +#include <iostream> +#include <fstream> +#include <sstream> +#include <string> +#include <vector> + +using namespace uhd; + +enum firmware_code { + USRP_FPGA_LOAD_SUCCESS, + USRP_FPGA_ALREADY_LOADED, + USRP_FIRMWARE_LOAD_SUCCESS, + USRP_FIRMWARE_ALREADY_LOADED +}; + +#define FX2_FIRMWARE_LOAD 0xa0 + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +/*! + * Create a file hash + * The hash will be used to identify the loaded firmware and fpga image + * \param filename file used to generate hash value + * \return hash value in a size_t type + */ +static size_t generate_hash(const char *filename) +{ + std::ifstream file(filename); + if (!file) + std::cerr << "error: cannot open input file " << filename << std::endl; + + size_t hash = 0; + + char ch; + while (file.get(ch)) { + boost::hash_combine(hash, ch); + } + + if (!file.eof()) + std::cerr << "error: file error " << filename << std::endl; + + file.close(); + return hash; +} + + +/*! + * Verify checksum of a Intel HEX record + * \param record a line from an Intel HEX file + * \return true if record is valid, false otherwise + */ +static bool checksum(std::string *record) +{ + + size_t len = record->length(); + unsigned int i; + unsigned char sum = 0; + unsigned int val; + + for (i = 1; i < len; i += 2) { + std::istringstream(record->substr(i, 2)) >> std::hex >> val; + sum += val; + } + + if (sum == 0) + return true; + else + return false; +} + + +/*! + * Parse Intel HEX record + * + * \param record a line from an Intel HEX file + * \param len output length of record + * \param addr output address + * \param type output type + * \param data output data + * \return true if record is sucessfully read, false on error + */ +bool parse_record(std::string *record, unsigned int &len, + unsigned int &addr, unsigned int &type, + unsigned char* data) +{ + unsigned int i; + std::string _data; + unsigned int val; + + if (record->substr(0, 1) != ":") + return false; + + std::istringstream(record->substr(1, 2)) >> std::hex >> len; + std::istringstream(record->substr(3, 4)) >> std::hex >> addr; + std::istringstream(record->substr(7, 2)) >> std::hex >> type; + + for (i = 0; i < len; i++) { + std::istringstream(record->substr(9 + 2 * i, 2)) >> std::hex >> val; + data[i] = (unsigned char) val; + } + + return true; +} + + +/*! + * USRP control implementation for device discovery and configuration + */ +class usrp_ctrl_impl : public usrp_ctrl { +public: + usrp_ctrl_impl(uhd::transport::usb_control::sptr ctrl_transport) + { + _ctrl_transport = ctrl_transport; + } + + + ~usrp_ctrl_impl(void) + { + /* NOP */ + } + + + int usrp_load_firmware(std::string filestring, bool force) + { + const char *filename = filestring.c_str(); + + size_t hash = generate_hash(filename); + + size_t loaded_hash; + if (usrp_get_firmware_hash(loaded_hash) < 0) { + std::cerr << "firmware hash retrieval failed" << std::endl; + return -1; + } + + if (!force && (hash == loaded_hash)) + return USRP_FIRMWARE_ALREADY_LOADED; + + //FIXME: verify types + unsigned int len; + unsigned int addr; + unsigned int type; + unsigned char data[512]; + + int ret; + std::ifstream file; + file.open(filename, std::ifstream::in); + + if (!file.good()) { + std::cerr << "cannot open firmware input file" << std::endl; + return -1; + } + + unsigned char reset_y = 1; + unsigned char reset_n = 0; + + //hit the reset line + usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, + &reset_y, 1); + + while (!file.eof()) { + std::string record; + file >> record; + + //check for valid record + if (!checksum(&record) || + !parse_record(&record, len, addr, type, data)) { + std::cerr << "error: bad record" << std::endl; + file.close(); + return -1; + } + + //type 0x00 is data + if (type == 0x00) { + ret = usrp_control_write(FX2_FIRMWARE_LOAD, addr, 0, + data, len); + if (ret < 0) { + std::cerr << "error: usrp_control_write failed: "; + std::cerr << ret << std::endl; + file.close(); + return -1; + } + } + //type 0x00 is end + else if (type == 0x01) { + usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, + &reset_n, 1); + usrp_set_firmware_hash(hash); + file.close(); + + //wait for things to settle + boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); + + return USRP_FIRMWARE_LOAD_SUCCESS; + } + //type anything else is unhandled + else { + std::cerr << "error: unsupported record" << std::endl; + file.close(); + return -1; + } + } + + //file did not end + std::cerr << "error: bad record" << std::endl; + file.close(); + return -1; + } + + + int usrp_load_fpga(std::string filestring) + { + const char *filename = filestring.c_str(); + + size_t hash = generate_hash(filename); + + size_t loaded_hash; + if (usrp_get_fpga_hash(loaded_hash) < 0) { + std::cerr << "fpga hash retrieval failed" << std::endl; + return -1; + } + + if (hash == loaded_hash) + return USRP_FPGA_ALREADY_LOADED; + const int ep0_size = 64; + unsigned char buf[ep0_size]; + int ret; + + FILE *fp; + if ((fp = fopen(filename, "rb")) == NULL) { + std::cerr << "cannot open fpga input file" << std::endl; + fclose(fp); + return -1; + } + + if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_BEGIN) < 0) { + std::cerr << "fpga load error" << std::endl; + fclose(fp); + return -1; + } + + ssize_t n; + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) { + ret = usrp_control_write(VRQ_FPGA_LOAD, 0, FL_XFER, + buf, n); + if (ret != n) { + std::cerr << "fpga load error " << ret << std::endl; + fclose(fp); + return -1; + } + } + + if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_END) < 0) { + std::cerr << "fpga load error" << std::endl; + fclose(fp); + return -1; + } + + usrp_set_fpga_hash(hash); + fclose(fp); + return 0; + } + + + int usrp_set_led(int led_num, bool on) + { + return usrp_control_write_cmd(VRQ_SET_LED, on, led_num); + } + + + int usrp_get_firmware_hash(size_t &hash) + { + return usrp_control_read(0xa0, USRP_HASH_SLOT_0_ADDR, 0, + (unsigned char*) &hash, sizeof(size_t)); + } + + + int usrp_set_firmware_hash(size_t hash) + { + return usrp_control_write(0xa0, USRP_HASH_SLOT_0_ADDR, 0, + (unsigned char*) &hash, sizeof(size_t)); + + } + + + int usrp_get_fpga_hash(size_t &hash) + { + return usrp_control_read(0xa0, USRP_HASH_SLOT_1_ADDR, 0, + (unsigned char*) &hash, sizeof(size_t)); + } + + + int usrp_set_fpga_hash(size_t hash) + { + return usrp_control_write(0xa0, USRP_HASH_SLOT_1_ADDR, 0, + (unsigned char*) &hash, sizeof(size_t)); + } + + int usrp_tx_enable(bool on) + { + return usrp_control_write_cmd(VRQ_FPGA_SET_TX_ENABLE, on, 0); + } + + + int usrp_rx_enable(bool on) + { + return usrp_control_write_cmd(VRQ_FPGA_SET_RX_ENABLE, on, 0); + } + + + int usrp_tx_reset(bool on) + { + return usrp_control_write_cmd(VRQ_FPGA_SET_TX_RESET, on, 0); + } + + + int usrp_control_write(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) + { + return _ctrl_transport->submit(VRT_VENDOR_OUT, // bmReqeustType + request, // bRequest + value, // wValue + index, // wIndex + buff, // data + length); // wLength + } + + + int usrp_control_read(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) + { + return _ctrl_transport->submit(VRT_VENDOR_IN, // bmReqeustType + request, // bRequest + value, // wValue + index, // wIndex + buff, // data + length); // wLength + } + + + int usrp_control_write_cmd(uint8_t request, uint16_t value, uint16_t index) + { + return usrp_control_write(request, value, index, 0, 0); + } + + +private: + uhd::transport::usb_control::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public make function for usrp_ctrl interface + **********************************************************************/ +usrp_ctrl::sptr usrp_ctrl::make(uhd::transport::usb_control::sptr ctrl_transport){ + return sptr(new usrp_ctrl_impl(ctrl_transport)); +} + diff --git a/host/lib/usrp/usrp1/usrp1_ctrl.hpp b/host/lib/usrp/usrp1/usrp1_ctrl.hpp new file mode 100644 index 000000000..deedec4e8 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_ctrl.hpp @@ -0,0 +1,132 @@ +// +// Copyright 2010 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_USRP_CTRL_HPP +#define INCLUDED_USRP_CTRL_HPP + +#include <uhd/transport/usb_control.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class usrp_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp_ctrl> sptr; + + /*! + * Make a usrp control object from a control transport + * \param ctrl_transport a USB control transport + * \return a new usrp control object + */ + static sptr make(uhd::transport::usb_control::sptr ctrl_transport); + + /*! + * Load firmware in Intel HEX Format onto device + * \param filename name of firmware file + * \param force reload firmware if already loaded + * \return 0 on success, error code otherwise + */ + virtual int usrp_load_firmware(std::string filename, + bool force = false) = 0; + + /*! + * Load fpga file onto usrp + * \param filename name of fpga image + * \return 0 on success, error code otherwise + */ + virtual int usrp_load_fpga(std::string filename) = 0; + + /*! + * Set led usrp + * \param led_num which LED to control (0 or 1) + * \param on turn LED on or off + * \return 0 on success, error code otherwise + */ + virtual int usrp_set_led(int led_num, bool on) = 0; + + /*! + * Get firmware hash + * \param hash a size_t hash value + * \return 0 on success, error code otherwise + */ + virtual int usrp_get_firmware_hash(size_t &hash) = 0; + + /*! + * Set firmware hash + * \param hash a size_t hash value + * \return 0 on success, error code otherwise + */ + virtual int usrp_set_firmware_hash(size_t hash) = 0; + + /*! + * Get fpga hash + * \param hash a size_t hash value + * \return 0 on success, error code otherwise + */ + virtual int usrp_get_fpga_hash(size_t &hash) = 0; + + /*! + * Set fpga hash + * \param hash a size_t hash value + * \return 0 on success, error code otherwise + */ + virtual int usrp_set_fpga_hash(size_t hash) = 0; + + /*! + * Set rx enable or disable + * \param on enable or disable value + * \return 0 on success, error code otherwise + */ + virtual int usrp_rx_enable(bool on) = 0; + + /*! + * Set rx enable or disable + * \param on enable or disable value + * \return 0 on success, error code otherwise + */ + virtual int usrp_tx_enable(bool on) = 0; + + /*! + * Submit an IN transfer + * \param request device specific request + * \param value device specific field + * \param index device specific field + * \param buff buffer to place data + * \return number of bytes read or error + */ + virtual int usrp_control_read(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) = 0; + + /*! + * Submit an OUT transfer + * \param request device specific request + * \param value device specific field + * \param index device specific field + * \param buff buffer of data to be sent + * \return number of bytes written or error + */ + virtual int usrp_control_write(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) = 0; + +}; + +#endif /* INCLUDED_USRP_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_iface.cpp b/host/lib/usrp/usrp1/usrp1_iface.cpp new file mode 100644 index 000000000..8756a21c9 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.cpp @@ -0,0 +1,262 @@ +// +// Copyright 2010 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 "usrp1_iface.hpp" +#include "usrp_commands.h" +#include <uhd/utils/assert.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/format.hpp> +#include <stdexcept> +#include <iostream> +#include <iomanip> + +using namespace uhd; +using namespace uhd::transport; + +static const bool iface_debug = false; + +class usrp1_iface_impl : public usrp1_iface{ +public: + /******************************************************************* + * Structors + ******************************************************************/ + usrp1_iface_impl(usrp_ctrl::sptr ctrl_transport) + { + _ctrl_transport = ctrl_transport; + } + + ~usrp1_iface_impl(void) + { + /* NOP */ + } + + /******************************************************************* + * Peek and Poke + ******************************************************************/ + void poke32(boost::uint32_t addr, boost::uint32_t value) + { + boost::uint32_t swapped = byteswap(value); + + if (iface_debug) { + std::cout.fill('0'); + std::cout << "poke32("; + std::cout << std::dec << std::setw(2) << addr << ", 0x"; + std::cout << std::hex << std::setw(8) << value << ")" << std::endl; + } + + boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; + boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + + int ret =_ctrl_transport->usrp_control_write( + VRQ_SPI_WRITE, + addr & 0x7f, + (w_index_h << 8) | (w_index_l << 0), + (unsigned char*) &swapped, + sizeof(boost::uint32_t)); + + if (ret < 0) + std::cerr << "USRP: failed memory write: " << ret << std::endl; + } + + void poke16(boost::uint32_t, boost::uint16_t) + { + //fpga only handles 32 bit writes + std::cerr << "USRP: unsupported operation: poke16()" << std::endl; + } + + boost::uint32_t peek32(boost::uint32_t addr) + { + uint32_t value_out; + + boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; + boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + + int ret = _ctrl_transport->usrp_control_read( + VRQ_SPI_READ, + 0x80 | (addr & 0x7f), + (w_index_h << 8) | (w_index_l << 0), + (unsigned char*) &value_out, + sizeof(boost::uint32_t)); + + if (ret < 0) + std::cerr << "USRP: failed memory read: " << ret << std::endl; + + return byteswap(value_out); + } + + boost::uint16_t peek16(boost::uint32_t addr) + { + uint32_t val = peek32(addr); + return boost::uint16_t(val & 0xff); + } + + /******************************************************************* + * I2C + ******************************************************************/ + static const size_t max_i2c_data_bytes = 64; + + void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes) + { + UHD_ASSERT_THROW(bytes.size() < max_i2c_data_bytes); + + unsigned char buff[max_i2c_data_bytes]; + std::copy(bytes.begin(), bytes.end(), buff); + + int ret = _ctrl_transport->usrp_control_write(VRQ_I2C_WRITE, + addr & 0xff, + 0, + buff, + bytes.size()); + + // TODO throw and catch i2c failures during eeprom read + if (iface_debug && (ret < 0)) + std::cerr << "USRP: failed i2c write: " << ret << std::endl; + } + + byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes) + { + UHD_ASSERT_THROW(num_bytes < max_i2c_data_bytes); + + unsigned char buff[max_i2c_data_bytes]; + int ret = _ctrl_transport->usrp_control_read(VRQ_I2C_READ, + addr & 0xff, + 0, + buff, + num_bytes); + + // TODO throw and catch i2c failures during eeprom read + if (iface_debug && ((ret < 0) || (unsigned)ret < (num_bytes))) { + std::cerr << "USRP: failed i2c read: " << ret << std::endl; + return byte_vector_t(num_bytes, 0xff); + } + + byte_vector_t out_bytes; + for (size_t i = 0; i < num_bytes; i++) + out_bytes.push_back(buff[i]); + + return out_bytes; + } + + /******************************************************************* + * SPI + * + * For non-readback transactions use the SPI_WRITE command, which is + * simpler and uses the USB control buffer for OUT data. No data + * needs to be returned. + * + * For readback transactions use SPI_TRANSACT, which places up to + * 4 bytes of OUT data in the device request fields and uses the + * control buffer for IN data. + ******************************************************************/ + boost::uint32_t transact_spi(int which_slave, + const spi_config_t &, + boost::uint32_t bits, + size_t num_bits, + bool readback) + { + UHD_ASSERT_THROW((num_bits <= 32) && !(num_bits % 8)); + size_t num_bytes = num_bits / 8; + + // Byteswap on num_bytes + unsigned char buff[4] = { 0 }; + for (size_t i = 1; i <= num_bytes; i++) + buff[num_bytes - i] = (bits >> ((i - 1) * 8)) & 0xff; + + if (readback) { + boost::uint8_t w_len_h = which_slave & 0xff; + boost::uint8_t w_len_l = num_bytes & 0xff; + + int ret = _ctrl_transport->usrp_control_read( + VRQ_SPI_TRANSACT, + (buff[0] << 8) | (buff[1] << 0), + (buff[2] << 8) | (buff[3] << 0), + buff, + (w_len_h << 8) | (w_len_l << 0)); + + if (ret < 0) { + std::cout << "USRP: failed SPI readback transaction: " + << std::dec << ret << std::endl; + } + + boost::uint32_t val = (((boost::uint32_t)buff[0]) << 0) | + (((boost::uint32_t)buff[1]) << 8) | + (((boost::uint32_t)buff[2]) << 16) | + (((boost::uint32_t)buff[3]) << 24); + return val; + } + else { + boost::uint8_t w_index_h = which_slave & 0xff; + boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_0) & 0xff; + + int ret =_ctrl_transport->usrp_control_write( + VRQ_SPI_WRITE, + 0x00, + (w_index_h << 8) | (w_index_l << 0), + buff, num_bytes); + + if (ret < 0) { + std::cout << "USRP: failed SPI transaction: " + << std::dec << ret << std::endl; + } + + return 0; + } + } + + /******************************************************************* + * Firmware + * + * This call is deprecated. + ******************************************************************/ + void write_firmware_cmd(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char *buff, + boost::uint16_t length) + { + int ret; + + if (request & 0x80) { + ret = _ctrl_transport->usrp_control_read(request, + value, + index, + buff, + length); + } + else { + ret = _ctrl_transport->usrp_control_write(request, + value, + index, + buff, + length); + } + + if (ret < 0) + std::cerr << "USRP: failed firmware command: " << ret << std::endl; + } + +private: + usrp_ctrl::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +usrp1_iface::sptr usrp1_iface::make(usrp_ctrl::sptr ctrl_transport) +{ + return sptr(new usrp1_iface_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/usrp1/usrp1_iface.hpp b/host/lib/usrp/usrp1/usrp1_iface.hpp new file mode 100644 index 000000000..9a3fdd6bc --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.hpp @@ -0,0 +1,100 @@ +// +// Copyright 2010 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_USRP1_IFACE_HPP +#define INCLUDED_USRP1_IFACE_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include "usrp1_ctrl.hpp" + +/*! + * The usrp1 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp1_iface : boost::noncopyable, public uhd::i2c_iface{ +public: + typedef boost::shared_ptr<usrp1_iface> sptr; + + /*! + * Make a new usrp1 interface with the control transport. + * \param ctrl_transport the usrp controller object + * \return a new usrp1 interface object + */ + static sptr make(usrp_ctrl::sptr ctrl_transport); + + /*! + * Write a register (32 bits) + * \param addr the address + * \param data the 32bit data + */ + virtual void poke32(boost::uint32_t addr, boost::uint32_t data) = 0; + + /*! + * Read a register (32 bits) + * \param addr the address + * \return the 32bit data + */ + virtual boost::uint32_t peek32(boost::uint32_t addr) = 0; + + /*! + * Write a register (16 bits) + * \param addr the address + * \param data the 16bit data + */ + virtual void poke16(boost::uint32_t addr, boost::uint16_t data) = 0; + + /*! + * read a register (16 bits) + * \param addr the address + * \return the 16bit data + */ + virtual boost::uint16_t peek16(boost::uint32_t addr) = 0; + + /*! + * Perform an spi transaction. + * \param which_slave the slave device number + * \param config spi config args + * \param data the bits to write + * \param num_bits how many bits in data + * \param readback true to readback a value + * \return spi data if readback set + */ + virtual boost::uint32_t transact_spi(int which_slave, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback) = 0; + + /*! + * Perform a general USB firmware OUT operation + * \param request + * \param value + * \param index + * \param data + * \return + */ + virtual void write_firmware_cmd(boost::uint8_t request, + boost::uint16_t value, + boost::uint16_t index, + unsigned char* buff, + boost::uint16_t length) = 0; +}; + +#endif /* INCLUDED_USRP1_IFACE_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp new file mode 100644 index 000000000..3c3306525 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -0,0 +1,218 @@ +// +// Copyright 2010 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 "usrp1_impl.hpp" +#include "usrp1_ctrl.hpp" +#include "fpga_regs_standard.h" +#include "usrp_spi_defs.h" +#include <uhd/transport/usb_control.hpp> +#include <uhd/usrp/device_props.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +const std::vector<usrp1_impl::dboard_slot_t> usrp1_impl::_dboard_slots = boost::assign::list_of + (usrp1_impl::DBOARD_SLOT_A)(usrp1_impl::DBOARD_SLOT_B) +; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t usrp1_find(const device_addr_t &hint) +{ + device_addrs_t usrp1_addrs; + + //return an empty list of addresses when type is set to non-usrp1 + if (hint.has_key("type") and hint["type"] != "usrp1") return usrp1_addrs; + + //extract the firmware path for the USRP1 + std::string usrp1_fw_image = find_image_path( + hint.has_key("fw")? hint["fw"] : "usrp1_fw.ihx" + ); + std::cout << "USRP1 firmware image: " << usrp1_fw_image << std::endl; + + //see what we got on the USB bus + std::vector<usb_device_handle::sptr> device_list = + usb_device_handle::get_device_list(); + + //find the usrps and load firmware + BOOST_FOREACH(usb_device_handle::sptr handle, device_list) { + if (handle->get_vendor_id() == 0xfffe && + handle->get_product_id() == 0x0002) { + + usb_control::sptr ctrl_transport = usb_control::make(handle); + usrp_ctrl::sptr usrp_ctrl = usrp_ctrl::make(ctrl_transport); + usrp_ctrl->usrp_load_firmware(usrp1_fw_image); + } + } + + //get descriptors again with serial number + device_list = usb_device_handle::get_device_list(); + + BOOST_FOREACH(usb_device_handle::sptr handle, device_list) { + if (handle->get_vendor_id() == 0xfffe && + handle->get_product_id() == 0x0002) { + + device_addr_t new_addr; + new_addr["type"] = "usrp1"; + new_addr["serial"] = handle->get_serial(); + usrp1_addrs.push_back(new_addr); + } + } + + return usrp1_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp1_make(const device_addr_t &device_addr) +{ + //extract the FPGA path for the USRP1 + std::string usrp1_fpga_image = find_image_path( + device_addr.has_key("fpga")? device_addr["fpga"] : "usrp1_fpga.rbf" + ); + std::cout << "USRP1 FPGA image: " << usrp1_fpga_image << std::endl; + + //try to match the given device address with something on the USB bus + std::vector<usb_device_handle::sptr> device_list = + usb_device_handle::get_device_list(); + + //create data and control transports + usb_zero_copy::sptr data_transport; + usrp_ctrl::sptr usrp_ctrl; + + BOOST_FOREACH(usb_device_handle::sptr handle, device_list) { + if (handle->get_vendor_id() == 0xfffe && + handle->get_product_id() == 0x0002 && + handle->get_serial() == device_addr["serial"]) { + + usb_control::sptr ctrl_transport = usb_control::make(handle); + usrp_ctrl = usrp_ctrl::make(ctrl_transport); + usrp_ctrl->usrp_load_fpga(usrp1_fpga_image); + + data_transport = usb_zero_copy::make(handle, // identifier + 6, // IN endpoint + 2, // OUT endpoint + 2 * (1 << 20), // buffer size + 16384); // transfer size + break; + } + } + + //create the usrp1 implementation guts + return device::sptr(new usrp1_impl(data_transport, usrp_ctrl)); +} + +UHD_STATIC_BLOCK(register_usrp1_device){ + device::register_device(&usrp1_find, &usrp1_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp1_impl::usrp1_impl(uhd::transport::usb_zero_copy::sptr data_transport, + usrp_ctrl::sptr ctrl_transport) + : _data_transport(data_transport), _ctrl_transport(ctrl_transport) +{ + _iface = usrp1_iface::make(ctrl_transport); + + //create clock interface + _clock_ctrl = usrp1_clock_ctrl::make(_iface); + + //create codec interface + _codec_ctrls[DBOARD_SLOT_A] = usrp1_codec_ctrl::make( + _iface, _clock_ctrl, SPI_ENABLE_CODEC_A + ); + _codec_ctrls[DBOARD_SLOT_B] = usrp1_codec_ctrl::make( + _iface, _clock_ctrl, SPI_ENABLE_CODEC_B + ); + + //initialize the codecs + codec_init(); + + //initialize the mboard + mboard_init(); + + //initialize the dboards + dboard_init(); + + //initialize the dsps + rx_dsp_init(); + + //initialize the dsps + tx_dsp_init(); + + //initialize the send/recv + io_init(); + + //turn on the transmitter + _ctrl_transport->usrp_tx_enable(true); +} + +usrp1_impl::~usrp1_impl(void){ + /* NOP */ +} + +bool usrp1_impl::recv_async_msg(uhd::async_metadata_t &, size_t timeout_ms){ + //dummy fill-in for the recv_async_msg + boost::this_thread::sleep(boost::posix_time::milliseconds(timeout_ms)); + return false; +} + +/*********************************************************************** + * Device Get + **********************************************************************/ +void usrp1_impl::get(const wax::obj &key_, wax::obj &val) +{ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<device_prop_t>()){ + case DEVICE_PROP_NAME: + val = std::string("usrp1 device"); + return; + + case DEVICE_PROP_MBOARD: + UHD_ASSERT_THROW(key.name == ""); + val = _mboard_proxy->get_link(); + return; + + case DEVICE_PROP_MBOARD_NAMES: + val = prop_names_t(1, ""); //vector of size 1 with empty string + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * Device Set + **********************************************************************/ +void usrp1_impl::set(const wax::obj &, const wax::obj &) +{ + UHD_THROW_PROP_SET_ERROR(); +} diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp new file mode 100644 index 000000000..c2f693eeb --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -0,0 +1,197 @@ +// +// Copyright 2010 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 "usrp1_iface.hpp" +#include "usrp1_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/usb_zero_copy.hpp> + +#ifndef INCLUDED_USRP1_IMPL_HPP +#define INCLUDED_USRP1_IMPL_HPP + +/*! + * Simple wax obj proxy class: + * Provides a wax obj interface for a set and a get function. + * This allows us to create nested properties structures + * while maintaining flattened code within the implementation. + */ +class wax_obj_proxy : public wax::obj { +public: + typedef boost::function<void(const wax::obj &, wax::obj &)> get_t; + typedef boost::function<void(const wax::obj &, const wax::obj &)> set_t; + typedef boost::shared_ptr<wax_obj_proxy> sptr; + + static sptr make(const get_t &get, const set_t &set){ + return sptr(new wax_obj_proxy(get, set)); + } + +private: + get_t _get; set_t _set; + wax_obj_proxy(const get_t &get, const set_t &set): _get(get), _set(set) {}; + void get(const wax::obj &key, wax::obj &val) {return _get(key, val);} + void set(const wax::obj &key, const wax::obj &val) {return _set(key, val);} +}; + +/*! + * USRP1 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class usrp1_impl : public uhd::device { +public: + //! used everywhere to differentiate slots/sides... + enum dboard_slot_t{ + DBOARD_SLOT_A = 'A', + DBOARD_SLOT_B = 'B' + }; + //and a way to enumerate through a list of the above... + static const std::vector<dboard_slot_t> _dboard_slots; + + //structors + usrp1_impl(uhd::transport::usb_zero_copy::sptr data_transport, + usrp_ctrl::sptr ctrl_transport); + + ~usrp1_impl(void); + + //the io interface + size_t send(const std::vector<const void *> &, + size_t, + const uhd::tx_metadata_t &, + const uhd::io_type_t &, + send_mode_t); + + size_t recv(const std::vector<void *> &, + size_t, uhd::rx_metadata_t &, + const uhd::io_type_t &, + recv_mode_t, + size_t timeout); + + size_t get_max_send_samps_per_packet(void) const { return 0; } + size_t get_max_recv_samps_per_packet(void) const { return 0; } + bool recv_async_msg(uhd::async_metadata_t &, size_t); + +private: + /*! + * Make a usrp1 dboard interface. + * \param iface the usrp1 interface object + * \param clock the clock control interface + * \param codec the codec control interface + * \param dboard_slot the slot identifier + * \param rx_dboard_id the db id for the rx board (used for evil dbsrx purposes) + * \return a sptr to a new dboard interface + */ + static uhd::usrp::dboard_iface::sptr make_dboard_iface( + usrp1_iface::sptr iface, + usrp1_clock_ctrl::sptr clock, + usrp1_codec_ctrl::sptr codec, + dboard_slot_t dboard_slot, + const uhd::usrp::dboard_id_t &rx_dboard_id + ); + + //interface to ioctls and file descriptor + usrp1_iface::sptr _iface; + + //handle io stuff + UHD_PIMPL_DECL(io_impl) _io_impl; + void io_init(void); + void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd); + void handle_overrun(size_t); + + //underrun and overrun poll intervals + size_t _rx_samps_per_poll_interval; + size_t _tx_samps_per_poll_interval; + + //otw types + uhd::otw_type_t _rx_otw_type; + uhd::otw_type_t _tx_otw_type; + + //configuration shadows + uhd::clock_config_t _clock_config; + uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + + //clock control + usrp1_clock_ctrl::sptr _clock_ctrl; + + //ad9862 codec control interface + uhd::dict<dboard_slot_t, usrp1_codec_ctrl::sptr> _codec_ctrls; + + //codec properties interfaces + void codec_init(void); + void rx_codec_get(const wax::obj &, wax::obj &, dboard_slot_t); + void rx_codec_set(const wax::obj &, const wax::obj &, dboard_slot_t); + void tx_codec_get(const wax::obj &, wax::obj &, dboard_slot_t); + void tx_codec_set(const wax::obj &, const wax::obj &, dboard_slot_t); + uhd::dict<dboard_slot_t, wax_obj_proxy::sptr> _rx_codec_proxies, _tx_codec_proxies; + + //device functions and settings + void get(const wax::obj &, wax::obj &); + void set(const wax::obj &, const wax::obj &); + + //mboard functions and settings + void mboard_init(void); + void mboard_get(const wax::obj &, wax::obj &); + void mboard_set(const wax::obj &, const wax::obj &); + wax_obj_proxy::sptr _mboard_proxy; + + //xx dboard functions and settings + void dboard_init(void); + uhd::dict<dboard_slot_t, uhd::usrp::dboard_manager::sptr> _dboard_managers; + uhd::dict<dboard_slot_t, uhd::usrp::dboard_iface::sptr> _dboard_ifaces; + + //rx dboard functions and settings + uhd::dict<dboard_slot_t, uhd::usrp::dboard_eeprom_t> _rx_db_eeproms; + void rx_dboard_get(const wax::obj &, wax::obj &, dboard_slot_t); + void rx_dboard_set(const wax::obj &, const wax::obj &, dboard_slot_t); + uhd::dict<dboard_slot_t, wax_obj_proxy::sptr> _rx_dboard_proxies; + + //tx dboard functions and settings + uhd::dict<dboard_slot_t, uhd::usrp::dboard_eeprom_t> _tx_db_eeproms; + void tx_dboard_get(const wax::obj &, wax::obj &, dboard_slot_t); + void tx_dboard_set(const wax::obj &, const wax::obj &, dboard_slot_t); + uhd::dict<dboard_slot_t, wax_obj_proxy::sptr> _tx_dboard_proxies; + + //rx dsp functions and settings + void rx_dsp_init(void); + void rx_dsp_get(const wax::obj &, wax::obj &); + void rx_dsp_set(const wax::obj &, const wax::obj &); + double _rx_dsp_freq; size_t _rx_dsp_decim; + wax_obj_proxy::sptr _rx_dsp_proxy; + + //tx dsp functions and settings + void tx_dsp_init(void); + void tx_dsp_get(const wax::obj &, wax::obj &); + void tx_dsp_set(const wax::obj &, const wax::obj &); + double _tx_dsp_freq; size_t _tx_dsp_interp; + wax_obj_proxy::sptr _tx_dsp_proxy; + + //transports + uhd::transport::usb_zero_copy::sptr _data_transport; + usrp_ctrl::sptr _ctrl_transport; +}; + +#endif /* INCLUDED_USRP1_IMPL_HPP */ |