nuttx/wireless/ieee802154/mac802154.c

1409 lines
45 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/****************************************************************************
* wireless/ieee802154/mac802154.c
*
* Copyright (C) 2016 Sebastien Lorquet. All rights reserved.
* Copyright (C) 2017 Gregory Nutt. All rights reserved.
* Copyright (C) 2017 Verge Inc. All rights reserved.
*
* Author: Sebastien Lorquet <sebastien@lorquet.fr>
* Author: Gregory Nutt <gnutt@nuttx.org>
* Author: Anthony Merlino <anthony@vergeaero.com>
*
* 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <string.h>
#include <nuttx/kmalloc.h>
#include <nuttx/wqueue.h>
#include <nuttx/mm/iob.h>
#include "mac802154.h"
#include "mac802154_notif.h"
#include "mac802154_internal.h"
#include "mac802154_assoc.h"
#include "mac802154_data.h"
#include "mac802154_poll.h"
#include <nuttx/wireless/ieee802154/ieee802154_mac.h>
#include <nuttx/wireless/ieee802154/ieee802154_radio.h>
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Data structure pools and allocation helpers */
static void mac802154_resetqueues(FAR struct ieee802154_privmac_s *priv);
/* IEEE 802.15.4 PHY Interface OPs */
static int mac802154_poll(FAR const struct ieee802154_radiocb_s *radiocb,
bool gts, FAR struct ieee802154_txdesc_s **tx_desc);
static void mac802154_txdone(FAR const struct ieee802154_radiocb_s *radiocb,
FAR struct ieee802154_txdesc_s *tx_desc);
static void mac802154_txdone_worker(FAR void *arg);
static void mac802154_rxframe(FAR const struct ieee802154_radiocb_s *radiocb,
FAR struct ieee802154_data_ind_s *ind);
static void mac802154_rxframe_worker(FAR void *arg);
static void mac802154_rx_datareq(FAR struct ieee802154_privmac_s *priv,
FAR struct ieee802154_data_ind_s *ind);
static void mac802154_rx_dataframe(FAR struct ieee802154_privmac_s *priv,
FAR struct ieee802154_data_ind_s *ind);
static void mac802154_purge_worker(FAR void *arg);
/* Watchdog Timeout Functions */
static void mac802154_timeout_expiry(int argc, uint32_t arg, ...);
static uint32_t mac802154_symtoticks(FAR struct ieee802154_privmac_s *priv,
uint32_t symbols);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mac802154_resetqueues
*
* Description:
* Initializes the various queues used in the MAC layer. Called on creation
* of MAC.
*
****************************************************************************/
static void mac802154_resetqueues(FAR struct ieee802154_privmac_s *priv)
{
int i;
sq_init(&priv->txdone_queue);
sq_init(&priv->csma_queue);
sq_init(&priv->gts_queue);
sq_init(&priv->indirect_queue);
sq_init(&priv->dataind_queue);
/* Initialize the tx descriptor allocation pool */
sq_init(&priv->txdesc_queue);
for (i = 0; i < CONFIG_MAC802154_NTXDESC; i++)
{
sq_addlast((FAR sq_entry_t *)&priv->txdesc_pool[i], &priv->txdesc_queue);
}
sem_init(&priv->txdesc_sem, 0, CONFIG_MAC802154_NTXDESC);
/* Initialize the notifcation allocation pool */
mac802154_notifpool_init(priv);
}
/****************************************************************************
* Name: mac802154_txdesc_pool
*
* Description:
* This function allocates a tx descriptor and the dependent notification (data
* confirmation) from the free list. The notification and tx descriptor will
* be freed seperately, both by the MAC layer either directly, or through
* mac802154_notif_free in the case of the notification.
*
* Assumptions:
* priv MAC struct is locked when calling.
*
* Notes:
* If any of the semaphore waits inside this function get interrupted, the
* function will release the MAC layer. If this function returns -EINTR, the
* calling code should NOT release the MAC semaphore.
*
****************************************************************************/
int mac802154_txdesc_alloc(FAR struct ieee802154_privmac_s *priv,
FAR struct ieee802154_txdesc_s **txdesc,
bool allow_interrupt)
{
int ret;
FAR struct ieee802154_notif_s *notif;
/* Try and take a count from the semaphore. If this succeeds, we have
* "reserved" the structure, but still need to unlink it from the free list.
* The MAC is already locked, so there shouldn't be any other conflicting calls
*/
ret = sem_trywait(&priv->txdesc_sem);
if (ret == OK)
{
*txdesc = (FAR struct ieee802154_txdesc_s *)sq_remfirst(&priv->txdesc_queue);
}
else
{
/* Unlock MAC so that other work can be done to free a notification */
mac802154_givesem(&priv->exclsem);
/* Take a count from the tx desc semaphore, waiting if necessary. We
* only return from here with an error if we are allowing interruptions
* and we received a signal */
ret = mac802154_takesem(&priv->txdesc_sem, allow_interrupt);
if (ret < 0)
{
/* MAC is already released */
return -EINTR;
}
/* If we've taken a count from the semaphore, we have "reserved" the struct
* but now we need to pop it off of the free list. We need to re-lock the
* MAC in order to ensure this happens correctly.
*/
ret = mac802154_takesem(&priv->exclsem, allow_interrupt);
if (ret < 0)
{
mac802154_givesem(&priv->txdesc_sem);
return -EINTR;
}
/* We can now safely unlink the next free structure from the free list */
*txdesc = (FAR struct ieee802154_txdesc_s *)sq_remfirst(&priv->txdesc_queue);
}
/* We have now successfully allocated the tx descriptor. Now we need to allocate
* the notification for the data confirmation that gets passed along with the
* tx descriptor. These are allocated together, but not freed together.
*/
ret = mac802154_notif_alloc(priv, &notif, allow_interrupt);
if (ret < 0)
{
/* The mac802154_notif_alloc function follows the same rules as this
* function. If it returns -EINTR, the MAC layer is already released
*/
/* We need to free the txdesc */
mac802154_txdesc_free(priv, *txdesc);
return -EINTR;
}
(*txdesc)->conf = &notif->u.dataconf;
return OK;
}
/****************************************************************************
* Name: mac802154_setupindirect
*
* Description:
* Internal function used by various parts of the MAC layer. This function
* places the provided tx descriptor in the indirect list and manages the
* scheduling for purging the transaction if it does not get extracted in
* time.
*
* Assumptions:
* Called with the MAC locked
*
****************************************************************************/
void mac802154_setupindirect(FAR struct ieee802154_privmac_s *priv,
FAR struct ieee802154_txdesc_s *txdesc)
{
uint32_t ticks;
uint32_t symbols;
/* Link the tx descriptor into the list */
sq_addlast((FAR sq_entry_t *)txdesc, &priv->indirect_queue);
/* Update the timestamp for purging the transaction */
/* The maximum time (in unit periods) that a transaction is stored by a
* coordinator and indicated in its beacon. The unit period is governed by
* macBeaconOrder, BO, as follows: For 0 ≤ BO ≤ 14, the unit period will be
* aBaseSuperframeDuration × 2 BO . For BO = 15, the unit period will be
* aBaseSuperframeDuration. [1] pg. 129
*/
if (priv->beaconorder < 15)
{
symbols = priv->trans_persisttime *
(IEEE802154_BASE_SUPERFRAME_DURATION * (1 << priv->beaconorder));
}
else
{
symbols = priv->trans_persisttime * IEEE802154_BASE_SUPERFRAME_DURATION;
}
ticks = mac802154_symtoticks(priv, symbols);
txdesc->purge_time = clock_systimer() + ticks;
/* Check to see if the purge indirect timer is scheduled. If it is, when the
* timer fires, it will schedule the next purge timer event. Inherently, the
* queue will be in order of which transaction needs to be purged next.
*
* If the purge indirect timer has not been scheduled, schedule it for when
* this transaction should expire.
*/
if (work_available(&priv->purge_work))
{
//work_queue(MAC802154_WORK, &priv->purge_work, mac802154_purge_worker,
// (FAR void *)priv, ticks);
}
}
/****************************************************************************
* Name: mac802154_purge_worker
*
* Description:
* Worker function scheduled in order to purge expired indirect transactions.
* The first element in the list should always be removed. The list is searched
* and transactions are removed until a transaction has not yet expired. Then
* if there are any remaining transactions, the work function is rescheduled
* for the next expiring transaction.
*
****************************************************************************/
static void mac802154_purge_worker(FAR void *arg)
{
FAR struct ieee802154_privmac_s *priv =
(FAR struct ieee802154_privmac_s *)arg;
FAR struct ieee802154_txdesc_s *txdesc;
/* Get exclusive access to the driver structure. We don't care about any
* signals so don't allow interruptions
*/
mac802154_takesem(&priv->exclsem, false);
while (1)
{
/* Pop transactions off indirect queue until the transaction timeout has not
* passed.
*/
txdesc = (FAR struct ieee802154_txdesc_s *)sq_peek(&priv->indirect_queue);
if (txdesc == NULL)
{
break;
}
/* Should probably check a little ahead and remove the transaction if it is within
* a certain number of clock ticks away. There is no since in scheduling the
* timer to expire in only a few ticks.
*/
if (clock_systimer() >= txdesc->purge_time)
{
/* Unlink the transaction */
sq_remfirst(&priv->indirect_queue);
/* Free the IOB, the notification, and the tx descriptor */
iob_free(txdesc->frame);
((FAR struct mac802154_notif_s *)txdesc->conf)->flink = priv->notif_free;
priv->notif_free = ((FAR struct mac802154_notif_s *)txdesc->conf);
mac802154_txdesc_free(priv, txdesc);
wlinfo("Indirect TX purged");
}
else
{
/* Reschedule the transaction for the next timeout */
work_queue(MAC802154_WORK, &priv->purge_work, mac802154_purge_worker,
(FAR void *)priv, txdesc->purge_time - clock_systimer());
break;
}
}
}
/****************************************************************************
* Name: mac802154_poll
*
* Description:
* Called from the radio driver through the callback struct. This function is
* called when the radio has room for another transaction. If the MAC
* layer has a transaction, it copies it into the supplied buffer and
* returns the length. A descriptor is also populated with the transaction.
*
****************************************************************************/
static int mac802154_poll(FAR const struct ieee802154_radiocb_s *radiocb,
bool gts, FAR struct ieee802154_txdesc_s **txdesc)
{
FAR struct mac802154_radiocb_s *cb =
(FAR struct mac802154_radiocb_s *)radiocb;
FAR struct ieee802154_privmac_s *priv;
DEBUGASSERT(cb != NULL && cb->priv != NULL);
priv = cb->priv;
/* Get exclusive access to the driver structure. Ignore any EINTR signals */
mac802154_takesem(&priv->exclsem, false);
if (gts)
{
/* Check to see if there are any GTS transactions waiting */
*txdesc = (FAR struct ieee802154_txdesc_s *)sq_remfirst(&priv->gts_queue);
}
else
{
/* Check to see if there are any CSMA transactions waiting */
*txdesc = (FAR struct ieee802154_txdesc_s *)sq_remfirst(&priv->csma_queue);
}
mac802154_givesem(&priv->exclsem);
if (*txdesc != NULL)
{
return (*txdesc)->frame->io_len;
}
return 0;
}
/****************************************************************************
* Name: mac802154_txdone
*
* Description:
* Called from the radio driver through the callback struct. This function is
* called when the radio has completed a transaction. The txdesc passed gives
* provides information about the completed transaction including the original
* handle provided when the transaction was created and the status of the
* transaction. This function copies the descriptor and schedules work to
* handle the transaction without blocking the radio.
*
****************************************************************************/
static void mac802154_txdone(FAR const struct ieee802154_radiocb_s *radiocb,
FAR struct ieee802154_txdesc_s *txdesc)
{
FAR struct mac802154_radiocb_s *cb =
(FAR struct mac802154_radiocb_s *)radiocb;
FAR struct ieee802154_privmac_s *priv;
DEBUGASSERT(cb != NULL && cb->priv != NULL);
priv = cb->priv;
/* Get exclusive access to the driver structure. We don't care about any
* signals so don't allow interruptions
*/
mac802154_takesem(&priv->exclsem, false);
sq_addlast((FAR sq_entry_t *)txdesc, &priv->txdone_queue);
mac802154_givesem(&priv->exclsem);
/* Schedule work with the work queue to process the completion further */
if (work_available(&priv->tx_work))
{
work_queue(MAC802154_WORK, &priv->tx_work, mac802154_txdone_worker,
(FAR void *)priv, 0);
}
}
/****************************************************************************
* Name: mac802154_txdone_worker
*
* Description:
* Worker function scheduled from mac802154_txdone. This function pops any
* TX descriptors off of the list and calls the next highest layers callback
* to inform the layer of the completed transaction and the status of it.
*
****************************************************************************/
static void mac802154_txdone_worker(FAR void *arg)
{
FAR struct ieee802154_privmac_s *priv =
(FAR struct ieee802154_privmac_s *)arg;
FAR struct ieee802154_txdesc_s *txdesc;
FAR struct ieee802154_notif_s *notif;
FAR struct mac802154_notif_s *privnotif;
/* Get exclusive access to the driver structure. We don't care about any
* signals so don't allow interruptions
*/
mac802154_takesem(&priv->exclsem, false);
while (1)
{
txdesc = (FAR struct ieee802154_txdesc_s *)sq_remfirst(&priv->txdone_queue);
if (txdesc == NULL)
{
break;
}
/* Cast the data_conf to a notification. We get both the private and public
* notification structure to make it easier to use.
*/
privnotif = (FAR struct mac802154_notif_s *)txdesc->conf;
notif = &privnotif->pub;
switch(txdesc->frametype)
{
case IEEE802154_FRAME_DATA:
{
notif->notiftype = IEEE802154_NOTIFY_CONF_DATA;
/* Release the MAC, call the callback, get exclusive access again */
mac802154_givesem(&priv->exclsem);
priv->cb->notify(priv->cb, notif);
mac802154_takesem(&priv->exclsem, false);
}
break;
case IEEE802154_FRAME_COMMAND:
{
switch (priv->curr_cmd)
{
case IEEE802154_CMD_ASSOC_REQ:
mac802154_txdone_assocreq(priv, txdesc);
break;
case IEEE802154_CMD_ASSOC_RESP:
break;
case IEEE802154_CMD_DISASSOC_NOT:
break;
case IEEE802154_CMD_DATA_REQ:
/* Data requests can be sent for 3 different reasons.
*
* 1. On a beacon-enabled PAN, this command shall be sent
* by a device when macAutoRequest is equal to TRUE and
* a beacon frame indicating that data are pending for
* that device is received from its coordinator.
* 2. when instructed to do so by the next higher layer on
* reception of the MLME-POLL.request primitive.
* 3. a device may send this command to the coordinator
* macResponseWaitTime after the acknowledgment to an
* association request command.
*/
switch (priv->curr_op)
{
case MAC802154_OP_ASSOC:
mac802154_txdone_datareq_assoc(priv, txdesc);
break;
case MAC802154_OP_POLL:
mac802154_txdone_datareq_poll(priv, txdesc);
break;
default:
break;
}
break;
case IEEE802154_CMD_PANID_CONF_NOT:
break;
case IEEE802154_CMD_ORPHAN_NOT:
break;
case IEEE802154_CMD_BEACON_REQ:
break;
case IEEE802154_CMD_COORD_REALIGN:
break;
case IEEE802154_CMD_GTS_REQ:
break;
default:
/* We can deallocate the data conf notification as it is no
* longer needed. We can't use the public function here
* since we already have the MAC locked.
*/
privnotif->flink = priv->notif_free;
priv->notif_free = privnotif;
break;
}
}
break;
default:
{
/* We can deallocate the data conf notification as it is no longer
* needed. We can't use the public function here since we already
* have the MAC locked.
*/
privnotif->flink = priv->notif_free;
priv->notif_free = privnotif;
}
break;
}
/* Free the IOB and the tx descriptor */
iob_free(txdesc->frame);
mac802154_txdesc_free(priv, txdesc);
}
mac802154_givesem(&priv->exclsem);
}
/****************************************************************************
* Name: mac802154_rxframe
*
* Description:
* Called from the radio driver through the callback struct. This function is
* called when the radio has received a frame. The frame is passed in an iob,
* so that we can free it when we are done processing. A pointer to the RX
* descriptor is passed along with the iob, but it must be copied here as it
* is allocated directly on the caller's stack. We simply link the frame,
* copy the RX descriptor, and schedule a worker to process the frame later so
* that we do not hold up the radio.
*
****************************************************************************/
static void mac802154_rxframe(FAR const struct ieee802154_radiocb_s *radiocb,
FAR struct ieee802154_data_ind_s *ind)
{
FAR struct mac802154_radiocb_s *cb =
(FAR struct mac802154_radiocb_s *)radiocb;
FAR struct ieee802154_privmac_s *priv;
DEBUGASSERT(cb != NULL && cb->priv != NULL);
priv = cb->priv;
/* Get exclusive access to the driver structure. We don't care about any
* signals so if we see one, just go back to trying to get access again.
*/
mac802154_takesem(&priv->exclsem, false);
/* Push the iob onto the tail of the frame list for processing */
sq_addlast((FAR sq_entry_t *)ind, &priv->dataind_queue);
wlinfo("frame received\n");
mac802154_givesem(&priv->exclsem);
/* Schedule work with the work queue to process the completion further */
if (work_available(&priv->rx_work))
{
work_queue(MAC802154_WORK, &priv->rx_work, mac802154_rxframe_worker,
(FAR void *)priv, 0);
}
}
/****************************************************************************
* Name: mac802154_rxframe_worker
*
* Description:
* Worker function scheduled from mac802154_rxframe. This function processes
* any frames in the list. Frames intended to be consumed by the MAC layer
* will not produce any callbacks to the next highest layer. Frames intended
* for the application layer will be forwarded to them.
*
****************************************************************************/
static void mac802154_rxframe_worker(FAR void *arg)
{
FAR struct ieee802154_privmac_s *priv =
(FAR struct ieee802154_privmac_s *)arg;
FAR struct ieee802154_data_ind_s *ind;
FAR struct iob_s *frame;
uint16_t *frame_ctrl;
bool panid_comp;
uint8_t ftype;
while(1)
{
/* Get exclusive access to the driver structure. We don't care about any
* signals so if we see one, just go back to trying to get access again.
*/
mac802154_takesem(&priv->exclsem, false);
/* Pop the iob from the head of the frame list for processing */
ind = (FAR struct ieee802154_data_ind_s *)sq_remfirst(&priv->dataind_queue);
/* Once we pop off the indication, we don't need to keep the mac locked */
mac802154_givesem(&priv->exclsem);
if (ind == NULL)
{
return;
}
/* Get a local copy of the frame to make it easier to access */
frame = ind->frame;
/* Set a local pointer to the frame control then move the offset past
* the frame control field
*/
frame_ctrl = (uint16_t *)&frame->io_data[frame->io_offset];
frame->io_offset += 2;
/* We use the data_ind_s as a container for the frame information even if
* this isn't a data frame
*/
ind->src.mode = (*frame_ctrl & IEEE802154_FRAMECTRL_SADDR) >>
IEEE802154_FRAMECTRL_SHIFT_SADDR;
ind->dest.mode = (*frame_ctrl & IEEE802154_FRAMECTRL_DADDR) >>
IEEE802154_FRAMECTRL_SHIFT_DADDR;
panid_comp = (*frame_ctrl & IEEE802154_FRAMECTRL_PANIDCOMP) >>
IEEE802154_FRAMECTRL_SHIFT_PANIDCOMP;
ind->dsn = frame->io_data[frame->io_offset++];
/* If the destination address is included */
if (ind->dest.mode != IEEE802154_ADDRMODE_NONE)
{
/* Get the destination PAN ID */
memcpy(&ind->dest.panid, &frame->io_data[frame->io_offset], 2);
frame->io_offset += 2;
if (ind->dest.mode == IEEE802154_ADDRMODE_SHORT)
{
memcpy(&ind->dest.saddr, &frame->io_data[frame->io_offset], 2);
frame->io_offset += 2;
}
else if (ind->dest.mode == IEEE802154_ADDRMODE_EXTENDED)
{
memcpy(&ind->dest.eaddr[0], &frame->io_data[frame->io_offset],
IEEE802154_EADDR_LEN);
frame->io_offset += IEEE802154_EADDR_LEN;
}
}
if (ind->src.mode != IEEE802154_ADDRMODE_NONE)
{
/* If the source address is included, and the PAN ID compression field
* is set, get the PAN ID from the header.
*/
if (panid_comp)
{
/* The source PAN ID is equal to the destination PAN ID */
ind->src.panid = ind->dest.panid;
}
else
{
memcpy(&ind->src.panid, &frame->io_data[frame->io_offset], 2);
frame->io_offset += 2;
}
if (ind->src.mode == IEEE802154_ADDRMODE_SHORT)
{
memcpy(&ind->src.saddr, &frame->io_data[frame->io_offset], 2);
frame->io_offset += 2;
}
else if (ind->src.mode == IEEE802154_ADDRMODE_EXTENDED)
{
memcpy(&ind->src.eaddr[0], &frame->io_data[frame->io_offset],
IEEE802154_EADDR_LEN);
frame->io_offset += 8;
}
}
ftype = (*frame_ctrl & IEEE802154_FRAMECTRL_FTYPE) >>
IEEE802154_FRAMECTRL_SHIFT_FTYPE;
switch (ftype)
{
case IEEE802154_FRAME_DATA:
{
mac802154_rx_dataframe(priv, ind);
}
break;
case IEEE802154_FRAME_COMMAND:
{
/* Get the command type. The command type is always the first
* field after the MHR. Consu;me the byte by increasing offset so that
* subsequent functions can start from the byte after the command ID.
*/
uint8_t cmdtype = frame->io_data[frame->io_offset++];
switch (cmdtype)
{
case IEEE802154_CMD_ASSOC_REQ:
mac802154_rx_assocreq(priv, ind);
break;
case IEEE802154_CMD_ASSOC_RESP:
mac802154_rx_assocresp(priv, ind);
break;
case IEEE802154_CMD_DISASSOC_NOT:
break;
case IEEE802154_CMD_DATA_REQ:
mac802154_rx_datareq(priv, ind);
break;
case IEEE802154_CMD_PANID_CONF_NOT:
break;
case IEEE802154_CMD_ORPHAN_NOT:
break;
case IEEE802154_CMD_BEACON_REQ:
break;
case IEEE802154_CMD_COORD_REALIGN:
break;
case IEEE802154_CMD_GTS_REQ:
break;
}
/* Free the data indication struct from the pool */
ieee802154_ind_free(ind);
}
break;
case IEEE802154_FRAME_BEACON:
{
/* TODO: Add logic here to handle extracting association response from
* coordinator if beacon tracking was enabled during the Association
* operation.
*
* txdesc = mac802154_assoc_getresp(priv);
* sq_addlast((FAR sq_entry_t *)txdesc, &priv->csma_queue);
*/
}
break;
case IEEE802154_FRAME_ACK:
{
/* The radio layer is responsible for handling all ACKs and retries.
* If for some reason an ACK gets here, just throw it out.
*/
wlinfo("ACK received\n");
ieee802154_ind_free(ind);
}
break;
}
}
}
/****************************************************************************
* Name: mac802154_rx_dataframe
*
* Description:
* Function called from the generic RX Frame worker to parse and handle the
* reception of a data frame.
*
****************************************************************************/
static void mac802154_rx_dataframe(FAR struct ieee802154_privmac_s *priv,
FAR struct ieee802154_data_ind_s *ind)
{
FAR struct ieee802154_notif_s *notif;
/* Get exclusive access to the MAC */
mac802154_takesem(&priv->exclsem, false);
/* If we are currently performing a POLL operation and we've
* received a data response, use the addressing information
* to determine if it is extracted data. If the addressing info
* matches, notify the next highest layer using POLL.confirm
* primitive. If the addressing information does not match,
* handle the transaction like any other data transaction.
*
* Note: We can't receive frames without addressing information
* unless we are the PAN coordinator. And in that situation, we
* wouldn't be performing a POLL operation. Meaning:
*
* If the current operation is POLL, we aren't the PAN coordinator
* so the incoming frame CAN'T
*
* FIXME: Fix documentation
*/
if (priv->curr_op == MAC802154_OP_POLL || priv->curr_op == MAC802154_OP_ASSOC)
{
/* If we are in promiscuous mode, we need to check if the
* frame is even for us first. If the address is not ours,
* then handle the frame like a normal transaction.
*/
if (priv->promisc)
{
if (ind->dest.panid != priv->addr.panid)
{
goto notify_with_lock;
}
if (ind->dest.mode == IEEE802154_ADDRMODE_SHORT &&
ind->dest.saddr != priv->addr.saddr)
{
goto notify_with_lock;
}
else if (ind->dest.mode == IEEE802154_ADDRMODE_EXTENDED &&
(memcmp(&ind->dest.eaddr[0], &priv->addr.eaddr[0],
IEEE802154_EADDR_LEN) != 0))
{
goto notify_with_lock;
}
else
{
goto notify_with_lock;
}
}
/* If this was our extracted data, the source addressing field can only
* be NONE if we are trying to extract data from the PAN coordinator.
* A PAN coordinator shouldn't be sending us a frame if it wasn't
* our extracted data. Therefore just assume if the address mode is set
* to NONE, we process it as our extracted frame
*/
if (ind->src.mode != priv->cmd_desc->destaddr.mode)
{
goto notify_with_lock;
}
if (ind->src.mode == IEEE802154_ADDRMODE_SHORT &&
ind->src.saddr != priv->cmd_desc->destaddr.saddr)
{
goto notify_with_lock;
}
else if (ind->src.mode == IEEE802154_ADDRMODE_EXTENDED &&
(memcmp(&ind->src.eaddr[0], &priv->cmd_desc->destaddr.eaddr[0],
IEEE802154_EADDR_LEN) != 0))
{
goto notify_with_lock;
}
/* If we've gotten this far, the frame is our extracted data. Cancel the
* timeout */
mac802154_timercancel(priv);
/* If a frame is received from the coordinator with a zero length payload
* or if the frame is a MAC command frame, the MLME will issue the
* MLME-POLL.confirm primitive with a status of NO_DATA. [1] pg. 111
*/
mac802154_notif_alloc(priv, &notif, false);
if (priv->curr_op == MAC802154_OP_POLL)
{
notif->notiftype = IEEE802154_NOTIFY_CONF_POLL;
if (ind->frame->io_offset == ind->frame->io_len)
{
ieee802154_ind_free(ind);
notif->u.pollconf.status = IEEE802154_STATUS_NO_DATA;
}
else
{
notif->u.pollconf.status = IEEE802154_STATUS_SUCCESS;
}
}
else if (priv->curr_op == MAC802154_OP_ASSOC)
{
/* If we ever receive a data frame back as a response to the
* association request, we assume it means there wasn't any data.
*/
notif->notiftype = IEEE802154_NOTIFY_CONF_ASSOC;
notif->u.assocconf.status = IEEE802154_STATUS_NO_DATA;
}
/* We are no longer performing the association operation */
priv->curr_op = MAC802154_OP_NONE;
priv->cmd_desc = NULL;
mac802154_givesem(&priv->op_sem);
/* Release the MAC */
mac802154_givesem(&priv->exclsem);
priv->cb->notify(priv->cb, notif);
/* If there was data, pass it along */
if (ind->frame->io_len > ind->frame->io_offset)
{
goto notify_without_lock;
}
}
else
{
notify_with_lock:
mac802154_givesem(&priv->exclsem);
notify_without_lock:
/* If there is a registered MCPS callback receiver registered,
* send the frame, otherwise, throw it out.
*/
if (priv->cb->rxframe != NULL)
{
priv->cb->rxframe(priv->cb, ind);
}
else
{
/* Free the data indication struct from the pool */
ieee802154_ind_free(ind);
}
}
}
/****************************************************************************
* Name: mac802154_rx_datareq
*
* Description:
* Function called from the generic RX Frame worker to parse and handle the
* reception of an Data Request MAC command frame.
*
****************************************************************************/
static void mac802154_rx_datareq(FAR struct ieee802154_privmac_s *priv,
FAR struct ieee802154_data_ind_s *ind)
{
FAR struct ieee802154_txdesc_s *txdesc;
FAR struct iob_s *iob;
uint16_t *frame_ctrl;
/* Get exclusive access to the MAC */
mac802154_takesem(&priv->exclsem, false);
/* Search the list of indirect transactions to see if there are any waiting
* for the requesting device.
*/
/* TODO: I believe there is an issue here. If there is for some reason a
* outgoing data frame to a device who is currently requesting association,
* we will send the data frame as a response to an association request. We
* need to check for this condition.
*/
txdesc = (FAR struct ieee802154_txdesc_s *)sq_peek(&priv->indirect_queue);
if (txdesc == NULL)
{
goto no_data;
}
do
{
if (txdesc->destaddr.mode == ind->src.mode)
{
if (txdesc->destaddr.mode == IEEE802154_ADDRMODE_SHORT)
{
if (txdesc->destaddr.saddr == ind->src.saddr)
{
/* Remove the transaction from the queue */
sq_rem((FAR sq_entry_t *)txdesc, &priv->indirect_queue);
/* The addresses match, send the transaction immediately */
priv->radio->txdelayed(priv->radio, txdesc, 0);
break;
}
}
else if (txdesc->destaddr.mode == IEEE802154_ADDRMODE_EXTENDED)
{
if (memcmp(&txdesc->destaddr.eaddr[0], &ind->src.eaddr[0],
sizeof(IEEE802154_EADDR_LEN)) == 0)
{
/* Remove the transaction from the queue */
sq_rem((FAR sq_entry_t *)txdesc, &priv->indirect_queue);
/* The addresses match, send the transaction immediately */
priv->radio->txdelayed(priv->radio, txdesc, 0);
break;
}
}
else
{
DEBUGASSERT(false);
}
}
txdesc = (FAR struct ieee802154_txdesc_s *)sq_next((FAR sq_entry_t *)txdesc);
if (txdesc == NULL)
{
goto no_data;
}
}
while (1);
mac802154_givesem(&priv->exclsem);
return;
no_data:
/* If there is no data frame pending for the requesting device, the coordinator
* shall send a data frame without requesting acknowledgment to the device
* containing a zero length payload, indicating that no data are present, using
* one of the mechanisms described in this subclause. [1] pg. 43
*/
/* Allocate an IOB to put the frame in */
iob = iob_alloc(false);
DEBUGASSERT(iob != NULL);
iob->io_flink = NULL;
iob->io_len = 0;
iob->io_offset = 0;
iob->io_pktlen = 0;
iob->io_len += 2;
/* Cast the first two bytes of the IOB to a uint16_t frame control field */
frame_ctrl = (FAR uint16_t *)&iob->io_data[0];
/* Ensure we start with a clear frame control field */
*frame_ctrl = 0;
/* Set the frame type to Data */
*frame_ctrl |= IEEE802154_FRAME_DATA << IEEE802154_FRAMECTRL_SHIFT_FTYPE;
/* Each time a data or a MAC command frame is generated, the MAC sublayer
* shall copy the value of macDSN into the Sequence Number field of the MHR
* of the outgoing frame and then increment it by one. [1] pg. 40.
*/
iob->io_data[iob->io_len++] = priv->dsn++;
/* Use the source address information from the received data request to
* respond.
*/
*((uint16_t *)&iob->io_data[iob->io_len]) = ind->src.panid;
iob->io_len += 2;
if (ind->src.mode == IEEE802154_ADDRMODE_SHORT)
{
memcpy(&iob->io_data[iob->io_len], &ind->src.saddr, 2);
iob->io_len += 2;
}
else if (ind->src.mode == IEEE802154_ADDRMODE_EXTENDED)
{
memcpy(&iob->io_data[iob->io_len], &ind->src.eaddr, IEEE802154_EADDR_LEN);
iob->io_len += IEEE802154_EADDR_LEN;
}
else
{
DEBUGASSERT(false);
}
/* Set the destination addr mode inside the frame control field */
*frame_ctrl |= (ind->src.mode << IEEE802154_FRAMECTRL_SHIFT_DADDR);
/* Check if the source PAN ID of the incoming request is the same as ours. */
if (ind->src.panid == priv->addr.panid)
{
*frame_ctrl |= IEEE802154_FRAMECTRL_PANIDCOMP;
}
else
{
/* Copy in our PAN ID */
memcpy(&iob->io_data[iob->io_len], &priv->addr.panid, 2);
iob->io_len += 2;
}
/* Copy in our address using the mode that the device used to address us */
if (ind->dest.mode == IEEE802154_ADDRMODE_SHORT)
{
memcpy(&iob->io_data[iob->io_len], &priv->addr.saddr, 2);
iob->io_len += 2;
*frame_ctrl |= (IEEE802154_ADDRMODE_SHORT << IEEE802154_FRAMECTRL_SHIFT_SADDR);
}
else
{
memcpy(&iob->io_data[iob->io_len], &ind->dest.eaddr, IEEE802154_EADDR_LEN);
iob->io_len += IEEE802154_EADDR_LEN;
*frame_ctrl |= (IEEE802154_ADDRMODE_EXTENDED << IEEE802154_FRAMECTRL_SHIFT_SADDR);
}
/* Allocate the txdesc, waiting if necessary, allow interruptions */
mac802154_txdesc_alloc(priv, &txdesc, false);
txdesc->frame = iob;
txdesc->frametype = IEEE802154_FRAME_DATA;
mac802154_givesem(&priv->exclsem);
priv->radio->txdelayed(priv->radio, txdesc, 0);
}
/****************************************************************************
* Name: mac802154_symtoticks
*
* Description:
* Helper function for converting symbols to system clock ticks
*
* Assumptions:
* priv MAC struct is locked when calling.
*
****************************************************************************/
static uint32_t mac802154_symtoticks(FAR struct ieee802154_privmac_s *priv,
uint32_t symbols)
{
union ieee802154_attr_u attrval;
uint32_t ret;
/* First, get the symbol duration from the radio layer. Symbol duration is
* returned in picoseconds to ensure precision is kept when multiplying to
* get overall times.
*/
priv->radio->get_attr(priv->radio, IEEE802154_ATTR_PHY_SYMBOL_DURATION,
&attrval);
/* After this step, ret represents microseconds */
ret = ((uint64_t)attrval.phy.symdur_picosec * symbols) / (1000 * 1000);
/* This method should only be used for things that can be late. For instance,
* it's always okay to wait a little longer before disabling your receiver.
* Therefore, we force the tick count to round up.
*/
if (ret % USEC_PER_TICK == 0)
{
ret = ret/USEC_PER_TICK;
}
else
{
ret = ret/USEC_PER_TICK;
ret++;
}
return ret;
}
/****************************************************************************
* Name: mac802154_timerstart
*
* Description:
* Helper function wrapping the watchdog timer interface. Helps isolate
* different operations from having to worry about work queues and watchdog
* timers.
*
* Assumptions:
* priv MAC struct is locked when calling.
*
****************************************************************************/
int mac802154_timerstart(FAR struct ieee802154_privmac_s *priv,
uint32_t numsymbols, mac802154_worker_t worker)
{
/* TODO: Add check to make sure timer is not already being used. I'd like to
* design this so that it absolutely never happens */
/* Convert the number of symbols to the number of CPU ticks */
uint32_t ticks = mac802154_symtoticks(priv, numsymbols);
/* Save the function pointer to call if the timeout expires */
priv->timeout_worker = worker;
/* Start the watchdog */
wd_start(priv->timeout, (int32_t)ticks, mac802154_timeout_expiry, 1, (uint32_t)priv);
return OK;
}
/****************************************************************************
* Function: mac802154_timeout_expiry
*
* Description:
* The watchdog timed out. Called from the timer interrupt handler.
*
* 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 mac802154_timeout_expiry(int argc, uint32_t arg, ...)
{
FAR struct ieee802154_privmac_s *priv = (FAR struct ieee802154_privmac_s *)arg;
/* There should never be a case where the timeout is used twice at the same
* time. */
DEBUGASSERT(work_available(&priv->timeout_work));
/* Check to make sure the function pointer is still valid */
DEBUGASSERT(priv->timeout_worker != NULL);
work_queue(MAC802154_WORK, &priv->timeout_work, (worker_t)priv->timeout_worker,
priv, 0);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: mac802154_create
*
* Description:
* Create a 802.15.4 MAC device from a 802.15.4 compatible radio device.
*
* The returned MAC structure should be passed to either the next highest
* layer in the network stack, or registered with a mac802154dev character
* or network drivers. In any of these scenarios, the next highest layer
* should register a set of callbacks with the MAC layer by setting the
* mac->cbs member.
*
* NOTE: This API does not create any device accessible to userspace. If
* you want to call these APIs from userspace, you have to wrap your mac
* in a character device via mac802154_device.c.
*
* Input Parameters:
* radiodev - an instance of an IEEE 802.15.4 radio
*
* Returned Value:
* An opaque reference to the MAC state data.
*
****************************************************************************/
MACHANDLE mac802154_create(FAR struct ieee802154_radio_s *radiodev)
{
FAR struct ieee802154_privmac_s *mac;
FAR struct ieee802154_radiocb_s *radiocb;
uint8_t eaddr[IEEE802154_EADDR_LEN];
int i;
/* Allocate object */
mac = (FAR struct ieee802154_privmac_s *)
kmm_zalloc(sizeof(struct ieee802154_privmac_s));
if (mac == NULL)
{
return NULL;
}
/* Allow exclusive access to the privmac struct */
sem_init(&mac->exclsem, 0, 1);
/* Allow exclusive access to the dedicated command transaction */
sem_init(&mac->op_sem, 0, 1);
/* Setup watchdog for extraction timeout */
mac->timeout = wd_create();
/* Initialize fields */
mac->radio = radiodev;
mac802154_req_reset((MACHANDLE)mac, true);
/* Initialize the Radio callbacks */
mac->radiocb.priv = mac;
radiocb = &mac->radiocb.cb;
radiocb->poll = mac802154_poll;
radiocb->txdone = mac802154_txdone;
radiocb->rxframe = mac802154_rxframe;
/* Bind our callback structure */
radiodev->bind(radiodev, &mac->radiocb.cb);
/* Initialize our various data pools */
ieee802154_indpool_initialize();
mac802154_resetqueues(mac);
/* Set the default extended address */
for (i = 0; i < IEEE802154_EADDR_LEN; i++)
{
eaddr[i] = (CONFIG_IEEE802154_DEFAULT_EADDR >> (8 * i)) & 0xFF;
}
memcpy(&mac->addr.eaddr, &eaddr[0], IEEE802154_EADDR_LEN);
mac->radio->set_attr(mac->radio, IEEE802154_ATTR_MAC_EXTENDED_ADDR,
(union ieee802154_attr_u *)&eaddr[0]);
return (MACHANDLE)mac;
}