/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_IEEE802154_CC2520_CRYPTO #include #include #endif /* CONFIG_IEEE802154_CC2520_CRYPTO */ #include #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 */