1443 lines
32 KiB
C
1443 lines
32 KiB
C
/* ieee802154_cc2520.c - TI CC2520 driver */
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL
|
|
#define SYS_LOG_DOMAIN "dev/cc2520"
|
|
#include <logging/sys_log.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <kernel.h>
|
|
#include <arch/cpu.h>
|
|
|
|
#include <board.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 CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL == 4
|
|
static inline void _cc2520_print_gpio_config(struct device *dev)
|
|
{
|
|
struct cc2520_context *cc2520 = dev->driver_data;
|
|
|
|
SYS_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));
|
|
SYS_LOG_DBG("GPIOPOLARITY: 0x%x",
|
|
read_reg_gpiopolarity(cc2520));
|
|
SYS_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);
|
|
|
|
SYS_LOG_DBG("EXCFLAG0:");
|
|
|
|
if (flag & EXCFLAG0_RF_IDLE) {
|
|
SYS_LOG_BACKEND_FN("RF_IDLE ");
|
|
}
|
|
|
|
if (flag & EXCFLAG0_TX_FRM_DONE) {
|
|
SYS_LOG_BACKEND_FN("TX_FRM_DONE ");
|
|
}
|
|
|
|
if (flag & EXCFLAG0_TX_ACK_DONE) {
|
|
SYS_LOG_BACKEND_FN("TX_ACK_DONE ");
|
|
}
|
|
|
|
if (flag & EXCFLAG0_TX_UNDERFLOW) {
|
|
SYS_LOG_BACKEND_FN("TX_UNDERFLOW ");
|
|
}
|
|
|
|
if (flag & EXCFLAG0_TX_OVERFLOW) {
|
|
SYS_LOG_BACKEND_FN("TX_OVERFLOW ");
|
|
}
|
|
|
|
if (flag & EXCFLAG0_RX_UNDERFLOW) {
|
|
SYS_LOG_BACKEND_FN("RX_UNDERFLOW ");
|
|
}
|
|
|
|
if (flag & EXCFLAG0_RX_OVERFLOW) {
|
|
SYS_LOG_BACKEND_FN("RX_OVERFLOW ");
|
|
}
|
|
|
|
if (flag & EXCFLAG0_RXENABLE_ZERO) {
|
|
SYS_LOG_BACKEND_FN("RXENABLE_ZERO");
|
|
}
|
|
|
|
SYS_LOG_BACKEND_FN("\n");
|
|
|
|
flag = read_reg_excflag1(cc2520);
|
|
|
|
SYS_LOG_DBG("EXCFLAG1:");
|
|
|
|
if (flag & EXCFLAG1_RX_FRM_DONE) {
|
|
SYS_LOG_BACKEND_FN("RX_FRM_DONE ");
|
|
}
|
|
|
|
if (flag & EXCFLAG1_RX_FRM_ACCEPTED) {
|
|
SYS_LOG_BACKEND_FN("RX_FRM_ACCEPTED ");
|
|
}
|
|
|
|
if (flag & EXCFLAG1_SRC_MATCH_DONE) {
|
|
SYS_LOG_BACKEND_FN("SRC_MATCH_DONE ");
|
|
}
|
|
|
|
if (flag & EXCFLAG1_SRC_MATCH_FOUND) {
|
|
SYS_LOG_BACKEND_FN("SRC_MATCH_FOUND ");
|
|
}
|
|
|
|
if (flag & EXCFLAG1_FIFOP) {
|
|
SYS_LOG_BACKEND_FN("FIFOP ");
|
|
}
|
|
|
|
if (flag & EXCFLAG1_SFD) {
|
|
SYS_LOG_BACKEND_FN("SFD ");
|
|
}
|
|
|
|
if (flag & EXCFLAG1_DPU_DONE_L) {
|
|
SYS_LOG_BACKEND_FN("DPU_DONE_L ");
|
|
}
|
|
|
|
if (flag & EXCFLAG1_DPU_DONE_H) {
|
|
SYS_LOG_BACKEND_FN("DPU_DONE_H");
|
|
}
|
|
|
|
SYS_LOG_BACKEND_FN("\n");
|
|
}
|
|
|
|
static inline void _cc2520_print_errors(struct cc2520_context *cc2520)
|
|
{
|
|
u8_t flag = read_reg_excflag2(cc2520);
|
|
|
|
SYS_LOG_DBG("EXCFLAG2:");
|
|
|
|
if (flag & EXCFLAG2_MEMADDR_ERROR) {
|
|
SYS_LOG_BACKEND_FN("MEMADDR_ERROR ");
|
|
}
|
|
|
|
if (flag & EXCFLAG2_USAGE_ERROR) {
|
|
SYS_LOG_BACKEND_FN("USAGE_ERROR ");
|
|
}
|
|
|
|
if (flag & EXCFLAG2_OPERAND_ERROR) {
|
|
SYS_LOG_BACKEND_FN("OPERAND_ERROR ");
|
|
}
|
|
|
|
if (flag & EXCFLAG2_SPI_ERROR) {
|
|
SYS_LOG_BACKEND_FN("SPI_ERROR ");
|
|
}
|
|
|
|
if (flag & EXCFLAG2_RF_NO_LOCK) {
|
|
SYS_LOG_BACKEND_FN("RF_NO_LOCK ");
|
|
}
|
|
|
|
if (flag & EXCFLAG2_RX_FRM_ABORTED) {
|
|
SYS_LOG_BACKEND_FN("RX_FRM_ABORTED ");
|
|
}
|
|
|
|
if (flag & EXCFLAG2_RFBUFMOV_TIMEOUT) {
|
|
SYS_LOG_BACKEND_FN("RFBUFMOV_TIMEOUT");
|
|
}
|
|
|
|
SYS_LOG_BACKEND_FN("\n");
|
|
}
|
|
#else
|
|
#define _cc2520_print_gpio_config(...)
|
|
#define _cc2520_print_exceptions(...)
|
|
#define _cc2520_print_errors(...)
|
|
#endif /* CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL == 4 */
|
|
|
|
|
|
/*********************
|
|
* Generic functions *
|
|
********************/
|
|
#define _usleep(usec) k_busy_wait(usec)
|
|
|
|
bool _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 (_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 = 100;
|
|
u8_t status;
|
|
|
|
do {
|
|
status = _cc2520_status(cc2520);
|
|
_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;
|
|
|
|
SYS_LOG_DBG("0x%x", pan_id);
|
|
|
|
pan_id = sys_le16_to_cpu(pan_id);
|
|
|
|
if (!write_mem_pan_id(cc2520, (u8_t *) &pan_id)) {
|
|
SYS_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;
|
|
|
|
SYS_LOG_DBG("0x%x", short_addr);
|
|
|
|
short_addr = sys_le16_to_cpu(short_addr);
|
|
|
|
if (!write_mem_short_addr(cc2520, (u8_t *) &short_addr)) {
|
|
SYS_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)) {
|
|
SYS_LOG_ERR("Failed");
|
|
return -EIO;
|
|
}
|
|
|
|
SYS_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 _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 _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 = 10;
|
|
u8_t status;
|
|
|
|
do {
|
|
_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 (_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 *frag, u8_t len)
|
|
{
|
|
if (!_cc2520_access(ctx, true, CC2520_INS_RXBUF, 0, frag->data, len)) {
|
|
return false;
|
|
}
|
|
|
|
if (read_reg_excflag0(ctx) & EXCFLAG0_RX_UNDERFLOW) {
|
|
SYS_LOG_ERR("RX underflow!");
|
|
return false;
|
|
}
|
|
|
|
net_buf_add(frag, 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 <= 50) {
|
|
lqi = 0;
|
|
} else if (lqi >= 110) {
|
|
lqi = 255;
|
|
} else {
|
|
lqi = (lqi - 50) << 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 (!_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_buf *pkt_frag = NULL;
|
|
struct net_pkt *pkt;
|
|
u8_t pkt_len;
|
|
|
|
while (1) {
|
|
pkt = NULL;
|
|
|
|
k_sem_take(&cc2520->rx_lock, K_FOREVER);
|
|
|
|
if (cc2520->overflow) {
|
|
SYS_LOG_ERR("RX overflow!");
|
|
cc2520->overflow = false;
|
|
|
|
goto flush;
|
|
}
|
|
|
|
pkt_len = read_rxfifo_length(cc2520) & 0x7f;
|
|
if (!verify_rxfifo_validity(cc2520, pkt_len)) {
|
|
SYS_LOG_ERR("Invalid content");
|
|
goto flush;
|
|
}
|
|
|
|
pkt = net_pkt_get_reserve_rx(0, K_NO_WAIT);
|
|
if (!pkt) {
|
|
SYS_LOG_ERR("No pkt available");
|
|
goto flush;
|
|
}
|
|
|
|
pkt_frag = net_pkt_get_frag(pkt, K_NO_WAIT);
|
|
if (!pkt_frag) {
|
|
SYS_LOG_ERR("No pkt_frag available");
|
|
goto flush;
|
|
}
|
|
|
|
net_pkt_frag_insert(pkt, pkt_frag);
|
|
|
|
if (!IS_ENABLED(CONFIG_IEEE802154_RAW_MODE)) {
|
|
pkt_len -= 2;
|
|
}
|
|
|
|
if (!read_rxfifo_content(cc2520, pkt_frag, pkt_len)) {
|
|
SYS_LOG_ERR("No content read");
|
|
goto flush;
|
|
}
|
|
|
|
if (!verify_crc(cc2520, pkt)) {
|
|
SYS_LOG_ERR("Bad packet CRC");
|
|
goto out;
|
|
}
|
|
|
|
if (ieee802154_radio_handle_ack(cc2520->iface, pkt) == NET_OK) {
|
|
SYS_LOG_DBG("ACK packet handled");
|
|
goto out;
|
|
}
|
|
|
|
SYS_LOG_DBG("Caught a packet (%u)", pkt_len);
|
|
|
|
if (net_recv_data(cc2520->iface, pkt) < 0) {
|
|
SYS_LOG_DBG("Packet dropped by NET stack");
|
|
goto out;
|
|
}
|
|
|
|
net_analyze_stack("CC2520 Rx Fiber stack",
|
|
K_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)) {
|
|
SYS_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;
|
|
|
|
SYS_LOG_DBG("%u", channel);
|
|
|
|
if (channel < 11 || channel > 26) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* See chapter 16 */
|
|
channel = 11 + 5 * (channel - 11);
|
|
|
|
if (!write_reg_freqctrl(cc2520, FREQCTRL_FREQ(channel))) {
|
|
SYS_LOG_ERR("Failed");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cc2520_set_filter(struct device *dev,
|
|
enum ieee802154_filter_type type,
|
|
const struct ieee802154_filter *filter)
|
|
{
|
|
SYS_LOG_DBG("Applying filter %u", type);
|
|
|
|
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 -EINVAL;
|
|
}
|
|
|
|
static int cc2520_set_txpower(struct device *dev, s16_t dbm)
|
|
{
|
|
struct cc2520_context *cc2520 = dev->driver_data;
|
|
u8_t pwr;
|
|
|
|
SYS_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:
|
|
SYS_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 - net_pkt_ll_reserve(pkt);
|
|
u8_t len = net_pkt_ll_reserve(pkt) + frag->len;
|
|
struct cc2520_context *cc2520 = dev->driver_data;
|
|
u8_t retry = 2;
|
|
bool status;
|
|
|
|
SYS_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)) {
|
|
SYS_LOG_ERR("Cannot feed in TX fifo");
|
|
goto error;
|
|
}
|
|
|
|
if (!verify_txfifo_status(cc2520, len)) {
|
|
SYS_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)) {
|
|
SYS_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
|
|
SYS_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;
|
|
|
|
SYS_LOG_DBG("");
|
|
|
|
if (!instruct_sxoscon(cc2520) ||
|
|
!instruct_srxon(cc2520) ||
|
|
!verify_osc_stabilization(cc2520)) {
|
|
SYS_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;
|
|
|
|
SYS_LOG_DBG("");
|
|
|
|
flush_rxfifo(cc2520);
|
|
|
|
enable_fifop_interrupt(cc2520, false);
|
|
enable_sfd_interrupt(cc2520, false);
|
|
|
|
if (!instruct_srfoff(cc2520) ||
|
|
!instruct_sxoscoff(cc2520)) {
|
|
SYS_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);
|
|
_usleep(150);
|
|
|
|
set_vreg_en(dev, 0);
|
|
_usleep(250);
|
|
|
|
/* Then to ACTIVE mode */
|
|
set_vreg_en(dev, 1);
|
|
_usleep(250);
|
|
|
|
set_reset(dev, 1);
|
|
_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(
|
|
CONFIG_IEEE802154_CC2520_SPI_DRV_NAME);
|
|
if (!cc2520->spi) {
|
|
SYS_LOG_ERR("Unable to get SPI device");
|
|
return -ENODEV;
|
|
}
|
|
|
|
#if defined(CONFIG_IEEE802154_CC2520_GPIO_SPI_CS)
|
|
cs_ctrl.gpio_dev = device_get_binding(
|
|
CONFIG_IEEE802154_CC2520_GPIO_SPI_CS_DRV_NAME);
|
|
if (!cs_ctrl.gpio_dev) {
|
|
SYS_LOG_ERR("Unable to get GPIO SPI CS device");
|
|
return -ENODEV;
|
|
}
|
|
|
|
cs_ctrl.gpio_pin = CONFIG_IEEE802154_CC2520_GPIO_SPI_CS_PIN;
|
|
cs_ctrl.delay = 0;
|
|
|
|
cc2520->spi_cfg.cs = &cs_ctrl;
|
|
|
|
SYS_LOG_DBG("SPI GPIO CS configured on %s:%u",
|
|
CONFIG_IEEE802154_CC2520_GPIO_SPI_CS_DRV_NAME,
|
|
CONFIG_IEEE802154_CC2520_GPIO_SPI_CS_PIN);
|
|
#endif /* CONFIG_IEEE802154_CC2520_GPIO_SPI_CS */
|
|
|
|
cc2520->spi_cfg.frequency = CONFIG_IEEE802154_CC2520_SPI_FREQ;
|
|
cc2520->spi_cfg.operation = SPI_WORD_SET(8);
|
|
cc2520->spi_cfg.slave = CONFIG_IEEE802154_CC2520_SPI_SLAVE;
|
|
|
|
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) {
|
|
SYS_LOG_ERR("Configuring GPIOS failed");
|
|
return -EIO;
|
|
}
|
|
|
|
if (configure_spi(dev) != 0) {
|
|
SYS_LOG_ERR("Configuring SPI failed");
|
|
return -EIO;
|
|
}
|
|
|
|
SYS_LOG_DBG("GPIO and SPI configured");
|
|
|
|
if (power_on_and_setup(dev) != 0) {
|
|
SYS_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);
|
|
|
|
SYS_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);
|
|
|
|
SYS_LOG_DBG("");
|
|
|
|
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,
|
|
.iface_api.send = ieee802154_radio_send,
|
|
|
|
.get_capabilities = cc2520_get_capabilities,
|
|
.cca = cc2520_cca,
|
|
.set_channel = cc2520_set_channel,
|
|
.set_filter = cc2520_set_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 _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 _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;
|
|
|
|
SYS_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) {
|
|
SYS_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 = 0;
|
|
|
|
if (!apkt->pkt->out_buf || !apkt->pkt->out_buf_max) {
|
|
SYS_LOG_ERR("Out buffer needs to be set");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ctx->key.bit_stream || !ctx->keylen) {
|
|
SYS_LOG_ERR("No key installed");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!(ctx->flags & CAP_INPLACE_OPS)) {
|
|
SYS_LOG_ERR("It supports only in-place operation");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!apkt->ad || !apkt->ad_len) {
|
|
SYS_LOG_ERR("CCM needs associated data");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (apkt->pkt->in_buf && apkt->pkt->in_buf - apkt->ad_len != apkt->ad) {
|
|
SYS_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) {
|
|
SYS_LOG_ERR("Auth only needs a tag length");
|
|
return -EINVAL;
|
|
}
|
|
|
|
in_buf = apkt->ad;
|
|
in_len = apkt->ad_len;
|
|
|
|
*auth_crypt = 0;
|
|
} 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 = 3;
|
|
} 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)) {
|
|
SYS_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)) {
|
|
SYS_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)) {
|
|
SYS_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) {
|
|
SYS_LOG_ERR("Invalid crypto packet to operate with");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (apkt->tag) {
|
|
SYS_LOG_ERR("CCM encryption does not take a tag");
|
|
return -EINVAL;
|
|
}
|
|
|
|
m = insert_crypto_parameters(ctx, apkt, ccm_nonce, &auth_crypt);
|
|
if (m < 0) {
|
|
SYS_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) {
|
|
SYS_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)) {
|
|
SYS_LOG_ERR("CCM or reading result from RAM failed");
|
|
return -EIO;
|
|
}
|
|
|
|
apkt->tag = apkt->pkt->out_buf + apkt->pkt->in_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) {
|
|
SYS_LOG_ERR("Invalid crypto packet to operate with");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->mode_params.ccm_info.tag_len && !apkt->tag) {
|
|
SYS_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)) {
|
|
SYS_LOG_ERR("UCCM or reading result from RAM failed");
|
|
return -EIO;
|
|
}
|
|
|
|
if (m && (!(read_reg_dpustat(cc2520) & DPUSTAT_AUTHSTAT_H))) {
|
|
SYS_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) {
|
|
SYS_LOG_ERR("Wrong algo (%u) or mode (%u)", algo, mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->mode_params.ccm_info.nonce_len != 13) {
|
|
SYS_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)
|
|
{
|
|
SYS_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 */
|