zephyr/drivers/ieee802154/ieee802154_cc2520.c
Andrew Boie 4e5c093e66 kernel: demote K_THREAD_STACK_BUFFER() to private
This macro is slated for complete removal, as it's not possible
on arches with an MPU stack guard to know the true buffer bounds
without also knowing the runtime state of its associated thread.

As removing this completely would be invasive to where we are
in the 1.14 release, demote to a private kernel Z_ API instead.
The current way that the macro is being used internally will
not cause any undue harm, we just don't want any external code
depending on it.

The final work to remove this (and overhaul stack specification in
general) will take place in 1.15 in the context of #14269

Fixes: #14766

Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
2019-04-05 16:10:02 -04:00

1425 lines
31 KiB
C

/* ieee802154_cc2520.c - TI CC2520 driver */
/*
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_MODULE_NAME ieee802154_cc2520
#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 <device.h>
#include <init.h>
#include <net/net_if.h>
#include <net/net_pkt.h>
#include <misc/byteorder.h>
#include <string.h>
#include <random/rand32.h>
#include <gpio.h>
#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
#include <crypto/cipher.h>
#include <crypto/cipher_structs.h>
#endif /* CONFIG_IEEE802154_CC2520_CRYPTO */
#include <net/ieee802154_radio.h>
#include "ieee802154_cc2520.h"
/**
* Content is split as follows:
* 1 - Debug related functions
* 2 - Generic helper functions (for any parts)
* 3 - GPIO related functions
* 4 - TX related helper functions
* 5 - RX related helper functions
* 6 - Radio device API functions
* 7 - Legacy radio device API functions
* 8 - Initialization
*/
#define CC2520_AUTOMATISM (FRMCTRL0_AUTOCRC | FRMCTRL0_AUTOACK)
#define CC2520_TX_THRESHOLD (0x7F)
#define CC2520_FCS_LENGTH (2)
#if defined(CONFIG_IEEE802154_CC2520_GPIO_SPI_CS)
static struct spi_cs_control cs_ctrl;
#endif
/*********
* DEBUG *
********/
#if LOG_LEVEL == LOG_LEVEL_DBG
static inline void cc2520_print_gpio_config(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
LOG_DBG("GPIOCTRL0/1/2/3/4/5 = 0x%x/0x%x/0x%x/0x%x/0x%x/0x%x",
read_reg_gpioctrl0(cc2520),
read_reg_gpioctrl1(cc2520),
read_reg_gpioctrl2(cc2520),
read_reg_gpioctrl3(cc2520),
read_reg_gpioctrl4(cc2520),
read_reg_gpioctrl5(cc2520));
LOG_DBG("GPIOPOLARITY: 0x%x",
read_reg_gpiopolarity(cc2520));
LOG_DBG("GPIOCTRL: 0x%x",
read_reg_gpioctrl(cc2520));
}
static inline void cc2520_print_exceptions(struct cc2520_context *cc2520)
{
u8_t flag = read_reg_excflag0(cc2520);
LOG_DBG("EXCFLAG0:");
if (flag & EXCFLAG0_RF_IDLE) {
LOG_DBG(" RF_IDLE");
}
if (flag & EXCFLAG0_TX_FRM_DONE) {
LOG_DBG(" TX_FRM_DONE");
}
if (flag & EXCFLAG0_TX_ACK_DONE) {
LOG_DBG(" TX_ACK_DONE");
}
if (flag & EXCFLAG0_TX_UNDERFLOW) {
LOG_DBG(" TX_UNDERFLOW");
}
if (flag & EXCFLAG0_TX_OVERFLOW) {
LOG_DBG(" TX_OVERFLOW");
}
if (flag & EXCFLAG0_RX_UNDERFLOW) {
LOG_DBG(" RX_UNDERFLOW");
}
if (flag & EXCFLAG0_RX_OVERFLOW) {
LOG_DBG(" RX_OVERFLOW");
}
if (flag & EXCFLAG0_RXENABLE_ZERO) {
LOG_DBG(" RXENABLE_ZERO");
}
flag = read_reg_excflag1(cc2520);
LOG_DBG("EXCFLAG1:");
if (flag & EXCFLAG1_RX_FRM_DONE) {
LOG_DBG(" RX_FRM_DONE");
}
if (flag & EXCFLAG1_RX_FRM_ACCEPTED) {
LOG_DBG(" RX_FRM_ACCEPTED");
}
if (flag & EXCFLAG1_SRC_MATCH_DONE) {
LOG_DBG(" SRC_MATCH_DONE");
}
if (flag & EXCFLAG1_SRC_MATCH_FOUND) {
LOG_DBG(" SRC_MATCH_FOUND");
}
if (flag & EXCFLAG1_FIFOP) {
LOG_DBG(" FIFOP");
}
if (flag & EXCFLAG1_SFD) {
LOG_DBG(" SFD");
}
if (flag & EXCFLAG1_DPU_DONE_L) {
LOG_DBG(" DPU_DONE_L");
}
if (flag & EXCFLAG1_DPU_DONE_H) {
LOG_DBG(" DPU_DONE_H");
}
}
static inline void cc2520_print_errors(struct cc2520_context *cc2520)
{
u8_t flag = read_reg_excflag2(cc2520);
LOG_DBG("EXCFLAG2:");
if (flag & EXCFLAG2_MEMADDR_ERROR) {
LOG_DBG(" MEMADDR_ERROR");
}
if (flag & EXCFLAG2_USAGE_ERROR) {
LOG_DBG(" USAGE_ERROR");
}
if (flag & EXCFLAG2_OPERAND_ERROR) {
LOG_DBG(" OPERAND_ERROR");
}
if (flag & EXCFLAG2_SPI_ERROR) {
LOG_DBG(" SPI_ERROR");
}
if (flag & EXCFLAG2_RF_NO_LOCK) {
LOG_DBG(" RF_NO_LOCK");
}
if (flag & EXCFLAG2_RX_FRM_ABORTED) {
LOG_DBG(" RX_FRM_ABORTED");
}
if (flag & EXCFLAG2_RFBUFMOV_TIMEOUT) {
LOG_DBG(" RFBUFMOV_TIMEOUT");
}
}
#else
#define cc2520_print_gpio_config(...)
#define cc2520_print_exceptions(...)
#define cc2520_print_errors(...)
#endif /* LOG_LEVEL == LOG_LEVEL_DBG */
/*********************
* Generic functions *
********************/
#define z_usleep(usec) k_busy_wait(usec)
bool z_cc2520_access(struct cc2520_context *ctx, bool read, u8_t ins,
u16_t addr, void *data, size_t length)
{
u8_t cmd_buf[2];
struct spi_buf buf[2] = {
{
.buf = cmd_buf,
.len = 1,
},
{
.buf = data,
.len = length,
}
};
struct spi_buf_set tx = {
.buffers = buf,
};
cmd_buf[0] = ins;
if (ins == CC2520_INS_MEMRD || ins == CC2520_INS_MEMWR) {
buf[0].len = 2;
cmd_buf[0] |= (u8_t)(addr >> 8);
cmd_buf[1] = (u8_t)(addr & 0xff);
} else if (ins == CC2520_INS_REGRD || ins == CC2520_INS_REGWR) {
cmd_buf[0] |= (u8_t)(addr & 0xff);
}
if (read) {
const struct spi_buf_set rx = {
.buffers = buf,
.count = 2
};
tx.count = 1;
return (spi_transceive(ctx->spi, &ctx->spi_cfg, &tx, &rx) == 0);
}
tx.count = data ? 2 : 1;
return (spi_write(ctx->spi, &ctx->spi_cfg, &tx) == 0);
}
static inline u8_t cc2520_status(struct cc2520_context *ctx)
{
u8_t status;
if (z_cc2520_access(ctx, true, CC2520_INS_SNOP, 0, &status, 1)) {
return status;
}
return 0;
}
static bool verify_osc_stabilization(struct cc2520_context *cc2520)
{
u8_t timeout = 100U;
u8_t status;
do {
status = cc2520_status(cc2520);
z_usleep(1);
timeout--;
} while (!(status & CC2520_STATUS_XOSC_STABLE_N_RUNNING) && timeout);
return !!(status & CC2520_STATUS_XOSC_STABLE_N_RUNNING);
}
static inline u8_t *get_mac(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
#if defined(CONFIG_IEEE802154_CC2520_RANDOM_MAC)
u32_t *ptr = (u32_t *)(cc2520->mac_addr + 4);
UNALIGNED_PUT(sys_rand32_get(), ptr);
cc2520->mac_addr[7] = (cc2520->mac_addr[7] & ~0x01) | 0x02;
#else
cc2520->mac_addr[4] = CONFIG_IEEE802154_CC2520_MAC4;
cc2520->mac_addr[5] = CONFIG_IEEE802154_CC2520_MAC5;
cc2520->mac_addr[6] = CONFIG_IEEE802154_CC2520_MAC6;
cc2520->mac_addr[7] = CONFIG_IEEE802154_CC2520_MAC7;
#endif
cc2520->mac_addr[0] = 0x00;
cc2520->mac_addr[1] = 0x12;
cc2520->mac_addr[2] = 0x4b;
cc2520->mac_addr[3] = 0x00;
return cc2520->mac_addr;
}
static int cc2520_set_pan_id(struct device *dev, u16_t pan_id)
{
struct cc2520_context *cc2520 = dev->driver_data;
LOG_DBG("0x%x", pan_id);
pan_id = sys_le16_to_cpu(pan_id);
if (!write_mem_pan_id(cc2520, (u8_t *) &pan_id)) {
LOG_ERR("Failed");
return -EIO;
}
return 0;
}
static int cc2520_set_short_addr(struct device *dev, u16_t short_addr)
{
struct cc2520_context *cc2520 = dev->driver_data;
LOG_DBG("0x%x", short_addr);
short_addr = sys_le16_to_cpu(short_addr);
if (!write_mem_short_addr(cc2520, (u8_t *) &short_addr)) {
LOG_ERR("Failed");
return -EIO;
}
return 0;
}
static int cc2520_set_ieee_addr(struct device *dev, const u8_t *ieee_addr)
{
struct cc2520_context *cc2520 = dev->driver_data;
if (!write_mem_ext_addr(cc2520, (void *)ieee_addr)) {
LOG_ERR("Failed");
return -EIO;
}
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]);
return 0;
}
/******************
* GPIO functions *
*****************/
static inline void set_reset(struct device *dev, u32_t value)
{
struct cc2520_context *cc2520 = dev->driver_data;
gpio_pin_write(cc2520->gpios[CC2520_GPIO_IDX_RESET].dev,
cc2520->gpios[CC2520_GPIO_IDX_RESET].pin, value);
}
static inline void set_vreg_en(struct device *dev, u32_t value)
{
struct cc2520_context *cc2520 = dev->driver_data;
gpio_pin_write(cc2520->gpios[CC2520_GPIO_IDX_VREG_EN].dev,
cc2520->gpios[CC2520_GPIO_IDX_VREG_EN].pin, value);
}
static inline u32_t get_fifo(struct cc2520_context *cc2520)
{
u32_t pin_value;
gpio_pin_read(cc2520->gpios[CC2520_GPIO_IDX_FIFO].dev,
cc2520->gpios[CC2520_GPIO_IDX_FIFO].pin, &pin_value);
return pin_value;
}
static inline u32_t get_fifop(struct cc2520_context *cc2520)
{
u32_t pin_value;
gpio_pin_read(cc2520->gpios[CC2520_GPIO_IDX_FIFOP].dev,
cc2520->gpios[CC2520_GPIO_IDX_FIFOP].pin, &pin_value);
return pin_value;
}
static inline u32_t get_cca(struct cc2520_context *cc2520)
{
u32_t pin_value;
gpio_pin_read(cc2520->gpios[CC2520_GPIO_IDX_CCA].dev,
cc2520->gpios[CC2520_GPIO_IDX_CCA].pin, &pin_value);
return pin_value;
}
static inline void sfd_int_handler(struct device *port,
struct gpio_callback *cb, u32_t pins)
{
struct cc2520_context *cc2520 =
CONTAINER_OF(cb, struct cc2520_context, sfd_cb);
if (atomic_get(&cc2520->tx) == 1) {
atomic_set(&cc2520->tx, 0);
k_sem_give(&cc2520->tx_sync);
}
}
static inline void fifop_int_handler(struct device *port,
struct gpio_callback *cb, u32_t pins)
{
struct cc2520_context *cc2520 =
CONTAINER_OF(cb, struct cc2520_context, fifop_cb);
/* Note: Errata document - 1.2 */
if (!get_fifop(cc2520) && !get_fifop(cc2520)) {
return;
}
if (!get_fifo(cc2520)) {
cc2520->overflow = true;
}
k_sem_give(&cc2520->rx_lock);
}
static void enable_fifop_interrupt(struct cc2520_context *cc2520,
bool enable)
{
if (enable) {
gpio_pin_enable_callback(
cc2520->gpios[CC2520_GPIO_IDX_FIFOP].dev,
cc2520->gpios[CC2520_GPIO_IDX_FIFOP].pin);
} else {
gpio_pin_disable_callback(
cc2520->gpios[CC2520_GPIO_IDX_FIFOP].dev,
cc2520->gpios[CC2520_GPIO_IDX_FIFOP].pin);
}
}
static void enable_sfd_interrupt(struct cc2520_context *cc2520,
bool enable)
{
if (enable) {
gpio_pin_enable_callback(
cc2520->gpios[CC2520_GPIO_IDX_SFD].dev,
cc2520->gpios[CC2520_GPIO_IDX_SFD].pin);
} else {
gpio_pin_disable_callback(
cc2520->gpios[CC2520_GPIO_IDX_SFD].dev,
cc2520->gpios[CC2520_GPIO_IDX_SFD].pin);
}
}
static inline void setup_gpio_callbacks(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
gpio_init_callback(&cc2520->sfd_cb, sfd_int_handler,
BIT(cc2520->gpios[CC2520_GPIO_IDX_SFD].pin));
gpio_add_callback(cc2520->gpios[CC2520_GPIO_IDX_SFD].dev,
&cc2520->sfd_cb);
gpio_init_callback(&cc2520->fifop_cb, fifop_int_handler,
BIT(cc2520->gpios[CC2520_GPIO_IDX_FIFOP].pin));
gpio_add_callback(cc2520->gpios[CC2520_GPIO_IDX_FIFOP].dev,
&cc2520->fifop_cb);
}
/****************
* TX functions *
***************/
static inline bool write_txfifo_length(struct cc2520_context *ctx, u8_t len)
{
u8_t length = len + CC2520_FCS_LENGTH;
return z_cc2520_access(ctx, false, CC2520_INS_TXBUF, 0, &length, 1);
}
static inline bool write_txfifo_content(struct cc2520_context *ctx,
u8_t *frame, u8_t len)
{
return z_cc2520_access(ctx, false, CC2520_INS_TXBUF, 0, frame, len);
}
static inline bool verify_txfifo_status(struct cc2520_context *cc2520,
u8_t len)
{
if (read_reg_txfifocnt(cc2520) < len ||
(read_reg_excflag0(cc2520) & EXCFLAG0_TX_UNDERFLOW)) {
return false;
}
return true;
}
static inline bool verify_tx_done(struct cc2520_context *cc2520)
{
u8_t timeout = 10U;
u8_t status;
do {
z_usleep(1);
timeout--;
status = read_reg_excflag0(cc2520);
} while (!(status & EXCFLAG0_TX_FRM_DONE) && timeout);
return !!(status & EXCFLAG0_TX_FRM_DONE);
}
/****************
* RX functions *
***************/
static inline void flush_rxfifo(struct cc2520_context *cc2520)
{
/* Note: Errata document - 1.1 */
enable_fifop_interrupt(cc2520, false);
instruct_sflushrx(cc2520);
instruct_sflushrx(cc2520);
enable_fifop_interrupt(cc2520, true);
write_reg_excflag0(cc2520, EXCFLAG0_RESET_RX_FLAGS);
}
static inline u8_t read_rxfifo_length(struct cc2520_context *ctx)
{
u8_t len;
if (z_cc2520_access(ctx, true, CC2520_INS_RXBUF, 0, &len, 1)) {
return len;
}
return 0;
}
static inline bool read_rxfifo_content(struct cc2520_context *ctx,
struct net_buf *buf, u8_t len)
{
if (!z_cc2520_access(ctx, true, CC2520_INS_RXBUF, 0, buf->data, len)) {
return false;
}
if (read_reg_excflag0(ctx) & EXCFLAG0_RX_UNDERFLOW) {
LOG_ERR("RX underflow!");
return false;
}
net_buf_add(buf, len);
return true;
}
static inline void insert_radio_noise_details(struct net_pkt *pkt, u8_t *buf)
{
u8_t lqi;
net_pkt_set_ieee802154_rssi(pkt, buf[0]);
/**
* CC2520 does not provide an LQI but a correlation factor.
* See Section 20.6
* Such calculation can be loosely used to transform it to lqi:
* corr <= 50 ? lqi = 0
* or:
* corr >= 110 ? lqi = 255
* else:
* lqi = (lqi - 50) * 4
*/
lqi = buf[1] & CC2520_FCS_CORRELATION;
if (lqi <= 50U) {
lqi = 0U;
} else if (lqi >= 110U) {
lqi = 255U;
} else {
lqi = (lqi - 50U) << 2;
}
net_pkt_set_ieee802154_lqi(pkt, lqi);
}
static inline bool verify_crc(struct cc2520_context *ctx, struct net_pkt *pkt)
{
u8_t fcs[2];
if (!z_cc2520_access(ctx, true, CC2520_INS_RXBUF, 0, &fcs, 2)) {
return false;
}
if (!(fcs[1] & CC2520_FCS_CRC_OK)) {
return false;
}
insert_radio_noise_details(pkt, fcs);
return true;
}
static inline bool verify_rxfifo_validity(struct cc2520_context *ctx,
u8_t pkt_len)
{
if (pkt_len < 2 || read_reg_rxfifocnt(ctx) != pkt_len) {
return false;
}
return true;
}
static void cc2520_rx(int arg)
{
struct device *dev = INT_TO_POINTER(arg);
struct cc2520_context *cc2520 = dev->driver_data;
struct net_pkt *pkt;
u8_t pkt_len;
while (1) {
pkt = NULL;
k_sem_take(&cc2520->rx_lock, K_FOREVER);
if (cc2520->overflow) {
LOG_ERR("RX overflow!");
cc2520->overflow = false;
goto flush;
}
pkt_len = read_rxfifo_length(cc2520) & 0x7f;
if (!verify_rxfifo_validity(cc2520, pkt_len)) {
LOG_ERR("Invalid content");
goto flush;
}
pkt = net_pkt_alloc_with_buffer(cc2520->iface, pkt_len,
AF_UNSPEC, 0, K_NO_WAIT);
if (!pkt) {
LOG_ERR("No pkt available");
goto flush;
}
if (!IS_ENABLED(CONFIG_IEEE802154_RAW_MODE)) {
pkt_len -= 2U;
}
if (!read_rxfifo_content(cc2520, pkt->buffer, pkt_len)) {
LOG_ERR("No content read");
goto flush;
}
if (!verify_crc(cc2520, pkt)) {
LOG_ERR("Bad packet CRC");
goto out;
}
if (ieee802154_radio_handle_ack(cc2520->iface, pkt) == NET_OK) {
LOG_DBG("ACK packet handled");
goto out;
}
LOG_DBG("Caught a packet (%u)", pkt_len);
if (net_recv_data(cc2520->iface, pkt) < 0) {
LOG_DBG("Packet dropped by NET stack");
goto out;
}
net_analyze_stack("CC2520 Rx Fiber stack",
Z_THREAD_STACK_BUFFER(cc2520->cc2520_rx_stack),
K_THREAD_STACK_SIZEOF(cc2520->cc2520_rx_stack));
continue;
flush:
cc2520_print_exceptions(cc2520);
cc2520_print_errors(cc2520);
flush_rxfifo(cc2520);
out:
if (pkt) {
net_pkt_unref(pkt);
}
}
}
/********************
* Radio device API *
*******************/
static enum ieee802154_hw_caps cc2520_get_capabilities(struct device *dev)
{
/* ToDo: Add support for IEEE802154_HW_PROMISC */
return IEEE802154_HW_FCS |
IEEE802154_HW_2_4_GHZ |
IEEE802154_HW_FILTER;
}
static int cc2520_cca(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
if (!get_cca(cc2520)) {
LOG_WRN("Busy");
return -EBUSY;
}
return 0;
}
static int cc2520_set_channel(struct device *dev, u16_t channel)
{
struct cc2520_context *cc2520 = dev->driver_data;
LOG_DBG("%u", channel);
if (channel < 11 || channel > 26) {
return -EINVAL;
}
/* See chapter 16 */
channel = 11 + (channel - 11) * 5U;
if (!write_reg_freqctrl(cc2520, FREQCTRL_FREQ(channel))) {
LOG_ERR("Failed");
return -EIO;
}
return 0;
}
static int cc2520_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 cc2520_set_ieee_addr(dev, filter->ieee_addr);
} else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) {
return cc2520_set_short_addr(dev, filter->short_addr);
} else if (type == IEEE802154_FILTER_TYPE_PAN_ID) {
return cc2520_set_pan_id(dev, filter->pan_id);
}
return -ENOTSUP;
}
static int cc2520_set_txpower(struct device *dev, s16_t dbm)
{
struct cc2520_context *cc2520 = dev->driver_data;
u8_t pwr;
LOG_DBG("%d", dbm);
/* See chapter 19 part 8 */
switch (dbm) {
case 5:
pwr = 0xF7;
break;
case 3:
pwr = 0xF2;
break;
case 2:
pwr = 0xAB;
break;
case 1:
pwr = 0x13;
break;
case 0:
pwr = 0x32;
break;
case -2:
pwr = 0x81;
break;
case -4:
pwr = 0x88;
break;
case -7:
pwr = 0x2C;
break;
case -18:
pwr = 0x03;
break;
default:
goto error;
}
if (!write_reg_txpower(cc2520, pwr)) {
goto error;
}
return 0;
error:
LOG_ERR("Failed");
return -EIO;
}
static int cc2520_tx(struct device *dev,
struct net_pkt *pkt,
struct net_buf *frag)
{
u8_t *frame = frag->data;
u8_t len = frag->len;
struct cc2520_context *cc2520 = dev->driver_data;
u8_t retry = 2U;
bool status;
LOG_DBG("%p (%u)", frag, len);
if (!write_reg_excflag0(cc2520, EXCFLAG0_RESET_TX_FLAGS) ||
!write_txfifo_length(cc2520, len) ||
!write_txfifo_content(cc2520, frame, len)) {
LOG_ERR("Cannot feed in TX fifo");
goto error;
}
if (!verify_txfifo_status(cc2520, len)) {
LOG_ERR("Did not write properly into TX FIFO");
goto error;
}
#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
k_sem_take(&cc2520->access_lock, K_FOREVER);
#endif
/* 1 retry is allowed here */
do {
atomic_set(&cc2520->tx, 1);
k_sem_init(&cc2520->tx_sync, 0, UINT_MAX);
if (!instruct_stxoncca(cc2520)) {
LOG_ERR("Cannot start transmission");
goto error;
}
k_sem_take(&cc2520->tx_sync, 10);
retry--;
status = verify_tx_done(cc2520);
} while (!status && retry);
#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
k_sem_give(&cc2520->access_lock);
#endif
if (status) {
return 0;
}
error:
#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
k_sem_give(&cc2520->access_lock);
#endif
LOG_ERR("No TX_FRM_DONE");
cc2520_print_exceptions(cc2520);
cc2520_print_errors(cc2520);
atomic_set(&cc2520->tx, 0);
instruct_sflushtx(cc2520);
return -EIO;
}
static int cc2520_start(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
if (!instruct_sxoscon(cc2520) ||
!instruct_srxon(cc2520) ||
!verify_osc_stabilization(cc2520)) {
LOG_ERR("Error starting CC2520");
return -EIO;
}
flush_rxfifo(cc2520);
enable_fifop_interrupt(cc2520, true);
enable_sfd_interrupt(cc2520, true);
return 0;
}
static int cc2520_stop(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
flush_rxfifo(cc2520);
enable_fifop_interrupt(cc2520, false);
enable_sfd_interrupt(cc2520, false);
if (!instruct_srfoff(cc2520) ||
!instruct_sxoscoff(cc2520)) {
LOG_ERR("Error stopping CC2520");
return -EIO;
}
return 0;
}
/******************
* Initialization *
*****************/
static int power_on_and_setup(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
/* Switching to LPM2 mode */
set_reset(dev, 0);
z_usleep(150);
set_vreg_en(dev, 0);
z_usleep(250);
/* Then to ACTIVE mode */
set_vreg_en(dev, 1);
z_usleep(250);
set_reset(dev, 1);
z_usleep(150);
if (!verify_osc_stabilization(cc2520)) {
return -EIO;
}
/* Default settings to always write (see chapter 28 part 1) */
if (!write_reg_txpower(cc2520, CC2520_TXPOWER_DEFAULT) ||
!write_reg_ccactrl0(cc2520, CC2520_CCACTRL0_DEFAULT) ||
!write_reg_mdmctrl0(cc2520, CC2520_MDMCTRL0_DEFAULT) ||
!write_reg_mdmctrl1(cc2520, CC2520_MDMCTRL1_DEFAULT) ||
!write_reg_rxctrl(cc2520, CC2520_RXCTRL_DEFAULT) ||
!write_reg_fsctrl(cc2520, CC2520_FSCTRL_DEFAULT) ||
!write_reg_fscal1(cc2520, CC2520_FSCAL1_DEFAULT) ||
!write_reg_agcctrl1(cc2520, CC2520_AGCCTRL1_DEFAULT) ||
!write_reg_adctest0(cc2520, CC2520_ADCTEST0_DEFAULT) ||
!write_reg_adctest1(cc2520, CC2520_ADCTEST1_DEFAULT) ||
!write_reg_adctest2(cc2520, CC2520_ADCTEST2_DEFAULT)) {
return -EIO;
}
/* EXTCLOCK0: Disabling external clock
* FRMCTRL0: AUTOACK and AUTOCRC enabled
* FRMCTRL1: SET_RXENMASK_ON_TX and IGNORE_TX_UNDERF
* FRMFILT0: Frame filtering (setting CC2520_FRAME_FILTERING)
* FIFOPCTRL: Set TX threshold (setting CC2520_TX_THRESHOLD)
*/
if (!write_reg_extclock(cc2520, 0) ||
!write_reg_frmctrl0(cc2520, CC2520_AUTOMATISM) ||
!write_reg_frmctrl1(cc2520, FRMCTRL1_IGNORE_TX_UNDERF |
FRMCTRL1_SET_RXENMASK_ON_TX) ||
!write_reg_frmfilt0(cc2520, FRMFILT0_FRAME_FILTER_EN |
FRMFILT0_MAX_FRAME_VERSION(3)) ||
!write_reg_frmfilt1(cc2520, FRMFILT1_ACCEPT_ALL) ||
!write_reg_srcmatch(cc2520, SRCMATCH_DEFAULTS) ||
!write_reg_fifopctrl(cc2520,
FIFOPCTRL_FIFOP_THR(CC2520_TX_THRESHOLD))) {
return -EIO;
}
/* Cleaning up TX fifo */
instruct_sflushtx(cc2520);
setup_gpio_callbacks(dev);
cc2520_print_gpio_config(dev);
return 0;
}
static inline int configure_spi(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
cc2520->spi = device_get_binding(DT_TI_CC2520_0_BUS_NAME);
if (!cc2520->spi) {
LOG_ERR("Unable to get SPI device");
return -ENODEV;
}
#if defined(CONFIG_IEEE802154_CC2520_GPIO_SPI_CS)
cs_ctrl.gpio_dev = device_get_binding(
DT_TI_CC2520_0_CS_GPIO_CONTROLLER);
if (!cs_ctrl.gpio_dev) {
LOG_ERR("Unable to get GPIO SPI CS device");
return -ENODEV;
}
cs_ctrl.gpio_pin = DT_TI_CC2520_0_CS_GPIO_PIN;
cs_ctrl.delay = 0U;
cc2520->spi_cfg.cs = &cs_ctrl;
LOG_DBG("SPI GPIO CS configured on %s:%u",
DT_TI_CC2520_0_CS_GPIO_CONTROLLER,
DT_TI_CC2520_0_CS_GPIO_PIN);
#endif /* CONFIG_IEEE802154_CC2520_GPIO_SPI_CS */
cc2520->spi_cfg.frequency = DT_TI_CC2520_0_SPI_MAX_FREQUENCY;
cc2520->spi_cfg.operation = SPI_WORD_SET(8);
cc2520->spi_cfg.slave = DT_TI_CC2520_0_BASE_ADDRESS;
return 0;
}
static int cc2520_init(struct device *dev)
{
struct cc2520_context *cc2520 = dev->driver_data;
atomic_set(&cc2520->tx, 0);
k_sem_init(&cc2520->rx_lock, 0, UINT_MAX);
#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
k_sem_init(&cc2520->access_lock, 1, 1);
#endif
cc2520->gpios = cc2520_configure_gpios();
if (!cc2520->gpios) {
LOG_ERR("Configuring GPIOS failed");
return -EIO;
}
if (configure_spi(dev) != 0) {
LOG_ERR("Configuring SPI failed");
return -EIO;
}
LOG_DBG("GPIO and SPI configured");
if (power_on_and_setup(dev) != 0) {
LOG_ERR("Configuring CC2520 failed");
return -EIO;
}
k_thread_create(&cc2520->cc2520_rx_thread, cc2520->cc2520_rx_stack,
CONFIG_IEEE802154_CC2520_RX_STACK_SIZE,
(k_thread_entry_t)cc2520_rx,
dev, NULL, NULL, K_PRIO_COOP(2), 0, 0);
LOG_INF("CC2520 initialized");
return 0;
}
static void cc2520_iface_init(struct net_if *iface)
{
struct device *dev = net_if_get_device(iface);
struct cc2520_context *cc2520 = dev->driver_data;
u8_t *mac = get_mac(dev);
net_if_set_link_addr(iface, mac, 8, NET_LINK_IEEE802154);
cc2520->iface = iface;
ieee802154_init(iface);
}
static struct cc2520_context cc2520_context_data;
static struct ieee802154_radio_api cc2520_radio_api = {
.iface_api.init = cc2520_iface_init,
.get_capabilities = cc2520_get_capabilities,
.cca = cc2520_cca,
.set_channel = cc2520_set_channel,
.filter = cc2520_filter,
.set_txpower = cc2520_set_txpower,
.start = cc2520_start,
.stop = cc2520_stop,
.tx = cc2520_tx,
};
#if defined(CONFIG_IEEE802154_RAW_MODE)
DEVICE_AND_API_INIT(cc2520, CONFIG_IEEE802154_CC2520_DRV_NAME,
cc2520_init, &cc2520_context_data, NULL,
POST_KERNEL, CONFIG_IEEE802154_CC2520_INIT_PRIO,
&cc2520_radio_api);
#else
NET_DEVICE_INIT(cc2520, CONFIG_IEEE802154_CC2520_DRV_NAME,
cc2520_init, &cc2520_context_data, NULL,
CONFIG_IEEE802154_CC2520_INIT_PRIO,
&cc2520_radio_api, IEEE802154_L2,
NET_L2_GET_CTX_TYPE(IEEE802154_L2), 125);
NET_STACK_INFO_ADDR(RX, cc2520,
CONFIG_IEEE802154_CC2520_RX_STACK_SIZE,
CONFIG_IEEE802154_CC2520_RX_STACK_SIZE,
cc2520_context_data.cc2520_rx_stack,
0);
#endif
#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
static inline bool cc2520_read_ram(struct cc2520_context *ctx, u16_t addr,
u8_t *data_buf, u8_t len)
{
return z_cc2520_access(ctx, true, CC2520_INS_MEMRD,
addr, data_buf, len);
}
static inline bool cc2520_write_ram(struct cc2520_context *ctx, u16_t addr,
u8_t *data_buf, u8_t len)
{
return z_cc2520_access(ctx, false, CC2520_INS_MEMWR,
addr, data_buf, len);
}
static inline bool instruct_uccm_ccm(struct cc2520_context *cc2520,
bool uccm,
u8_t key_addr,
u8_t auth_crypt,
u8_t nonce_addr,
u16_t input_addr,
u16_t output_addr,
u8_t in_len,
u8_t m)
{
u8_t cmd[9];
const struct spi_buf buf[1] = {
{
.buf = cmd,
.len = 9,
},
};
const struct spi_buf_set tx = {
.buffers = buf,
.count = 1
};
int ret;
LOG_DBG("%sCCM(P={01} K={%02x} C={%02x} N={%02x}"
" A={%03x} E={%03x} F{%02x} M={%02x})",
uccm ? "U" : "", key_addr, auth_crypt, nonce_addr,
input_addr, output_addr, in_len, m);
cmd[0] = uccm ? CC2520_INS_UCCM | 1 : CC2520_INS_CCM | 1;
cmd[1] = key_addr;
cmd[2] = (auth_crypt & 0x7f);
cmd[3] = nonce_addr;
cmd[4] = (u8_t)(((input_addr & 0x0f00) >> 4) |
((output_addr & 0x0f00) >> 8));
cmd[5] = (u8_t)(input_addr & 0x00ff);
cmd[6] = (u8_t)(output_addr & 0x00ff);
cmd[7] = (in_len & 0x7f);
cmd[8] = (m & 0x03);
k_sem_take(&cc2520->access_lock, K_FOREVER);
ret = spi_write(cc2520->spi, &cc2520->spi_cfg, &tx);
k_sem_give(&cc2520->access_lock);
if (ret) {
LOG_ERR("%sCCM Failed", uccm ? "U" : "");
return false;
}
return true;
}
static inline void generate_nonce(u8_t *ccm_nonce, u8_t *nonce,
struct cipher_aead_pkt *apkt, u8_t m)
{
nonce[0] = 0 | (apkt->ad_len ? 0x40 : 0) | (m << 3) | 1;
memcpy(&nonce[1], ccm_nonce, 13);
nonce[14] = (u8_t)(apkt->pkt->in_len >> 8);
nonce[15] = (u8_t)(apkt->pkt->in_len);
/* See section 26.8.1 */
sys_mem_swap(nonce, 16);
}
static int insert_crypto_parameters(struct cipher_ctx *ctx,
struct cipher_aead_pkt *apkt,
u8_t *ccm_nonce, u8_t *auth_crypt)
{
struct cc2520_context *cc2520 = ctx->device->driver_data;
u8_t data[128];
u8_t *in_buf;
u8_t in_len;
u8_t m = 0U;
if (!apkt->pkt->out_buf || !apkt->pkt->out_buf_max) {
LOG_ERR("Out buffer needs to be set");
return -EINVAL;
}
if (!ctx->key.bit_stream || !ctx->keylen) {
LOG_ERR("No key installed");
return -EINVAL;
}
if (!(ctx->flags & CAP_INPLACE_OPS)) {
LOG_ERR("It supports only in-place operation");
return -EINVAL;
}
if (!apkt->ad || !apkt->ad_len) {
LOG_ERR("CCM needs associated data");
return -EINVAL;
}
if (apkt->pkt->in_buf && apkt->pkt->in_buf - apkt->ad_len != apkt->ad) {
LOG_ERR("In-place needs ad and input in same memory");
return -EINVAL;
}
if (!apkt->pkt->in_buf) {
if (!ctx->mode_params.ccm_info.tag_len) {
LOG_ERR("Auth only needs a tag length");
return -EINVAL;
}
in_buf = apkt->ad;
in_len = apkt->ad_len;
*auth_crypt = 0U;
} else {
in_buf = data;
memcpy(in_buf, apkt->ad, apkt->ad_len);
memcpy(in_buf + apkt->ad_len,
apkt->pkt->in_buf, apkt->pkt->in_len);
in_len = apkt->ad_len + apkt->pkt->in_len;
*auth_crypt = !apkt->tag ? apkt->pkt->in_len :
apkt->pkt->in_len - ctx->mode_params.ccm_info.tag_len;
}
if (ctx->mode_params.ccm_info.tag_len) {
if ((ctx->mode_params.ccm_info.tag_len >> 2) > 3) {
m = 3U;
} else {
m = ctx->mode_params.ccm_info.tag_len >> 2;
}
}
/* Writing the frame in RAM */
if (!cc2520_write_ram(cc2520, CC2520_MEM_DATA, in_buf, in_len)) {
LOG_ERR("Cannot write the frame in RAM");
return -EIO;
}
/* See section 26.8.1 */
sys_memcpy_swap(data, ctx->key.bit_stream, ctx->keylen);
/* Writing the key in RAM */
if (!cc2520_write_ram(cc2520, CC2520_MEM_KEY, data, 16)) {
LOG_ERR("Cannot write the key in RAM");
return -EIO;
}
generate_nonce(ccm_nonce, data, apkt, m);
/* Writing the nonce in RAM */
if (!cc2520_write_ram(cc2520, CC2520_MEM_NONCE, data, 16)) {
LOG_ERR("Cannot write the nonce in RAM");
return -EIO;
}
return m;
}
static int cc2520_crypto_ccm(struct cipher_ctx *ctx,
struct cipher_aead_pkt *apkt,
u8_t *ccm_nonce)
{
struct cc2520_context *cc2520 = ctx->device->driver_data;
u8_t auth_crypt;
int m;
if (!apkt || !apkt->pkt) {
LOG_ERR("Invalid crypto packet to operate with");
return -EINVAL;
}
m = insert_crypto_parameters(ctx, apkt, ccm_nonce, &auth_crypt);
if (m < 0) {
LOG_ERR("Inserting crypto parameters failed");
return m;
}
apkt->pkt->out_len = apkt->pkt->in_len + apkt->ad_len +
(m ? ctx->mode_params.ccm_info.tag_len : 0);
if (apkt->pkt->out_len > apkt->pkt->out_buf_max) {
LOG_ERR("Result will not fit into out buffer %u vs %u",
apkt->pkt->out_len, apkt->pkt->out_buf_max);
return -ENOBUFS;
}
if (!instruct_uccm_ccm(cc2520, false, CC2520_MEM_KEY >> 4, auth_crypt,
CC2520_MEM_NONCE >> 4, CC2520_MEM_DATA,
0x000, apkt->ad_len, m) ||
!cc2520_read_ram(cc2520, CC2520_MEM_DATA,
apkt->pkt->out_buf, apkt->pkt->out_len)) {
LOG_ERR("CCM or reading result from RAM failed");
return -EIO;
}
if (apkt->tag) {
memcpy(apkt->tag, apkt->pkt->out_buf + apkt->pkt->in_len,
ctx->mode_params.ccm_info.tag_len);
}
return 0;
}
static int cc2520_crypto_uccm(struct cipher_ctx *ctx,
struct cipher_aead_pkt *apkt,
u8_t *ccm_nonce)
{
struct cc2520_context *cc2520 = ctx->device->driver_data;
u8_t auth_crypt;
int m;
if (!apkt || !apkt->pkt) {
LOG_ERR("Invalid crypto packet to operate with");
return -EINVAL;
}
if (ctx->mode_params.ccm_info.tag_len && !apkt->tag) {
LOG_ERR("In case of MIC you need to provide a tag");
return -EINVAL;
}
m = insert_crypto_parameters(ctx, apkt, ccm_nonce, &auth_crypt);
if (m < 0) {
return m;
}
apkt->pkt->out_len = apkt->pkt->in_len + apkt->ad_len;
if (!instruct_uccm_ccm(cc2520, true, CC2520_MEM_KEY >> 4, auth_crypt,
CC2520_MEM_NONCE >> 4, CC2520_MEM_DATA,
0x000, apkt->ad_len, m) ||
!cc2520_read_ram(cc2520, CC2520_MEM_DATA,
apkt->pkt->out_buf, apkt->pkt->out_len)) {
LOG_ERR("UCCM or reading result from RAM failed");
return -EIO;
}
if (m && (!(read_reg_dpustat(cc2520) & DPUSTAT_AUTHSTAT_H))) {
LOG_ERR("Authentication of the frame failed");
return -EBADMSG;
}
return 0;
}
static int cc2520_crypto_hw_caps(struct device *dev)
{
return CAP_RAW_KEY | CAP_INPLACE_OPS | CAP_SYNC_OPS;
}
static int cc2520_crypto_begin_session(struct device *dev,
struct cipher_ctx *ctx,
enum cipher_algo algo,
enum cipher_mode mode,
enum cipher_op op_type)
{
if (algo != CRYPTO_CIPHER_ALGO_AES || mode != CRYPTO_CIPHER_MODE_CCM) {
LOG_ERR("Wrong algo (%u) or mode (%u)", algo, mode);
return -EINVAL;
}
if (ctx->mode_params.ccm_info.nonce_len != 13U) {
LOG_ERR("Nonce length erroneous (%u)",
ctx->mode_params.ccm_info.nonce_len);
return -EINVAL;
}
if (op_type == CRYPTO_CIPHER_OP_ENCRYPT) {
ctx->ops.ccm_crypt_hndlr = cc2520_crypto_ccm;
} else {
ctx->ops.ccm_crypt_hndlr = cc2520_crypto_uccm;
}
ctx->ops.cipher_mode = mode;
ctx->device = dev;
return 0;
}
static int cc2520_crypto_free_session(struct device *dev,
struct cipher_ctx *ctx)
{
ARG_UNUSED(dev);
ctx->ops.ccm_crypt_hndlr = NULL;
ctx->device = NULL;
return 0;
}
static int cc2520_crypto_init(struct device *dev)
{
LOG_INF("CC2520 crypto part initialized");
return 0;
}
struct crypto_driver_api cc2520_crypto_api = {
.query_hw_caps = cc2520_crypto_hw_caps,
.begin_session = cc2520_crypto_begin_session,
.free_session = cc2520_crypto_free_session,
.crypto_async_callback_set = NULL
};
DEVICE_AND_API_INIT(cc2520_crypto, CONFIG_IEEE802154_CC2520_CRYPTO_DRV_NAME,
cc2520_crypto_init, &cc2520_context_data, NULL,
POST_KERNEL, CONFIG_IEEE802154_CC2520_CRYPTO_INIT_PRIO,
&cc2520_crypto_api);
#endif /* CONFIG_IEEE802154_CC2520_CRYPTO */