500 lines
11 KiB
C
500 lines
11 KiB
C
/* ieee802154_nrf5.c - nRF5 802.15.4 driver */
|
|
|
|
/*
|
|
* Copyright (c) 2017 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define LOG_MODULE_NAME ieee802154_nrf5
|
|
#define LOG_LEVEL CONFIG_IEEE802154_DRIVER_LOG_LEVEL
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
|
|
|
|
#include <errno.h>
|
|
|
|
#include <kernel.h>
|
|
#include <arch/cpu.h>
|
|
|
|
#include <soc.h>
|
|
#include <device.h>
|
|
#include <init.h>
|
|
#include <net/net_if.h>
|
|
#include <net/net_pkt.h>
|
|
|
|
#if defined(CONFIG_NET_L2_OPENTHREAD)
|
|
#include <net/openthread.h>
|
|
#endif
|
|
|
|
#include <misc/byteorder.h>
|
|
#include <string.h>
|
|
#include <random/rand32.h>
|
|
|
|
#include <net/ieee802154_radio.h>
|
|
|
|
#include "nrf52840.h"
|
|
#include "ieee802154_nrf5.h"
|
|
#include "nrf_802154.h"
|
|
|
|
struct nrf5_802154_config {
|
|
void (*irq_config_func)(struct device *dev);
|
|
};
|
|
|
|
static struct nrf5_802154_data nrf5_data;
|
|
|
|
#define ACK_TIMEOUT K_MSEC(10)
|
|
|
|
/* Convenience defines for RADIO */
|
|
#define NRF5_802154_DATA(dev) \
|
|
((struct nrf5_802154_data * const)(dev)->driver_data)
|
|
|
|
#define NRF5_802154_CFG(dev) \
|
|
((struct nrf5_802154_config * const)(dev)->config->config_info)
|
|
|
|
static void nrf5_get_eui64(u8_t *mac)
|
|
{
|
|
memcpy(mac, (const u32_t *)&NRF_FICR->DEVICEID, 8);
|
|
}
|
|
|
|
static void nrf5_rx_thread(void *arg1, void *arg2, void *arg3)
|
|
{
|
|
struct device *dev = (struct device *)arg1;
|
|
struct nrf5_802154_data *nrf5_radio = NRF5_802154_DATA(dev);
|
|
struct net_pkt *pkt;
|
|
struct nrf5_802154_rx_frame *rx_frame;
|
|
u8_t pkt_len;
|
|
|
|
ARG_UNUSED(arg2);
|
|
ARG_UNUSED(arg3);
|
|
|
|
while (1) {
|
|
pkt = NULL;
|
|
rx_frame = NULL;
|
|
|
|
LOG_DBG("Waiting for frame");
|
|
|
|
rx_frame = k_fifo_get(&nrf5_radio->rx_fifo, K_FOREVER);
|
|
|
|
__ASSERT_NO_MSG(rx_frame->psdu);
|
|
|
|
/* rx_mpdu contains length, psdu, fcs|lqi
|
|
* The last 2 bytes contain LQI or FCS, depending if
|
|
* automatic CRC handling is enabled or not, respectively.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_IEEE802154_RAW_MODE) ||
|
|
IS_ENABLED(CONFIG_NET_L2_OPENTHREAD)) {
|
|
pkt_len = rx_frame->psdu[0];
|
|
} else {
|
|
pkt_len = rx_frame->psdu[0] - NRF5_FCS_LENGTH;
|
|
}
|
|
|
|
__ASSERT_NO_MSG(pkt_len <= CONFIG_NET_BUF_DATA_SIZE);
|
|
|
|
LOG_DBG("Frame received");
|
|
|
|
pkt = net_pkt_alloc_with_buffer(nrf5_radio->iface, pkt_len,
|
|
AF_UNSPEC, 0, K_NO_WAIT);
|
|
if (!pkt) {
|
|
LOG_ERR("No pkt available");
|
|
goto drop;
|
|
}
|
|
|
|
if (net_pkt_write(pkt, rx_frame->psdu + 1, pkt_len)) {
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_set_ieee802154_lqi(pkt, rx_frame->lqi);
|
|
net_pkt_set_ieee802154_rssi(pkt, rx_frame->rssi);
|
|
|
|
LOG_DBG("Caught a packet (%u) (LQI: %u)",
|
|
pkt_len, rx_frame->lqi);
|
|
|
|
if (net_recv_data(nrf5_radio->iface, pkt) < 0) {
|
|
LOG_ERR("Packet dropped by NET stack");
|
|
goto drop;
|
|
}
|
|
|
|
nrf_802154_buffer_free_raw(rx_frame->psdu);
|
|
rx_frame->psdu = NULL;
|
|
|
|
if (CONFIG_IEEE802154_DRIVER_LOG_LEVEL >= LOG_LEVEL_DBG) {
|
|
net_analyze_stack(
|
|
"nRF5 rx stack",
|
|
K_THREAD_STACK_BUFFER(nrf5_radio->rx_stack),
|
|
K_THREAD_STACK_SIZEOF(nrf5_radio->rx_stack));
|
|
}
|
|
|
|
continue;
|
|
|
|
drop:
|
|
nrf_802154_buffer_free_raw(rx_frame->psdu);
|
|
rx_frame->psdu = NULL;
|
|
|
|
if (pkt) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Radio device API */
|
|
|
|
static enum ieee802154_hw_caps nrf5_get_capabilities(struct device *dev)
|
|
{
|
|
return IEEE802154_HW_FCS | IEEE802154_HW_2_4_GHZ |
|
|
IEEE802154_HW_TX_RX_ACK | IEEE802154_HW_FILTER;
|
|
}
|
|
|
|
|
|
static int nrf5_cca(struct device *dev)
|
|
{
|
|
struct nrf5_802154_data *nrf5_radio = NRF5_802154_DATA(dev);
|
|
|
|
if (!nrf_802154_cca()) {
|
|
LOG_DBG("CCA failed");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* The nRF driver guarantees that a callback will be called once
|
|
* the CCA function is done, thus unlocking the semaphore.
|
|
*/
|
|
k_sem_take(&nrf5_radio->cca_wait, K_FOREVER);
|
|
|
|
LOG_DBG("Channel free? %d", nrf5_radio->channel_free);
|
|
|
|
return nrf5_radio->channel_free ? 0 : -EBUSY;
|
|
}
|
|
|
|
static int nrf5_set_channel(struct device *dev, u16_t channel)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
LOG_DBG("%u", channel);
|
|
|
|
if (channel < 11 || channel > 26) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
nrf_802154_channel_set(channel);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nrf5_set_pan_id(struct device *dev, u16_t pan_id)
|
|
{
|
|
u8_t pan_id_le[2];
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
sys_put_le16(pan_id, pan_id_le);
|
|
nrf_802154_pan_id_set(pan_id_le);
|
|
|
|
LOG_DBG("0x%x", pan_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nrf5_set_short_addr(struct device *dev, u16_t short_addr)
|
|
{
|
|
u8_t short_addr_le[2];
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
sys_put_le16(short_addr, short_addr_le);
|
|
nrf_802154_short_address_set(short_addr_le);
|
|
|
|
LOG_DBG("0x%x", short_addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nrf5_set_ieee_addr(struct device *dev, const u8_t *ieee_addr)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
LOG_DBG("IEEE address %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
|
|
ieee_addr[7], ieee_addr[6], ieee_addr[5], ieee_addr[4],
|
|
ieee_addr[3], ieee_addr[2], ieee_addr[1], ieee_addr[0]);
|
|
|
|
nrf_802154_extended_address_set(ieee_addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nrf5_filter(struct device *dev, bool set,
|
|
enum ieee802154_filter_type type,
|
|
const struct ieee802154_filter *filter)
|
|
{
|
|
LOG_DBG("Applying filter %u", type);
|
|
|
|
if (!set) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (type == IEEE802154_FILTER_TYPE_IEEE_ADDR) {
|
|
return nrf5_set_ieee_addr(dev, filter->ieee_addr);
|
|
} else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) {
|
|
return nrf5_set_short_addr(dev, filter->short_addr);
|
|
} else if (type == IEEE802154_FILTER_TYPE_PAN_ID) {
|
|
return nrf5_set_pan_id(dev, filter->pan_id);
|
|
}
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int nrf5_set_txpower(struct device *dev, s16_t dbm)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
LOG_DBG("%d", dbm);
|
|
|
|
nrf_802154_tx_power_set(dbm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nrf5_tx(struct device *dev,
|
|
struct net_pkt *pkt,
|
|
struct net_buf *frag)
|
|
{
|
|
struct nrf5_802154_data *nrf5_radio = NRF5_802154_DATA(dev);
|
|
u8_t payload_len = frag->len;
|
|
u8_t *payload = frag->data;
|
|
|
|
LOG_DBG("%p (%u)", payload, payload_len);
|
|
|
|
nrf5_radio->tx_psdu[0] = payload_len + NRF5_FCS_LENGTH;
|
|
memcpy(nrf5_radio->tx_psdu + 1, payload, payload_len);
|
|
|
|
/* Reset semaphore in case ACK was received after timeout */
|
|
k_sem_reset(&nrf5_radio->tx_wait);
|
|
|
|
if (!nrf_802154_transmit_raw(nrf5_radio->tx_psdu, false)) {
|
|
LOG_ERR("Cannot send frame");
|
|
return -EIO;
|
|
}
|
|
|
|
LOG_DBG("Sending frame (ch:%d, txpower:%d)",
|
|
nrf_802154_channel_get(), nrf_802154_tx_power_get());
|
|
|
|
/* Wait for ack to be received */
|
|
if (k_sem_take(&nrf5_radio->tx_wait, ACK_TIMEOUT)) {
|
|
LOG_DBG("ACK not received");
|
|
|
|
if (!nrf_802154_receive()) {
|
|
LOG_ERR("Failed to switch back to receive state");
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
LOG_DBG("Result: %d", nrf5_data.tx_result);
|
|
|
|
if (nrf5_radio->tx_result == NRF_802154_TX_ERROR_NONE) {
|
|
/* ACK frame not used currently. */
|
|
if (nrf5_radio->ack != NULL) {
|
|
nrf_802154_buffer_free_raw(nrf5_radio->ack);
|
|
}
|
|
|
|
nrf5_radio->ack = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
static int nrf5_start(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
if (!nrf_802154_receive()) {
|
|
LOG_ERR("Failed to enter receive state");
|
|
return -EIO;
|
|
}
|
|
|
|
LOG_DBG("nRF5 802154 radio started (channel: %d)",
|
|
nrf_802154_channel_get());
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nrf5_stop(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
if (!nrf_802154_sleep()) {
|
|
LOG_ERR("Error while stopping radio");
|
|
return -EIO;
|
|
}
|
|
|
|
LOG_DBG("nRF5 802154 radio stopped");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nrf5_radio_irq(void *arg)
|
|
{
|
|
ARG_UNUSED(arg);
|
|
|
|
nrf_802154_radio_irq_handler();
|
|
}
|
|
|
|
static void nrf5_config(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
IRQ_CONNECT(NRF5_IRQ_RADIO_IRQn, NRF_802154_IRQ_PRIORITY,
|
|
nrf5_radio_irq, NULL, 0);
|
|
irq_enable(NRF5_IRQ_RADIO_IRQn);
|
|
}
|
|
|
|
static int nrf5_init(struct device *dev)
|
|
{
|
|
const struct nrf5_802154_config *nrf5_radio_cfg = NRF5_802154_CFG(dev);
|
|
struct nrf5_802154_data *nrf5_radio = NRF5_802154_DATA(dev);
|
|
|
|
k_fifo_init(&nrf5_radio->rx_fifo);
|
|
k_sem_init(&nrf5_radio->tx_wait, 0, 1);
|
|
k_sem_init(&nrf5_radio->cca_wait, 0, 1);
|
|
|
|
nrf_802154_init();
|
|
|
|
nrf5_radio_cfg->irq_config_func(dev);
|
|
|
|
k_thread_create(&nrf5_radio->rx_thread, nrf5_radio->rx_stack,
|
|
CONFIG_IEEE802154_NRF5_RX_STACK_SIZE,
|
|
nrf5_rx_thread, dev, NULL, NULL,
|
|
K_PRIO_COOP(2), 0, 0);
|
|
|
|
k_thread_name_set(&nrf5_radio->rx_thread, "802154 RX");
|
|
|
|
LOG_INF("nRF5 802154 radio initialized");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void nrf5_iface_init(struct net_if *iface)
|
|
{
|
|
struct device *dev = net_if_get_device(iface);
|
|
struct nrf5_802154_data *nrf5_radio = NRF5_802154_DATA(dev);
|
|
|
|
nrf5_get_eui64(nrf5_radio->mac);
|
|
net_if_set_link_addr(iface, nrf5_radio->mac, sizeof(nrf5_radio->mac),
|
|
NET_LINK_IEEE802154);
|
|
|
|
nrf5_radio->iface = iface;
|
|
|
|
ieee802154_init(iface);
|
|
}
|
|
|
|
/* nRF5 radio driver callbacks */
|
|
|
|
void nrf_802154_received_raw(uint8_t *data, int8_t power, uint8_t lqi)
|
|
{
|
|
for (u32_t i = 0; i < ARRAY_SIZE(nrf5_data.rx_frames); i++) {
|
|
if (nrf5_data.rx_frames[i].psdu != NULL) {
|
|
continue;
|
|
}
|
|
|
|
nrf5_data.rx_frames[i].psdu = data;
|
|
nrf5_data.rx_frames[i].rssi = power;
|
|
nrf5_data.rx_frames[i].lqi = lqi;
|
|
|
|
k_fifo_put(&nrf5_data.rx_fifo, &nrf5_data.rx_frames[i]);
|
|
|
|
return;
|
|
}
|
|
|
|
__ASSERT(false, "Not enough rx frames allocated for 15.4 driver");
|
|
}
|
|
|
|
void nrf_802154_receive_failed(nrf_802154_rx_error_t error)
|
|
{
|
|
/* Intentionally empty. */
|
|
}
|
|
|
|
void nrf_802154_transmitted_raw(const uint8_t *frame, uint8_t *ack,
|
|
int8_t power, uint8_t lqi)
|
|
{
|
|
ARG_UNUSED(frame);
|
|
ARG_UNUSED(power);
|
|
ARG_UNUSED(lqi);
|
|
|
|
nrf5_data.tx_result = NRF_802154_TX_ERROR_NONE;
|
|
nrf5_data.ack = ack;
|
|
|
|
k_sem_give(&nrf5_data.tx_wait);
|
|
}
|
|
|
|
void nrf_802154_transmit_failed(const uint8_t *frame,
|
|
nrf_802154_tx_error_t error)
|
|
{
|
|
ARG_UNUSED(frame);
|
|
|
|
nrf5_data.tx_result = error;
|
|
|
|
k_sem_give(&nrf5_data.tx_wait);
|
|
}
|
|
|
|
void nrf_802154_cca_done(bool channel_free)
|
|
{
|
|
nrf5_data.channel_free = channel_free;
|
|
|
|
k_sem_give(&nrf5_data.cca_wait);
|
|
}
|
|
|
|
void nrf_802154_cca_failed(nrf_802154_cca_error_t error)
|
|
{
|
|
ARG_UNUSED(error);
|
|
|
|
nrf5_data.channel_free = false;
|
|
|
|
k_sem_give(&nrf5_data.cca_wait);
|
|
}
|
|
|
|
static const struct nrf5_802154_config nrf5_radio_cfg = {
|
|
.irq_config_func = nrf5_config,
|
|
};
|
|
|
|
static struct ieee802154_radio_api nrf5_radio_api = {
|
|
.iface_api.init = nrf5_iface_init,
|
|
|
|
.get_capabilities = nrf5_get_capabilities,
|
|
.cca = nrf5_cca,
|
|
.set_channel = nrf5_set_channel,
|
|
.filter = nrf5_filter,
|
|
.set_txpower = nrf5_set_txpower,
|
|
.start = nrf5_start,
|
|
.stop = nrf5_stop,
|
|
.tx = nrf5_tx,
|
|
};
|
|
|
|
#if defined(CONFIG_NET_L2_IEEE802154)
|
|
#define L2 IEEE802154_L2
|
|
#define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(IEEE802154_L2)
|
|
#define MTU 125
|
|
#elif defined(CONFIG_NET_L2_OPENTHREAD)
|
|
#define L2 OPENTHREAD_L2
|
|
#define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(OPENTHREAD_L2)
|
|
#define MTU 1280
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_L2_IEEE802154) || defined(CONFIG_NET_L2_OPENTHREAD)
|
|
NET_DEVICE_INIT(nrf5_154_radio, CONFIG_IEEE802154_NRF5_DRV_NAME,
|
|
nrf5_init, &nrf5_data, &nrf5_radio_cfg,
|
|
CONFIG_IEEE802154_NRF5_INIT_PRIO,
|
|
&nrf5_radio_api, L2,
|
|
L2_CTX_TYPE, MTU);
|
|
|
|
NET_STACK_INFO_ADDR(RX, nrf5_154_radio,
|
|
CONFIG_IEEE802154_NRF5_RX_STACK_SIZE,
|
|
CONFIG_IEEE802154_NRF5_RX_STACK_SIZE,
|
|
nrf5_data.rx_stack, 0);
|
|
#else
|
|
DEVICE_AND_API_INIT(nrf5_154_radio, CONFIG_IEEE802154_NRF5_DRV_NAME,
|
|
nrf5_init, &nrf5_data, &nrf5_radio_cfg,
|
|
POST_KERNEL, CONFIG_IEEE802154_NRF5_INIT_PRIO,
|
|
&nrf5_radio_api);
|
|
#endif
|