nuttx/drivers/wireless/spirit/drivers/spirit_netdev.c

2890 lines
84 KiB
C

/****************************************************************************
* drivers/wireless/spirit/drivers/spirit_netdev.c
*
* Copyright (C) 2017 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/* A note about threading: There driver uses both the high priority (HP)
* and low priority (LP) work queues. An objective is this threading design
* is to minimize the interactions between the two work queues so that the
* work will be able progress without interlocks.
*
* The HP work queue handles (1) all time-critical interrupt handling, and
* (2) initiation of TX transfers. Most interactions with the spirit part
* occur on the HP work queue. Exceptions: Initialization, I/F up, and I/F
* down.
*
* The LP work queue is the primary interface between the Spirit radio
* network driver and the NuttX network. The LP work queue performs (1)
* input of all frames into the network, (2) queuing of all output frames,
* and all network housekeeping such as periodic polling.
*
* Interrupt handling is verify brief since it only schedules the interrupt
* work to occur on the HP work queue. Several things are done for the
* interrupt handling, but the primary things are: (1) receipt of incoming
* packets, and (2) handling of the completion of TX transfers.
*
* The receipt of the incoming packet is handled on the HP work worker thread.
* The received packet is extracted from the hardware, saved in an I/O
* buffer (IOB), and queued in the RX packet queue. Processing of the
* received packet is scheduled to occur on the LP worker thread where each IOB
* removed from RX packet queue is passed to the network via
* sixlowpan_input().
*
* The entire network logic runs on the LP worker thread. If the receipt of
* the incoming packet generates outgoing TX frames, these are queued by in
* LP work queue. Outgoing frames may also be queued as a consequence of
* period (or TX available) polling. In either case, the outgoing packet is
* queued and processing of the packet -- including the Spirit interface
* interaction -- is scheduled to occur in the HP work queue.
*
* Scheduling of the outgoing TX packet is also handled in the interrupt
* handling logic when an interrupt is received indicating the completion of
* previous transfer and that the hardware is ready to send another packet.
*
* In this way, the interrupt handling and TX processing is serialized on
* the HP worker thread and no special interlocking is required. Network level
* work is similar serialized on the LP worker thread (and also via the network
* locking mechanism). So the only real interactions between the logic
* running on the LP and HP worker threads is in the access to the RX and TX
* packet queues. Semaphore protection is necessary while accessing these
* queues.
*
* A special case is the TX timeout which must be handled on the HP work
* queue since it will reset the spirit interface.
*
* NOTE: If debug output is enabled, then that can disrupt the above
* balance interactions between the HP and LP worker thread actions: HP work
* might get delayed waiting to generate debug output when that output
* device is locked in the LP queue. One way around this is to enable
* buffered syslog output and larger serial console TX buffer size:
*
* CONFIG_SYSLOG_BUFFER=y <-- enable SYSLOG buffering
* CONFIG_SYSLOG_INTBUFFER=y <-- Enable interrupt output buffering
* CONFIG_SYSLOG_INTBUFSIZE=512
* CONFIG_IOB_NBUFFERS=64 <-- Increase the number of IOBs
* CONFIG_USART1_TXBUFSIZE=1024 <-- Increase the console output buffer
* size
*
* Another consideration is the nature of the GPIO interrupts. For STM32,
* for example, disabling the Spirit interrupt tears down the the entire
* interrupt setup so, for example, any interrupts that are received while
* interrupts are disable, aka torn down, will be lost. Hence, it may be
* necessary to process pending interrupts whenever interrupts are re-enabled.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/compiler.h>
#include <assert.h>
#include <debug.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <semaphore.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/wqueue.h>
#include <nuttx/fs/fs.h>
#include <nuttx/mm/iob.h>
#include <nuttx/spi/spi.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/radiodev.h>
#include <nuttx/net/sixlowpan.h>
#include <nuttx/wireless/spirit.h>
#include <nuttx/wireless/pktradio.h>
#include "spirit_config.h"
#include "spirit_types.h"
#include "spirit_general.h"
#include "spirit_irq.h"
#include "spirit_spi.h"
#include "spirit_gpio.h"
#include "spirit_linearfifo.h"
#include "spirit_commands.h"
#include "spirit_radio.h"
#include "spirit_pktstack.h"
#include "spirit_qi.h"
#include "spirit_management.h"
#include "spirit_timer.h"
#include "spirit_csma.h"
#include <arch/board/board.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if !defined(CONFIG_SCHED_LPWORK) || !defined(CONFIG_SCHED_HPWORK)
# error Both high and low priority work queues required in this driver
#endif
#ifndef CONFIG_SPI_EXCHANGE
# error CONFIG_SPI_EXCHANGE required for this driver
#endif
#if !defined(CONFIG_NET) || !defined(CONFIG_NET_6LOWPAN)
# error 6LoWPAN network support is required.
#endif
#ifndef CONFIG_SPIRIT_PKTLEN
# define CONFIG_SPIRIT_PKTLEN SPIRIT_MAX_FIFO_LEN
#endif
#if CONFIG_SPIRIT_PKTLEN > SPIRIT_MAX_FIFO_LEN && \
!defined(CONFIG_SPIRIT_FIFOS)
# error Without CONFIG_SPIRIT_FIFOS, need CONFIG_SPIRIT_PKTLEN <= SPIRIT_MAX_FIFO_LEN
# undef CONFIG_SPIRIT_PKTLEN
# define CONFIG_SPIRIT_PKTLEN SPIRIT_MAX_FIFO_LEN
#endif
/* The packet length width field specifies log2 or the number of bits used to
* transfer the packet length.
*/
#if CONFIG_SPIRIT_PKTLEN < 2
# define PKT_LENGTH_WIDTH 1 /* 0 - 1 */
#elif CONFIG_SPIRIT_PKTLEN < 4
# define PKT_LENGTH_WIDTH 2 /* 2 - 3 */
#elif CONFIG_SPIRIT_PKTLEN < 8
# define PKT_LENGTH_WIDTH 3 /* 4 - 7 */
#elif CONFIG_SPIRIT_PKTLEN < 16
# define PKT_LENGTH_WIDTH 4 /* 8 - 15 */
#elif CONFIG_SPIRIT_PKTLEN < 32
# define PKT_LENGTH_WIDTH 5 /* 16 - 31 */
#elif CONFIG_SPIRIT_PKTLEN < 64
# define PKT_LENGTH_WIDTH 6 /* 32 - 63 */
#elif CONFIG_SPIRIT_PKTLEN < 128
# define PKT_LENGTH_WIDTH 7 /* 63 - 127 */
#elif CONFIG_SPIRIT_PKTLEN < 256
# define PKT_LENGTH_WIDTH 8 /* 128 - 255 */
#elif CONFIG_SPIRIT_PKTLEN < 512
# define PKT_LENGTH_WIDTH 9 /* 256 - 255 */
#elif CONFIG_SPIRIT_PKTLEN < 1024
# define PKT_LENGTH_WIDTH 10 /* 512 - 1023 */
#elif CONFIG_SPIRIT_PKTLEN < 2048
# define PKT_LENGTH_WIDTH 11 /* 1024 - 2047 */
#elif CONFIG_SPIRIT_PKTLEN < 4096
# define PKT_LENGTH_WIDTH 12 /* 2048 - 4095 */
#elif CONFIG_SPIRIT_PKTLEN < 8192
# define PKT_LENGTH_WIDTH 13 /* 4096 - 8191 */
#elif CONFIG_SPIRIT_PKTLEN < 16384
# define PKT_LENGTH_WIDTH 14 /* 8192 - 16383 */
#elif CONFIG_SPIRIT_PKTLEN < 32768
# define PKT_LENGTH_WIDTH 15 /* 16384 - 32767 */
#elif CONFIG_SPIRIT_PKTLEN < 65536
# define PKT_LENGTH_WIDTH 16 /* 32768 - 65535 */
#else
# error Invalid CONFIG_SPIRIT_PKTLEN
#endif
/* Default node address */
#if defined(CONFIG_NET_STARHUB) && defined(CONFIG_SPIRIT_HUBNODE)
# define SPIRIT_NODE_ADDR CONFIG_SPIRIT_HUBNODE
#else
# define SPIRIT_NODE_ADDR 0x34
#endif
/* Linear FIFO thresholds */
#define SPIRIT_RXFIFO_ALMOSTFULL (3 * SPIRIT_MAX_FIFO_LEN / 4)
#define SPIRIT_TXFIFO_ALMOSTEMPTY (1 * SPIRIT_MAX_FIFO_LEN / 4)
/* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per second */
#define SPIRIT_WDDELAY (1*CLK_TCK)
/* Maximum number of retries (10) */
#define SPIRIT_MAX_RETX PKT_N_RETX_10
/* RX timeout = 1.5 seconds. Transmitter will wait this amount timer for
* an ACK from the receiver (per transmission).
*/
#define SPIRIT_RXTIMEOUT 1500.0
/* Failsafe TX timeout = MAX_RETX * RXTIMEOUT + 1 = 16 seconds */
#define SPIRIT_TXTIMEOUT (16*CLK_TCK)
/****************************************************************************
* Private Types
****************************************************************************/
enum spirit_driver_state_e
{
DRIVER_STATE_IDLE = 0,
DRIVER_STATE_SENDING,
DRIVER_STATE_RECEIVING
};
/* SPIRIT1 device instance
*
* Make sure that struct ieee802154_radio_s remains first. If not it will break the
* code
*/
struct spirit_driver_s
{
struct radio_driver_s radio; /* Interface understood by the network */
struct spirit_library_s spirit; /* Spirit library state */
FAR const struct spirit_lower_s *lower; /* Low-level MCU-specific support */
FAR struct pktradio_metadata_s *txhead; /* Head of pending TX transfers */
FAR struct pktradio_metadata_s *txtail; /* Tail of pending TX transfers */
FAR struct pktradio_metadata_s *rxhead; /* Head of completed RX transfers */
FAR struct pktradio_metadata_s *rxtail; /* Tail of completed RX transfers */
#ifdef CONFIG_SPIRIT_FIFOS
FAR struct iob_s *rxbuffer; /* Receiving into this buffer */
#endif
struct work_s irqwork; /* Interrupt continuation work (HP) */
struct work_s txwork; /* TX work queue support (HP) */
struct work_s rxwork; /* RX work queue support (LP) */
struct work_s pollwork; /* TX network poll work (LP) */
WDOG_ID txpoll; /* TX poll timer */
WDOG_ID txtimeout; /* TX timeout timer */
sem_t rxsem; /* Exclusive access to the RX queue */
sem_t txsem; /* Exclusive access to the TX queue */
bool ifup; /* Spirit is on and interface is up */
bool needpoll; /* Timer poll needed */
uint8_t state; /* See enum spirit_driver_state_e */
uint8_t counter; /* Count used with TX timeout */
uint8_t prescaler; /* Prescaler used with TX timeout */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Helpers */
static void spirit_rxlock(FAR struct spirit_driver_s *priv);
static inline void spirit_rxunlock(FAR struct spirit_driver_s *priv);
static void spirit_txlock(FAR struct spirit_driver_s *priv);
static inline void spirit_txunlock(FAR struct spirit_driver_s *priv);
static void spirit_set_ipaddress(FAR struct net_driver_s *dev);
static int spirit_set_readystate(FAR struct spirit_driver_s *priv);
/* TX-related logic */
static void spirit_transmit_work(FAR void *arg);
static void spirit_schedule_transmit_work(FAR struct spirit_driver_s *priv);
static int spirit_txpoll_callback(FAR struct net_driver_s *dev);
/* RX-related logic */
static void spirit_receive_work(FAR void *arg);
static void spirit_schedule_receive_work(FAR struct spirit_driver_s *priv);
/* Interrupt handling */
static void spirit_interrupt_work(FAR void *arg);
static int spirit_interrupt(int irq, FAR void *context, FAR void *arg);
/* Watchdog timer expirations */
static void spirit_txtimeout_work(FAR void *arg);
static void spirit_txtimeout_expiry(int argc, wdparm_t arg, ...);
static void spirit_txpoll_work(FAR void *arg);
static void spirit_txpoll_expiry(int argc, wdparm_t arg, ...);
/* NuttX callback functions */
static int spirit_ifup(FAR struct net_driver_s *dev);
static int spirit_ifdown(FAR struct net_driver_s *dev);
static int spirit_txavail(FAR struct net_driver_s *dev);
#ifdef CONFIG_NET_MCASTGROUP
static int spirit_addmac(FAR struct net_driver_s *dev,
FAR const uint8_t *mac);
static int spirit_rmmac(FAR struct net_driver_s *dev,
FAR const uint8_t *mac);
#endif
#ifdef CONFIG_NETDEV_IOCTL
static int spirit_ioctl(FAR struct net_driver_s *dev, int cmd,
unsigned long arg);
#endif
static int spirit_get_mhrlen(FAR struct radio_driver_s *netdev,
FAR const void *meta);
static int spirit_req_data(FAR struct radio_driver_s *netdev,
FAR const void *meta, FAR struct iob_s *framelist);
static int spirit_properties(FAR struct radio_driver_s *netdev,
FAR struct radiodev_properties_s *properties);
/* Initialization */
int spirit_hw_initialize(FAR struct spirit_driver_s *dev,
FAR struct spi_dev_s *spi);
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef CONFIG_NET_6LOWPAN
/* One single packet buffer */
static struct sixlowpan_reassbuf_s g_iobuffer;
#endif
/* Spirit radio initialization */
static const struct radio_init_s g_radio_init =
{
SPIRIT_BASE_FREQUENCY, /* base_frequency selected in board.h */
SPIRIT_CHANNEL_SPACE, /* chspace selected in board.h */
SPIRIT_XTAL_OFFSET_PPM, /* foffset selected in board.h */
SPIRIT_CHANNEL_NUMBER, /* chnum selected in board.h */
SPIRIT_MODULATION_SELECT, /* modselect selected in board.h */
SPIRIT_DATARATE, /* datarate selected in board.h */
SPIRIT_FREQ_DEVIATION, /* freqdev selected in board.h */
SPIRIT_BANDWIDTH /* bandwidth selected in board.h */
};
/* Spirit PktSTack initialization */
static const struct spirit_pktstack_init_s g_pktstack_init =
{
SPIRIT_SYNC_WORD, /* syncword selected in board.h */
SPIRIT_PREAMBLE_LENGTH, /* premblen selected in board.h*/
SPIRIT_SYNC_LENGTH, /* synclen selected in board.h */
PKT_LENGTH_VAR, /* fixvarlen variable packet length */
PKT_LENGTH_WIDTH, /* pktlenwidth from CONFIG_SPIRIT_PKTLEN */
#ifdef CONFIG_SPIRIT_CRCDISABLE
PKT_NO_CRC, /* crcmode none */
#else
SPIRIT_CRC_MODE, /* crcmode selected in board.h */
#endif
SPIRIT_CONTROL_LENGTH, /* ctrllen selected in board.h */
SPIRIT_EN_FEC, /* fec selected in board.h */
SPIRIT_EN_WHITENING /* datawhite selected in board.h */
};
/* LLP Configuration */
static const struct spirit_pktstack_llp_s g_llp_init =
{
S_ENABLE, /* autoack */
S_DISABLE, /* piggyback */
PKT_N_RETX_10 /* maxretx */
};
/* GPIO Configuration.
*
* REVISIT: Assumes interrupt is on GPIO3. Might need to be configurable.
*/
static const struct spirit_gpio_init_s g_gpioinit =
{
SPIRIT_GPIO_3, /* gpiopin */
SPIRIT_GPIO_MODE_DIGITAL_OUTPUT_LP, /* gpiomode */
SPIRIT_GPIO_DIG_OUT_IRQ /* gpioio */
};
/* CSMA initialization */
static const struct spirit_csma_init_s g_csma_init =
{
1, /* BU counter seed */
S_ENABLE, /* enable persistent mode */
TBIT_TIME_64, /* Tcca time */
TCCA_TIME_3, /* Lcca length */
3, /* max nr of backoffs (<8) */
8 /* BU prescaler */
};
#ifdef CONFIG_SPIRIT_PROMISICUOUS
static struct spirit_pktstack_address_s g_addrinit =
{
S_DISABLE, /* Disable filtering on node address */
SPIRIT_NODE_ADDR, /* Node address (Temporary, until assigned) */
S_DISABLE, /* Disable filtering on multicast address */
SPIRIT_MCAST_ADDRESS, /* Multicast address */
S_DISABLE, /* Disable filtering on broadcast address */
SPIRIT_BCAST_ADDRESS /* Broadcast address */
};
#else
static struct spirit_pktstack_address_s g_addrinit =
{
S_ENABLE, /* Enable filtering on node address */
SPIRIT_NODE_ADDR, /* Node address (Temporary, until assigned) */
#ifdef CONFIG_SPIRIT_MULTICAST
S_ENABLE, /* Enable filtering on multicast address */
#else
S_DISABLE, /* Disable filtering on multicast address */
#endif
SPIRIT_MCAST_ADDRESS, /* Multicast address */
#ifdef CONFIG_SPIRIT_BROADCAST
S_ENABLE, /* Enable filtering on broadcast address */
#else
S_DISABLE, /* Disable filtering on broadcast address */
#endif
SPIRIT_BCAST_ADDRESS /* Broadcast address */
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: spirit_rxlock
*
* Description:
* Get exclusive access to the incoming RX packet queue.
*
* Input Parameters:
* priv - Reference to a driver state structure instance
*
* Returned Value:
* None
*
****************************************************************************/
static void spirit_rxlock(FAR struct spirit_driver_s *priv)
{
int ret;
do
{
/* Take the semaphore (perhaps waiting) */
ret = nxsem_wait(&priv->rxsem);
/* The only case that an error should occur here is if the wait was
* awakened by a signal.
*/
DEBUGASSERT(ret == OK || ret == -EINTR);
}
while (ret == -EINTR);
}
/****************************************************************************
* Name: spirit_rxunlock
*
* Description:
* Relinquish exclusive access to the incoming RX packet queue.
*
* Input Parameters:
* priv - Reference to a driver state structure instance
*
* Returned Value:
* None
*
****************************************************************************/
static inline void spirit_rxunlock(FAR struct spirit_driver_s *priv)
{
nxsem_post(&priv->rxsem);
}
/****************************************************************************
* Name: spirit_txlock
*
* Description:
* Get exclusive access to the outgoing TX packet queue.
*
* Input Parameters:
* priv - Reference to a driver state structure instance
*
* Returned Value:
* None
*
****************************************************************************/
static void spirit_txlock(FAR struct spirit_driver_s *priv)
{
int ret;
do
{
/* Take the semaphore (perhaps waiting) */
ret = nxsem_wait(&priv->txsem);
/* The only case that an error should occur here is if the wait was
* awakened by a signal.
*/
DEBUGASSERT(ret == OK || ret == -EINTR);
}
while (ret == -EINTR);
}
/****************************************************************************
* Name: spirit_txunlock
*
* Description:
* Relinquish exclusive access to the outgoing TX packet queue.
*
* Input Parameters:
* priv - Reference to a driver state structure instance
*
* Returned Value:
* None
*
****************************************************************************/
static inline void spirit_txunlock(FAR struct spirit_driver_s *priv)
{
nxsem_post(&priv->txsem);
}
/****************************************************************************
* Name: spirit_set_ipaddress
*
* Description:
* Set the advertised node addressing. External logic must set a unique
* 8-bit node-address for the radio. We will then derive the IPv6
* address for that.
*
* Input Parameters:
* spirit - Reference to a Spirit library state structure instance
*
* Returned Value:
* OK on success; a negated errno on a timeout
*
****************************************************************************/
static void spirit_set_ipaddress(FAR struct net_driver_s *dev)
{
FAR struct netdev_varaddr_s *addr;
/* Get a convenient pointer to the PktRadio variable length address struct */
addr = (FAR struct netdev_varaddr_s *)&dev->d_mac.radio;
/* Has a node address been assigned? */
if (addr->nv_addrlen == 0)
{
/* No.. Use the default address */
wlwarn("WARNING: No address assigned. Using %02x\n",
SPIRIT_NODE_ADDR);
addr->nv_addrlen = 1;
addr->nv_addr[0] = SPIRIT_NODE_ADDR;
}
/* Then set the IP address derived from the node address */
dev->d_ipv6addr[0] = HTONS(0xfe80);
dev->d_ipv6addr[1] = 0;
dev->d_ipv6addr[2] = 0;
dev->d_ipv6addr[3] = 0;
dev->d_ipv6addr[4] = 0;
dev->d_ipv6addr[5] = HTONS(0x00ff);
dev->d_ipv6addr[6] = HTONS(0xfe00);
dev->d_ipv6addr[7] = (uint16_t)addr->nv_addr[0] << 8 ^ 0x0200;
}
/****************************************************************************
* Name: spirit_set_readystate
*
* Description:
* Got to the READY state (if possible).
*
* Input Parameters:
* spirit - Reference to a Spirit library state structure instance
*
* Returned Value:
* OK on success; a negated errno on a timeout
*
* Assumptions:
* We have exclusive access to the driver state and to the spirit library.
*
****************************************************************************/
static int spirit_set_readystate(FAR struct spirit_driver_s *priv)
{
FAR struct spirit_library_s *spirit = &priv->spirit;
int ret;
DEBUGASSERT(priv->lower != NULL && priv->lower->enable != NULL);
priv->lower->enable(priv->lower, false);
ret = spirit_command(spirit, CMD_FLUSHRXFIFO);
if (ret < 0)
{
goto errout_with_irqdisable;
}
ret = spirit_update_status(spirit);
if (ret < 0)
{
goto errout_with_irqdisable;
}
if (spirit->u.state.MC_STATE == MC_STATE_STANDBY)
{
ret = spirit_command(spirit, CMD_READY);
}
else if(spirit->u.state.MC_STATE == MC_STATE_RX)
{
ret = spirit_command(spirit, CMD_SABORT);
}
if (ret < 0)
{
goto errout_with_irqdisable;
}
ret = spirit_irq_clr_pending(spirit);
errout_with_irqdisable:
priv->lower->enable(priv->lower, true);
return ret;
}
/****************************************************************************
* Name: spirit_free_txhead
*
* Description:
* Free the IOB and the meta data at the head of the TX packet queue.
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* Running on the HP worker thread.
*
****************************************************************************/
static void spirit_free_txhead(FAR struct spirit_driver_s *priv)
{
FAR struct pktradio_metadata_s *pktmeta;
/* Remove the metadata and contained IOB from the head of the TX queue */
pktmeta = priv->txhead;
DEBUGASSERT(pktmeta != NULL);
priv->txhead = pktmeta->pm_flink;
if (priv->txhead == NULL)
{
priv->txtail = NULL;
}
/* Free the IOB contained in the metadata container */
iob_free(pktmeta->pm_iob);
/* Then free the meta data container itself */
pktradio_metadata_free(pktmeta);
}
/****************************************************************************
* Name: spirit_transmit_work
*
* Description:
* Start hardware transmission.
*
* Input Parameters:
* arg - Reference to the driver state structure (cast to NULL)
*
* Returned Value:
* None
*
* Assumptions:
* Running on the HP worker thread.
*
****************************************************************************/
static void spirit_transmit_work(FAR void *arg)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
FAR struct spirit_library_s *spirit = &priv->spirit;
FAR struct pktradio_metadata_s *pktmeta;
FAR struct iob_s *iob;
int ret;
DEBUGASSERT(priv != NULL);
/* Check if there are any pending transfers in the TX AND if the hardware
* is not busy with another reception or transmission.
*/
wlinfo("txhead=%p state=%u\n", priv->txhead, priv->state);
/* Take the TX packet queue lock to assure that it is not currently being
* modified on the LP worker thread. NOTE that it is not harmful to hold the
* TX packet queue lock throughout this function; the LP worker thread cannot
* run until this completes.
*/
spirit_txlock(priv);
while (priv->txhead != NULL && priv->state == DRIVER_STATE_IDLE)
{
/* Peek at the packet at the head of the TX queue */
pktmeta = priv->txhead;
DEBUGASSERT(pktmeta != NULL && pktmeta->pm_iob != NULL);
iob = pktmeta->pm_iob;
/* Checks if the payload length is supported */
if (iob->io_len > CONFIG_SPIRIT_PKTLEN)
{
NETDEV_RXDROPPED(&priv->radio.r_dev);
spirit_free_txhead(priv);
continue;
}
/* Reset state to ready */
ret = spirit_set_readystate(priv);
if (ret < 0)
{
wlerr("ERROR: Failed to set READY state: %d\n", ret);
goto errout_with_lock;
}
/* Make sure that the TX linear FIFO is completely empty */
ret = spirit_command(spirit, COMMAND_FLUSHTXFIFO);
if (ret < 0)
{
wlerr("ERROR: Failed to flush TX FIFO\n");
goto errout_with_lock;
}
/* Sets the length of the packet to send */
wlinfo("Payload length=%u\n", iob->io_len);
ret = spirit_pktstack_set_payloadlen(spirit, iob->io_len);
if (ret < 0)
{
wlerr("ERROR: Failed to set payload length: %d\n", ret);
goto errout_with_lock;
}
/* Set the destination address */
DEBUGASSERT(pktmeta->pm_dest.pa_addrlen == 1);
wlinfo("txdestaddr=%02x\n", pktmeta->pm_dest.pa_addr[0]);
ret = spirit_pktcommon_set_txdestaddr(spirit,
pktmeta->pm_dest.pa_addr[0]);
if (ret < 0)
{
wlerr("ERROR: Failed to set TX destaddr to %02x: %d\n",
pktmeta->pm_dest.pa_addr[0], ret);
goto errout_with_lock;
}
/* Enable CSMA */
wlinfo("Enable CSMA and send packet\n");
ret = spirit_csma_enable(spirit, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Failed to enable CSMA: %d\n", ret);
goto errout_with_lock;
}
/* Write the packet to the linear FIFO */
ret = spirit_fifo_write(spirit, iob->io_data, iob->io_len);
if (ret < 0)
{
wlerr("ERROR: Write to linear FIFO failed: %d\n", ret);
goto errout_with_csma;
}
#if 0 /* Not verified */
/* If we are sending to the multicast or broadcast address, we should
* disable ACKs, retries, and RX timeouts.
*
* Check if this is a broadcast, multicast, or unicast transfer
*/
if (pktmeta->pm_dest.pa_addr[0] == SPIRIT_BCAST_ADDRESS ||
pktmeta->pm_dest.pa_addr[0] == SPIRIT_MCAST_ADDRESS)
{
/* Broadcast or multicast... Disable the ACK response */
ret = spirit_pktcommon_enable_txautoack(spirit, S_DISABLE);
if (ret < 0)
{
wlerr("ERROR: spirit_pktcommon_enable_txautoack failed: %d\n",
ret);
goto errout_with_csma;
}
/* Make certain that the RX timeout is disabled */
ret = spirit_timer_set_rxtimeout_counter(spirit, 0);
if (ret < 0)
{
wlerr("ERROR: spirit_timer_set_rxtimeout_counter failed: %d\n",
ret);
goto errout_with_csma;
}
}
else
{
/* Unicast.. enable the Auto ACK response */
ret = spirit_pktcommon_enable_txautoack(spirit, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: spirit_pktcommon_enable_txautoack failed: %d\n",
ret);
goto errout_with_csma;
}
/* And start the RX timeout */
ret = spirit_timer_setup_rxtimeout(spirit, priv->counter,
priv->prescaler);
if (ret < 0)
{
wlerr("ERROR: spirit_timer_setup_rxtimeout failed: %d\n", ret);
goto errout_with_csma;
}
}
#else
/* Start the RX timeout */
ret = spirit_timer_setup_rxtimeout(spirit, priv->counter,
priv->prescaler);
if (ret < 0)
{
wlerr("ERROR: spirit_timer_setup_rxtimeout failed: %d\n", ret);
goto errout_with_csma;
}
#endif
/* Put the SPIRIT1 into TX state. This starts the transmission */
ret = spirit_command(spirit, COMMAND_TX);
if (ret < 0)
{
wlerr("ERROR: Write to send TX command: %d\n", ret);
goto errout_with_rxtimeout;
}
/* Wait until we have successfully entered the TX state.
* This fails normally on race conditions where Spirit enters the
* the RX state asynchronously.
*/
ret = spirit_waitstatus(spirit, MC_STATE_TX, 5);
if (ret < 0)
{
wlinfo("Failed to go to TX state: %d\n", ret);
goto errout_with_rxtimeout;
}
priv->state = DRIVER_STATE_SENDING;
/* We can now free the packet meta data and IOB at the head of the TX
* packet queue.
*/
spirit_free_txhead(priv);
/* Setup the TX timeout watchdog (perhaps restarting the timer) */
(void)wd_start(priv->txtimeout, SPIRIT_TXTIMEOUT,
spirit_txtimeout_expiry, 1, (wdparm_t)priv);
}
spirit_txunlock(priv);
return;
errout_with_rxtimeout:
(void)spirit_timer_set_rxtimeout_counter(spirit, 0);
errout_with_csma:
(void)spirit_csma_enable(spirit, S_DISABLE);
errout_with_lock:
spirit_txunlock(priv);
NETDEV_TXERRORS(&priv->radio.r_dev);
return;
}
/****************************************************************************
* Name: spirit_schedule_transmit_work
*
* Description:
* Schedule to send data on the HP worker thread.
*
* Input Parameters:
* priv - Reference to driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* Called from logic running either the HP worker thread.
*
****************************************************************************/
static void spirit_schedule_transmit_work(FAR struct spirit_driver_s *priv)
{
if (priv->txhead != NULL && priv->state == DRIVER_STATE_IDLE &&
work_available(&priv->txwork))
{
/* Schedule to perform the TX processing on the worker thread. */
work_queue(HPWORK, &priv->txwork, spirit_transmit_work, priv, 0);
}
}
/****************************************************************************
* Name: spirit_txpoll_callback
*
* Description:
* The transmitter is available, check if the network has any outgoing
* packets ready to send. This is a callback from devif_poll().
* devif_poll() may be called:
*
* 1. When the preceding TX packet send is complete,
* 2. When the preceding TX packet send timesout and the interface is reset
* 3. 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:
* Called from network logic, running on the LP worker thread with the
* network locked.
*
****************************************************************************/
static int spirit_txpoll_callback(FAR struct net_driver_s *dev)
{
/* If zero is returned, the polling will continue until all connections have
* been examined.
*
* REVISIT: Should we halt polling if there are packets in flight.
*/
return 0;
}
/****************************************************************************
* Name: spirit_receive_work
*
* Description:
* Pass received packets to the network on the LP worker thread.
*
* Input Parameters:
* arg - Reference to driver state structure (cast to void *)
*
* Returned Value:
* None
*
* Assumptions:
* Running on the LP worker thread.
*
****************************************************************************/
static void spirit_receive_work(FAR void *arg)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
FAR struct pktradio_metadata_s *pktmeta;
FAR struct iob_s *iob;
int ret;
DEBUGASSERT(priv != NULL);
/* We need to have exclusive access to the RX queue; new RX packets may be
* queued asynchronously by interrupt level logic.
*/
spirit_rxlock(priv);
while (priv->rxhead != NULL)
{
/* Remove the contained IOB from the RX queue */
pktmeta = priv->rxhead;
priv->rxhead = pktmeta->pm_flink;
pktmeta->pm_flink = NULL;
/* Did the RX queue become empty? */
if (priv->rxhead == NULL)
{
priv->rxtail = NULL;
}
spirit_rxunlock(priv);
/* Remove the IOB from the container */
iob = pktmeta->pm_iob;
pktmeta->pm_iob = NULL;
/* Make sure the our single packet buffer is attached */
priv->radio.r_dev.d_buf = g_iobuffer.rb_buf;
priv->radio.r_dev.d_len = 0;
/* Send the next frame to the network */
wlinfo("Send frame %p to the network: Offset=%u Length=%u\n",
iob, iob->io_offset, iob->io_len);
net_lock();
ret = sixlowpan_input(&priv->radio, iob, (FAR void *)pktmeta);
if (ret < 0)
{
wlerr("ERROR: sixlowpan_input returned %d\n", ret);
NETDEV_RXERRORS(&priv->radio.r_dev);
NETDEV_ERRORS(&priv->radio.r_dev);
}
net_unlock();
/* sixlowpan_input() will free the IOB, but we must free the struct
* pktradio_metadata_s container here.
*/
pktradio_metadata_free(pktmeta);
/* Get exclusive access as needed at the top of the loop */
spirit_rxlock(priv);
}
spirit_rxunlock(priv);
}
/****************************************************************************
* Name: spirit_schedule_receive_work
*
* Description:
* Schedule to receive data on the LP worker thread.
*
* Input Parameters:
* priv - Reference to driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* Called from logic running the HP worker thread.
*
****************************************************************************/
static inline void spirit_schedule_receive_work(FAR struct spirit_driver_s *priv)
{
/* Schedule to perform the RX processing on the worker thread. */
work_queue(LPWORK, &priv->rxwork, spirit_receive_work, priv, 0);
}
/****************************************************************************
* Name: spirit_interrupt_work
*
* Description:
* Actual thread to handle the irq outside of privaleged mode.
*
****************************************************************************/
static void spirit_interrupt_work(FAR void *arg)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
FAR struct spirit_library_s *spirit;
struct spirit_irqset_s irqstatus;
DEBUGASSERT(priv != NULL);
spirit = &priv->spirit;
/* Get the set of pending ineterrupts from the radio.
* NOTE: The pending interrupts are cleared as a side-effect of reading
* the IRQ status register.
*/
DEBUGVERIFY(spirit_irq_get_pending(spirit, &irqstatus));
wlinfo("Pending: %08lx\n", *(FAR unsigned long *)&irqstatus);
/* Process the Spirit1 interrupt */
/* First check for errors */
if (irqstatus.IRQ_RX_FIFO_ERROR != 0)
{
wlwarn("WARNING: Rx FIFO Error\n");
/* Discard RX data */
DEBUGVERIFY(spirit_command(spirit, CMD_FLUSHRXFIFO));
irqstatus.IRQ_RX_DATA_READY = 0;
irqstatus.IRQ_VALID_SYNC = 0;
#ifdef CONFIG_SPIRIT_FIFOS
irqstatus.IRQ_RX_FIFO_ALMOST_FULL = 0;
/* Discard any packet buffer that might have been allocated */
if (priv->rxbuffer != NULL)
{
iob_free(priv->rxbuffer);
priv->rxbuffer = NULL;
}
#endif
/* Revert the receiving state */
if (priv->state == DRIVER_STATE_RECEIVING)
{
priv->state = DRIVER_STATE_IDLE;
}
/* Update error statistics */
NETDEV_RXERRORS(&priv->radio.r_dev);
NETDEV_ERRORS(&priv->radio.r_dev);
/* Disable CSMA and the RX timeout */
DEBUGVERIFY(spirit_timer_set_rxtimeout_counter(spirit, 0));
DEBUGVERIFY(spirit_csma_enable(spirit, S_DISABLE));
/* Send any pending packets */
spirit_schedule_transmit_work(priv);
}
if (irqstatus.IRQ_TX_FIFO_ERROR != 0 ||
irqstatus.IRQ_MAX_RE_TX_REACH != 0 ||
irqstatus.IRQ_MAX_BO_CCA_REACH != 0)
{
#ifdef CONFIG_DEBUG_WIRELESS_WARN
wlwarn("WARNING: Tx Error\n");
if (irqstatus.IRQ_TX_FIFO_ERROR != 0)
{
wlwarn(" Tx FIFO Error\n");
}
if (irqstatus.IRQ_MAX_RE_TX_REACH != 0)
{
wlwarn(" Max retries reached\n");
}
if (irqstatus.IRQ_MAX_BO_CCA_REACH != 0)
{
wlwarn(" Max backoff reached\n");
}
#endif
/* Discard TX data in the FIFO */
DEBUGVERIFY(spirit_command(spirit, COMMAND_FLUSHTXFIFO));
irqstatus.IRQ_TX_DATA_SENT = 0;
/* Were we in the sending state? */
if (priv->state == DRIVER_STATE_SENDING)
{
/* Yes.. Cancel the TX timeout */
wd_cancel(priv->txtimeout);
/* Revert the sending state */
priv->state = DRIVER_STATE_IDLE;
}
/* Update error statistics */
NETDEV_TXERRORS(&priv->radio.r_dev);
NETDEV_ERRORS(&priv->radio.r_dev);
/* Disable CSMA and the RX timeout */
DEBUGVERIFY(spirit_timer_set_rxtimeout_counter(spirit, 0));
DEBUGVERIFY(spirit_csma_enable(spirit, S_DISABLE));
/* Send any pending packets */
spirit_schedule_transmit_work(priv);
}
/* The IRQ_TX_DATA_SENT bit notifies that a packet was sent. */
if (irqstatus.IRQ_TX_DATA_SENT != 0)
{
wlinfo("Data sent\n");
/* Cancel the TX timeout */
wd_cancel(priv->txtimeout);
/* Put the Spirit back in the receiving state */
DEBUGVERIFY(spirit_management_rxstrobe(spirit));
DEBUGVERIFY(spirit_command(spirit, CMD_RX));
if (priv->state == DRIVER_STATE_SENDING)
{
priv->state = DRIVER_STATE_IDLE;
}
/* Update statistics */
NETDEV_TXDONE(&priv->radio.r_dev);
/* Disable CSMA and the RX timeout */
DEBUGVERIFY(spirit_timer_set_rxtimeout_counter(spirit, 0));
DEBUGVERIFY(spirit_csma_enable(spirit, S_DISABLE));
/* Check if there are more packets to send */
spirit_schedule_transmit_work(priv);
}
#if defined(CONFIG_SPIRIT_FIFOS) && CONFIG_SPIRIT_PKTLEN > SPIRIT_MAX_FIFO_LEN
/* The IRQ_TX_FIFO_ALMOST_EMPTY notifies an nearly empty TX fifo.
* Necessary for sending large packets > sizeof(TX FIFO).
*/
if (irqstatus.IRQ_TX_FIFO_ALMOST_EMPTY != 0)
{
wlinfo("TX FIFO almost empty\n");
#warning Missing logic
}
#endif
/* The IRQ_VALID_SYNC bit is used to notify a new packet is coming */
if (irqstatus.IRQ_VALID_SYNC != 0)
{
wlinfo("Valid sync\n");
/* I have seen multiple Valid Sync interrupts following an RX error
* condition.
*/
if (priv->state != DRIVER_STATE_RECEIVING)
{
/* As a race condition, the TX state, but overriden by concurrent
* RX activity? This assertion here *does* fire:
*
* DEBUGASSERT(priv->state == DRIVER_STATE_IDLE);
*/
priv->state = DRIVER_STATE_RECEIVING;
}
#ifdef CONFIG_SPIRIT_FIFOS
/* Pre-allocate an IOB to hold the received data */
if (priv->rxbuffer == NULL)
{
priv->rxbuffer = iob_alloc(0);
}
if (priv->rxbuffer != NULL)
{
priv->rxbuffer->io_len = 0;
priv->rxbuffer->io_offset = 0;
priv->rxbuffer->io_pktlen = 0;
priv->rxbuffer->io_flink = NULL;
}
#endif
}
/* The IRQ_RX_DATA_READY notifies that a new packet has been received */
if (irqstatus.IRQ_RX_DATA_READY != 0)
{
FAR struct pktradio_metadata_s *pktmeta;
FAR struct iob_s *iob;
uint8_t offset;
uint8_t count;
wlinfo("Data ready\n");
NETDEV_RXPACKETS(&priv->radio.r_dev);
#ifdef CONFIG_SPIRIT_FIFOS
/* Do not process RX FIFO almost full interrupt */
irqstatus.IRQ_RX_FIFO_ALMOST_FULL = 0;
/* There should be a packet buffer that was allocated when the data sync
* interrupt was processed.
*/
iob = priv->rxbuffer;
priv->rxbuffer = NULL;
/* Get the offset to the data from the last RX FIFO almost full
* interrupt.
*/
offset = 0;
if (iob != NULL)
{
offset = iob->io_len;
}
#else
offset = 0;
#endif
/* Get the number of bytes avaialable in the RX FIFO */
count = spirit_fifo_get_rxcount(spirit);
wlinfo("Receiving %u bytes (%u total)\n", count, count + offset);
if ((offset + count) > CONFIG_IOB_BUFSIZE)
{
wlwarn("WARNING: Packet too large... dropping\n");
/* Flush the RX FIFO and revert the receiving state */
DEBUGVERIFY(spirit_command(spirit, CMD_FLUSHRXFIFO));
priv->state = DRIVER_STATE_IDLE;
/* Update error statistics */
NETDEV_RXDROPPED(&priv->radio.r_dev);
}
else
{
#ifdef CONFIG_SPIRIT_FIFOS
if (iob == NULL)
#endif
{
/* Allocate an I/O buffer to hold the received packet. */
iob = iob_alloc(0);
}
if (iob == NULL)
{
wlerr("ERROR: Failed to allocate IOB... dropping\n");
/* Flush the RX FIFO and revert the receiving state */
DEBUGVERIFY(spirit_command(spirit, CMD_FLUSHRXFIFO));
priv->state = DRIVER_STATE_IDLE;
/* Update error statistics */
NETDEV_RXDROPPED(&priv->radio.r_dev);
}
else
{
/* Read the remainder of the packet into the I/O buffer */
DEBUGVERIFY(spirit_fifo_read(spirit, &iob->io_data[offset], count));
iob->io_len = spirit_pktstack_get_rxpktlen(spirit);
iob->io_offset = 0;
iob->io_pktlen = iob->io_len;
iob->io_flink = NULL;
/* Flush the RX FIFO and revert the receiving state */
DEBUGVERIFY(spirit_command(spirit, CMD_FLUSHRXFIFO));
priv->state = DRIVER_STATE_IDLE;
/* Create the packet meta data and forward to the network. This
* must be done on the LP worker thread with the network locked.
*/
pktmeta = pktradio_metadata_allocate();
if (pktmeta == NULL)
{
wlerr("ERROR: Failed to allocate metadata... dropping\n");
NETDEV_RXDROPPED(&priv->radio.r_dev);
iob_free(iob);
}
else
{
/* Get the packet meta data. This consists only of the
* source and destination addresses.
*/
pktmeta->pm_iob = iob;
pktmeta->pm_src.pa_addrlen = 1;
pktmeta->pm_src.pa_addr[0] =
spirit_pktcommon_get_rxsrcaddr(spirit);
pktmeta->pm_dest.pa_addrlen = 1;
pktmeta->pm_dest.pa_addr[0] =
spirit_pktcommon_get_nodeaddress(spirit);
wlinfo("RX srcaddr=%02x destaddr=%02x\n",
pktmeta->pm_src.pa_addr[0],
pktmeta->pm_dest.pa_addr[0]);
/* Add the contained IOB to the tail of the queue of
* completed RX transfers.
*/
pktmeta->pm_flink = NULL;
spirit_rxlock(priv);
if (priv->rxtail == NULL)
{
priv->rxhead = pktmeta;
}
else
{
priv->rxtail->pm_flink = pktmeta;
}
priv->rxtail = pktmeta;
spirit_rxunlock(priv);
/* Forward the packet to the network. This must be done
* on the LP worker thread with the network locked.
*/
spirit_schedule_receive_work(priv);
}
}
}
/* Schedule to send any pending packets */
spirit_schedule_transmit_work(priv);
}
#ifdef CONFIG_SPIRIT_FIFOS
/* The IRQ_RX_FIFO_ALMOST_FULL notifies an nearly full RX fifo.
* Necessary for receiving large packets > sizeof(RX FIFO).
*/
if (irqstatus.IRQ_RX_FIFO_ALMOST_FULL != 0)
{
FAR struct iob_s *iob;
uint8_t offset;
uint8_t count;
wlinfo("RX FIFO almost full\n");
/* There should be a packet buffer that was allocated when the data sync
* interrupt was processed.
*/
if (priv->rxbuffer != NULL)
{
iob = priv->rxbuffer;
offset = iob->io_len;
}
else
{
/* If not, then allocate one now. */
priv->rxbuffer = iob_alloc(0);
iob = priv->rxbuffer;
offset = 0;
}
if (iob != NULL)
{
/* Get the number of bytes avaialable in the RX FIFO */
count = spirit_fifo_get_rxcount(spirit);
wlinfo("Receiving %u bytes (%u so far)\n", count, count + offset);
if ((offset + count) > CONFIG_IOB_BUFSIZE)
{
wlwarn("WARNING: Packet too large... dropping\n");
/* Flush the RX FIFO and revert the receiving state */
DEBUGVERIFY(spirit_command(spirit, CMD_FLUSHRXFIFO));
priv->state = DRIVER_STATE_IDLE;
/* Update statistics */
NETDEV_RXDROPPED(&priv->radio.r_dev);
/* Free the IOB */
priv->rxbuffer = NULL;
iob_free(iob);
}
else
{
/* Read more of the packet into the I/O buffer */
DEBUGVERIFY(spirit_fifo_read(spirit, &iob->io_data[offset], count));
iob->io_len = count + offset;
iob->io_offset = 0;
iob->io_pktlen = iob->io_len;
iob->io_flink = NULL;
}
}
}
#endif
/* IRQ_RX_DATA_DISC indicates that Rx data was discarded */
if (irqstatus.IRQ_RX_DATA_DISC != 0)
{
#if defined(CONFIG_DEBUG_WIRELESS_INFO)
/* Discarded packets are a normal consequence of address filtering. */
wlinfo("Data discarded: Node addr=%02x RX dest addr=%02x\n",
spirit_pktcommon_get_nodeaddress(spirit),
spirit_pktcommon_get_rxdestaddr(spirit));
wlinfo(" CRC error=%u RX timeout=%u\n",
irqstatus.IRQ_CRC_ERROR, irqstatus.IRQ_RX_TIMEOUT);
#elif defined(CONFIG_DEBUG_WIRELESS_INFO)
/* Discards due to CRC errors (or RX timeouts if we used them)
* are indications of a real problem.
*/
if (irqstatus.IRQ_CRC_ERROR != 0)
{
wlwarn("WARNING: Data discarded due to CRC filter\n",
}
#endif
/* Flush the RX FIFO and revert the receiving state */
DEBUGVERIFY(spirit_command(spirit, CMD_FLUSHRXFIFO));
/* Revert the receiving state */
if (priv->state == DRIVER_STATE_RECEIVING)
{
priv->state = DRIVER_STATE_IDLE;
}
/* Statistics are not updated. Nor should they be updated for the
* case of packets that failed the address filter. RX timeouts are not
* enabled. CRC failures would depend on if the packet was destined
* for us or not. So, we do nothing.
*/
#ifdef CONFIG_SPIRIT_FIFOS
/* Discard any packet buffer that might have been allocated */
if (priv->rxbuffer != NULL)
{
iob_free(priv->rxbuffer);
priv->rxbuffer = NULL;
}
#endif
/* Send any pending packets */
spirit_schedule_transmit_work(priv);
}
/* Check the Spirit status. If it is READY, the setup the RX state */
DEBUGVERIFY(spirit_update_status(spirit));
wlinfo("MC_STATE=%02x\n", spirit->u.state.MC_STATE);
if (spirit->u.state.MC_STATE == MC_STATE_READY)
{
wlinfo("Go to RX state (%02x)\n", MC_STATE_RX);
/* Set up to receive */
DEBUGVERIFY(spirit_command(spirit, CMD_RX));
/* Wait for Spirit to enter the Rx state (or timeut) */
DEBUGVERIFY(spirit_waitstatus(spirit, MC_STATE_RX, 1));
}
}
/****************************************************************************
* Name: spirit_interrupt
*
* Description:
* Actual interrupt handler ran inside privileged space.
*
****************************************************************************/
static int spirit_interrupt(int irq, FAR void *context, FAR void *arg)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
DEBUGASSERT(priv != NULL);
/* We cannot do SPI transfers from the interrupt handler because
* semaphores are probably used to lock the SPI bus. SPI drivers may also
* use interrupts and DMA. In these cases, we will defer processing to
* the HP worker thread. This is also much kinder in the use of system
* resources and is, therefore, probably a good thing to do in any event.
*/
return work_queue(HPWORK, &priv->irqwork, spirit_interrupt_work,
(FAR void *)priv, 0);
}
/****************************************************************************
* Name: spirit_txtimeout_work
*
* Description:
* Perform TX timeout related work from the HP worker thread
*
* Input Parameters:
* arg - The argument passed when work_queue() as called.
*
* Returned Value:
* OK on success
*
* Assumptions:
* Runs on the HP worker thread.
*
****************************************************************************/
static void spirit_txtimeout_work(FAR void *arg)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
wlwarn("WARNING: TX Timeout. state=%u\n", priv->state);
/* Are we in the sending state? If not, then this must be a spurious
* timout.
*/
if (priv->state == DRIVER_STATE_SENDING)
{
/* Lock the network and serialize driver operations if necessary.
* NOTE: Serialization is only required in the case where the driver
* work is performed on an LP worker thread and where more than one LP
* worker thread has been configured.
*/
net_lock();
/* Increment statistics and dump debug info */
NETDEV_TXTIMEOUTS(&priv->radio.r_dev);
/* Then reset the hardware */
spirit_ifdown(&priv->radio.r_dev);
spirit_ifup(&priv->radio.r_dev);
/* Then schedule to poll the network for new XMIT data on the LP
* worker thread.
*/
work_queue(LPWORK, &priv->pollwork, spirit_txpoll_work, priv, 0);
net_unlock();
}
}
/****************************************************************************
* Name: spirit_txtimeout_expiry
*
* Description:
* Our TX watchdog timed out. Called from the timer interrupt handler.
* The last TX never completed. Reset the hardware and start again.
*
* Input Parameters:
* argc - The number of available arguments
* arg - The first argument
*
* Returned Value:
* None
*
* Assumptions:
* Global interrupts are disabled by the watchdog logic.
*
****************************************************************************/
static void spirit_txtimeout_expiry(int argc, wdparm_t arg, ...)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
DEBUGASSERT(priv != NULL && priv->lower != NULL);
/* Disable further Spirit interrupts. This will prevent some race
* conditions with interrupt work. There is still a potential race
* condition with interrupt work that is already queued and in progress.
*/
DEBUGASSERT(priv->lower->enable != NULL);
priv->lower->enable(priv->lower, false);
/* Schedule to perform the TX timeout processing on the HP worker thread. */
work_queue(HPWORK, &priv->txwork, spirit_txtimeout_work, priv, 0);
}
/****************************************************************************
* Name: spirit_txpoll_work
*
* Description:
* Perform periodic polling from the worker thread
*
* Input Parameters:
* arg - The argument passed when work_queue() as called.
*
* Returned Value:
* OK on success
*
* Assumptions:
* Scheduled on the LP worker thread from the poll timer expiration
* handler.
*
****************************************************************************/
static void spirit_txpoll_work(FAR void *arg)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
/* Lock the network and serialize driver operations if necessary.
* NOTE: Serialization is only required in the case where the driver work
* is performed on an LP worker thread and where more than one LP worker
* thread has been configured.
*/
net_lock();
#ifdef CONFIG_NET_6LOWPAN
/* Make sure the our single packet buffer is attached */
priv->radio.r_dev.d_buf = g_iobuffer.rb_buf;
#endif
/* Do nothing if the network is not yet UP */
if (!priv->ifup)
{
priv->needpoll = false;
}
/* Is a periodic poll needed? */
else if (priv->needpoll)
{
/* Perform the periodic poll */
priv->needpoll = false;
(void)devif_timer(&priv->radio.r_dev, spirit_txpoll_callback);
/* Setup the watchdog poll timer again */
(void)wd_start(priv->txpoll, SPIRIT_WDDELAY, spirit_txpoll_expiry, 1,
(wdparm_t)priv);
}
else
{
/* Perform a normal, asynchronous poll for new TX data */
(void)devif_poll(&priv->radio.r_dev, spirit_txpoll_callback);
}
net_unlock();
}
/****************************************************************************
* Name: spirit_txpoll_expiry
*
* Description:
* Periodic timer handler. Called from the timer interrupt handler.
*
* Input Parameters:
* argc - The number of available arguments
* arg - The first argument
*
* Returned Value:
* None
*
* Assumptions:
* Global interrupts are disabled by the watchdog logic.
*
****************************************************************************/
static void spirit_txpoll_expiry(int argc, wdparm_t arg, ...)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)arg;
/* Schedule to perform the poll work on the LP worker thread. */
priv->needpoll = true;
work_queue(LPWORK, &priv->pollwork, spirit_txpoll_work, priv, 0);
}
/****************************************************************************
* Name: spirit_ifup
*
* Description:
* NuttX Callback: Bring up the Spirit interface when an IP address is
* provided
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* Called from the network layer and running on the LP working thread.
* This interracts with the spirit hardware, but does so while the network
* is down and with Spirit interrupts disabled.
*
****************************************************************************/
static int spirit_ifup(FAR struct net_driver_s *dev)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)dev->d_private;
FAR struct spirit_library_s *spirit;
int ret;
DEBUGASSERT(priv != NULL);
spirit = &priv->spirit;
if (!priv->ifup)
{
/* Set the node IP address based on the assigned 8-bit node address */
spirit_set_ipaddress(dev);
wlinfo("Bringing up: %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]);
/* Disable spirit interrupts */
DEBUGASSERT(priv->lower->enable != NULL);
priv->lower->enable(priv->lower, false);
/* Ensure we are in READY state before we go from there to Rx.
* Since spirit interrupts are disabled, we don't need to be concerned
* about mutual exclusion.
*/
wlinfo("Go to the ready state\n");
ret = spirit_command(spirit, CMD_READY);
if (ret < 0)
{
return ret;
}
ret = spirit_waitstatus(spirit, MC_STATE_READY, 5);
if (ret < 0)
{
wlerr("ERROR: Failed to go to READY state: %d\n", ret);
goto error_with_ifalmostup;
}
/* Now we go to Rx */
wlinfo("Go to RX state (%02x)\n", MC_STATE_RX);
ret = spirit_command(spirit, CMD_RX);
if (ret < 0)
{
goto error_with_ifalmostup;
}
ret = spirit_waitstatus(spirit, MC_STATE_RX, 5);
if (ret < 0)
{
wlerr("ERROR: Failed to go to RX state: %d\n", ret);
goto error_with_ifalmostup;
}
/* Instantiate the assigned node address in hardware */
DEBUGASSERT(dev->d_mac.radio.nv_addrlen == 1);
wlinfo("Set node address to %02x\n",
dev->d_mac.radio.nv_addr[0]);
ret = spirit_pktcommon_set_nodeaddress(spirit,
dev->d_mac.radio.nv_addr[0]);
if (ret < 0)
{
wlerr("ERROR: Failed to set node address: %d\n", ret);
goto error_with_ifalmostup;
}
/* Set and activate a timer process */
(void)wd_start(priv->txpoll, SPIRIT_WDDELAY, spirit_txpoll_expiry, 1,
(wdparm_t)priv);
/* Enables the interrupts from the SPIRIT1 */
DEBUGASSERT(priv->lower->enable != NULL);
priv->lower->enable(priv->lower, true);
/* We are up! */
priv->ifup = true;
}
return OK;
error_with_ifalmostup:
priv->ifup = true;
(void)spirit_ifdown(dev);
return ret;
}
/****************************************************************************
* Name: spirit_ifdown
*
* Description:
* NuttX Callback: Stop the interface.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* Called from the network layer and running on the LP working thread.
* This interracts with the spirit hardware, but does so with Spirit
* interrupts disabled.
*
****************************************************************************/
static int spirit_ifdown(FAR struct net_driver_s *dev)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)dev->d_private;
FAR struct spirit_library_s *spirit;
int ret = OK;
DEBUGASSERT(priv != NULL);
spirit = &priv->spirit;
if (priv->ifup)
{
irqstate_t flags;
int status;
/* Disable the Spirit interrupt */
flags = enter_critical_section();
DEBUGASSERT(priv->lower->enable != NULL);
priv->lower->enable(priv->lower, false);
/* Cancel the TX poll timer and TX timeout timers */
wd_cancel(priv->txpoll);
wd_cancel(priv->txtimeout);
leave_critical_section(flags);
/* First stop Rx/Tx
* Since spirit interrupts are disabled, we don't need to be concerned
* about mutual exclusion.
*/
status = spirit_command(spirit, CMD_SABORT);
if (status < 0 && ret == 0)
{
ret = status;
}
/* Clear any pending irqs */
status = spirit_irq_clr_pending(spirit);
if (status < 0 && ret == 0)
{
ret = status;
}
status = spirit_waitstatus(spirit, MC_STATE_READY, 5);
if (status < 0 && ret == 0)
{
wlerr("ERROR: Failed to go to READY state: %d\n", ret);
ret = status;
}
/* Put the SPIRIT1 in STANDBY */
status = spirit_command(spirit, CMD_STANDBY);
if (status < 0 && ret == 0)
{
ret = status;
}
status = spirit_waitstatus(spirit, MC_STATE_STANDBY, 5);
if (status < 0 && ret == 0)
{
wlerr("ERROR: Failed to go to STANBY state: %d\n", ret);
ret = status;
}
priv->ifup = false;
}
return ret;
}
/****************************************************************************
* Name: spirit_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 from network logic running on the LP worker thread
*
****************************************************************************/
static int spirit_txavail(FAR struct net_driver_s *dev)
{
FAR struct spirit_driver_s *priv = (FAR struct spirit_driver_s *)dev->d_private;
/* Schedule to serialize the poll on the LP worker thread. */
work_queue(LPWORK, &priv->pollwork, spirit_txpoll_work, priv, 0);
return OK;
}
/****************************************************************************
* Name: spirit_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
*
****************************************************************************/
#ifdef CONFIG_NET_MCASTGROUP
static int spirit_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
{
return -ENOSYS;
}
#endif
/****************************************************************************
* Name: spirit_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
*
****************************************************************************/
#ifdef CONFIG_NET_MCASTGROUP
static int spirit_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
{
return -ENOSYS;
}
#endif
/****************************************************************************
* Name: spirit_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 spirit_ioctl(FAR struct net_driver_s *dev, int cmd,
unsigned long arg)
{
FAR struct pktradio_ifreq_s *cmddata;
FAR struct spirit_driver_s *priv;
int ret = -ENOTTY;
DEBUGASSERT(dev != NULL && dev->d_private != NULL && arg != 0ul);
priv = (FAR struct spirit_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 = spirit_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 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: spirit_get_mhrlen
*
* Description:
* Calculate the MAC header length given the frame meta-data.
*
* Input Parameters:
* netdev - The network 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.
*
* Assumptions:
* Called from network logic running on the low-priority worker thread.
*
****************************************************************************/
static int spirit_get_mhrlen(FAR struct radio_driver_s *netdev,
FAR const void *meta)
{
DEBUGASSERT(netdev != NULL && netdev->r_dev.d_private != NULL && meta != NULL);
/* There is no header on the Spirit radio payload */
return 0;
}
/****************************************************************************
* Name: spirit_req_data
*
* Description:
* Requests the transfer of a list of frames to the MAC. We get here
* indirectly as a consequence of a TX poll that generates a series of
* 6LoWPAN radio packets.
*
* Input Parameters:
* netdev - The network 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.
*
* Assumptions:
* Called from network logic with the network locked.
* Running on the LP worker thread.
*
****************************************************************************/
static int spirit_req_data(FAR struct radio_driver_s *netdev,
FAR const void *meta, FAR struct iob_s *framelist)
{
FAR struct spirit_driver_s *priv;
FAR const struct pktradio_metadata_s *metain;
FAR struct pktradio_metadata_s *pktmeta;
FAR struct iob_s *iob;
wlinfo("Received framelist\n");
DEBUGASSERT(netdev != NULL && netdev->r_dev.d_private != NULL);
priv = (FAR struct spirit_driver_s *)netdev->r_dev.d_private;
DEBUGASSERT(meta != NULL && framelist != NULL);
metain = (FAR const struct pktradio_metadata_s *)meta;
/* Add the incoming list of frames to the MAC's outgoing queue */
for (iob = framelist; iob != NULL; iob = framelist)
{
/* Increment statistics */
NETDEV_TXPACKETS(&priv->radio.r_dev);
/* Remove the IOB from the queue */
framelist = iob->io_flink;
iob->io_flink = NULL;
/* Note that there is no header applied to the outgoing payload */
DEBUGASSERT(iob->io_offset == 0 && iob->io_len > 0);
/* Allocate a metadata container to hold the IOB. This is just like
* the we got from the network, be we will copy it so that we can
* control the life of the container.
*
* REVISIT: To bad we cound not just simply allocate the structure
* on the network side. But that behavior is incompatible with how
* IEEE 802.15.4 works.
*/
pktmeta = pktradio_metadata_allocate();
if (pktmeta == NULL)
{
wlerr("ERROR: Failed to allocate metadata... dropping\n");
NETDEV_RXDROPPED(&priv->radio.r_dev);
iob_free(iob);
continue;
}
/* Save the IOB and addressing information in the newly allocated
* container.
*/
memcpy(&pktmeta->pm_src, &metain->pm_src,
sizeof(struct pktradio_addr_s));
memcpy(&pktmeta->pm_dest, &metain->pm_dest,
sizeof(struct pktradio_addr_s));
pktmeta->pm_iob = iob;
/* Add the IOB container to tail of the queue of outgoing IOBs. */
pktmeta->pm_flink = NULL;
spirit_txlock(priv);
if (priv->txtail == NULL)
{
priv->txhead = pktmeta;
}
else
{
priv->txtail->pm_flink = pktmeta;
}
priv->txtail = pktmeta;
spirit_txunlock(priv);
/* If are no transmissions or receptions in progress, then schedule
* tranmission of the frame in the IOB at the head of the IOB queue.
*/
spirit_schedule_transmit_work(priv);
}
return OK;
}
/****************************************************************************
* Name: spirit_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 properities will be returned.
*
* Returned Value:
* Zero (OK) returned on success; a negated errno value is returned on
* any failure.
*
****************************************************************************/
static int spirit_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 = 1; /* Length of an address */
properties->sp_framelen = CONFIG_SPIRIT_PKTLEN; /* Fixed packet length */
/* Multicast address */
properties->sp_mcast.nv_addrlen = 1;
properties->sp_mcast.nv_addr[0] = SPIRIT_MCAST_ADDRESS;
/* Broadcast address */
properties->sp_bcast.nv_addrlen = 1;
properties->sp_bcast.nv_addr[0] = SPIRIT_BCAST_ADDRESS;
#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;
}
/****************************************************************************
* Name: spirit_hw_initialize
*
* Description:
* Initialize the Spirit1 radio.
*
****************************************************************************/
int spirit_hw_initialize(FAR struct spirit_driver_s *priv,
FAR struct spi_dev_s *spi)
{
FAR struct spirit_library_s *spirit = &priv->spirit;
int ret;
wlinfo("Initialize spirit hardware\n");
/* Configures the Spirit1 radio library */
spirit->spi = spi;
spirit->xtal_frequency = SPIRIT_XTAL_FREQUENCY;
priv->ifup = false;
/* Reset the Spirit1 radio part */
wlinfo("Spirit Reset\n");
DEBUGASSERT(priv->lower != NULL && priv->lower->reset != NULL);
ret = priv->lower->reset(priv->lower) ;
if (ret < 0)
{
wlerr("ERROR: SDN reset failed: %d\n", ret);
return ret;
}
/* Soft reset of Spirit1 core */
#if 0
ret = spirit_command(spirit, COMMAND_SRES);
if (ret < 0)
{
wlerr("ERROR: Soft reset failed: %d\n", ret);
return ret;
}
#endif
ret = spirit_waitstatus(spirit, MC_STATE_READY, 100);
if (ret < 0)
{
wlerr("ERROR: Failed to go to READY state: %d\n", ret);
return ret;
}
/* Perform VCO calibration WA when the radio is initialized */
wlinfo("Peform VCO calibration\n");
spirit_radio_enable_wavco_calibration(spirit, S_ENABLE);
/* Configure the Spirit1 radio part */
wlinfo("Configure the spirit radio\n");
ret = spirit_radio_initialize(spirit, &g_radio_init);
if (ret < 0)
{
wlerr("ERROR: pirit_radio_initialize failed: %d\n", ret);
return ret;
}
ret = spirit_radio_set_palevel_dbm(spirit, 0, SPIRIT_POWER_DBM);
if (ret < 0)
{
wlerr("ERROR: spirit_radio_set_palevel_dbm failed: %d\n", ret);
return ret;
}
ret =spirit_radio_set_palevel_maxindex(spirit, 0);
if (ret < 0)
{
wlerr("ERROR: spirit_radio_set_palevel_maxindex failed: %d\n", ret);
return ret;
}
/* Configures the SPIRIT1 packet handling logic */
wlinfo("Configure STack packets\n");
ret = spirit_pktstack_initialize(spirit, &g_pktstack_init);
if (ret < 0)
{
wlerr("ERROR: spirit_radio_set_palevel_maxindex failed: %d\n", ret);
return ret;
}
ret = spirit_pktstack_llp_initialize(spirit, &g_llp_init);
if (ret < 0)
{
wlerr("ERROR: spirit_pktstack_llp_initialize failed: %d\n", ret);
return ret;
}
/* Configure address filtering */
wlinfo("Configure address filtering\n");
ret = spirit_pktstack_address_initialize(spirit, &g_addrinit);
if (ret < 0)
{
wlerr("ERROR: spirit_pktstack_address_initialize failed: %d\n", ret);
return ret;
}
#ifdef CONFIG_SPIRIT_FIFOS
/* Configure the linear FIFOs */
wlinfo("Configure linear FIFOs\n");
ret = spirit_fifo_set_rxalmostfull(spirit, SPIRIT_RXFIFO_ALMOSTFULL);
if (ret < 0)
{
wlerr("ERROR: spirit_fifo_set_rxalmostfull failed: %d\n", ret);
return ret;
}
#if CONFIG_SPIRIT_PKTLEN > SPIRIT_MAX_FIFO_LEN
ret = spirit_fifo_set_txalmostempty(spirit, SPIRIT_TXFIFO_ALMOSTEMPTY);
if (ret < 0)
{
wlerr("ERROR: spirit_fifo_set_txalmostempty failed: %d\n", ret);
return ret;
}
#endif
#endif
/* Enable the following interrupt sources, routed to GPIO */
wlinfo("Configure Interrupts\n");
ret = spirit_irq_disable_all(spirit);
if (ret < 0)
{
wlerr("ERROR: spirit_irq_disable_all failed: %d\n", ret);
return ret;
}
ret = spirit_irq_clr_pending(spirit);
if (ret < 0)
{
wlerr("ERROR: spirit_irq_clr_pending failed: %d\n", ret);
return ret;
}
ret = spirit_irq_enable(spirit, TX_DATA_SENT, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable TX_DATA_SENT failed: %d\n", ret);
return ret;
}
ret = spirit_irq_enable(spirit, RX_DATA_READY, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable RX_DATA_READY failed: %d\n", ret);
return ret;
}
ret = spirit_irq_enable(spirit, VALID_SYNC, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable VALID_SYNC failed: %d\n", ret);
return ret;
}
ret = spirit_irq_enable(spirit, RX_DATA_DISC, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable RX_DATA_DISC failed: %d\n", ret);
return ret;
}
ret = spirit_irq_enable(spirit, MAX_RE_TX_REACH, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable MAX_RE_TX_REACH failed: %d\n", ret);
return ret;
}
#ifdef CONFIG_SPIRIT_FIFOS
#if CONFIG_SPIRIT_PKTLEN > SPIRIT_MAX_FIFO_LEN
ret = spirit_irq_enable(spirit, TX_FIFO_ALMOST_EMPTY, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable TX_FIFO_ALMOST_EMPTY failed: %d\n", ret);
return ret;
}
#endif
ret = spirit_irq_enable(spirit, RX_FIFO_ALMOST_FULL, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable RX_FIFO_ALMOST_FULL failed: %d\n", ret);
return ret;
}
#endif
ret = spirit_irq_enable(spirit, TX_FIFO_ERROR, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable TX_FIFO_ERROR failed: %d\n", ret);
return ret;
}
ret = spirit_irq_enable(spirit, RX_FIFO_ERROR, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable RX_FIFO_ERROR failed: %d\n", ret);
return ret;
}
ret = spirit_irq_enable(spirit, MAX_BO_CCA_REACH, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: Enable MAX_BO_CCA_REACH failed: %d\n", ret);
return ret;
}
/* Configure Spirit1 */
wlinfo("Configure Spriti1\n");
ret = spirit_radio_persistentrx(spirit, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: spirit_radio_persistentrx failed: %d\n", ret);
return ret;
}
ret = spirit_qi_set_sqithreshold(spirit, SQI_TH_0);
if (ret < 0)
{
wlerr("ERROR: spirit_qi_set_sqithreshold failed: %d\n", ret);
return ret;
}
ret = spirit_qi_enable_sqicheck(spirit, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: spirit_qi_enable_sqicheck failed: %d\n", ret);
return ret;
}
ret = spirit_qi_set_rssithreshold_dbm(spirit, SPIRIT_CCA_THRESHOLD);
if (ret < 0)
{
wlerr("ERROR: spirit_qi_set_rssithreshold_dbm failed: %d\n", ret);
return ret;
}
/* Initialize counter and prescaler values that will be used with
* with RX timeouts for ACKs.
*/
spirit_timer_calc_rxtimeout_values(spirit, SPIRIT_RXTIMEOUT,
&priv->counter, &priv->prescaler);
ret = spirit_timer_set_rxtimeout_stopcondition(spirit,
SQI_ABOVE_THRESHOLD);
if (ret < 0)
{
wlerr("ERROR: spirit_timer_set_rxtimeout_stopcondition failed: %d\n", ret);
return ret;
}
ret = spirit_timer_set_rxtimeout_counter(spirit, 0); /* 0=Disables timeout */
if (ret < 0)
{
wlerr("ERROR: spirit_timer_set_rxtimeout_counter failed: %d\n", ret);
return ret;
}
ret = spirit_radio_afcfreezeonsync(spirit, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: spirit_radio_afcfreezeonsync failed: %d\n", ret);
return ret;
}
#if 0
ret = spirit_calibration_rco(spirit, S_ENABLE);
if (ret < 0)
{
wlerr("ERROR: spirit_calibration_rco failed: %d\n", ret);
return ret;
}
#endif
/* Configure the radio to route the IRQ signal to its GPIO 3 */
wlinfo("Configure Spirt GPIOs\n");
ret = spirit_gpio_initialize(spirit, &g_gpioinit);
if (ret < 0)
{
wlerr("ERROR: spirit_gpio_initialize failed: %d\n", ret);
return ret;
}
/* Setup CSMA/CA */
wlinfo("Configure Spirt CSMA\n");
ret = spirit_csma_initialize(spirit, &g_csma_init);
if (ret < 0)
{
wlerr("ERROR: spirit_csma_initialize failed: %d\n", ret);
return ret;
}
/* Puts the SPIRIT1 in STANDBY mode (125us -> rx/tx) */
wlinfo("Go to STANDBY\n");
ret = spirit_command(spirit, CMD_STANDBY);
if (ret < 0)
{
wlerr("ERROR: Failed to go to STANDBY: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Function: spirit_netdev_initialize
*
* Description:
* Initialize the IEEE802.15.4 driver and register it as a network device.
*
* Input Parameters:
* spi - A reference to the platform's SPI driver for the spirit1
* lower - The MCU-specific interrupt used to control low-level MCU
* functions (i.e., spirit1 GPIO interrupts).
*
* Returned Value:
* OK on success; Negated errno on failure.
*
****************************************************************************/
int spirit_netdev_initialize(FAR struct spi_dev_s *spi,
FAR const struct spirit_lower_s *lower)
{
FAR struct spirit_driver_s *priv;
FAR struct radio_driver_s *radio;
FAR struct net_driver_s *dev;
int ret;
/* Allocate a driver state structure instance */
priv = (FAR struct spirit_driver_s *)kmm_zalloc(sizeof(struct spirit_driver_s));
if (priv == NULL)
{
wlerr("ERROR: Failed to allocate device structure\n");
return -ENOMEM;
}
/* Attach the interface, lower driver, and devops */
priv->lower = lower;
/* Create a watchdog for timing polling for and timing of transmissions */
priv->txpoll = wd_create(); /* Create periodic poll timer */
priv->txtimeout = wd_create(); /* Create TX timeout timer */
DEBUGASSERT(priv->txpoll != NULL && priv->txtimeout != NULL);
nxsem_init(&priv->rxsem, 0, 1); /* Access to RX packet queue */
nxsem_init(&priv->txsem, 0, 1); /* Access to TX packet queue */
/* Initialize the IEEE 802.15.4 network device fields */
radio = &priv->radio;
radio->r_get_mhrlen = spirit_get_mhrlen; /* Get MAC header length */
radio->r_req_data = spirit_req_data; /* Enqueue frame for transmission */
radio->r_properties = spirit_properties; /* Return radio properties */
/* Initialize the common network device fields */
dev = &radio->r_dev;
dev->d_ifup = spirit_ifup; /* I/F up (new IP address) callback */
dev->d_ifdown = spirit_ifdown; /* I/F down callback */
dev->d_txavail = spirit_txavail; /* New TX data callback */
#ifdef CONFIG_NET_MCASTGROUP
dev->d_addmac = spirit_addmac; /* Add multicast MAC address */
dev->d_rmmac = spirit_rmmac; /* Remove multicast MAC address */
#endif
#ifdef CONFIG_NETDEV_IOCTL
dev->d_ioctl = spirit_ioctl; /* Handle network IOCTL commands */
#endif
dev->d_private = (FAR void *)priv; /* Used to recover private state from dev */
/* Make sure that the PktRadio common logic has been initialized */
pktradio_metadata_initialize();
/* Initialize device */
ret = spirit_hw_initialize(priv, spi);
if (ret < 0)
{
wlerr("ERROR: spirit_hw_initialize failed: %d\n", ret);
goto errout_with_attach;
}
/* Register the device with the OS so that socket IOCTLs can be performed. */
ret = netdev_register(dev, NET_LL_PKTRADIO);
if (ret < 0)
{
wlerr("ERROR: netdev_register failed: %d\n", ret);
goto errout_with_attach;
}
/* Attach irq */
ret = lower->attach(lower, spirit_interrupt, priv);
if (ret < 0)
{
wlerr("ERROR: Failed to attach interrupt: %d\n", ret);
goto errout_with_alloc;
}
/* Enable Radio IRQ */
lower->enable(lower, true);
return OK;
errout_with_attach:
(void)lower->attach(lower, NULL, NULL);
errout_with_alloc:
kmm_free(priv);
return ret;
}