/**************************************************************************** * drivers/wireless/xbee/drivers/xbee.c * * Copyright (C) 2017 Verge Inc. All rights reserved. * Author: Anthony Merlino * * 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 * 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 #include #include #include #include #include #include #include #include #include #include #include #include "xbee.h" #include "xbee_mac.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define XBEE_ATQUERY_TIMEOUT MSEC2TICK(100) #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND #define XBEE_LOCKUP_QUERYTIME MSEC2TICK(500) #define XBEE_LOCKUP_QUERYATTEMPTS 20 #endif /**************************************************************************** * Private Types ****************************************************************************/ /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int xbee_interrupt(int irq, FAR void *context, FAR void *arg); static void xbee_attnworker(FAR void *arg); static bool xbee_validate_apiframe(uint8_t frametype, uint16_t framelen); static bool xbee_verify_checksum(FAR const struct iob_s *iob); static void xbee_process_apiframes(FAR struct xbee_priv_s *priv, FAR struct iob_s *iob); static void xbee_process_txstatus(FAR struct xbee_priv_s *priv, uint8_t frameid, uint8_t status); static void xbee_process_rxframe(FAR struct xbee_priv_s *priv, FAR struct iob_s *frame, enum ieee802154_addrmode_e addrmode); static void xbee_notify(FAR struct xbee_priv_s *priv, FAR struct ieee802154_primitive_s *primitive); static void xbee_notify_worker(FAR void *arg); static void xbee_atquery_timeout(int argc, uint32_t arg, ...); #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND static void xbee_lockupcheck_timeout(int argc, uint32_t arg, ...); static void xbee_lockupcheck_worker(FAR void *arg); static void xbee_backup_worker(FAR void *arg); static void xbee_lockupcheck_reschedule(FAR struct xbee_priv_s *priv); #endif /**************************************************************************** * Private Data ****************************************************************************/ /**************************************************************************** * Public Data ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: xbee_interrupt * * Description: * Hardware interrupt handler * * Input Parameters: * irq - Number of the IRQ that generated the interrupt * context - Interrupt register state save info (architecture-specific) * * Returned Value: * OK on success * * Assumptions: * ****************************************************************************/ static int xbee_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg; DEBUGASSERT(priv != NULL); priv->attn_latched = true; /* In complex environments, we cannot do SPI transfers from the interrupt * handler because semaphores are probably used to lock the SPI bus. In * this case, we will defer processing to the 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. */ if (work_available(&priv->attnwork)) { return work_queue(HPWORK, &priv->attnwork, xbee_attnworker, (FAR void *)priv, 0); } return OK; } /**************************************************************************** * Name: xbee_attnworker * * Description: * Perform interrupt handling (Attention) logic outside of the interrupt handler * (on the work queue thread). * * Input Parameters: * arg - The reference to the driver structure (cast to void*) * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static void xbee_attnworker(FAR void *arg) { FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg; FAR struct iob_s *iobhead = NULL; FAR struct iob_s *iob = NULL; FAR struct iob_s *previob = NULL; uint16_t rxframelen = 0; DEBUGASSERT(priv); DEBUGASSERT(priv->spi); /* Allocate an IOB for the incoming data. */ iob = iob_alloc(false, IOBUSER_WIRELESS_RAD802154); iob->io_flink = NULL; iob->io_len = 0; iob->io_offset = 0; iob->io_pktlen = 0; /* Keep a reference to the first IOB. If we need to allocate more than * one to hold each API frame, then we will still have this reference to * the head of the list */ iobhead = iob; /* NOTE: There is a helpful side-effect to trying to get the SPI Lock here * even when there is a write going on. That is, if the SPI write are on a * thread with lower priority, trying to get the lock here should boost the * priority of that thread, helping move along the low-level driver work * that really should be happening in a high priority way anyway. */ SPI_LOCK(priv->spi, 1); SPI_SETBITS(priv->spi, 8); SPI_SETMODE(priv->spi, SPIDEV_MODE0); SPI_SETFREQUENCY(priv->spi, CONFIG_IEEE802154_XBEE_FREQUENCY); /* Assert CS */ SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), true); /* Check to make sure all the data hasn't already been clocked in and * we just need to process it. */ if (priv->attn_latched && !priv->lower->poll(priv->lower)) { priv->attn_latched = false; } if (priv->attn_latched) { while (priv->lower->poll(priv->lower) && iob != NULL) { DEBUGASSERT(iob->io_len <= CONFIG_IOB_BUFSIZE); SPI_RECVBLOCK(priv->spi, &iob->io_data[iob->io_len], 1); switch (iob->io_len) { case XBEE_APIFRAMEINDEX_STARTBYTE: { if (iob->io_data[iob->io_len] == XBEE_STARTBYTE) { iob->io_len++; } } break; case XBEE_APIFRAMEINDEX_LENGTHMSB: { rxframelen = iob->io_data[iob->io_len++] << 8; } break; case XBEE_APIFRAMEINDEX_LENGTHLSB: { rxframelen |= iob->io_data[iob->io_len++]; rxframelen += XBEE_APIFRAME_OVERHEAD; } break; case XBEE_APIFRAMEINDEX_TYPE: { /* Check that the length and frame type make sense together */ if (!xbee_validate_apiframe(iob->io_data[iob->io_len], rxframelen - XBEE_APIFRAME_OVERHEAD)) { wlwarn("invalid length on incoming API frame. Dropping!\n"); iob->io_len = 0; } else { iob->io_len++; } } break; default: { if (iob->io_len == rxframelen - 1) { iob->io_len++; if (xbee_verify_checksum(iob)) { /* This API frame is complete. Allocate a new IOB * and link it to the existing one. When we are all * finished we will pass this IOB list along for * processing. */ iob->io_flink = iob_tryalloc(false, IOBUSER_WIRELESS_RAD802154); iob = iob->io_flink; if (iob != NULL) { iob->io_flink = NULL; iob->io_len = 0; iob->io_offset = 0; iob->io_pktlen = 0; } } else { wlwarn("invalid checksum on incoming API frame. Dropping!\n"); iob->io_len = 0; } } else { iob->io_len++; } } break; } } if (!priv->lower->poll(priv->lower)) { priv->attn_latched = false; } } /* The last IOB in the list (or the only one) may be able to be freed since * it may not have any valid data. If it contains some data, but not a whole * API frame, something is wrong, so we just warn the user and drop the * data. If the data was valid, the ATTN line should have stayed asserted * until all the data was clocked in. So if we don't have a full frame, * we can only drop it. */ if (iob != NULL) { if (iob->io_len < XBEE_APIFRAME_OVERHEAD || iob->io_len != rxframelen) { if (iobhead == iob) { iobhead = NULL; } else { previob = iobhead; while (previob->io_flink != iob) { previob = previob->io_flink; } previob->io_flink = NULL; } if (iob->io_len > 0) { wlwarn("Partial API frame clocked in. Dropping!\n"); } iob_free(iob, IOBUSER_WIRELESS_RAD802154); } } if (iobhead != NULL) { if (priv->rx_apiframes == NULL) { priv->rx_apiframes = iobhead; } else { iob = priv->rx_apiframes; while (iob->io_flink != NULL) { iob = iob->io_flink; } iob->io_flink = iobhead; } } /* Before unlocking the SPI bus, we "detach" the IOB list from the private * struct and keep a copy. When the SPI bus becomes free, more data can * be clocked in from an SPI write. By detaching the IOB list, we can process * the incoming data without holding up the SPI bus */ iobhead = priv->rx_apiframes; priv->rx_apiframes = NULL; /* De-assert CS */ SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), false); /* Relinquish control of the SPI Bus */ SPI_LOCK(priv->spi, 0); if (iobhead != NULL) { xbee_process_apiframes(priv, iobhead); } /* If an interrupt occured while the worker was running, it was not * scheduled since there is a good chance this function has already handled * it as part of the previous ATTN assertion. Therefore, if the ATTN * line is asserted again reschedule the work. */ if (priv->attn_latched) { work_queue(HPWORK, &priv->attnwork, xbee_attnworker, (FAR void *)priv, 0); } } /**************************************************************************** * Name: xbee_validate_apiframe * * Description: * Verifies that the API frame type is known and that the length makes * sense for that frame type. * * Input Parameters: * frame - pointer to the frame data * datalen - The size of the data section of the frame. This is the value * included as the second and third byte of the frame. * * Returned Value: * true - Frame type is known and length is logical * false - Frame type is unknown or length is invalid for frame type * ****************************************************************************/ static bool xbee_validate_apiframe(uint8_t frametype, uint16_t datalen) { switch (frametype) { case XBEE_APIFRAME_MODEMSTATUS: { if (datalen != 2) { return false; } } break; case XBEE_APIFRAME_ATRESPONSE: { return true; } break; case XBEE_APIFRAME_TXSTATUS: { if (datalen != 3) { return false; } } break; case XBEE_APIFRAME_RX_EADDR: { if (datalen < 14) { return false; } } break; case XBEE_APIFRAME_RX_SADDR: { if (datalen < 8) { return false; } } break; default: { return false; } break; } return true; } /**************************************************************************** * Name: xbee_verify_checksum * * Description: * Verifies API frame checksum. * * Input Parameters: * frame - pointer to the frame data * framelen - size of the overall frame. NOT the data length field * * Returned Value: * true - Checksum is valid * false - Checksum is invalid * ****************************************************************************/ static bool xbee_verify_checksum(FAR const struct iob_s *iob) { int i; uint8_t checksum = 0; DEBUGASSERT(iob->io_len > XBEE_APIFRAME_OVERHEAD); /* Skip the start byte and frame length, but include the checksum */ for (i = 3; i < iob->io_len; i++) { checksum += iob->io_data[i]; } if (checksum != 0xFF) { wlwarn("Invalid checksum\n"); return false; } return true; } /**************************************************************************** * Name: xbee_process_apiframes * * Description: * Processes a list of complete API frames. * * Assumptions: * Frame has already been validated using frame type and frame length and * the checksum has also been verified. * ****************************************************************************/ static void xbee_process_apiframes(FAR struct xbee_priv_s *priv, FAR struct iob_s *framelist) { FAR struct ieee802154_primitive_s *primitive; FAR struct iob_s *frame; FAR struct iob_s *nextframe; FAR char *command; DEBUGASSERT(framelist != NULL); frame = framelist; /* At the end of each iteration, frame is set to the next frame in the list * and the IOB is freed. If the IOB is not supposed to be freed, per the * API frame type, the logic must update the frame to the next frame in the * list and use continue to skip freeing the IOB. */ while (frame) { #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND /* Any time we receive an API frame from the XBee we reschedule our lockup * timeout. Receiving an API frame is an indication that the XBee is not * locked up. */ xbee_lockupcheck_reschedule(priv); #endif /* Skip over start byte and length */ frame->io_offset += XBEE_APIFRAMEINDEX_TYPE; switch (frame->io_data[frame->io_offset++]) { case XBEE_APIFRAME_MODEMSTATUS: { #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND /* If the Modem Status indicates that the coordinator has just formed * a new network, we know that the channel and PAN ID are locked in. * In case we need to reset the radio due to a lockup, we tell the * XBee to write the parameters to non-volatile memory so that upon * reset, we can resume operation */ if (frame->io_data[frame->io_offset] == 0x06) { if (work_available(&priv->backupwork)) { work_queue(LPWORK, &priv->backupwork, xbee_backup_worker, (FAR void *)priv, 0); } } #endif wlinfo("Modem Status: %d\n", frame->io_data[frame->io_offset++]); } break; case XBEE_APIFRAME_ATRESPONSE: { frame->io_offset++; /* Skip over frame index */ command = (FAR char *)&frame->io_data[frame->io_offset]; frame->io_offset += 2; wlinfo("AT Response Received: %.*s\n", 2, command); /* Make sure the command status is OK=0 */ if (frame->io_data[frame->io_offset]) { wlwarn("AT Command Error: %d\n", frame->io_data[frame->io_offset]); } else { frame->io_offset++; if (memcmp(command, "ID", 2) == 0) { priv->addr.panid[1] = frame->io_data[frame->io_offset++]; priv->addr.panid[0] = frame->io_data[frame->io_offset++]; } else if (memcmp(command, "SH", 2) == 0) { priv->addr.eaddr[7] = frame->io_data[frame->io_offset++]; priv->addr.eaddr[6] = frame->io_data[frame->io_offset++]; priv->addr.eaddr[5] = frame->io_data[frame->io_offset++]; priv->addr.eaddr[4] = frame->io_data[frame->io_offset++]; } else if (memcmp(command, "SL", 2) == 0) { priv->addr.eaddr[3] = frame->io_data[frame->io_offset++]; priv->addr.eaddr[2] = frame->io_data[frame->io_offset++]; priv->addr.eaddr[1] = frame->io_data[frame->io_offset++]; priv->addr.eaddr[0] = frame->io_data[frame->io_offset++]; } else if (memcmp(command, "MY", 2) == 0) { priv->addr.saddr[1] = frame->io_data[frame->io_offset++]; priv->addr.saddr[0] = frame->io_data[frame->io_offset++]; } else if (memcmp(command, "CH", 2) == 0) { priv->chan = frame->io_data[frame->io_offset++]; } else if (memcmp(command, "VR", 2) == 0) { priv->firmwareversion = frame->io_data[frame->io_offset++] << 8; priv->firmwareversion |= frame->io_data[frame->io_offset++]; } else if (memcmp(command, "AI", 2) == 0) { wlinfo("Association Indication: %d\n", frame->io_data[frame->io_offset]); /* 0xFF = No assocication status determined yet. */ if (frame->io_data[frame->io_offset] != 0xFF && frame->io_data[frame->io_offset] != 0x13) { wd_cancel(priv->assocwd); priv->associating = false; primitive = ieee802154_primitive_allocate(); primitive->type = IEEE802154_PRIMITIVE_CONF_ASSOC; if (frame->io_data[frame->io_offset] == 0) { primitive->u.assocconf.status = IEEE802154_STATUS_SUCCESS; #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND /* Upon successful association, we know that the * channel and PAN ID give us a valid connection. * In case we need to reset the radio due to a lockup, * we tell the XBee to write the parameters to * non-volatile memory so that upon reset, we can * resume operation */ if (work_available(&priv->backupwork)) { work_queue(LPWORK, &priv->backupwork, xbee_backup_worker, (FAR void *)priv, 0); } #endif } else { primitive->u.assocconf.status = IEEE802154_STATUS_FAILURE; } xbee_notify(priv, primitive); } } else if (memcmp(command, "A1", 2) == 0) { wlinfo("Endpoint Association: %d\n", frame->io_data[frame->io_offset]); } else if (memcmp(command, "A2", 2) == 0) { wlinfo("Coordinator Association: %d\n", frame->io_data[frame->io_offset]); } else if (memcmp(command, "CE", 2) == 0) { wlinfo("Coordinator Enable: %d\n", frame->io_data[frame->io_offset]); } else if (memcmp(command, "SP", 2) == 0) { wlinfo("Sleep Period: %dsec\n", frame->io_data[frame->io_offset]/100); } else if (memcmp(command, "RR", 2) == 0) { wlinfo("XBee Retries: %d\n", frame->io_data[frame->io_offset]); } else if (memcmp(command, "MM", 2) == 0) { wlinfo("Mac Mode: %d\n", frame->io_data[frame->io_offset]); } else if (memcmp(command, "PL", 2) == 0) { wlinfo("Power Level: %d\n", frame->io_data[frame->io_offset]); priv->pwrlvl = frame->io_data[frame->io_offset++]; } else if (memcmp(command, "PM", 2) == 0) { wlinfo("Boost Mode (Power Mode): %d\n", frame->io_data[frame->io_offset]); priv->boostmode = frame->io_data[frame->io_offset++]; } else if (memcmp(command, "WR", 2) == 0) { wlinfo("Write Complete: %d\n", frame->io_data[frame->io_offset]); } else { wlwarn("Unhandled AT Response: %.*s\n", 2, command); } /* If this is the command we are querying for */ if ((priv->querycmd[0] == *command) && (priv->querycmd[1] == *(command + 1))) { wd_cancel(priv->atquery_wd); priv->querydone = true; nxsem_post(&priv->atresp_sem); } } } break; case XBEE_APIFRAME_TXSTATUS: { xbee_process_txstatus(priv, frame->io_data[frame->io_offset], frame->io_data[frame->io_offset + 1]); } break; case XBEE_APIFRAME_RX_EADDR: { nextframe = frame->io_flink; frame->io_flink = NULL; xbee_process_rxframe(priv, frame, IEEE802154_ADDRMODE_EXTENDED); frame = nextframe; /* xbee_process_rxframe takes care of freeing the IOB or passing * it along to the next highest layer */ continue; } break; case XBEE_APIFRAME_RX_SADDR: { nextframe = frame->io_flink; frame->io_flink = NULL; xbee_process_rxframe(priv, frame, IEEE802154_ADDRMODE_SHORT); frame = nextframe; /* xbee_process_rxframe takes care of freeing the IOB or passing * it along to the next highest layer */ continue; } break; default: { /* This really should never happen since xbee_validateframe should * have caught it. */ wlwarn("Unknown frame type: %d\n", frame[XBEE_APIFRAMEINDEX_TYPE]); } break; } /* IOB free logic assumes that a valid io_flink entry in the IOB that * is being freed indicates that the IOB is a part of an IOB chain. Since * that is not the case here and we are just using the io_flink field * as a way of managing a list of independent frames, we set the io_flink * to NULL prior to freeing it. */ nextframe = frame->io_flink; frame->io_flink = NULL; iob_free(frame, IOBUSER_WIRELESS_RAD802154); frame = nextframe; } } /**************************************************************************** * Name: xbee_process_rxframe * * Description: * Process an incoming RX frame. * ****************************************************************************/ static void xbee_process_rxframe(FAR struct xbee_priv_s *priv, FAR struct iob_s *frame, enum ieee802154_addrmode_e addrmode) { FAR struct ieee802154_primitive_s *primitive; FAR struct ieee802154_data_ind_s *dataind; DEBUGASSERT(frame != NULL); primitive = ieee802154_primitive_allocate(); dataind = &primitive->u.dataind; primitive->type = IEEE802154_PRIMITIVE_IND_DATA; dataind->frame = frame; if (addrmode == IEEE802154_ADDRMODE_EXTENDED) { dataind->dest.mode = IEEE802154_ADDRMODE_EXTENDED; dataind->src.mode = IEEE802154_ADDRMODE_EXTENDED; dataind->src.eaddr[7] = frame->io_data[frame->io_offset++]; dataind->src.eaddr[6] = frame->io_data[frame->io_offset++]; dataind->src.eaddr[5] = frame->io_data[frame->io_offset++]; dataind->src.eaddr[4] = frame->io_data[frame->io_offset++]; dataind->src.eaddr[3] = frame->io_data[frame->io_offset++]; dataind->src.eaddr[2] = frame->io_data[frame->io_offset++]; dataind->src.eaddr[1] = frame->io_data[frame->io_offset++]; dataind->src.eaddr[0] = frame->io_data[frame->io_offset++]; } else { dataind->src.mode = IEEE802154_ADDRMODE_SHORT; dataind->src.saddr[1] = frame->io_data[frame->io_offset++]; dataind->src.saddr[0] = frame->io_data[frame->io_offset++]; } /* The XBee does not give us information about how the device was addressed. * It only indicates the source mode. Therefore, we make the assumption that * if the saddr is set, we we're addressed using the saddr, otherwise we must * have been addressed using the eaddr. */ memcpy(&dataind->dest, &priv->addr, sizeof(struct ieee802154_addr_s)); if (IEEE802154_SADDRCMP(priv->addr.saddr, &IEEE802154_SADDR_BCAST) || IEEE802154_SADDRCMP(priv->addr.saddr, &IEEE802154_SADDR_UNSPEC)) { dataind->dest.mode = IEEE802154_ADDRMODE_EXTENDED; } else { dataind->dest.mode = IEEE802154_ADDRMODE_SHORT; } dataind->rssi = frame->io_data[frame->io_offset++]; frame->io_offset++; /* Skip options byte */ frame->io_len--; /* Remove the checksum */ xbee_notify(priv, primitive); wlinfo("Received frame. RSSI: -%d dbm\n", dataind->rssi); } /**************************************************************************** * Name: xbee_process_txstatus * * Description: * Process an incoming TX status message. This searches the list of pending * tx requests and notifies the * ****************************************************************************/ static void xbee_process_txstatus(FAR struct xbee_priv_s *priv, uint8_t frameid, uint8_t status) { FAR struct ieee802154_primitive_s *primitive; primitive = ieee802154_primitive_allocate(); primitive->type = IEEE802154_PRIMITIVE_CONF_DATA; switch (status) { case 0x00: primitive->u.dataconf.status = IEEE802154_STATUS_SUCCESS; break; case 0x01: case 0x21: primitive->u.dataconf.status = IEEE802154_STATUS_NO_ACK; break; case 0x02: primitive->u.dataconf.status = IEEE802154_STATUS_CHANNEL_ACCESS_FAILURE; break; default: primitive->u.dataconf.status = IEEE802154_STATUS_FAILURE; break; } if (status != IEEE802154_STATUS_SUCCESS) { wlwarn("TX done. Frame ID: %d Status: 0x%02x\n", frameid, status); } else { wlinfo("TX done. Frame ID: %d Status: 0x%02x\n", frameid, status); } xbee_notify(priv, primitive); /* If this is the frame we are currently waiting on, cancel the timeout, and * notify the waiting thread that the transmit is done */ if (priv->frameid == frameid) { wd_cancel(priv->reqdata_wd); priv->txdone = true; nxsem_post(&priv->txdone_sem); } } /**************************************************************************** * Name: xbee_notify * * Description: * Queue the primitive in the queue and queue work on the LPWORK * queue if is not already scheduled. * * Assumptions: * Called with the MAC locked * ****************************************************************************/ static void xbee_notify(FAR struct xbee_priv_s *priv, FAR struct ieee802154_primitive_s *primitive) { while (nxsem_wait(&priv->primitive_sem) < 0); sq_addlast((FAR sq_entry_t *)primitive, &priv->primitive_queue); nxsem_post(&priv->primitive_sem); if (work_available(&priv->notifwork)) { work_queue(LPWORK, &priv->notifwork, xbee_notify_worker, (FAR void *)priv, 0); } } /**************************************************************************** * Name: xbee_notify_worker * * Description: * Pop each primitive off the queue and call the registered * callbacks. There is special logic for handling ieee802154_data_ind_s. * ****************************************************************************/ static void xbee_notify_worker(FAR void *arg) { FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg; FAR struct xbee_maccb_s *cb; FAR struct ieee802154_primitive_s *primitive; int ret; DEBUGASSERT(priv != NULL); while (nxsem_wait(&priv->primitive_sem) < 0); primitive = (FAR struct ieee802154_primitive_s *)sq_remfirst(&priv->primitive_queue); nxsem_post(&priv->primitive_sem); while (primitive != NULL) { /* Data indications are a special case since the frame can only be passed to * one place. The return value of the notify call is used to accept or reject * the primitive. In the case of the data indication, there can only be one * accept. Callbacks are stored in order of there receiver priority ordered * when the callbacks are bound in mac802154_bind(). */ if (primitive->type == IEEE802154_PRIMITIVE_IND_DATA) { bool dispose = true; primitive->nclients = 1; for (cb = priv->cb; cb != NULL; cb = cb->flink) { if (cb->notify != NULL) { ret = cb->notify(cb, primitive); if (ret >= 0) { /* The receiver accepted and disposed of the frame and it's * meta-data. We are done. */ dispose = false; break; } } } if (dispose) { iob_free(primitive->u.dataind.frame, IOBUSER_WIRELESS_RAD802154); ieee802154_primitive_free(primitive); } } else { /* Set the number of clients count so that the primitive resources will be * preserved until all clients are finished with it. */ primitive->nclients = priv->nclients; /* Try to notify every registered MAC client */ for (cb = priv->cb; cb != NULL; cb = cb->flink) { if (cb->notify != NULL) { ret = cb->notify(cb, primitive); if (ret < 0) { ieee802154_primitive_free(primitive); } } else { ieee802154_primitive_free(primitive); } } } /* Get the next primitive then loop */ while (nxsem_wait(&priv->primitive_sem) < 0); primitive = (FAR struct ieee802154_primitive_s *)sq_remfirst(&priv->primitive_queue); nxsem_post(&priv->primitive_sem); } } /**************************************************************************** * Name: xbee_atquery_timeout * * Description: * This function runs when an AT Query has timed out waiting for a response * from the XBee module. This really should never happen, but if it does, * handle it gracefully by retrying the query. * * Input Parameters: * argc - The number of available arguments * arg - The first argument * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static void xbee_atquery_timeout(int argc, uint32_t arg, ...) { FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg; DEBUGASSERT(priv != NULL); wlwarn("AT Query timeout\n"); /* Wake the pending query thread so it can retry */ nxsem_post(&priv->atresp_sem); } #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND /**************************************************************************** * Name: xbee_lockupcheck_timeout * * Description: * This function runs when a query to the XBee has not been issued for awhile. * We query periodically in an effort to detect if the XBee has locked up. * * Input Parameters: * argc - The number of available arguments * arg - The first argument * * Returned Value: * None * * Assumptions: * ****************************************************************************/ static void xbee_lockupcheck_timeout(int argc, uint32_t arg, ...) { FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg; DEBUGASSERT(priv != NULL); if (work_available(&priv->lockupwork)) { work_queue(LPWORK, &priv->lockupwork, xbee_lockupcheck_worker, (FAR void *)priv, 0); } } /**************************************************************************** * Name: xbee_lockupcheck_worker * * Description: * Perform an AT query to make sure the XBee is still responsive. If we've * gotten here, it means we haven't talked to the XBee in a while. This * is to workaround an issue where the XBee locks up. In this condition, * most of the time, querying it kicks it out of the state. In some conditions * though, the XBee locks up to the point where it needs to be reset. This * will be handled inside xbee_atquery; if we don't get a response we will * reset the XBee. * ****************************************************************************/ static void xbee_lockupcheck_worker(FAR void *arg) { FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg; DEBUGASSERT(priv != NULL); wlinfo("Issuing query to detect lockup\n"); xbee_query_chan(priv); } /**************************************************************************** * Name: xbee_backup_worker * * Description: * In the case that we need to reset the XBee to bring it out of a locked up * state, we need to be able to restore it's previous state seemlessly to resume * operation. In order to achieve this, we backup the parameters using the * "WR" AT command. We do this at strategic points as we don't know what type * of memory technology is being used and writing too often may reduce the lifetime * of the device. The two key points chosen are upon association for endpoint nodes, * and upon creating a new network for coordinator nodes. These conditions * indicate that the node is actively communicating on the network and that * the channel and pan ID are now set for the network. * ****************************************************************************/ static void xbee_backup_worker(FAR void *arg) { FAR struct xbee_priv_s *priv = (FAR struct xbee_priv_s *)arg; DEBUGASSERT(priv != NULL); xbee_save_params(priv); } static void xbee_lockupcheck_reschedule(FAR struct xbee_priv_s *priv) { wd_cancel(priv->lockup_wd); /* Kickoff the watchdog timer that will query the XBee periodically (if naturally * occuring queries do not occur). We query periodically to make sure the XBee * is still responsive. If during any query, the XBee does not respond after * multiple attempts, we restart the XBee to get it back in a working state */ (void)wd_start(priv->lockup_wd, XBEE_LOCKUP_QUERYTIME, xbee_lockupcheck_timeout, 1, (wdparm_t)priv); } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: xbee_init * * Description: * Initialize an XBee driver. The XBee device is assumed to be * in the post-reset state upon entry to this function. * * Input Parameters: * spi - A reference to the platform's SPI driver for the XBee * lower - The MCU-specific interrupt used to control low-level MCU * functions (i.e., XBee GPIO interrupts). * * Returned Value: * OK on success; Negated errno on failure. * * Assumptions: * ****************************************************************************/ XBEEHANDLE xbee_init(FAR struct spi_dev_s *spi, FAR const struct xbee_lower_s *lower) { FAR struct xbee_priv_s *priv; /* Allocate object */ priv = (FAR struct xbee_priv_s *) kmm_zalloc(sizeof(struct xbee_priv_s)); if (priv == NULL) { wlinfo("Failed allocation xbee_priv_s structure\n"); return NULL; } /* Attach irq */ if (lower->attach(lower, xbee_interrupt, priv) != OK) { wlinfo("Failed to attach IRQ with XBee lower half\n"); kmm_free(priv); return NULL; } priv->lower = lower; priv->spi = spi; nxsem_init(&priv->primitive_sem, 0, 1); nxsem_init(&priv->atquery_sem, 0, 1); nxsem_init(&priv->tx_sem, 0, 1); nxsem_init(&priv->txdone_sem, 0, 0); nxsem_setprotocol(&priv->txdone_sem, SEM_PRIO_NONE); ieee802154_primitivepool_initialize(); sq_init(&priv->primitive_queue); priv->assocwd = wd_create(); priv->atquery_wd = wd_create(); priv->reqdata_wd = wd_create(); #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND priv->lockup_wd = wd_create(); #endif priv->frameid = 0; /* Frame ID should never be 0, but it is incremented * in xbee_next_frameid before being used so it will be 1 */ priv->querycmd[0] = 0; priv->querycmd[1] = 0; /* Initialize the saddr */ IEEE802154_SADDRCOPY(priv->addr.saddr, &IEEE802154_SADDR_UNSPEC); /* Reset the XBee radio */ priv->lower->reset(priv->lower); /* Enable interrupts */ priv->lower->enable(priv->lower, true); /* Trigger a query to tell the XBee to operate in SPI mode. By default the XBee * uses the UART interface. It switches automatically when a valid SPI frame is received. * * Use this opportunity to pull the extended address */ xbee_query_eaddr(priv); #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND xbee_lockupcheck_reschedule(priv); #endif return (XBEEHANDLE)priv; } /**************************************************************************** * Name: xbee_send_apiframe * * Description: * Write an api frame over SPI * ****************************************************************************/ void xbee_send_apiframe(FAR struct xbee_priv_s *priv, FAR const uint8_t *frame, uint16_t framelen) { FAR struct iob_s *iob; FAR struct iob_s *previob; FAR struct iob_s *iobhead; uint16_t rxframelen = 0; int i; /* Get access to SPI bus, set relevant settings */ SPI_LOCK(priv->spi, 1); SPI_SETBITS(priv->spi, 8); SPI_SETMODE(priv->spi, SPIDEV_MODE0); SPI_SETFREQUENCY(priv->spi, CONFIG_IEEE802154_XBEE_FREQUENCY); /* Assert CS */ SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), true); /* Allocate an IOB for the incoming data. The XBee supports full-duplex * SPI communication. This means that the MISO data can become valid at any * time. This requires us to process incoming MISO data to see if it is valid. * * If we can't allocate an IOB, then we have to just drop the incoming data. */ iob = iob_tryalloc(false, IOBUSER_WIRELESS_RAD802154); iob->io_flink = NULL; iob->io_len = 0; iob->io_offset = 0; iob->io_pktlen = 0; /* Keep a reference to the first IOB. If we need to allocate more than * one to hold each API frame, then we will still have this reference to the * head of the list */ iobhead = iob; i = 0; while (i < framelen || (priv->lower->poll(priv->lower) && iob != NULL)) { if (i < framelen) { if (iob) { iob->io_data[iob->io_len] = SPI_SEND(priv->spi, frame[i++]); } else { SPI_SEND(priv->spi, frame[i++]); } } else { SPI_RECVBLOCK(priv->spi, &iob->io_data[iob->io_len], 1); } /* attn_latched should be set true immediately from the interrupt. Any * data prior to that can be completely ignored. */ if (priv->attn_latched && iob != NULL) { DEBUGASSERT(iob->io_len <= CONFIG_IOB_BUFSIZE); switch (iob->io_len) { case XBEE_APIFRAMEINDEX_STARTBYTE: { if (iob->io_data[iob->io_len] == XBEE_STARTBYTE) { iob->io_len++; } } break; case XBEE_APIFRAMEINDEX_LENGTHMSB: { rxframelen = iob->io_data[iob->io_len++] << 8; } break; case XBEE_APIFRAMEINDEX_LENGTHLSB: { rxframelen |= iob->io_data[iob->io_len++]; rxframelen += XBEE_APIFRAME_OVERHEAD; } break; case XBEE_APIFRAMEINDEX_TYPE: { /* Check that the length and frame type make sense together */ if (!xbee_validate_apiframe(iob->io_data[iob->io_len], rxframelen - XBEE_APIFRAME_OVERHEAD)) { wlwarn("invalid length on incoming API frame. Dropping!\n"); iob->io_len = 0; } else { iob->io_len++; } } break; default: { if (iob->io_len == rxframelen - 1) { iob->io_len++; if (xbee_verify_checksum(iob)) { /* This API frame is complete. Allocate a new IOB * and link it to the existing one. When we are all * finished we will pass this IOB list along for * processing. */ iob->io_flink = iob_tryalloc(false, IOBUSER_WIRELESS_RAD802154); iob = iob->io_flink; if (iob != NULL) { iob->io_flink = NULL; iob->io_len = 0; iob->io_offset = 0; iob->io_pktlen = 0; } } else { wlwarn("invalid checksum on incoming API frame. Dropping!\n"); iob->io_len = 0; } } else { iob->io_len++; } } break; } } } /* The last IOB in the list (or the only one) may be able to be freed since * it may not have any valid data. If it contains some data, but not a whole * API frame, something is wrong, so we just warn the user and drop the * data. If the data was valid, the ATTN line should have stayed asserted * until all the data was clocked in. So if we don't have a full frame, * we can only drop it. */ if (iob != NULL) { if (iob->io_len < XBEE_APIFRAME_OVERHEAD || iob->io_len != rxframelen) { if (iobhead == iob) { iobhead = NULL; } else { previob = iobhead; while (previob->io_flink != iob) { previob = previob->io_flink; } previob->io_flink = NULL; } if (iob->io_len > 0) { wlwarn("Partial API frame clocked in. Dropping!\n"); } iob_free(iob, IOBUSER_WIRELESS_RAD802154); } } if (iobhead != NULL) { if (priv->rx_apiframes == NULL) { priv->rx_apiframes = iobhead; } else { iob = priv->rx_apiframes; while (iob->io_flink != NULL) { iob = iob->io_flink; } iob->io_flink = iobhead; } } /* De-assert CS */ SPI_SELECT(priv->spi, SPIDEV_IEEE802154(0), false); /* Relinquish control of the SPI Bus */ SPI_LOCK(priv->spi,0); } /**************************************************************************** * Name: xbee_atquery * * Description: * Sends AT Query and waits for response from device * ****************************************************************************/ int xbee_atquery(FAR struct xbee_priv_s *priv, FAR const char *atcommand) { int ret; #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND int retries = XBEE_LOCKUP_QUERYATTEMPTS; #endif /* Only allow one query at a time */ ret = nxsem_wait(&priv->atquery_sem); if (ret < 0) { DEBUGASSERT(ret == -EINTR || ret == -ECANCELED); return ret; } priv->querydone = false; /* We reinitialize this every time, in case something gets out of phase with * the timeout and the received response. */ nxsem_init(&priv->atresp_sem, 0, 0); nxsem_setprotocol(&priv->atresp_sem, SEM_PRIO_NONE); do { /* There is a note in the XBee datasheet: Once you issue a WR command, * do not send any additional characters to the device until after * you receive the OK response. * * If we are issuing a WR command, don't set a timeout. We will have to rely * on the XBee getting back to us reliably. */ if (memcmp(atcommand, "WR", 2) != 0) { /* Setup a timeout */ (void)wd_start(priv->atquery_wd, XBEE_ATQUERY_TIMEOUT, xbee_atquery_timeout, 1, (wdparm_t)priv); } /* Send the query */ priv->querycmd[0] = *atcommand; priv->querycmd[1] = *(atcommand + 1); xbee_send_atquery(priv, atcommand); /* Wait for the response to be received */ ret = nxsem_wait(&priv->atresp_sem); if (ret < 0) { DEBUGASSERT(ret == -EINTR || ret == -ECANCELED); wd_cancel(priv->atquery_wd); priv->querycmd[0] = 0; priv->querycmd[1] = 0; nxsem_post(&priv->atquery_sem); return ret; } #ifdef CONFIG_XBEE_LOCKUP_WORKAROUND if (--retries == 0 && !priv->querydone) { wlerr("XBee not responding. Resetting.\n"); priv->lower->reset(priv->lower); retries = XBEE_LOCKUP_QUERYATTEMPTS; } #endif } while (!priv->querydone); nxsem_post(&priv->atquery_sem); return OK; } /**************************************************************************** * Name: xbee_send_atquery * * Description: * Helper function to send the AT query to the XBee * ****************************************************************************/ void xbee_send_atquery(FAR struct xbee_priv_s *priv, FAR const char *atcommand) { uint8_t frame[8]; wlinfo("AT Query: %c%c\n", *atcommand, *(atcommand + 1)); frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 4; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 1; frame[5] = *atcommand; frame[6] = *(atcommand + 1); xbee_insert_checksum(frame, 8); xbee_send_apiframe(priv, frame, 8); } /**************************************************************************** * Name: xbee_set_panid * * Description: * Sends API frame with AT command request in order to set the PAN ID * (Network ID) of the device. * ****************************************************************************/ void xbee_set_panid(FAR struct xbee_priv_s *priv, FAR const uint8_t *panid) { uint8_t frame[10]; IEEE802154_PANIDCOPY(priv->addr.panid, panid); frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 6; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'I'; frame[6] = 'D'; frame[7] = *(panid + 1); frame[8] = *(panid); xbee_insert_checksum(frame, 10); xbee_send_apiframe(priv, frame, 10); } /**************************************************************************** * Name: xbee_set_saddr * * Description: * Sends API frame with AT command request in order to set the Short Address * (Source Address (MY)) of the device * ****************************************************************************/ void xbee_set_saddr(FAR struct xbee_priv_s *priv, FAR const uint8_t *saddr) { uint8_t frame[10]; IEEE802154_SADDRCOPY(priv->addr.saddr, saddr); frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 6; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'M'; frame[6] = 'Y'; frame[7] = *(saddr + 1); frame[8] = *(saddr); xbee_insert_checksum(frame, 10); xbee_send_apiframe(priv, frame, 10); } /**************************************************************************** * Name: xbee_set_chan * * Description: * Sends API frame with AT command request in order to set the RF channel * (Operatin Channel) of the device. * ****************************************************************************/ void xbee_set_chan(FAR struct xbee_priv_s *priv, uint8_t chan) { uint8_t frame[9]; priv->chan = chan; frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 5; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'C'; frame[6] = 'H'; frame[7] = chan; xbee_insert_checksum(frame, 9); xbee_send_apiframe(priv, frame, 9); } /**************************************************************************** * Name: xbee_set_powerlevel * * Description: * Sends API frame with AT command request in order to set the RF power level * of the device. * ****************************************************************************/ void xbee_set_powerlevel(FAR struct xbee_priv_s *priv, uint8_t level) { uint8_t frame[9]; priv->pwrlvl = level; frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 5; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'P'; frame[6] = 'L'; frame[7] = level; xbee_insert_checksum(frame, 9); xbee_send_apiframe(priv, frame, 9); } /**************************************************************************** * Name: xbee_set_boostmode * * Description: * Sends API frame with AT command request in order to set the Boost mode * setting of the device. NOTE: XBee Pro always enables boost mode and * does not support this command * ****************************************************************************/ void xbee_set_boostmode(FAR struct xbee_priv_s *priv, uint8_t mode) { uint8_t frame[9]; if (mode > 1) return; priv->boostmode = mode; frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 5; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'P'; frame[6] = 'M'; frame[7] = mode; xbee_insert_checksum(frame, 9); xbee_send_apiframe(priv, frame, 9); } /**************************************************************************** * Name: xbee_set_epassocflags * * Description: * Set flags in 'A1' command register to determine how endpoint behaves * with regards to association. * ****************************************************************************/ void xbee_set_epassocflags(FAR struct xbee_priv_s *priv, uint8_t flags) { uint8_t frame[9]; frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 5; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'A'; frame[6] = '1'; frame[7] = flags; xbee_insert_checksum(frame, 9); xbee_send_apiframe(priv, frame, 9); } /**************************************************************************** * Name: xbee_set_coordassocflags * * Description: * Set flags in 'A2' command register to determine how coordinator behaves * with regards to association. * ****************************************************************************/ void xbee_set_coordassocflags(FAR struct xbee_priv_s *priv, uint8_t flags) { uint8_t frame[9]; frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 5; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'A'; frame[6] = '2'; frame[7] = flags; xbee_insert_checksum(frame, 9); xbee_send_apiframe(priv, frame, 9); } /**************************************************************************** * Name: xbee_set_sleepperiod * * Description: * Set Cyclic Sleep Period using 'SP' AT command. * ****************************************************************************/ void xbee_set_sleepperiod(FAR struct xbee_priv_s *priv, uint16_t period) { uint8_t frame[10]; frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 6; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'S'; frame[6] = 'P'; frame[7] = period >> 8; frame[8] = period; xbee_insert_checksum(frame, 10); xbee_send_apiframe(priv, frame, 10); } /**************************************************************************** * Name: xbee_enable_coord * * Description: * Enables/Disables coordinator mode using 'CE' command * ****************************************************************************/ void xbee_enable_coord(FAR struct xbee_priv_s *priv, bool enable) { uint8_t frame[9]; frame[0] = XBEE_STARTBYTE; frame[1] = 0; frame[2] = 5; frame[3] = XBEE_APIFRAME_ATCOMMMAND; frame[4] = 0; frame[5] = 'C'; frame[6] = 'E'; frame[7] = enable; xbee_insert_checksum(frame, 9); xbee_send_apiframe(priv, frame, 9); } /**************************************************************************** * Name: xbee_regdump * * Description: * Perform a series of queries updating struct and printing settings to SYSLOG. * ****************************************************************************/ void xbee_regdump(FAR struct xbee_priv_s *priv) { xbee_query_firmwareversion(priv); wlinfo("XBee Firmware Version: %04x\n", priv->firmwareversion); xbee_send_atquery(priv, "CE"); xbee_send_atquery(priv, "A1"); xbee_send_atquery(priv, "A2"); xbee_send_atquery(priv, "AI"); xbee_send_atquery(priv, "SP"); xbee_send_atquery(priv, "RR"); xbee_send_atquery(priv, "MM"); xbee_send_atquery(priv, "PL"); xbee_send_atquery(priv, "PM"); }