text: /****************************************************************************
   * wireless/pktradio/pktradio_loopback.c
   *
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.  The
   * ASF licenses this file to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance with the
   * License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
   * License for the specific language governing permissions and limitations
   * under the License.
   *
   ****************************************************************************/
  
  /****************************************************************************
   * Included Files
   ****************************************************************************/
  
  #include <nuttx/config.h>
  
  #include <stdint.h>
  #include <stdbool.h>
  #include <string.h>
  #include <errno.h>
  #include <assert.h>
  #include <debug.h>
  
  #include <arpa/inet.h>
  #include <net/if.h>
  
  #include <nuttx/wqueue.h>
  #include <nuttx/mm/iob.h>
  #include <nuttx/net/net.h>
  #include <nuttx/net/ip.h>
  #include <nuttx/net/radiodev.h>
  #include <nuttx/net/sixlowpan.h>
  #include <nuttx/wireless/pktradio.h>
  
  #ifdef CONFIG_PKTRADIO_LOOPBACK
  
  /****************************************************************************
   * Pre-processor Definitions
   ****************************************************************************/
  
  /* We need to have the work queue to handle SPI interrupts */
  
  #if !defined(CONFIG_SCHED_WORKQUEUE)
  #  error Worker thread support is required (CONFIG_SCHED_WORKQUEUE)
  #else
  #  if defined(CONFIG_SCHED_LPWORK)
  #    define LPBKWORK LPWORK
  #  elif defined(CONFIG_SCHED_HPWORK)
  #    define LPBKWORK HPWORK
  #  else
  #    error Neither CONFIG_SCHED_LPWORK nor CONFIG_SCHED_HPWORK defined
  #  endif
  #endif
  
  #ifndef CONFIG_WIRELESS_PKTRADIO
  #  error CONFIG_WIRELESS_PKTRADIO=y is required.
  #endif
  
  #ifndef CONFIG_NET_6LOWPAN
  #  error CONFIG_NET_6LOWPAN=y is required.
  #endif
  
  #if (CONFIG_PKTRADIO_ADDRLEN != 1) && (CONFIG_PKTRADIO_ADDRLEN != 2) && \
      (CONFIG_PKTRADIO_ADDRLEN != 8)
  #  error No support for CONFIG_PKTRADIO_ADDRLEN other than {1,2,8}
  #endif
  
  /* Fake value for MAC header length */
  
  #if CONFIG_IOB_BUFSIZE > 40
  #  define MAC_HDRLEN   4
  #else
  #  define MAC_HDRLEN   0
  #endif
  
  /****************************************************************************
   * Private Types
   ****************************************************************************/
  
  /* The lo_driver_s encapsulates all state information for a single hardware
   * interface
   */
  
  struct lo_driver_s
  {
    bool lo_bifup;               /* true:ifup false:ifdown */
    bool lo_pending;             /* True: TX poll pending */
    uint8_t lo_panid[2];         /* Fake PAN ID for testing */
    struct work_s lo_work;       /* For deferring poll work to the work queue */
    FAR struct iob_s *lo_head;   /* Head of IOBs queued for loopback */
    FAR struct iob_s *lo_tail;   /* Tail of IOBs queued for loopback */
  
    /* This holds the information visible to the NuttX network */
  
    struct radio_driver_s lo_radio;  /* Interface understood by the network */
  };
  
  /****************************************************************************
   * Private Data
   ****************************************************************************/
  
  static struct lo_driver_s g_loopback;
  #ifdef CONFIG_NET_6LOWPAN
  static struct sixlowpan_reassbuf_s g_iobuffer;
  #endif
  
  static uint8_t g_mac_addr[CONFIG_PKTRADIO_ADDRLEN] =
  {
  #if CONFIG_PKTRADIO_ADDRLEN == 1
    0xab
  #elif CONFIG_PKTRADIO_ADDRLEN == 2
    0xab, 0xcd
  #elif CONFIG_PKTRADIO_ADDRLEN == 8
    0x0c, 0xfa, 0xde, 0x00, 0xde, 0xad, 0xbe, 0xef
  #endif
  };
  
  /****************************************************************************
   * Private Function Prototypes
   ****************************************************************************/
  
  /* Utility functions */
  
  static void lo_addr2ip(FAR struct net_driver_s *dev);
  static inline void lo_netmask(FAR struct net_driver_s *dev);
  
  /* Polling logic */
  
  static int  lo_loopback(FAR struct net_driver_s *dev);
  static void lo_loopback_work(FAR void *arg);
  
  /* NuttX callback functions */
  
  static int  lo_ifup(FAR struct net_driver_s *dev);
  static int  lo_ifdown(FAR struct net_driver_s *dev);
  static void lo_txavail_work(FAR void *arg);
  static int  lo_txavail(FAR struct net_driver_s *dev);
  #ifdef CONFIG_NET_MCASTGROUP
  static int  lo_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac);
  static int  lo_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac);
  #endif
  #ifdef CONFIG_NETDEV_IOCTL
  static int  lo_ioctl(FAR struct net_driver_s *dev, int cmd,
                unsigned long arg);
  #endif
  static int lo_get_mhrlen(FAR struct radio_driver_s *netdev,
                FAR const void *meta);
  static int lo_req_data(FAR struct radio_driver_s *netdev,
                FAR const void *meta, FAR struct iob_s *framelist);
  static int lo_properties(FAR struct radio_driver_s *netdev,
                FAR struct radiodev_properties_s *properties);
  
  /****************************************************************************
   * Private Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: lo_addr2ip
   *
   * Description:
   *   Create a MAC-based IP address from the IEEE 802.15.14 short or extended
   *   address assigned to the node.
   *
   *  128  112  96   80    64   48   32   16
   * ---- ---- ---- ---- ---- ---- ---- ----
   * fe80 0000 0000 0000 0000 00ff fe00 xxxx 2-byte
   *                                            short address IEEE 48-bit MAC
   * fe80 0000 0000 0000 xxxx xxxx xxxx xxxx 8-byte
   *                                            extended address IEEE EUI-64
   *
   ****************************************************************************/
  
  static void lo_addr2ip(FAR struct net_driver_s *dev)
  {
    /* Set the MAC address as the saddr */
  
    dev->d_mac.radio.nv_addrlen = CONFIG_PKTRADIO_ADDRLEN;
    memcpy(dev->d_mac.radio.nv_addr, g_mac_addr, CONFIG_PKTRADIO_ADDRLEN);
  
    /* Set the IP address */
  
    dev->d_ipv6addr[0]  = HTONS(0xfe80);
    dev->d_ipv6addr[1]  = 0;
    dev->d_ipv6addr[2]  = 0;
    dev->d_ipv6addr[3]  = 0;
  
  #if CONFIG_PKTRADIO_ADDRLEN == 1
    /* Set the IP address based on the 1 byte address */
  
    dev->d_ipv6addr[4]  = 0;
    dev->d_ipv6addr[5]  = HTONS(0x00ff);
    dev->d_ipv6addr[6]  = HTONS(0xfe00);
    dev->d_ipv6addr[7]  = (uint16_t)g_mac_addr[0] << 8;
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 2
    /* Set the IP address based on the 2 byte address */
  
    dev->d_ipv6addr[4]  = 0;
    dev->d_ipv6addr[5]  = HTONS(0x00ff);
    dev->d_ipv6addr[6]  = HTONS(0xfe00);
    dev->d_ipv6addr[7]  = (uint16_t)g_mac_addr[0] << 8 |
                          (uint16_t)g_mac_addr[1];
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 8
    /* Set the IP address based on the 8-byte address */
  
    dev->d_ipv6addr[4]  = (uint16_t)g_mac_addr[0] << 8 |
                          (uint16_t)g_mac_addr[1];
    dev->d_ipv6addr[5]  = (uint16_t)g_mac_addr[2] << 8 |
                          (uint16_t)g_mac_addr[3];
    dev->d_ipv6addr[6]  = (uint16_t)g_mac_addr[4] << 8 |
                          (uint16_t)g_mac_addr[5];
    dev->d_ipv6addr[7]  = (uint16_t)g_mac_addr[6] << 8 |
                          (uint16_t)g_mac_addr[7];
  #endif
  }
  
  /****************************************************************************
   * Name: lo_netmask
   *
   * Description:
   *   Create a netmask of a MAC-based IP address which may be based on either
   *   the IEEE 802.15.14 short or extended address of the MAC.
   *
   *  128  112   96   80   64   48   32   16
   * ---- ---- ---- ---- ---- ---- ---- ----
   * fe80 0000 0000 0000 0000 00ff fe00 xxxx 2-byte
   *                                           short address IEEE 48-bit MAC
   * fe80 0000 0000 0000 xxxx xxxx xxxx xxxx 8-byte
   *                                           extended address IEEE EUI-64
   *
   ****************************************************************************/
  
  static inline void lo_netmask(FAR struct net_driver_s *dev)
  {
    dev->d_ipv6netmask[0]  = 0xffff;
    dev->d_ipv6netmask[1]  = 0xffff;
    dev->d_ipv6netmask[2]  = 0xffff;
    dev->d_ipv6netmask[3]  = 0xffff;
  
  #if CONFIG_PKTRADIO_ADDRLEN == 1
    dev->d_ipv6netmask[4]  = 0xffff;
    dev->d_ipv6netmask[5]  = 0xffff;
    dev->d_ipv6netmask[6]  = 0xffff;
    dev->d_ipv6netmask[7]  = HTONS(0xff00);
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 2
    dev->d_ipv6netmask[4]  = 0xffff;
    dev->d_ipv6netmask[5]  = 0xffff;
    dev->d_ipv6netmask[6]  = 0xffff;
    dev->d_ipv6netmask[7]  = 0;
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 8
    dev->d_ipv6netmask[4]  = 0;
    dev->d_ipv6netmask[5]  = 0;
    dev->d_ipv6netmask[6]  = 0;
    dev->d_ipv6netmask[7]  = 0;
  #endif
  }
  
  /****************************************************************************
   * Name: lo_loopback
   *
   * Description:
   *   Check if the network has any outgoing packets ready to send.  This is
   *   a callback from devif_poll() or devif_timer().  devif_poll() will be
   *   called only during normal TX polling.
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   OK on success; a negated errno on failure
   *
   * Assumptions:
   *   May or may not be called from an interrupt handler.  In either case,
   *   the network is locked.
   *
   ****************************************************************************/
  
  static int lo_loopback(FAR struct net_driver_s *dev)
  {
    FAR struct lo_driver_s *priv = (FAR struct lo_driver_s *)dev->d_private;
    struct pktradio_metadata_s pktmeta;
    FAR struct iob_s *iob;
    int ret;
  
    /* Create some fake metadata */
  
    memset(&pktmeta, 0, sizeof(struct pktradio_metadata_s));
  
    pktmeta.pm_src.pa_addrlen  = CONFIG_PKTRADIO_ADDRLEN;
    pktmeta.pm_dest.pa_addrlen = CONFIG_PKTRADIO_ADDRLEN;
  
    /* On loopback the local address is both the source and destination. */
  
    memcpy(pktmeta.pm_src.pa_addr, g_mac_addr, CONFIG_PKTRADIO_ADDRLEN);
    memcpy(pktmeta.pm_dest.pa_addr, g_mac_addr, CONFIG_PKTRADIO_ADDRLEN);
  
    /* Loop while there framelist to be sent, i.e., while the freme list is not
     * empty.  Sending, of course, just means relaying back through the network
     * for this driver.
     */
  
    while (priv->lo_head != NULL)
      {
        ninfo("Looping frame IOB %p\n", iob);
  
        /* Increment statistics */
  
        NETDEV_RXPACKETS(&priv->lo_radio.r_dev);
  
        /* Remove the IOB from the queue */
  
        iob           = priv->lo_head;
        priv->lo_head = iob->io_flink;
        iob->io_flink = NULL;
  
        /* Did the framelist become empty? */
  
        if (priv->lo_head == NULL)
          {
            priv->lo_tail = NULL;
          }
  
        /* Make sure the our single packet buffer is attached */
  
        priv->lo_radio.r_dev.d_buf = g_iobuffer.rb_buf;
  
        /* Return the next frame to the network */
  
        ninfo("Send frame %p to the network:  Offset=%u Length=%u\n",
              iob, iob->io_offset, iob->io_len);
  
        ret = sixlowpan_input(&priv->lo_radio, iob, (FAR void *)&pktmeta);
  
        /* Increment statistics */
  
        NETDEV_TXPACKETS(&priv->lo_radio.r_dev);
  
        if (ret < 0)
          {
            nerr("ERROR: sixlowpan_input returned %d\n", ret);
            NETDEV_TXERRORS(&priv->lo_radio.r_dev);
            NETDEV_ERRORS(&priv->lo_radio.r_dev);
          }
      }
  
    return 0;
  }
  
  /****************************************************************************
   * Name: lo_loopback_work
   *
   * Description:
   *   Perform loopback of received framelist.
   *
   * Input Parameters:
   *   arg - The argument passed when work_queue() as called.
   *
   * Returned Value:
   *   OK on success
   *
   * Assumptions:
   *   The network is locked
   *
   ****************************************************************************/
  
  static void lo_loopback_work(FAR void *arg)
  {
    FAR struct lo_driver_s *priv = (FAR struct lo_driver_s *)arg;
  
    /* Perform the loopback */
  
    net_lock();
    lo_loopback(&priv->lo_radio.r_dev);
    net_unlock();
  }
  
  /****************************************************************************
   * Name: lo_ifup
   *
   * Description:
   *   NuttX Callback: Bring up the Ethernet interface when an IP address is
   *   provided
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  static int lo_ifup(FAR struct net_driver_s *dev)
  {
    FAR struct lo_driver_s *priv = (FAR struct lo_driver_s *)dev->d_private;
  
    ninfo("Bringing up: IPv6 %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
          dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
          dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
          dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
  
  #if CONFIG_PKTRADIO_ADDRLEN == 1
    ninfo("Node: %02x\n",
           dev->d_mac.radio.nv_addr[0]);
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 2
    ninfo("Node: %02x:%02x\n",
           dev->d_mac.radio.nv_addr[0], dev->d_mac.radio.nv_addr[1]);
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 8
    ninfo("Node: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x PANID=%02x:%02x\n",
           dev->d_mac.radio.nv_addr[0], dev->d_mac.radio.nv_addr[1],
           dev->d_mac.radio.nv_addr[2], dev->d_mac.radio.nv_addr[3],
           dev->d_mac.radio.nv_addr[4], dev->d_mac.radio.nv_addr[5],
           dev->d_mac.radio.nv_addr[6], dev->d_mac.radio.nv_addr[7]);
  #endif
  
    priv->lo_bifup = true;
    return OK;
  }
  
  /****************************************************************************
   * Name: lo_ifdown
   *
   * Description:
   *   NuttX Callback: Stop the interface.
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  static int lo_ifdown(FAR struct net_driver_s *dev)
  {
    FAR struct lo_driver_s *priv = (FAR struct lo_driver_s *)dev->d_private;
  
    ninfo("IP up: %u\n", priv->lo_bifup);
  
    /* Mark the device "down" */
  
    priv->lo_bifup = false;
    return OK;
  }
  
  /****************************************************************************
   * Name: lo_txavail_work
   *
   * Description:
   *   Perform an out-of-cycle poll on the worker thread.
   *
   * Input Parameters:
   *   arg - Reference to the NuttX driver state structure (cast to void*)
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   Called on the higher priority worker thread.
   *
   ****************************************************************************/
  
  static void lo_txavail_work(FAR void *arg)
  {
    FAR struct lo_driver_s *priv = (FAR struct lo_driver_s *)arg;
  
    ninfo("TX available work. IP up: %u\n", priv->lo_bifup);
  
    /* Ignore the notification if the interface is not yet up */
  
    net_lock();
    if (priv->lo_bifup)
      {
        /* If so, then poll the network for new XMIT data */
  
  #ifdef CONFIG_NET_6LOWPAN
        /* Make sure the our single packet buffer is attached */
  
        priv->lo_radio.r_dev.d_buf = g_iobuffer.rb_buf;
  #endif
  
        /* Then perform the poll */
  
        devif_poll(&priv->lo_radio.r_dev, lo_loopback);
      }
  
    net_unlock();
  }
  
  /****************************************************************************
   * Name: lo_txavail
   *
   * Description:
   *   Driver callback invoked when new TX data is available.  This is a
   *   stimulus perform an out-of-cycle poll and, thereby, reduce the TX
   *   latency.
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *   Called in normal user mode
   *
   ****************************************************************************/
  
  static int lo_txavail(FAR struct net_driver_s *dev)
  {
    FAR struct lo_driver_s *priv = (FAR struct lo_driver_s *)dev->d_private;
  
    ninfo("Available: %u\n", work_available(&priv->lo_work));
  
    /* Is our single work structure available?  It may not be if there are
     * pending actions and we will have to ignore the Tx availability
     * action.
     */
  
    if (!work_available(&priv->lo_work) || priv->lo_head != NULL)
      {
        nwarn("WARNING: lo_work NOT available\n");
        priv->lo_pending = true;
      }
    else
      {
        /* Schedule to perform the interrupt processing on the worker thread. */
  
        priv->lo_pending = false;
        work_queue(LPBKWORK, &priv->lo_work, lo_txavail_work, priv, 0);
      }
  
    return OK;
  }
  
  /****************************************************************************
   * Name: lo_addmac
   *
   * Description:
   *   NuttX Callback: Add the specified MAC address to the hardware multicast
   *   address filtering
   *
   * Input Parameters:
   *   dev  - Reference to the NuttX driver state structure
   *   mac  - The MAC address to be added
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  #ifdef CONFIG_NET_MCASTGROUP
  static int lo_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
  {
  #if CONFIG_PKTRADIO_ADDRLEN == 1
    ninfo("MAC: %02x\n", mac[0]);
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 2
    ninfo("MAC: %02x:%02x\n", mac[0], mac[1]);
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 8
    ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], mac[6], mac[7]);
  #endif
  
    /* There is no multicast support in the loopback driver */
  
    return OK;
  }
  #endif
  
  /****************************************************************************
   * Name: lo_rmmac
   *
   * Description:
   *   NuttX Callback:
   *   Remove the specified MAC address from the hardware multicast
   *   address filtering
   *
   * Input Parameters:
   *   dev  - Reference to the NuttX driver state structure
   *   mac  - The MAC address to be removed
   *
   * Returned Value:
   *   None
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  #ifdef CONFIG_NET_MCASTGROUP
  static int lo_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
  {
  #if CONFIG_PKTRADIO_ADDRLEN == 1
    ninfo("MAC: %02x\n", mac[0]);
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 2
    ninfo("MAC: %02x:%02x\n", mac[0], mac[1]);
  
  #elif CONFIG_PKTRADIO_ADDRLEN == 8
    ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], mac[6], mac[7]);
  #endif
  
    /* There is no multicast support in the loopback driver */
  
    return OK;
  }
  #endif
  
  /****************************************************************************
   * Name: macnet_ioctl
   *
   * Description:
   *   Handle network IOCTL commands directed to this device.
   *
   * Input Parameters:
   *   dev - Reference to the NuttX driver state structure
   *   cmd - The IOCTL command
   *   arg - The argument for the IOCTL command
   *
   * Returned Value:
   *   OK on success; Negated errno on failure.
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  #ifdef CONFIG_NETDEV_IOCTL
  static int lo_ioctl(FAR struct net_driver_s *dev, int cmd,
                      unsigned long arg)
  {
    FAR struct pktradio_ifreq_s *cmddata;
    FAR struct lo_driver_s *priv;
    int ret = -ENOTTY;
  
    DEBUGASSERT(dev != NULL && dev->d_private != NULL && arg != 0ul);
    priv    = (FAR struct lo_driver_s *)dev->d_private;
    cmddata = (FAR struct pktradio_ifreq_s *)((uintptr_t)arg);
  
    switch (cmd)
      {
        /* SIOCPKTRADIOGGPROPS
         *   Description:   Get the radio properties
         *   Input:         Pointer to read-write instance of struct
         *                  pktradio_ifreq_s
         *   Output:        Properties returned in struct pktradio_ifreq_s
         *                  instance
         */
  
        case SIOCPKTRADIOGGPROPS:
          {
            FAR struct radio_driver_s *radio =
              (FAR struct radio_driver_s *)dev;
            FAR struct radiodev_properties_s *props =
              (FAR struct radiodev_properties_s *)&cmddata->pifr_props;
  
            ret = lo_properties(radio, props);
          }
          break;
  
        /* SIOCPKTRADIOSNODE
         *   Description:   Set the radio node address
         *   Input:         Pointer to read-only instance of struct
         *                  pktradio_ifreq_s
         *   Output:        None
         */
  
        case SIOCPKTRADIOSNODE:
          {
            FAR const struct pktradio_addr_s *newaddr =
              (FAR const struct pktradio_addr_s *)&cmddata->pifr_hwaddr;
  
            if (newaddr->pa_addrlen != 1)
              {
                ret = -EINVAL;
              }
            else
             {
                FAR struct netdev_varaddr_s *devaddr = &dev->d_mac.radio;
  
                devaddr->nv_addrlen = 1;
                devaddr->nv_addr[0] = newaddr->pa_addr[0];
  #if CONFIG_PKTRADIO_ADDRLEN > 1
                memset(&devaddr->pa_addr[1], 0, CONFIG_PKTRADIO_ADDRLEN - 1);
  #endif
                ret = OK;
              }
          }
          break;
  
        /* SIOCPKTRADIOGNODE
         *   Description:   Get the radio node address
         *   Input:         Pointer to read-write instance of
         *                  struct pktradio_ifreq_s
         *   Output:        Node address return in struct pktradio_ifreq_s
         *                  instance
         */
  
        case SIOCPKTRADIOGNODE:
          {
            FAR struct pktradio_addr_s *retaddr =
              (FAR struct pktradio_addr_s *)&cmddata->pifr_hwaddr;
            FAR const struct netdev_varaddr_s *devaddr = &dev->d_mac.radio;
  
            retaddr->pa_addrlen = devaddr->nv_addrlen;
            retaddr->pa_addr[0] = devaddr->nv_addr[0];
  #if CONFIG_PKTRADIO_ADDRLEN > 1
            memset(&addr->pa_addr[1], 0, CONFIG_PKTRADIO_ADDRLEN - 1);
  #endif
            ret = OK;
          }
          break;
  
        default:
          wlwarn("WARNING: Unrecognized IOCTL command: %02x\n", cmd);
          break;
      }
  
    UNUSED(priv);
    return ret;
  }
  #endif
  
  /****************************************************************************
   * Name: lo_get_mhrlen
   *
   * Description:
   *   Calculate the MAC header length given the frame meta-data.
   *
   * Input Parameters:
   *   netdev    - The networkd device that will mediate the MAC interface
   *   meta      - Obfuscated metadata structure needed to create the radio
   *               MAC header
   *
   * Returned Value:
   *   A non-negative MAC headeer length is returned on success; a negated
   *   errno value is returned on any failure.
   *
   ****************************************************************************/
  
  static int lo_get_mhrlen(FAR struct radio_driver_s *netdev,
                           FAR const void *meta)
  {
    return MAC_HDRLEN;
  }
  
  /****************************************************************************
   * Name: lo_req_data
   *
   * Description:
   *   Requests the transfer of a list of frames to the MAC.
   *
   * Input Parameters:
   *   netdev    - The networkd device that will mediate the MAC interface
   *   meta      - Obfuscated metadata structure needed to create the radio
   *               MAC header
   *   framelist - Head of a list of frames to be transferred.
   *
   * Returned Value:
   *   Zero (OK) returned on success; a negated errno value is returned on
   *   any failure.
   *
   ****************************************************************************/
  
  static int lo_req_data(FAR struct radio_driver_s *netdev,
                         FAR const void *meta, FAR struct iob_s *framelist)
  {
    FAR struct lo_driver_s *priv;
    FAR struct iob_s *iob;
  
    DEBUGASSERT(netdev != NULL && netdev->r_dev.d_private != NULL);
    priv = (FAR struct lo_driver_s *)netdev->r_dev.d_private;
  
    DEBUGASSERT(meta != NULL && framelist != NULL);
  
    /* Add the incoming list of framelist to queue of framelist to loopback */
  
    for (iob = framelist; iob != NULL; iob = framelist)
      {
        /* Increment statistics */
  
        NETDEV_RXPACKETS(&priv->lo_radio.r_dev);
  
        /* Remove the IOB from the queue */
  
        framelist     = iob->io_flink;
        iob->io_flink = NULL;
  
        ninfo("Queuing frame IOB %p\n", iob);
  
        /* Just zero the MAC header for test purposes */
  
        DEBUGASSERT(iob->io_offset == MAC_HDRLEN);
        memset(iob->io_data, 0, MAC_HDRLEN);
  
        /* Add the IOB to the tail of the queue of framelist to be looped
         * back
         */
  
        if (priv->lo_tail == NULL)
          {
            priv->lo_head = iob;
          }
        else
          {
            priv->lo_tail->io_flink = iob;
          }
  
        priv->lo_tail = iob;
      }
  
    /* Schedule to serialize the poll on the worker thread. */
  
    work_queue(LPBKWORK, &priv->lo_work, lo_loopback_work, priv, 0);
    return OK;
  }
  
  /****************************************************************************
   * Name: lo_properties
   *
   * Description:
   *   Different packet radios may have different properties.  If there are
   *   multiple packet radios, then those properties have to be queried at
   *   run time.  This information is provided to the 6LoWPAN network via the
   *   following structure.
   *
   * Input Parameters:
   *   netdev     - The network device to be queried
   *   properties - Location where radio properties will be returned.
   *
   * Returned Value:
   *   Zero (OK) returned on success; a negated errno value is returned on
   *   any failure.
   *
   ****************************************************************************/
  
  static int lo_properties(FAR struct radio_driver_s *netdev,
                           FAR struct radiodev_properties_s *properties)
  {
    DEBUGASSERT(netdev != NULL && properties != NULL);
    memset(properties, 0, sizeof(struct radiodev_properties_s));
  
    /* General */
  
    properties->sp_addrlen  = CONFIG_PKTRADIO_ADDRLEN; /* Length of an address */
    properties->sp_framelen = CONFIG_IOB_BUFSIZE;      /* Fixed frame length */
  
    /* Multicast address */
  
    properties->sp_mcast.nv_addrlen = CONFIG_PKTRADIO_ADDRLEN;
    memset(properties->sp_mcast.nv_addr, 0xee, RADIO_MAX_ADDRLEN);
  
    /* Broadcast address */
  
    properties->sp_bcast.nv_addrlen = CONFIG_PKTRADIO_ADDRLEN;
    memset(properties->sp_mcast.nv_addr, 0xff, RADIO_MAX_ADDRLEN);
  
  #ifdef CONFIG_NET_STARPOINT
    /* Star hub node address */
  
    properties->sp_hubnode.nv_addrlen = 1;
    properties->sp_hubnode.nv_addr[0] = CONFIG_SPIRIT_HUBNODE;
  #endif
  
    return OK;
  }
  
  /****************************************************************************
   * Public Functions
   ****************************************************************************/
  
  /****************************************************************************
   * Name: pktradio_loopback
   *
   * Description:
   *   Initialize and register the Ieee802.15.4 MAC loopback network driver.
   *
   * Input Parameters:
   *   None
   *
   * Returned Value:
   *   OK on success; Negated errno on failure.
   *
   * Assumptions:
   *
   ****************************************************************************/
  
  int pktradio_loopback(void)
  {
    FAR struct lo_driver_s *priv;
    FAR struct radio_driver_s *radio;
    FAR struct net_driver_s *dev;
  
    ninfo("Initializing\n");
  
    /* Get the interface structure associated with this interface number. */
  
    priv = &g_loopback;
  
    /* Initialize the driver structure */
  
    memset(priv, 0, sizeof(struct lo_driver_s));
  
    radio               = &priv->lo_radio;
    dev                 = &radio->r_dev;
    dev->d_ifup         = lo_ifup;          /* I/F up (new IP address) callback */
    dev->d_ifdown       = lo_ifdown;        /* I/F down callback */
    dev->d_txavail      = lo_txavail;       /* New TX data callback */
  #ifdef CONFIG_NET_MCASTGROUP
    dev->d_addmac       = lo_addmac;        /* Add multicast MAC address */
    dev->d_rmmac        = lo_rmmac;         /* Remove multicast MAC address */
  #endif
  #ifdef CONFIG_NETDEV_IOCTL
    dev->d_ioctl        = lo_ioctl;         /* Handle network IOCTL commands */
  #endif
    dev->d_private      = priv;             /* Used to recover private state from dev */
  
    /* Set the network mask and advertise our MAC-based IP address */
  
    lo_netmask(dev);
    lo_addr2ip(dev);
  
    /* Initialize the Network frame-related callbacks */
  
    radio->r_get_mhrlen = lo_get_mhrlen;    /* Get MAC header length */
    radio->r_req_data   = lo_req_data;      /* Enqueue frame for transmission */
    radio->r_properties = lo_properties;    /* Returns radio properties */
  
  #ifdef CONFIG_NET_6LOWPAN
    /* Make sure the our single packet buffer is attached.
     * We must do this before registering the device since,
     *  once the device is registered, a packet may
     * be attempted to be forwarded and require the buffer.
     */
  
    priv->lo_radio.r_dev.d_buf = g_iobuffer.rb_buf;
  #endif
  
    /* Register the loopabck device with the OS so that socket IOCTLs can
     * be performed.
     */
  
    netdev_register(&priv->lo_radio.r_dev, NET_LL_PKTRADIO);
  
    /* Put the network in the UP state */
  
    dev->d_flags = IFF_UP;
    return lo_ifup(&priv->lo_radio.r_dev);
  }
  
  #endif /* CONFIG_PKTRADIO_LOOPBACK */
  