diff options
| -rw-r--r-- | host/lib/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/clock_control.cpp | 16 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/clock_control.hpp | 7 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/dboard_iface.cpp (renamed from host/lib/usrp/usrp2/dboard_interface.cpp) | 77 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/dboard_impl.cpp | 10 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/dsp_impl.cpp | 13 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/io_impl.cpp | 1 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/mboard_impl.cpp | 37 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_iface.cpp | 168 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_iface.hpp | 103 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_impl.cpp | 110 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_impl.hpp | 55 | 
12 files changed, 378 insertions, 222 deletions
| diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 4f38c94c5..b9e525bad 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -103,10 +103,11 @@ UHD_PYTHON_GEN_SOURCE_FILE(  LIST(APPEND libuhd_sources      usrp/usrp2/clock_control.cpp      usrp/usrp2/dboard_impl.cpp -    usrp/usrp2/dboard_interface.cpp +    usrp/usrp2/dboard_iface.cpp      usrp/usrp2/dsp_impl.cpp      usrp/usrp2/io_impl.cpp      usrp/usrp2/mboard_impl.cpp +    usrp/usrp2/usrp2_iface.cpp      usrp/usrp2/usrp2_impl.cpp  ) diff --git a/host/lib/usrp/usrp2/clock_control.cpp b/host/lib/usrp/usrp2/clock_control.cpp index 2d27361a8..dcd7ce9da 100644 --- a/host/lib/usrp/usrp2/clock_control.cpp +++ b/host/lib/usrp/usrp2/clock_control.cpp @@ -28,8 +28,8 @@ using namespace uhd::usrp;   */  class clock_control_ad9510 : public clock_control{  public: -    clock_control_ad9510(usrp2_impl *impl){ -        _impl = impl; +    clock_control_ad9510(usrp2_iface::sptr iface){ +        _iface = iface;          _ad9510_regs.cp_current_setting = ad9510_regs_t::CP_CURRENT_SETTING_3_0MA;          this->write_reg(0x09); @@ -71,8 +71,8 @@ public:      ~clock_control_ad9510(void){          /* private clock enables, must be set here */ -        //this->enable_dac_clock(false); -        //this->enable_adc_clock(false); //FIXME cant do yet +        this->enable_dac_clock(false); +        this->enable_adc_clock(false);      }      //uses output clock 7 (cmos) @@ -114,7 +114,7 @@ private:       */      void write_reg(boost::uint8_t addr){          boost::uint32_t data = _ad9510_regs.get_write_reg(addr); -        _impl->transact_spi(SPI_SS_AD9510, spi_config_t::EDGE_RISE, data, 24, false /*no rb*/); +        _iface->transact_spi(SPI_SS_AD9510, spi_config_t::EDGE_RISE, data, 24, false /*no rb*/);      }      /*! @@ -144,13 +144,13 @@ private:          this->update_regs();      } -    usrp2_impl *_impl; +    usrp2_iface::sptr _iface;      ad9510_regs_t _ad9510_regs;  };  /***********************************************************************   * Public make function for the ad9510 clock control   **********************************************************************/ -clock_control::sptr clock_control::make_ad9510(usrp2_impl *impl){ -    return clock_control::sptr(new clock_control_ad9510(impl)); +clock_control::sptr clock_control::make_ad9510(usrp2_iface::sptr iface){ +    return clock_control::sptr(new clock_control_ad9510(iface));  } diff --git a/host/lib/usrp/usrp2/clock_control.hpp b/host/lib/usrp/usrp2/clock_control.hpp index 4302941f0..b64a53196 100644 --- a/host/lib/usrp/usrp2/clock_control.hpp +++ b/host/lib/usrp/usrp2/clock_control.hpp @@ -18,8 +18,7 @@  #ifndef INCLUDED_CLOCK_CONTROL_HPP  #define INCLUDED_CLOCK_CONTROL_HPP -class usrp2_impl; //dummy class - +#include "usrp2_iface.hpp"  #include <boost/shared_ptr.hpp>  #include <boost/utility.hpp> @@ -29,10 +28,10 @@ public:      /*!       * Make a clock config for the ad9510 ic. -     * \param impl a pointer to the usrp2 implementation object +     * \param _iface a pointer to the usrp2 interface object       * \return a new clock control object       */ -    static sptr make_ad9510(usrp2_impl *impl); +    static sptr make_ad9510(usrp2_iface::sptr iface);      /*!       * Enable/disable the rx dboard clock. diff --git a/host/lib/usrp/usrp2/dboard_interface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp index c74bdaf44..d0c4bf6c1 100644 --- a/host/lib/usrp/usrp2/dboard_interface.cpp +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -15,19 +15,21 @@  // along with this program.  If not, see <http://www.gnu.org/licenses/>.  // -#include "usrp2_impl.hpp" +#include "usrp2_iface.hpp" +#include "clock_control.hpp"  #include "usrp2_regs.hpp"  #include <uhd/types/dict.hpp>  #include <uhd/utils/assert.hpp>  #include <boost/assign/list_of.hpp> +#include <boost/asio.hpp> //htonl and ntohl  #include <algorithm>  using namespace uhd::usrp; -class usrp2_dboard_interface : public dboard_interface{ +class usrp2_dboard_iface : public dboard_interface{  public: -    usrp2_dboard_interface(usrp2_impl *impl); -    ~usrp2_dboard_interface(void); +    usrp2_dboard_iface(usrp2_iface::sptr iface, clock_control::sptr clk_ctrl); +    ~usrp2_dboard_iface(void);      void write_aux_dac(unit_t, int, int);      int read_aux_adc(unit_t, int); @@ -58,22 +60,27 @@ public:      );  private: -    usrp2_impl *_impl; +    usrp2_iface::sptr _iface; +    clock_control::sptr _clk_ctrl;      boost::uint32_t _ddr_shadow;  };  /***********************************************************************   * Make Function   **********************************************************************/ -dboard_interface::sptr make_usrp2_dboard_interface(usrp2_impl *impl){ -    return dboard_interface::sptr(new usrp2_dboard_interface(impl)); +dboard_interface::sptr make_usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    clock_control::sptr clk_ctrl +){ +    return dboard_interface::sptr(new usrp2_dboard_iface(iface, clk_ctrl));  }  /***********************************************************************   * Structors   **********************************************************************/ -usrp2_dboard_interface::usrp2_dboard_interface(usrp2_impl *impl){ -    _impl = impl; +usrp2_dboard_iface::usrp2_dboard_iface(usrp2_iface::sptr iface, clock_control::sptr clk_ctrl){ +    _iface = iface; +    _clk_ctrl = clk_ctrl;      _ddr_shadow = 0;      //set the selection mux to use atr @@ -81,28 +88,28 @@ usrp2_dboard_interface::usrp2_dboard_interface(usrp2_impl *impl){      for(size_t i = 0; i < 16; i++){          new_sels |= FRF_GPIO_SEL_ATR << (i*2);      } -    _impl->poke32(FR_GPIO_TX_SEL, new_sels); -    _impl->poke32(FR_GPIO_RX_SEL, new_sels); +    _iface->poke32(FR_GPIO_TX_SEL, new_sels); +    _iface->poke32(FR_GPIO_RX_SEL, new_sels);  } -usrp2_dboard_interface::~usrp2_dboard_interface(void){ +usrp2_dboard_iface::~usrp2_dboard_iface(void){      /* NOP */  }  /***********************************************************************   * Clocks   **********************************************************************/ -double usrp2_dboard_interface::get_clock_rate(unit_t){ -    return _impl->get_master_clock_freq(); +double usrp2_dboard_iface::get_clock_rate(unit_t){ +    return _iface->get_master_clock_freq();  } -void usrp2_dboard_interface::set_clock_enabled(unit_t unit, bool enb){ +void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){      switch(unit){      case UNIT_RX: -        _impl->get_clock_control()->enable_rx_dboard_clock(enb); +        _clk_ctrl->enable_rx_dboard_clock(enb);          return;      case UNIT_TX: -        _impl->get_clock_control()->enable_tx_dboard_clock(enb); +        _clk_ctrl->enable_tx_dboard_clock(enb);          return;      }  } @@ -118,18 +125,18 @@ static int unit_to_shift(dboard_interface::unit_t unit){      throw std::runtime_error("unknown unit type");  } -void usrp2_dboard_interface::set_gpio_ddr(unit_t unit, boost::uint16_t value){ +void usrp2_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint16_t value){      _ddr_shadow = \          (_ddr_shadow & ~(0xffff << unit_to_shift(unit))) |          (boost::uint32_t(value) << unit_to_shift(unit)); -    _impl->poke32(FR_GPIO_DDR, _ddr_shadow); +    _iface->poke32(FR_GPIO_DDR, _ddr_shadow);  } -boost::uint16_t usrp2_dboard_interface::read_gpio(unit_t unit){ -    return boost::uint16_t(_impl->peek32(FR_GPIO_IO) >> unit_to_shift(unit)); +boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ +    return boost::uint16_t(_iface->peek32(FR_GPIO_IO) >> unit_to_shift(unit));  } -void usrp2_dboard_interface::set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ +void usrp2_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){      //define mapping of unit to atr regs to register address      static const uhd::dict<          unit_t, uhd::dict<atr_reg_t, boost::uint32_t> @@ -147,7 +154,7 @@ void usrp2_dboard_interface::set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint              (ATR_REG_FULL_DUPLEX, FR_ATR_FULL_TXSIDE)          )      ; -    _impl->poke16(unit_to_atr_to_addr[unit][atr], value); +    _iface->poke16(unit_to_atr_to_addr[unit][atr], value);  }  /*********************************************************************** @@ -167,28 +174,28 @@ static boost::uint8_t unit_to_otw_spi_dev(dboard_interface::unit_t unit){      throw std::invalid_argument("unknown unit type");  } -void usrp2_dboard_interface::write_spi( +void usrp2_dboard_iface::write_spi(      unit_t unit,      const spi_config_t &config,      boost::uint32_t data,      size_t num_bits  ){ -    _impl->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, false /*no rb*/); +    _iface->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, false /*no rb*/);  } -boost::uint32_t usrp2_dboard_interface::read_write_spi( +boost::uint32_t usrp2_dboard_iface::read_write_spi(      unit_t unit,      const spi_config_t &config,      boost::uint32_t data,      size_t num_bits  ){ -    return _impl->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, true /*rb*/); +    return _iface->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, true /*rb*/);  }  /***********************************************************************   * I2C   **********************************************************************/ -void usrp2_dboard_interface::write_i2c(int i2c_addr, const byte_vector_t &buf){ +void usrp2_dboard_iface::write_i2c(int i2c_addr, const byte_vector_t &buf){      //setup the out data      usrp2_ctrl_data_t out_data;      out_data.id = htonl(USRP2_CTRL_ID_WRITE_THESE_I2C_VALUES_BRO); @@ -202,11 +209,11 @@ void usrp2_dboard_interface::write_i2c(int i2c_addr, const byte_vector_t &buf){      std::copy(buf.begin(), buf.end(), out_data.data.i2c_args.data);      //send and recv -    usrp2_ctrl_data_t in_data = _impl->ctrl_send_and_recv(out_data); +    usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);      ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_COOL_IM_DONE_I2C_WRITE_DUDE);  } -dboard_interface::byte_vector_t usrp2_dboard_interface::read_i2c(int i2c_addr, size_t num_bytes){ +dboard_interface::byte_vector_t usrp2_dboard_iface::read_i2c(int i2c_addr, size_t num_bytes){      //setup the out data      usrp2_ctrl_data_t out_data;      out_data.id = htonl(USRP2_CTRL_ID_DO_AN_I2C_READ_FOR_ME_BRO); @@ -217,7 +224,7 @@ dboard_interface::byte_vector_t usrp2_dboard_interface::read_i2c(int i2c_addr, s      ASSERT_THROW(num_bytes <= sizeof(out_data.data.i2c_args.data));      //send and recv -    usrp2_ctrl_data_t in_data = _impl->ctrl_send_and_recv(out_data); +    usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);      ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_HERES_THE_I2C_DATA_DUDE);      ASSERT_THROW(in_data.data.i2c_args.addr = num_bytes); @@ -244,7 +251,7 @@ static boost::uint8_t unit_to_otw(dboard_interface::unit_t unit){      throw std::invalid_argument("unknown unit type");  } -void usrp2_dboard_interface::write_aux_dac(unit_t unit, int which, int value){ +void usrp2_dboard_iface::write_aux_dac(unit_t unit, int which, int value){      //setup the out data      usrp2_ctrl_data_t out_data;      out_data.id = htonl(USRP2_CTRL_ID_WRITE_THIS_TO_THE_AUX_DAC_BRO); @@ -253,11 +260,11 @@ void usrp2_dboard_interface::write_aux_dac(unit_t unit, int which, int value){      out_data.data.aux_args.value = htonl(value);      //send and recv -    usrp2_ctrl_data_t in_data = _impl->ctrl_send_and_recv(out_data); +    usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);      ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_DONE_WITH_THAT_AUX_DAC_DUDE);  } -int usrp2_dboard_interface::read_aux_adc(unit_t unit, int which){ +int usrp2_dboard_iface::read_aux_adc(unit_t unit, int which){      //setup the out data      usrp2_ctrl_data_t out_data;      out_data.id = htonl(USRP2_CTRL_ID_READ_FROM_THIS_AUX_ADC_BRO); @@ -265,7 +272,7 @@ int usrp2_dboard_interface::read_aux_adc(unit_t unit, int which){      out_data.data.aux_args.which = which;      //send and recv -    usrp2_ctrl_data_t in_data = _impl->ctrl_send_and_recv(out_data); +    usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);      ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_DONE_WITH_THAT_AUX_ADC_DUDE);      return ntohl(in_data.data.aux_args.value);  } diff --git a/host/lib/usrp/usrp2/dboard_impl.cpp b/host/lib/usrp/usrp2/dboard_impl.cpp index 3ac805421..4b300de68 100644 --- a/host/lib/usrp/usrp2/dboard_impl.cpp +++ b/host/lib/usrp/usrp2/dboard_impl.cpp @@ -22,6 +22,8 @@  #include <uhd/usrp/dboard_props.hpp>  #include <uhd/utils/assert.hpp>  #include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/asio.hpp> //htonl and ntohl  #include <iostream>  using namespace uhd; @@ -34,7 +36,7 @@ void usrp2_impl::dboard_init(void){      //grab the dboard ids over the control line      usrp2_ctrl_data_t out_data;      out_data.id = htonl(USRP2_CTRL_ID_GIVE_ME_YOUR_DBOARD_IDS_BRO); -    usrp2_ctrl_data_t in_data = ctrl_send_and_recv(out_data); +    usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);      ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_THESE_ARE_MY_DBOARD_IDS_DUDE);      //extract the dboard ids an convert them @@ -43,7 +45,7 @@ void usrp2_impl::dboard_init(void){      //create a new dboard interface and manager      dboard_interface::sptr _dboard_interface( -        make_usrp2_dboard_interface(this) +        make_usrp2_dboard_iface(_iface, _clk_ctrl)      );      _dboard_manager = dboard_manager::make(          rx_dboard_id, tx_dboard_id, _dboard_interface @@ -82,7 +84,7 @@ void usrp2_impl::update_rx_mux_config(void){          rx_mux = (((rx_mux >> 0) & 0x3) << 2) | (((rx_mux >> 2) & 0x3) << 0);      } -    this->poke32(FR_DSP_RX_MUX, rx_mux); +    _iface->poke32(FR_DSP_RX_MUX, rx_mux);  }  void usrp2_impl::update_tx_mux_config(void){ @@ -95,7 +97,7 @@ void usrp2_impl::update_tx_mux_config(void){          tx_mux = (((tx_mux >> 0) & 0x1) << 1) | (((tx_mux >> 1) & 0x1) << 0);      } -    this->poke32(FR_DSP_TX_MUX, tx_mux); +    _iface->poke32(FR_DSP_TX_MUX, tx_mux);  }  /*********************************************************************** diff --git a/host/lib/usrp/usrp2/dsp_impl.cpp b/host/lib/usrp/usrp2/dsp_impl.cpp index 92b55d029..664d69948 100644 --- a/host/lib/usrp/usrp2/dsp_impl.cpp +++ b/host/lib/usrp/usrp2/dsp_impl.cpp @@ -20,6 +20,7 @@  #include <uhd/usrp/dsp_props.hpp>  #include <uhd/utils/assert.hpp>  #include <boost/format.hpp> +#include <boost/bind.hpp>  #include <boost/assign/list_of.hpp>  #include <boost/math/special_functions/round.hpp> @@ -82,11 +83,11 @@ void usrp2_impl::init_ddc_config(void){  void usrp2_impl::update_ddc_config(void){      //set the decimation -    this->poke32(FR_DSP_RX_DECIM_RATE, _ddc_decim); +    _iface->poke32(FR_DSP_RX_DECIM_RATE, _ddc_decim);      //set the scaling      static const boost::int16_t default_rx_scale_iq = 1024; -    this->poke32(FR_DSP_RX_SCALE_IQ, +    _iface->poke32(FR_DSP_RX_SCALE_IQ,          calculate_iq_scale_word(default_rx_scale_iq, default_rx_scale_iq)      );  } @@ -126,7 +127,7 @@ void usrp2_impl::ddc_set(const wax::obj &key, const wax::obj &val){              ASSERT_THROW(new_freq <= get_master_clock_freq()/2.0);              ASSERT_THROW(new_freq >= -get_master_clock_freq()/2.0);              _ddc_freq = new_freq; //shadow -            this->poke32(FR_DSP_RX_FREQ, +            _iface->poke32(FR_DSP_RX_FREQ,                  calculate_freq_word_and_update_actual_freq(_ddc_freq, get_master_clock_freq())              );          } @@ -170,10 +171,10 @@ void usrp2_impl::update_duc_config(void){      boost::int16_t scale = rint((4096*std::pow(2, ceil(log2(interp_cubed))))/(1.65*interp_cubed));      //set the interpolation -    this->poke32(FR_DSP_TX_INTERP_RATE, _ddc_decim); +    _iface->poke32(FR_DSP_TX_INTERP_RATE, _ddc_decim);      //set the scaling -    this->poke32(FR_DSP_TX_SCALE_IQ, calculate_iq_scale_word(scale, scale)); +    _iface->poke32(FR_DSP_TX_SCALE_IQ, calculate_iq_scale_word(scale, scale));  }  /*********************************************************************** @@ -211,7 +212,7 @@ void usrp2_impl::duc_set(const wax::obj &key, const wax::obj &val){              ASSERT_THROW(new_freq <= get_master_clock_freq()/2.0);              ASSERT_THROW(new_freq >= -get_master_clock_freq()/2.0);              _duc_freq = new_freq; //shadow -            this->poke32(FR_DSP_TX_FREQ, +            _iface->poke32(FR_DSP_TX_FREQ,                  calculate_freq_word_and_update_actual_freq(_duc_freq, get_master_clock_freq())              );          } diff --git a/host/lib/usrp/usrp2/io_impl.cpp b/host/lib/usrp/usrp2/io_impl.cpp index 87fc88ceb..367a1d9fb 100644 --- a/host/lib/usrp/usrp2/io_impl.cpp +++ b/host/lib/usrp/usrp2/io_impl.cpp @@ -18,6 +18,7 @@  #include "usrp2_impl.hpp"  #include <uhd/transport/convert_types.hpp>  #include <boost/format.hpp> +#include <boost/asio.hpp> //htonl and ntohl  #include <iostream>  using namespace uhd; diff --git a/host/lib/usrp/usrp2/mboard_impl.cpp b/host/lib/usrp/usrp2/mboard_impl.cpp index ea268651a..1521de1fe 100644 --- a/host/lib/usrp/usrp2/mboard_impl.cpp +++ b/host/lib/usrp/usrp2/mboard_impl.cpp @@ -22,6 +22,9 @@  #include <uhd/utils/assert.hpp>  #include <uhd/types/mac_addr.hpp>  #include <uhd/types/dict.hpp> +#include <boost/bind.hpp> +#include <boost/asio.hpp> //htonl and ntohl +#include <boost/assign/list_of.hpp>  using namespace uhd;  using namespace uhd::usrp; @@ -35,7 +38,7 @@ void usrp2_impl::mboard_init(void){          boost::bind(&usrp2_impl::mboard_set, this, _1, _2)      ); -    _clock_control = clock_control::make_ad9510(this); +    _clk_ctrl = clock_control::make_ad9510(_iface);      //setup the ad9777 dac      ad9777_regs_t ad9777_regs; @@ -58,14 +61,10 @@ void usrp2_impl::mboard_init(void){      //write all regs      for(boost::uint8_t addr = 0; addr <= 0xC; addr++){          boost::uint16_t data = ad9777_regs.get_write_reg(addr); -        this->transact_spi(SPI_SS_AD9777, spi_config_t::EDGE_RISE, data, 16, false /*no rb*/); +        _iface->transact_spi(SPI_SS_AD9777, spi_config_t::EDGE_RISE, data, 16, false /*no rb*/);      }  } -clock_control::sptr usrp2_impl::get_clock_control(void){ -    return _clock_control; -} -  void usrp2_impl::init_clock_config(void){      //setup the clock configuration settings      _clock_config.ref_source = clock_config_t::REF_INT; @@ -94,28 +93,28 @@ void usrp2_impl::update_clock_config(void){      }      //set the pps flags -    this->poke32(FR_TIME64_FLAGS, pps_flags); +    _iface->poke32(FR_TIME64_FLAGS, pps_flags);      //clock source ref 10mhz      switch(_clock_config.ref_source){ -    case clock_config_t::REF_INT : this->poke32(FR_CLOCK_CONTROL, 0x10); break; -    case clock_config_t::REF_SMA : this->poke32(FR_CLOCK_CONTROL, 0x1C); break; -    case clock_config_t::REF_MIMO: this->poke32(FR_CLOCK_CONTROL, 0x15); break; +    case clock_config_t::REF_INT : _iface->poke32(FR_CLOCK_CONTROL, 0x10); break; +    case clock_config_t::REF_SMA : _iface->poke32(FR_CLOCK_CONTROL, 0x1C); break; +    case clock_config_t::REF_MIMO: _iface->poke32(FR_CLOCK_CONTROL, 0x15); break;      }      //clock source ref 10mhz      bool use_external = _clock_config.ref_source != clock_config_t::REF_INT; -    this->get_clock_control()->enable_external_ref(use_external); +    _clk_ctrl->enable_external_ref(use_external);  }  void usrp2_impl::set_time_spec(const time_spec_t &time_spec, bool now){      //set ticks and seconds -    this->poke32(FR_TIME64_SECS, time_spec.secs); -    this->poke32(FR_TIME64_TICKS, time_spec.get_ticks(get_master_clock_freq())); +    _iface->poke32(FR_TIME64_SECS, time_spec.secs); +    _iface->poke32(FR_TIME64_TICKS, time_spec.get_ticks(get_master_clock_freq()));      //set the register to latch it all in      boost::uint32_t imm_flags = (now)? FRF_TIME64_LATCH_NOW : FRF_TIME64_LATCH_NEXT_PPS; -    this->poke32(FR_TIME64_IMM, imm_flags); +    _iface->poke32(FR_TIME64_IMM, imm_flags);  }  void usrp2_impl::issue_ddc_stream_cmd(const stream_cmd_t &stream_cmd){ @@ -151,7 +150,7 @@ void usrp2_impl::issue_ddc_stream_cmd(const stream_cmd_t &stream_cmd){      }      //send and recv -    usrp2_ctrl_data_t in_data = ctrl_send_and_recv(out_data); +    usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);      ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_GOT_THAT_STREAM_COMMAND_DUDE);  } @@ -170,7 +169,7 @@ void usrp2_impl::mboard_get(const wax::obj &key_, wax::obj &val){              out_data.id = htonl(USRP2_CTRL_ID_GIVE_ME_YOUR_MAC_ADDR_BRO);              //send and recv -            usrp2_ctrl_data_t in_data = ctrl_send_and_recv(out_data); +            usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);              ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_THIS_IS_MY_MAC_ADDR_DUDE);              //extract the address @@ -184,7 +183,7 @@ void usrp2_impl::mboard_get(const wax::obj &key_, wax::obj &val){              out_data.id = htonl(USRP2_CTRL_ID_GIVE_ME_YOUR_IP_ADDR_BRO);              //send and recv -            usrp2_ctrl_data_t in_data = ctrl_send_and_recv(out_data); +            usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);              ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_THIS_IS_MY_IP_ADDR_DUDE);              //extract the address @@ -272,7 +271,7 @@ void usrp2_impl::mboard_set(const wax::obj &key, const wax::obj &val){              std::copy(mac_addr.to_bytes(), mac_addr.to_bytes()+mac_addr_t::hlen, out_data.data.mac_addr);              //send and recv -            usrp2_ctrl_data_t in_data = ctrl_send_and_recv(out_data); +            usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);              ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_THIS_IS_MY_MAC_ADDR_DUDE);              return;          } @@ -284,7 +283,7 @@ void usrp2_impl::mboard_set(const wax::obj &key, const wax::obj &val){              out_data.data.ip_addr = htonl(boost::asio::ip::address_v4::from_string(val.as<std::string>()).to_ulong());              //send and recv -            usrp2_ctrl_data_t in_data = ctrl_send_and_recv(out_data); +            usrp2_ctrl_data_t in_data = _iface->ctrl_send_and_recv(out_data);              ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_THIS_IS_MY_IP_ADDR_DUDE);              return;          } diff --git a/host/lib/usrp/usrp2/usrp2_iface.cpp b/host/lib/usrp/usrp2/usrp2_iface.cpp new file mode 100644 index 000000000..5c84fd8d3 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -0,0 +1,168 @@ +// +// 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 "usrp2_iface.hpp" +#include <uhd/utils/assert.hpp> +#include <uhd/types/dict.hpp> +#include <boost/thread.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/assign/list_of.hpp> +#include <stdexcept> + +using namespace uhd; +using namespace uhd::usrp; + +class usrp2_iface_impl : public usrp2_iface{ +public: +/*********************************************************************** + * Structors + **********************************************************************/ +    usrp2_iface_impl(transport::udp_simple::sptr ctrl_transport){ +        _ctrl_transport = ctrl_transport; +    } + +    ~usrp2_iface_impl(void){ +        /* NOP */ +    } + +/*********************************************************************** + * Peek and Poke + **********************************************************************/ +    void poke32(boost::uint32_t addr, boost::uint32_t data){ +        return this->poke<boost::uint32_t>(addr, data); +    } + +    boost::uint32_t peek32(boost::uint32_t addr){ +        return this->peek<boost::uint32_t>(addr); +    } + +    void poke16(boost::uint32_t addr, boost::uint16_t data){ +        return this->poke<boost::uint16_t>(addr, data); +    } + +    boost::uint16_t peek16(boost::uint32_t addr){ +        return this->peek<boost::uint16_t>(addr); +    } + +/*********************************************************************** + * SPI + **********************************************************************/ +    boost::uint32_t transact_spi( +        int which_slave, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback +    ){ +        static const uhd::dict<spi_config_t::edge_t, int> spi_edge_to_otw = boost::assign::map_list_of +            (spi_config_t::EDGE_RISE, USRP2_CLK_EDGE_RISE) +            (spi_config_t::EDGE_FALL, USRP2_CLK_EDGE_FALL) +        ; + +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO); +        out_data.data.spi_args.dev = which_slave; +        out_data.data.spi_args.miso_edge = spi_edge_to_otw[config.miso_edge]; +        out_data.data.spi_args.mosi_edge = spi_edge_to_otw[config.mosi_edge]; +        out_data.data.spi_args.readback = (readback)? 1 : 0; +        out_data.data.spi_args.num_bits = num_bits; +        out_data.data.spi_args.data = htonl(data); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE); + +        return ntohl(out_data.data.spi_args.data); +    } + +/*********************************************************************** + * Send/Recv over control + **********************************************************************/ +    usrp2_ctrl_data_t ctrl_send_and_recv(const usrp2_ctrl_data_t &out_data){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        //fill in the seq number and send +        usrp2_ctrl_data_t out_copy = out_data; +        out_copy.seq = htonl(++_ctrl_seq_num); +        _ctrl_transport->send(boost::asio::buffer(&out_copy, sizeof(usrp2_ctrl_data_t))); + +        //loop until we get the packet or timeout +        while(true){ +            usrp2_ctrl_data_t in_data; +            size_t len = _ctrl_transport->recv(boost::asio::buffer(&in_data, sizeof(in_data))); +            if (len >= sizeof(usrp2_ctrl_data_t) and ntohl(in_data.seq) == _ctrl_seq_num){ +                return in_data; +            } +            if (len == 0) break; //timeout +            //didnt get seq or bad packet, continue looking... +        } +        throw std::runtime_error("usrp2 no control response"); +    } + +/*********************************************************************** + * Master Clock! Ahhhhh + **********************************************************************/ +    double get_master_clock_freq(void){ +        return 100e6; +    } + +private: +    //this lovely lady makes it all possible +    transport::udp_simple::sptr _ctrl_transport; + +    //used in send/recv +    boost::mutex _ctrl_mutex; +    boost::uint32_t _ctrl_seq_num; + +/*********************************************************************** + * Private Templated Peek and Poke + **********************************************************************/ +    template <class T> void poke(boost::uint32_t addr, T data){ +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_POKE_THIS_REGISTER_FOR_ME_BRO); +        out_data.data.poke_args.addr = htonl(addr); +        out_data.data.poke_args.data = htonl(boost::uint32_t(data)); +        out_data.data.poke_args.num_bytes = sizeof(T); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_OMG_POKED_REGISTER_SO_BAD_DUDE); +    } + +    template <class T> T peek(boost::uint32_t addr){ +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_PEEK_AT_THIS_REGISTER_FOR_ME_BRO); +        out_data.data.poke_args.addr = htonl(addr); +        out_data.data.poke_args.num_bytes = sizeof(T); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_WOAH_I_DEFINITELY_PEEKED_IT_DUDE); +        return T(ntohl(out_data.data.poke_args.data)); +    } + +}; + +/*********************************************************************** + * Public make function for usrp2 interface + **********************************************************************/ +usrp2_iface::sptr usrp2_iface::make(transport::udp_simple::sptr ctrl_transport){ +    return usrp2_iface::sptr(new usrp2_iface_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/usrp2/usrp2_iface.hpp b/host/lib/usrp/usrp2/usrp2_iface.hpp new file mode 100644 index 000000000..7b4321c0b --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.hpp @@ -0,0 +1,103 @@ +// +// 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_USRP2_IFACE_HPP +#define INCLUDED_USRP2_IFACE_HPP + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/usrp/dboard_interface.hpp> //spi config +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/cstdint.hpp> +#include "fw_common.h" + +/*! + * The usrp2 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp2_iface : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp2_iface> sptr; + +    /*! +     * Make a new usrp2 interface with the control transport. +     * \param ctrl_transport the udp transport object +     * \return a new usrp2 interface object +     */ +    static sptr make(uhd::transport::udp_simple::sptr ctrl_transport); + +    /*! +     * Perform a control transaction. +     * \param data a control data struct +     * \return the result control data +     */ +    virtual usrp2_ctrl_data_t ctrl_send_and_recv(const usrp2_ctrl_data_t &data) = 0; + +    /*! +     * 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::usrp::spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback +    ) = 0; + +    /*! +     * Get the master clock frequency. +     * \return the frequency in Hz +     */ +    virtual double get_master_clock_freq(void) = 0; +}; + +#endif /* INCLUDED_USRP2_IFACE_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 6d8c1a86c..3bdc5bd02 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -17,6 +17,7 @@  #include "usrp2_impl.hpp"  #include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_simple.hpp>  #include <uhd/usrp/device_props.hpp>  #include <uhd/utils/assert.hpp>  #include <uhd/utils/static.hpp> @@ -24,6 +25,7 @@  #include <boost/format.hpp>  #include <boost/foreach.hpp>  #include <boost/bind.hpp> +#include <boost/asio.hpp> //htonl and ntohl  #include <iostream>  using namespace uhd; @@ -61,7 +63,6 @@ uhd::device_addrs_t usrp2::find(const device_addr_t &hint){      }      //create a udp transport to communicate -    //TODO if an addr is not provided, search all interfaces?      std::string ctrl_port = boost::lexical_cast<std::string>(USRP2_UDP_CTRL_PORT);      udp_simple::sptr udp_transport = udp_simple::make_broadcast(          hint["addr"], ctrl_port @@ -128,9 +129,11 @@ usrp2_impl::usrp2_impl(      udp_simple::sptr ctrl_transport,      udp_zero_copy::sptr data_transport  ){ -    _ctrl_transport = ctrl_transport;      _data_transport = data_transport; +    //make a new interface for usrp2 stuff +    _iface = usrp2_iface::make(ctrl_transport); +      //load the allowed decim/interp rates      //_USRP2_RATES = range(4, 128+1, 1) + range(130, 256+1, 2) + range(260, 512+1, 4)      _allowed_decim_and_interp_rates.clear(); @@ -169,109 +172,6 @@ usrp2_impl::~usrp2_impl(void){  }  /*********************************************************************** - * Misc Access Methods - **********************************************************************/ -double usrp2_impl::get_master_clock_freq(void){ -    return 100e6; -} - -template <class T> void impl_poke(usrp2_impl *impl, boost::uint32_t addr, T data){ -    //setup the out data -    usrp2_ctrl_data_t out_data; -    out_data.id = htonl(USRP2_CTRL_ID_POKE_THIS_REGISTER_FOR_ME_BRO); -    out_data.data.poke_args.addr = htonl(addr); -    out_data.data.poke_args.data = htonl(boost::uint32_t(data)); -    out_data.data.poke_args.num_bytes = sizeof(T); - -    //send and recv -    usrp2_ctrl_data_t in_data = impl->ctrl_send_and_recv(out_data); -    ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_OMG_POKED_REGISTER_SO_BAD_DUDE); -} - -template <class T> T impl_peek(usrp2_impl *impl, boost::uint32_t addr){ -    //setup the out data -    usrp2_ctrl_data_t out_data; -    out_data.id = htonl(USRP2_CTRL_ID_PEEK_AT_THIS_REGISTER_FOR_ME_BRO); -    out_data.data.poke_args.addr = htonl(addr); -    out_data.data.poke_args.num_bytes = sizeof(T); - -    //send and recv -    usrp2_ctrl_data_t in_data = impl->ctrl_send_and_recv(out_data); -    ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_WOAH_I_DEFINITELY_PEEKED_IT_DUDE); -    return T(ntohl(out_data.data.poke_args.data)); -} - - -void usrp2_impl::poke32(boost::uint32_t addr, boost::uint32_t data){ -    return impl_poke<boost::uint32_t>(this, addr, data); -} - -boost::uint32_t usrp2_impl::peek32(boost::uint32_t addr){ -    return impl_peek<boost::uint32_t>(this, addr); -} - -void usrp2_impl::poke16(boost::uint32_t addr, boost::uint16_t data){ -    return impl_poke<boost::uint16_t>(this, addr, data); -} - -boost::uint16_t usrp2_impl::peek16(boost::uint32_t addr){ -    return impl_peek<boost::uint16_t>(this, addr); -} - -boost::uint32_t usrp2_impl::transact_spi( -    int which_slave, -    const spi_config_t &config, -    boost::uint32_t data, -    size_t num_bits, -    bool readback -){ -    static const uhd::dict<spi_config_t::edge_t, int> spi_edge_to_otw = boost::assign::map_list_of -        (spi_config_t::EDGE_RISE, USRP2_CLK_EDGE_RISE) -        (spi_config_t::EDGE_FALL, USRP2_CLK_EDGE_FALL) -    ; - -    //setup the out data -    usrp2_ctrl_data_t out_data; -    out_data.id = htonl(USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO); -    out_data.data.spi_args.dev = which_slave; -    out_data.data.spi_args.miso_edge = spi_edge_to_otw[config.miso_edge]; -    out_data.data.spi_args.mosi_edge = spi_edge_to_otw[config.mosi_edge]; -    out_data.data.spi_args.readback = (readback)? 1 : 0; -    out_data.data.spi_args.num_bits = num_bits; -    out_data.data.spi_args.data = htonl(data); - -    //send and recv -    usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); -    ASSERT_THROW(htonl(in_data.id) == USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE); - -    return ntohl(out_data.data.spi_args.data); -} - -/*********************************************************************** - * Control Send/Recv - **********************************************************************/ -usrp2_ctrl_data_t usrp2_impl::ctrl_send_and_recv(const usrp2_ctrl_data_t &out_data){ -    boost::mutex::scoped_lock lock(_ctrl_mutex); - -    //fill in the seq number and send -    usrp2_ctrl_data_t out_copy = out_data; -    out_copy.seq = htonl(++_ctrl_seq_num); -    _ctrl_transport->send(boost::asio::buffer(&out_copy, sizeof(usrp2_ctrl_data_t))); - -    //loop until we get the packet or timeout -    while(true){ -        usrp2_ctrl_data_t in_data; -        size_t len = _ctrl_transport->recv(asio::buffer(&in_data, sizeof(in_data))); -        if (len >= sizeof(usrp2_ctrl_data_t) and ntohl(in_data.seq) == _ctrl_seq_num){ -            return in_data; -        } -        if (len == 0) break; //timeout -        //didnt get seq or bad packet, continue looking... -    } -    throw std::runtime_error("usrp2 no control response"); -} - -/***********************************************************************   * Device Properties   **********************************************************************/  void usrp2_impl::get(const wax::obj &key_, wax::obj &val){ diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp index f2e823391..7eea1e250 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.hpp +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -18,31 +18,29 @@  #ifndef INCLUDED_USRP2_IMPL_HPP  #define INCLUDED_USRP2_IMPL_HPP +#include "usrp2_iface.hpp"  #include "clock_control.hpp"  #include <uhd/usrp/usrp2.hpp>  #include <uhd/types/dict.hpp>  #include <uhd/types/otw_type.hpp>  #include <uhd/types/stream_cmd.hpp>  #include <uhd/types/clock_config.hpp> -#include <boost/asio.hpp> -#include <boost/thread.hpp>  #include <boost/shared_ptr.hpp>  #include <boost/function.hpp> -#include <boost/assign/list_of.hpp>  #include <uhd/transport/vrt.hpp> -#include <uhd/transport/udp_simple.hpp>  #include <uhd/transport/udp_zero_copy.hpp>  #include <uhd/usrp/dboard_manager.hpp> -#include "fw_common.h" - -class usrp2_impl; //dummy class declaration  /*!   * Make a usrp2 dboard interface. - * \param impl a pointer to the usrp2 impl object + * \param iface the usrp2 interface object + * \param clk_ctrl the clock control object   * \return a sptr to a new dboard interface   */ -uhd::usrp::dboard_interface::sptr make_usrp2_dboard_interface(usrp2_impl *impl); +uhd::usrp::dboard_interface::sptr make_usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    clock_control::sptr clk_ctrl +);  /*!   * Simple wax obj proxy class: @@ -101,40 +99,22 @@ public:      ~usrp2_impl(void); -    //performs a control transaction -    usrp2_ctrl_data_t ctrl_send_and_recv(const usrp2_ctrl_data_t &); - -    //peek and poke registers -    void poke32(boost::uint32_t addr, boost::uint32_t data); -    boost::uint32_t peek32(boost::uint32_t addr); - -    void poke16(boost::uint32_t addr, boost::uint16_t data); -    boost::uint16_t peek16(boost::uint32_t addr); - -    //clock control -    clock_control::sptr get_clock_control(void); - -    //spi read and write -    boost::uint32_t transact_spi( -        int which_slave, -        const uhd::usrp::spi_config_t &config, -        boost::uint32_t data, -        size_t num_bits, -        bool readback -    ); - -    //misc access methods -    double get_master_clock_freq(void); -      //the io interface      size_t send(const boost::asio::const_buffer &, const uhd::tx_metadata_t &, const uhd::io_type_t &);      size_t recv(const boost::asio::mutable_buffer &, uhd::rx_metadata_t &, const uhd::io_type_t &);  private: +    double get_master_clock_freq(void){ +        return _iface->get_master_clock_freq(); +    } +      //device properties interface      void get(const wax::obj &, wax::obj &);      void set(const wax::obj &, const wax::obj &); -    clock_control::sptr _clock_control; + +    //interfaces +    clock_control::sptr _clk_ctrl; +    usrp2_iface::sptr _iface;      //the raw io interface (samples are in the usrp2 native format)      void recv_raw(uhd::rx_metadata_t &); @@ -158,13 +138,8 @@ private:      void io_init(void);      //udp transports for control and data -    uhd::transport::udp_simple::sptr _ctrl_transport;      uhd::transport::udp_zero_copy::sptr _data_transport; -    //private vars for dealing with send/recv control -    boost::uint32_t _ctrl_seq_num; -    boost::mutex _ctrl_mutex; -      //methods and shadows for clock configuration      uhd::clock_config_t _clock_config;      void init_clock_config(void); | 
