diff options
Diffstat (limited to 'host/lib/usrp_clock/octoclock')
| -rw-r--r-- | host/lib/usrp_clock/octoclock/CMakeLists.txt | 33 | ||||
| -rw-r--r-- | host/lib/usrp_clock/octoclock/common.h | 149 | ||||
| -rw-r--r-- | host/lib/usrp_clock/octoclock/octoclock_eeprom.cpp | 184 | ||||
| -rw-r--r-- | host/lib/usrp_clock/octoclock/octoclock_impl.cpp | 437 | ||||
| -rw-r--r-- | host/lib/usrp_clock/octoclock/octoclock_impl.hpp | 80 | ||||
| -rw-r--r-- | host/lib/usrp_clock/octoclock/octoclock_uart.cpp | 162 | ||||
| -rw-r--r-- | host/lib/usrp_clock/octoclock/octoclock_uart.hpp | 57 | 
7 files changed, 1102 insertions, 0 deletions
| diff --git a/host/lib/usrp_clock/octoclock/CMakeLists.txt b/host/lib/usrp_clock/octoclock/CMakeLists.txt new file mode 100644 index 000000000..e363bb9da --- /dev/null +++ b/host/lib/usrp_clock/octoclock/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright 2011-2014 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the OctoClock support +######################################################################## +LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF) + +IF(ENABLE_OCTOCLOCK) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_eeprom.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/octoclock_uart.cpp +    ) +ENDIF(ENABLE_OCTOCLOCK) diff --git a/host/lib/usrp_clock/octoclock/common.h b/host/lib/usrp_clock/octoclock/common.h new file mode 100644 index 000000000..96acbb30f --- /dev/null +++ b/host/lib/usrp_clock/octoclock/common.h @@ -0,0 +1,149 @@ +/* + * Copyright 2014 Ettus Research LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _OCTOCLOCK_COMMON_H_ +#define _OCTOCLOCK_COMMON_H_ + +#include <stdint.h> + +/* + * C++ macros used for code cleanliness and extern "C" declaration. + */ +#ifdef __cplusplus + +#define UHD_OCTOCLOCK_SEND_AND_RECV(xport, pkt_code, pkt_out, len, data) pkt_out.proto_ver = OCTOCLOCK_FW_COMPAT_NUM; \ +                                                                         pkt_out.code = pkt_code; \ +                                                                         xport->send(boost::asio::buffer(&pkt_out, sizeof(octoclock_packet_t))); \ +                                                                         len = xport->recv(boost::asio::buffer(data), 2); + +#define UHD_OCTOCLOCK_PACKET_MATCHES(pkt_code, pkt_out, pkt_in, len) (len > offsetof(octoclock_packet_t, data) and \ +                                                                      pkt_in->sequence == pkt_out.sequence and \ +                                                                      pkt_in->code == pkt_code) + +extern "C" { +#endif + +/* + * This code is used by both the C firmware and C++ host driver, so + * only valid C code should go in this section. + */ + +//These values are placed in the octoclock_packet_t.proto_ver field +#define OCTOCLOCK_BOOTLOADER_PROTO_VER 1234 +#define OCTOCLOCK_FW_COMPAT_NUM 2 + +//UDP ports assigned for different tasks +#define OCTOCLOCK_UDP_CTRL_PORT   50000 +#define OCTOCLOCK_UDP_GPSDO_PORT  50001 +#define OCTOCLOCK_UDP_FW_PORT     50002 +#define OCTOCLOCK_UDP_EEPROM_PORT 50003 + +typedef enum { +    NO_CODE, + +    OCTOCLOCK_QUERY_CMD, +    OCTOCLOCK_QUERY_ACK, + +    SEND_EEPROM_CMD, +    SEND_EEPROM_ACK, +    BURN_EEPROM_CMD, +    BURN_EEPROM_SUCCESS_ACK, +    BURN_EEPROM_FAILURE_ACK, +    CLEAR_EEPROM_CMD, +    CLEAR_EEPROM_ACK, + +    SEND_STATE_CMD, +    SEND_STATE_ACK, + +    RESET_CMD, +    RESET_ACK, + +    HOST_SEND_TO_GPSDO_CMD, +    HOST_SEND_TO_GPSDO_ACK, +    SEND_POOLSIZE_CMD, +    SEND_POOLSIZE_ACK, +    SEND_CACHE_STATE_CMD, +    SEND_CACHE_STATE_ACK, +    SEND_GPSDO_CACHE_CMD, +    SEND_GPSDO_CACHE_ACK, + +    PREPARE_FW_BURN_CMD, +    FW_BURN_READY_ACK, +    FILE_TRANSFER_CMD, +    FILE_TRANSFER_ACK, +    READ_FW_CMD, +    READ_FW_ACK, +    FINALIZE_BURNING_CMD, +    FINALIZE_BURNING_ACK, +} packet_code_t; + +typedef enum { +    NO_REF, +    INTERNAL, +    EXTERNAL +} ref_t; + +typedef enum { +    UP, +    DOWN +} switch_pos_t; + +#pragma pack(push,1) + +// Structure of values in EEPROM, starting in location 0 +typedef struct { +    uint8_t mac_addr[6]; +    uint32_t ip_addr; +    uint32_t dr_addr; +    uint32_t netmask; +    uint8_t serial[10]; +    uint8_t name[10]; +    uint8_t revision; +} octoclock_fw_eeprom_t; + +typedef struct { +    uint8_t external_detected; +    uint8_t gps_detected; +    uint8_t which_ref; +    uint8_t switch_pos; +} octoclock_state_t; + +typedef struct { +    uint8_t num_wraps; +    uint8_t pos; +} gpsdo_cache_state_t; + +typedef struct { +    uint32_t proto_ver; +    uint32_t sequence; +    uint8_t code; +    union { +        uint16_t len; +        gpsdo_cache_state_t state; +        uint16_t poolsize; +        uint16_t addr; +    }; +    uint8_t data[256]; +} octoclock_packet_t; + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif + +#endif /* _OCTOCLOCK_COMMON_H_ */ diff --git a/host/lib/usrp_clock/octoclock/octoclock_eeprom.cpp b/host/lib/usrp_clock/octoclock/octoclock_eeprom.cpp new file mode 100644 index 000000000..6d54cac70 --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_eeprom.cpp @@ -0,0 +1,184 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/mac_addr.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/foreach.hpp> + +#include <iostream> + +#include "common.h" + +typedef boost::asio::ip::address_v4 ip_v4; + +using namespace uhd; +using namespace uhd::usrp_clock; +using namespace uhd::transport; + +/*********************************************************************** + * Utility functions + **********************************************************************/ + +//! A wrapper around std::copy that takes ranges instead of iterators. +template<typename RangeSrc, typename RangeDst> inline +void byte_copy(const RangeSrc &src, RangeDst &dst){ +    std::copy(boost::begin(src), boost::end(src), boost::begin(dst)); +} + +//! create a string from a byte vector, return empty if invalid ascii +static const std::string bytes_to_string(const byte_vector_t &bytes){ +    std::string out; +    BOOST_FOREACH(boost::uint8_t byte, bytes){ +        if (byte < 32 or byte > 127) return out; +        out += byte; +    } +    return out; +} + +/*********************************************************************** + * Implementation + **********************************************************************/ +void octoclock_eeprom_t::_load(){ +    boost::uint32_t octoclock_data[udp_simple::mtu]; +    const octoclock_packet_t *pkt_in = reinterpret_cast<const octoclock_packet_t*>(octoclock_data); +    const octoclock_fw_eeprom_t *eeprom_in = reinterpret_cast<const octoclock_fw_eeprom_t*>(pkt_in->data); + +    octoclock_packet_t pkt_out; +    pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); +    size_t len = 0; + +    UHD_OCTOCLOCK_SEND_AND_RECV(xport, SEND_EEPROM_CMD, pkt_out, len, octoclock_data); +    if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_EEPROM_ACK, pkt_out, pkt_in, len)){ +        //MAC address +        byte_vector_t mac_bytes(eeprom_in->mac_addr, eeprom_in->mac_addr+6); +        (*this)["mac-addr"] = mac_addr_t::from_bytes(mac_bytes).to_string(); + +        //IP address +        boost::uint32_t ip_addr = uhd::htonx<boost::uint32_t>(eeprom_in->ip_addr); +        ip_v4::bytes_type ip_addr_bytes; +        memcpy(&ip_addr_bytes, &ip_addr, 4); +        (*this)["ip-addr"] = ip_v4(ip_addr_bytes).to_string(); + +        //Default router +        boost::uint32_t dr_addr = uhd::htonx<boost::uint32_t>(eeprom_in->dr_addr); +        ip_v4::bytes_type dr_addr_bytes; +        memcpy(&dr_addr_bytes, &dr_addr, 4); +        (*this)["gateway"] = ip_v4(dr_addr_bytes).to_string(); + +        //Netmask +        boost::uint32_t netmask = uhd::htonx<boost::uint32_t>(eeprom_in->netmask); +        ip_v4::bytes_type netmask_bytes; +        memcpy(&netmask_bytes, &netmask, 4); +        (*this)["netmask"] = ip_v4(netmask_bytes).to_string(); + +        //Serial +        std::string raw_serial((char*)eeprom_in->serial, 10); +        byte_vector_t serial_bytes(raw_serial.begin(), raw_serial.end()); +        (*this)["serial"] = bytes_to_string(serial_bytes); + +        //Name +        std::string raw_name((char*)eeprom_in->name, 10); +        byte_vector_t name_bytes(raw_name.begin(), raw_name.end()); +        (*this)["name"] = bytes_to_string(name_bytes); + +        //Revision +        (*this)["revision"] = boost::lexical_cast<std::string>(int(eeprom_in->revision)); +    } +    else throw uhd::runtime_error("Error loading OctoClock EEPROM."); +} + +void octoclock_eeprom_t::_store() const { +    boost::uint32_t octoclock_data[udp_simple::mtu]; +    const octoclock_packet_t *pkt_in = reinterpret_cast<const octoclock_packet_t *>(octoclock_data); + +    octoclock_packet_t pkt_out; +    pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); +    pkt_out.len = sizeof(octoclock_fw_eeprom_t); +    size_t len = 0; + +    octoclock_fw_eeprom_t *eeprom_out = reinterpret_cast<octoclock_fw_eeprom_t *>(&pkt_out.data); +    memset(eeprom_out, 0xFF, sizeof(octoclock_fw_eeprom_t)); + +    //MAC address +    if((*this).has_key("mac-addr")){ +        byte_copy(mac_addr_t::from_string((*this)["mac-addr"]).to_bytes(), eeprom_out->mac_addr); +    } + +    //IP address +    if((*this).has_key("ip-addr")){ +        ip_v4::bytes_type ip_addr_bytes = ip_v4::from_string((*this)["ip-addr"]).to_bytes(); +        memcpy(&eeprom_out->ip_addr, &ip_addr_bytes, 4); +        eeprom_out->ip_addr = uhd::htonx<boost::uint32_t>(eeprom_out->ip_addr); +    } + +    //Default router +    if((*this).has_key("gateway")){ +        ip_v4::bytes_type dr_addr_bytes = ip_v4::from_string((*this)["gateway"]).to_bytes(); +        memcpy(&eeprom_out->dr_addr, &dr_addr_bytes, 4); +        eeprom_out->dr_addr = uhd::htonx<boost::uint32_t>(eeprom_out->dr_addr); +    } + +    //Netmask +    if((*this).has_key("netmask")){ +        ip_v4::bytes_type netmask_bytes = ip_v4::from_string((*this)["netmask"]).to_bytes(); +        memcpy(&eeprom_out->netmask, &netmask_bytes, 4); +        eeprom_out->netmask = uhd::htonx<boost::uint32_t>(eeprom_out->netmask); +    } + +    //Serial +    if((*this).has_key("serial")){ +        byte_copy(byte_vector_t((*this)["serial"].begin(), (*this)["serial"].end()), eeprom_out->serial); +    } + +    //Name +    if((*this).has_key("name")){ +        byte_copy(byte_vector_t((*this)["name"].begin(), (*this)["name"].end()), eeprom_out->name); +    } + +    //Revision +    if((*this).has_key("revision")){ +        eeprom_out->revision = (*this)["revision"][0]-'0'; +    } + +    UHD_OCTOCLOCK_SEND_AND_RECV(xport, BURN_EEPROM_CMD, pkt_out, len, octoclock_data); +    if(not UHD_OCTOCLOCK_PACKET_MATCHES(BURN_EEPROM_SUCCESS_ACK, pkt_out, pkt_in, len)) +        throw uhd::runtime_error("Error writing to OctoClock EEPROM."); +} + +/*********************************************************************** + * Implementation of OctoClock EEPROM + **********************************************************************/ +octoclock_eeprom_t::octoclock_eeprom_t(void){ +    /* NOP */ +} + +octoclock_eeprom_t::octoclock_eeprom_t(udp_simple::sptr transport){ +    xport = transport; +    _load(); +} + +void octoclock_eeprom_t::commit() const{ +    if(!xport) throw uhd::runtime_error("There is no set device communication."); +    _store(); +} diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp new file mode 100644 index 000000000..8c207dd9f --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp @@ -0,0 +1,437 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> + +#include <boost/asio.hpp> +#include <boost/assign.hpp> +#include <boost/cstdint.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> + +#include <uhd/device.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> + +#include "octoclock_impl.hpp" +#include "octoclock_uart.hpp" +#include "common.h" + +using namespace uhd; +using namespace uhd::usrp_clock; +using namespace uhd::transport; +namespace asio = boost::asio; +namespace fs = boost::filesystem; + +/*********************************************************************** + * Discovery + **********************************************************************/ +device_addrs_t octoclock_find(const device_addr_t &hint){ +    //Handle the multi-device discovery +    device_addrs_t hints = separate_device_addr(hint); +    if (hints.size() > 1){ +        device_addrs_t found_devices; +        std::string error_msg; +        BOOST_FOREACH(const device_addr_t &hint_i, hints){ +            device_addrs_t found_devices_i = octoclock_find(hint_i); +            if (found_devices_i.size() != 1) error_msg += str(boost::format( +                "Could not resolve device hint \"%s\" to a single device." +            ) % hint_i.to_string()); +            else found_devices.push_back(found_devices_i[0]); +        } +        if (found_devices.empty()) return device_addrs_t(); +        if (not error_msg.empty()) throw uhd::value_error(error_msg); +        return device_addrs_t(1, combine_device_addrs(found_devices)); +    } + +    //Initialize the hint for a single device case +    UHD_ASSERT_THROW(hints.size() <= 1); +    hints.resize(1); //In case it was empty +    device_addr_t _hint = hints[0]; +    device_addrs_t octoclock_addrs; + +    //return an empty list of addresses when type is set to non-OctoClock +    if (hint.has_key("type") and hint["type"].find("octoclock") == std::string::npos) return octoclock_addrs; + +    //Return an empty list of addresses when a resource is specified, +    //since a resource is intended for a different, non-USB, device. +    if (hint.has_key("resource")) return octoclock_addrs; + +    //If no address was specified, send a broadcast on each interface +    if (not _hint.has_key("addr")){ +        BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()){ +            //avoid the loopback device +            if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + +            //create a new hint with this broadcast address +            device_addr_t new_hint = hint; +            new_hint["addr"] = if_addrs.bcast; + +            //call discover with the new hint and append results +            device_addrs_t new_octoclock_addrs = octoclock_find(new_hint); +            octoclock_addrs.insert(octoclock_addrs.begin(), +                new_octoclock_addrs.begin(), new_octoclock_addrs.end() +            ); +        } +        return octoclock_addrs; +    } + +    //Create a UDP transport to communicate +    udp_simple::sptr udp_transport = udp_simple::make_broadcast( +                                        _hint["addr"], +                                        BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT) +                                     ); + +    //Send a query packet +    octoclock_packet_t pkt_out; +    pkt_out.proto_ver = OCTOCLOCK_FW_COMPAT_NUM; +    pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); +    pkt_out.len = 0; +    pkt_out.code = OCTOCLOCK_QUERY_CMD; +    try{ +        udp_transport->send(boost::asio::buffer(&pkt_out, sizeof(pkt_out))); +    } +    catch(const std::exception &ex){ +        UHD_MSG(error) << "OctoClock network discovery error - " << ex.what() << std::endl; +    } +    catch(...){ +        UHD_MSG(error) << "OctoClock network discovery unknown error" << std::endl; +    } + +    boost::uint8_t octoclock_data[udp_simple::mtu]; +    const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + +    while(true){ +        size_t len = udp_transport->recv(asio::buffer(octoclock_data)); +        if(UHD_OCTOCLOCK_PACKET_MATCHES(OCTOCLOCK_QUERY_ACK, pkt_out, pkt_in, len)){ +            device_addr_t new_addr; +            new_addr["addr"] = udp_transport->get_recv_addr(); + +            //Attempt direct communication with OctoClock +            udp_simple::sptr ctrl_xport = udp_simple::make_connected( +                                              new_addr["addr"], +                                              BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT) +                                          ); +            UHD_OCTOCLOCK_SEND_AND_RECV(ctrl_xport, OCTOCLOCK_QUERY_CMD, pkt_out, len, octoclock_data); +            if(UHD_OCTOCLOCK_PACKET_MATCHES(OCTOCLOCK_QUERY_ACK, pkt_out, pkt_in, len)){ +                //If the OctoClock is in its bootloader, don't ask for details +                if(pkt_in->proto_ver == OCTOCLOCK_BOOTLOADER_PROTO_VER){ +                    new_addr["type"] = "octoclock-bootloader"; +                    octoclock_addrs.push_back(new_addr); +                } +                else{ +                    new_addr["type"] = "octoclock"; + +                    octoclock_eeprom_t oc_eeprom(ctrl_xport); +                    new_addr["name"] = oc_eeprom["name"]; +                    new_addr["serial"] = oc_eeprom["serial"]; + +                    //Filter based on optional keys (if any) +                    if( +                       (not _hint.has_key("name") or (_hint["name"] == new_addr["name"])) and +                       (not _hint.has_key("serial") or (_hint["serial"] == new_addr["serial"])) +                    ){ +                        octoclock_addrs.push_back(new_addr); +                    } +                } +            } +            else continue; +        } + +        if(len == 0) break; +    } + +    return octoclock_addrs; +} + +device::sptr octoclock_make(const device_addr_t &device_addr){ +    return device::sptr(new octoclock_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_octoclock_device){ +    device::register_device(&octoclock_find, &octoclock_make, device::CLOCK); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){ +    UHD_MSG(status) << "Opening an OctoClock device..." << std::endl; +    _type = device::CLOCK; +    device_addrs_t device_args = separate_device_addr(_device_addr); +    _sequence = std::rand(); + +    //////////////////////////////////////////////////////////////////// +    // Initialize the property tree +    //////////////////////////////////////////////////////////////////// +    _tree = property_tree::make(); +    _tree->create<std::string>("/name").set("OctoClock Device"); + +    for(size_t oci = 0; oci < device_args.size(); oci++){ +        const device_addr_t device_args_i = device_args[oci]; +        const std::string addr = device_args_i["addr"]; +        //Can't make a device out of an OctoClock in bootloader state +        if(device_args_i["type"] == "octoclock-bootloader"){ +            throw uhd::runtime_error(str(boost::format( +                    "\n\nThis device is in its bootloader state and cannot be used by UHD.\n" +                    "This may mean the firmware on the device has been corrupted and will\n" +                    "need to be burned again.\n\n" +                    "%s\n" +                ) % _get_images_help_message(addr))); +        } + +        const std::string oc = boost::lexical_cast<std::string>(oci); + +        //////////////////////////////////////////////////////////////////// +        // Set up UDP transports +        //////////////////////////////////////////////////////////////////// +        _oc_dict[oc].ctrl_xport = udp_simple::make_connected(addr, BOOST_STRINGIZE(OCTOCLOCK_UDP_CTRL_PORT)); +        _oc_dict[oc].gpsdo_xport = udp_simple::make_connected(addr, BOOST_STRINGIZE(OCTOCLOCK_UDP_GPSDO_PORT)); + +        const fs_path oc_path = "/mboards/" + oc; +        _tree->create<std::string>(oc_path / "name").set("OctoClock"); + +        //////////////////////////////////////////////////////////////////// +        // Check the firmware compatibility number +        //////////////////////////////////////////////////////////////////// +        boost::uint32_t fw_version = _get_fw_version(oc); +        if(fw_version != OCTOCLOCK_FW_COMPAT_NUM){ +            throw uhd::runtime_error(str(boost::format( +                    "\n\nPlease update your OctoClock's firmware.\n" +                    "Expected firmware compatibility number %d, but got %d:\n" +                    "The firmware build is not compatible with the host code build.\n\n" +                    "%s\n" +                ) % int(OCTOCLOCK_FW_COMPAT_NUM) % int(fw_version) % _get_images_help_message(addr))); +        } +        _tree->create<std::string>(oc_path / "fw_version").set(boost::lexical_cast<std::string>(int(fw_version))); + +        //////////////////////////////////////////////////////////////////// +        // Set up EEPROM +        //////////////////////////////////////////////////////////////////// +        _oc_dict[oc].eeprom = octoclock_eeprom_t(_oc_dict[oc].ctrl_xport); +        _tree->create<octoclock_eeprom_t>(oc_path / "eeprom") +            .set(_oc_dict[oc].eeprom) +            .subscribe(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1)); + +        //////////////////////////////////////////////////////////////////// +        // Initialize non-GPSDO sensors +        //////////////////////////////////////////////////////////////////// +        _tree->create<boost::uint32_t>(oc_path / "time") +            .publish(boost::bind(&octoclock_impl::_get_time, this, oc)); +        _tree->create<sensor_value_t>(oc_path / "sensors/ext_ref_detected") +            .publish(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc)); +        _tree->create<sensor_value_t>(oc_path / "sensors/gps_detected") +            .publish(boost::bind(&octoclock_impl::_gps_detected, this, oc)); +        _tree->create<sensor_value_t>(oc_path / "sensors/using_ref") +            .publish(boost::bind(&octoclock_impl::_which_ref, this, oc)); +        _tree->create<sensor_value_t>(oc_path / "sensors/switch_pos") +            .publish(boost::bind(&octoclock_impl::_switch_pos, this, oc)); + +        //////////////////////////////////////////////////////////////////// +        // Check reference and GPSDO +        //////////////////////////////////////////////////////////////////// +        std::string asterisk = (device_args.size() > 1) ? " * " : ""; + +        if(device_args.size() > 1){ +            UHD_MSG(status) << std::endl << "Checking status of " << addr << ":" << std::endl; +        } +        UHD_MSG(status) << boost::format("%sDetecting internal GPSDO...") % asterisk << std::flush; + +        _get_state(oc); +        if(_oc_dict[oc].state.gps_detected){ +            try{ +                _oc_dict[oc].gps = gps_ctrl::make(octoclock_make_uart_iface(_oc_dict[oc].gpsdo_xport)); + +                if(_oc_dict[oc].gps and _oc_dict[oc].gps->gps_detected()){ +                    BOOST_FOREACH(const std::string &name, _oc_dict[oc].gps->get_sensors()){ +                        _tree->create<sensor_value_t>(oc_path / "sensors" / name) +                            .publish(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name)); +                    } +                } +                else{ +                    //If GPSDO communication failed, set gps_detected to false +                    _oc_dict[oc].state.gps_detected = 0; +                    UHD_MSG(warning) << "Device reports that it has a GPSDO, but we cannot communicate with it." << std::endl; +                    std::cout << std::endl; +                } +            } +            catch(std::exception &e){ +                UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; +            } +        } +        else UHD_MSG(status) << "No GPSDO found" << std::endl; +        UHD_MSG(status) << boost::format("%sDetecting external reference...%s") % asterisk +                                                                                % _ext_ref_detected(oc).value +                        << std::endl; +        UHD_MSG(status) << boost::format("%sDetecting switch position...%s") % asterisk +                                                                             % _switch_pos(oc).value +                        << std::endl; +        std::string ref = _which_ref(oc).value; +        if(ref == "none") UHD_MSG(status) << boost::format("%sDevice is not using any reference") % asterisk << std::endl; +        else UHD_MSG(status) << boost::format("%sDevice is using %s reference") % asterisk +                                                                                % _which_ref(oc).value +                             << std::endl; +    } +} + +rx_streamer::sptr octoclock_impl::get_rx_stream(UHD_UNUSED(const stream_args_t &args)){ +    throw uhd::not_implemented_error("This function is incompatible with this device."); +} + +tx_streamer::sptr octoclock_impl::get_tx_stream(UHD_UNUSED(const stream_args_t &args)){ +    throw uhd::not_implemented_error("This function is incompatible with this device."); +} + +bool octoclock_impl::recv_async_msg(UHD_UNUSED(uhd::async_metadata_t&), UHD_UNUSED(double)){ +    throw uhd::not_implemented_error("This function is incompatible with this device."); +} + +void octoclock_impl::_set_eeprom(const std::string &oc, const octoclock_eeprom_t &oc_eeprom){ +    /* +     * The OctoClock needs a full octoclock_eeprom_t so as to not erase +     * what it currently has in the EEPROM, so store the relevant values +     * from the user's input and send that instead. +     */ +    BOOST_FOREACH(const std::string &key, oc_eeprom.keys()){ +        if(_oc_dict[oc].eeprom.has_key(key)) _oc_dict[oc].eeprom[key] = oc_eeprom[key]; +    } +    _oc_dict[oc].eeprom.commit(); +} + +boost::uint32_t octoclock_impl::_get_fw_version(const std::string &oc){ +    octoclock_packet_t pkt_out; +    pkt_out.sequence = _sequence++; +    pkt_out.len = 0; +    size_t len; + +    boost::uint8_t octoclock_data[udp_simple::mtu]; +    const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + +    UHD_OCTOCLOCK_SEND_AND_RECV(_oc_dict[oc].ctrl_xport, OCTOCLOCK_QUERY_CMD, pkt_out, len, octoclock_data); +    if(UHD_OCTOCLOCK_PACKET_MATCHES(OCTOCLOCK_QUERY_ACK, pkt_out, pkt_in, len)){ +        return pkt_in->proto_ver; +    } +    else throw uhd::runtime_error("Failed to retrive firmware version from OctoClock."); +} + +void octoclock_impl::_get_state(const std::string &oc){ +    octoclock_packet_t pkt_out; +    pkt_out.sequence = _sequence++; +    pkt_out.len = 0; +    size_t len = 0; + +    boost::uint8_t octoclock_data[udp_simple::mtu]; +    const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + +    UHD_OCTOCLOCK_SEND_AND_RECV(_oc_dict[oc].ctrl_xport, SEND_STATE_CMD, pkt_out, len, octoclock_data); +    if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_STATE_ACK, pkt_out, pkt_in, len)){ +        const octoclock_state_t *state = reinterpret_cast<const octoclock_state_t*>(pkt_in->data); +        _oc_dict[oc].state = *state; +    } +    else throw uhd::runtime_error("Failed to retrieve state information from OctoClock."); +} + +uhd::dict<ref_t, std::string> _ref_strings = boost::assign::map_list_of +    (NO_REF, "none") +    (INTERNAL, "internal") +    (EXTERNAL, "external") +; + +uhd::dict<switch_pos_t, std::string> _switch_pos_strings = boost::assign::map_list_of +    (UP, "Prefer internal") +    (DOWN, "Prefer external") +; + +sensor_value_t octoclock_impl::_ext_ref_detected(const std::string &oc){ +    _get_state(oc); + +    return sensor_value_t("External reference detected", (_oc_dict[oc].state.external_detected > 0), +                          "true", "false"); +} + +sensor_value_t octoclock_impl::_gps_detected(const std::string &oc){ +    //Don't check, this shouldn't change once device is turned on + +    return sensor_value_t("GPSDO detected", (_oc_dict[oc].state.gps_detected > 0), +                          "true", "false"); +} + +sensor_value_t octoclock_impl::_which_ref(const std::string &oc){ +    _get_state(oc); + +    if(not _ref_strings.has_key(ref_t(_oc_dict[oc].state.which_ref))){ +        throw uhd::runtime_error("Invalid reference detected."); +    } + +    return sensor_value_t("Using reference", _ref_strings[ref_t(_oc_dict[oc].state.which_ref)], ""); +} + +sensor_value_t octoclock_impl::_switch_pos(const std::string &oc){ +    _get_state(oc); + +    if(not _switch_pos_strings.has_key(switch_pos_t(_oc_dict[oc].state.switch_pos))){ +        throw uhd::runtime_error("Invalid switch position detected."); +    } + +    return sensor_value_t("Switch position", _switch_pos_strings[switch_pos_t(_oc_dict[oc].state.switch_pos)], ""); +} + +boost::uint32_t octoclock_impl::_get_time(const std::string &oc){ +    if(_oc_dict[oc].state.gps_detected){ +        std::string time_str = _oc_dict[oc].gps->get_sensor("gps_time").value; +        return boost::lexical_cast<boost::uint32_t>(time_str); +    } +    else throw uhd::runtime_error("This device cannot return a time."); +} + +std::string octoclock_impl::_get_images_help_message(const std::string &addr){ +    const std::string image_name = "octoclock_r4_fw.bin"; + +    //Check to see if image is in default location +    std::string image_location; +    try{ +        image_location = uhd::find_image_path(image_name); +    } +    catch(const std::exception &e){ +        return str(boost::format("Could not find %s in your images path.\n%s") +                   % image_name +                   % uhd::print_images_error()); +    } + +    //Get escape character +    #ifdef UHD_PLATFORM_WIN32 +    const std::string ml = "^\n    "; +    #else +    const std::string ml = "\\\n    "; +    #endif + +    //Get burner command +    const std::string burner_path = (fs::path(uhd::get_pkg_path()) / "bin" / "octoclock_firmware_burner").string(); +    const std::string burner_cmd = str(boost::format("%s %s--addr=\"%s\"") % burner_path % ml % addr); +    return str(boost::format("%s\n%s") % uhd::print_images_error() % burner_cmd); +} diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.hpp b/host/lib/usrp_clock/octoclock/octoclock_impl.hpp new file mode 100644 index 000000000..ab45cd5f0 --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_impl.hpp @@ -0,0 +1,80 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_OCTOCLOCK_IMPL_HPP +#define INCLUDED_OCTOCLOCK_IMPL_HPP + +#include <boost/shared_ptr.hpp> +#include <boost/thread.hpp> + +#include <uhd/device.hpp> +#include <uhd/stream.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/usrp_clock/octoclock_eeprom.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/sensors.hpp> + +#include "common.h" + +/*! + * OctoClock implementation guts + */ +class octoclock_impl : public uhd::device{ +public: +    octoclock_impl(const uhd::device_addr_t &); +    ~octoclock_impl(void) {}; + +    uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + +    uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + +    bool recv_async_msg(uhd::async_metadata_t&, double); + +private: +    struct oc_container_type{ +        uhd::usrp_clock::octoclock_eeprom_t eeprom; +        octoclock_state_t state; +        uhd::transport::udp_simple::sptr ctrl_xport; +        uhd::transport::udp_simple::sptr gpsdo_xport; +        uhd::gps_ctrl::sptr gps; +    }; +    uhd::dict<std::string, oc_container_type> _oc_dict; +    boost::uint32_t _sequence; + +    void _set_eeprom(const std::string &oc, const uhd::usrp_clock::octoclock_eeprom_t &oc_eeprom); + +    boost::uint32_t _get_fw_version(const std::string &oc); + +    void _get_state(const std::string &oc); + +    uhd::sensor_value_t _ext_ref_detected(const std::string &oc); + +    uhd::sensor_value_t _gps_detected(const std::string &oc); + +    uhd::sensor_value_t _which_ref(const std::string &oc); + +    uhd::sensor_value_t _switch_pos(const std::string &oc); + +    boost::uint32_t _get_time(const std::string &oc); + +    std::string _get_images_help_message(const std::string &addr); + +    boost::mutex _device_mutex; +}; + +#endif /* INCLUDED_OCTOCLOCK_IMPL_HPP */ diff --git a/host/lib/usrp_clock/octoclock/octoclock_uart.cpp b/host/lib/usrp_clock/octoclock/octoclock_uart.cpp new file mode 100644 index 000000000..eb3f40d9c --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_uart.cpp @@ -0,0 +1,162 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <iostream> +#include <string> +#include <string.h> + +#include <boost/algorithm/string.hpp> +#include <boost/asio.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> + +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> + +#include "common.h" +#include "octoclock_uart.hpp" + +namespace asio = boost::asio; +using namespace uhd::transport; + +#define NUM_WRAPS_EQUAL   (_state.num_wraps == _device_state.num_wraps) +#define POS_EQUAL         (_state.pos == _device_state.pos) +#define STATES_EQUAL      (NUM_WRAPS_EQUAL && POS_EQUAL) +#define LOCAL_STATE_AHEAD (_state.num_wraps > _device_state.num_wraps || \ +                           (NUM_WRAPS_EQUAL && _state.pos > _device_state.pos)) + +namespace uhd{ +    octoclock_uart_iface::octoclock_uart_iface(udp_simple::sptr udp): uart_iface(){ +        _udp = udp; +        _state.num_wraps = 0; +        _state.pos = 0; +        _device_state.num_wraps = 0; +        _device_state.pos = 0; +        size_t len = 0; + +        //Get pool size from device +        octoclock_packet_t pkt_out; +        pkt_out.sequence = uhd::htonx<boost::uint16_t>(std::rand()); +        pkt_out.len = 0; + +        boost::uint8_t octoclock_data[udp_simple::mtu]; +        const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + +        UHD_OCTOCLOCK_SEND_AND_RECV(_udp, SEND_POOLSIZE_CMD, pkt_out, len, octoclock_data); +        if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_POOLSIZE_ACK, pkt_out, pkt_in, len)){ +            _poolsize = pkt_in->poolsize; +            _cache.resize(_poolsize); +        } +        else throw uhd::runtime_error("Failed to communicate with GPSDO."); +    } + +    void octoclock_uart_iface::write_uart(const std::string &buf){ +        std::string to_send = boost::algorithm::replace_all_copy(buf, "\n", "\r\n"); +        size_t len = 0; + +        octoclock_packet_t pkt_out; +        pkt_out.sequence = uhd::htonx<boost::uint32_t>(std::rand()); +        pkt_out.len = to_send.size(); +        memcpy(pkt_out.data, to_send.c_str(), to_send.size()); + +        boost::uint8_t octoclock_data[udp_simple::mtu]; +        const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + +        UHD_OCTOCLOCK_SEND_AND_RECV(_udp, HOST_SEND_TO_GPSDO_CMD, pkt_out, len, octoclock_data); +        if(not UHD_OCTOCLOCK_PACKET_MATCHES(HOST_SEND_TO_GPSDO_ACK, pkt_out, pkt_in, len)){ +            throw uhd::runtime_error("Failed to send commands to GPSDO."); +        } +    } + +    std::string octoclock_uart_iface::read_uart(double timeout){ +        std::string result; + +        boost::system_time exit_time = boost::get_system_time() + boost::posix_time::milliseconds(long(timeout*1e3)); + +        while(boost::get_system_time() < exit_time){ +            _update_cache(); + +            for(char ch = _getchar(); ch != -1; ch = _getchar()){ +                if(ch == '\r') continue; //Skip carriage returns +                _rxbuff += ch; + +                //If newline found, return string +                if(ch == '\n'){ +                    result = _rxbuff; +                    _rxbuff.clear(); +                    return result; +                } +            } +            boost::this_thread::sleep(boost::posix_time::milliseconds(1)); +        } + +        return result; +    } + +    void octoclock_uart_iface::_update_cache(){ +        octoclock_packet_t pkt_out; +        pkt_out.len = 0; +        size_t len = 0; + +        boost::uint8_t octoclock_data[udp_simple::mtu]; +        const octoclock_packet_t *pkt_in = reinterpret_cast<octoclock_packet_t*>(octoclock_data); + +        if(STATES_EQUAL or LOCAL_STATE_AHEAD){ +            pkt_out.sequence++; +            UHD_OCTOCLOCK_SEND_AND_RECV(_udp, SEND_GPSDO_CACHE_CMD, pkt_out, len, octoclock_data); +            if(UHD_OCTOCLOCK_PACKET_MATCHES(SEND_GPSDO_CACHE_ACK, pkt_out, pkt_in, len)){ +                memcpy(&_cache[0], pkt_in->data, _poolsize); +                _device_state = pkt_in->state; +            } + +            boost::uint8_t delta_wraps = (_device_state.num_wraps - _state.num_wraps); +            if(delta_wraps > 1 or +               ((delta_wraps == 1) and (_device_state.pos >= _state.pos))){ + +                _state.pos = (_device_state.pos+1) % _poolsize; +                _state.num_wraps = (_device_state.num_wraps-1); + +                while((_cache[_state.pos] != '\n') and (_state.pos != _device_state.pos)){ +                    _state.pos = (_state.pos+1) % _poolsize; +                    //We may have wrapped around locally +                    if(_state.pos == 0) _state.num_wraps++; +                } +                _state.pos = (_state.pos+1) % _poolsize; +                //We may have wrapped around locally +                if(_state.pos == 0) _state.num_wraps++; +            } +        } +    } + +    char octoclock_uart_iface::_getchar(){ +        if(LOCAL_STATE_AHEAD){ +            return -1; +        } + +        char ch = _cache[_state.pos]; +        _state.pos = ((_state.pos+1) % _poolsize); +        //We may have wrapped around locally +        if(_state.pos == 0) _state.num_wraps++; + +        return ch; +    } + +    uart_iface::sptr octoclock_make_uart_iface(udp_simple::sptr udp){ +        return uart_iface::sptr(new octoclock_uart_iface(udp)); +    } +} diff --git a/host/lib/usrp_clock/octoclock/octoclock_uart.hpp b/host/lib/usrp_clock/octoclock/octoclock_uart.hpp new file mode 100644 index 000000000..05d1bb121 --- /dev/null +++ b/host/lib/usrp_clock/octoclock/octoclock_uart.hpp @@ -0,0 +1,57 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the uart_ifaceied 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_OCTOCLOCK_UART_HPP +#define INCLUDED_OCTOCLOCK_UART_HPP + +#include <vector> + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/serial.hpp> + +/*! + * The OctoClock doesn't take UART input per se but reads a specific + * packet type and sends the string from there through its own serial + * functions. + */ +namespace uhd{ +class octoclock_uart_iface : public uhd::uart_iface{ +public: +    octoclock_uart_iface(uhd::transport::udp_simple::sptr udp); +    ~octoclock_uart_iface(void) {}; + +    void write_uart(const std::string &buf); +    std::string read_uart(double timeout); + +private: +    uhd::transport::udp_simple::sptr _udp; + +    boost::uint16_t _poolsize; +    gpsdo_cache_state_t _state; +    gpsdo_cache_state_t _device_state; +    std::vector<boost::uint8_t> _cache; +    std::string _rxbuff; + +    void _update_cache(); +    char _getchar(); +}; + +uart_iface::sptr octoclock_make_uart_iface(uhd::transport::udp_simple::sptr udp); + +} + +#endif /* INCLUDED_OCTOCLOCK_UART_HPP */ | 
