/* * Copyright (c) 2021 Telink Semiconductor * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT telink_b91_zb #include "rf.h" #include "stimer.h" #define LOG_MODULE_NAME ieee802154_b91 #if defined(CONFIG_IEEE802154_DRIVER_LOG_LEVEL) #define LOG_LEVEL CONFIG_IEEE802154_DRIVER_LOG_LEVEL #else #define LOG_LEVEL LOG_LEVEL_NONE #endif #include LOG_MODULE_REGISTER(LOG_MODULE_NAME); #include #include #include #if defined(CONFIG_NET_L2_OPENTHREAD) #include #endif #include #include "ieee802154_b91.h" /* B91 data structure */ static struct b91_data data; /* Set filter PAN ID */ static int b91_set_pan_id(uint16_t pan_id) { uint8_t pan_id_le[B91_PAN_ID_SIZE]; sys_put_le16(pan_id, pan_id_le); memcpy(data.filter_pan_id, pan_id_le, B91_PAN_ID_SIZE); return 0; } /* Set filter short address */ static int b91_set_short_addr(uint16_t short_addr) { uint8_t short_addr_le[B91_SHORT_ADDRESS_SIZE]; sys_put_le16(short_addr, short_addr_le); memcpy(data.filter_short_addr, short_addr_le, B91_SHORT_ADDRESS_SIZE); return 0; } /* Set filter IEEE address */ static int b91_set_ieee_addr(const uint8_t *ieee_addr) { memcpy(data.filter_ieee_addr, ieee_addr, B91_IEEE_ADDRESS_SIZE); return 0; } /* Filter PAN ID, short address and IEEE address */ static bool b91_run_filter(uint8_t *rx_buffer) { /* Check destination PAN Id */ if (memcmp(&rx_buffer[B91_PAN_ID_OFFSET], data.filter_pan_id, B91_PAN_ID_SIZE) != 0 && memcmp(&rx_buffer[B91_PAN_ID_OFFSET], B91_BROADCAST_ADDRESS, B91_PAN_ID_SIZE) != 0) { return false; } /* Check destination address */ switch (rx_buffer[B91_DEST_ADDR_TYPE_OFFSET] & B91_DEST_ADDR_TYPE_MASK) { case B91_DEST_ADDR_TYPE_SHORT: /* First check if the destination is broadcast */ /* If not broadcast, check if length and address matches */ if (memcmp(&rx_buffer[B91_DEST_ADDR_OFFSET], B91_BROADCAST_ADDRESS, B91_SHORT_ADDRESS_SIZE) != 0 && memcmp(&rx_buffer[B91_DEST_ADDR_OFFSET], data.filter_short_addr, B91_SHORT_ADDRESS_SIZE) != 0) { return false; } break; case B91_DEST_ADDR_TYPE_IEEE: /* If not broadcast, check if length and address matches */ if ((net_if_get_link_addr(data.iface)->len != B91_IEEE_ADDRESS_SIZE) || memcmp(&rx_buffer[B91_DEST_ADDR_OFFSET], data.filter_ieee_addr, B91_IEEE_ADDRESS_SIZE) != 0) { return false; } break; default: return false; } return true; } /* Get MAC address */ static inline uint8_t *b91_get_mac(const struct device *dev) { struct b91_data *b91 = dev->data; #if defined(CONFIG_IEEE802154_B91_RANDOM_MAC) sys_rand_get(b91->mac_addr, sizeof(b91->mac_addr)); /* * Clear bit 0 to ensure it isn't a multicast address and set * bit 1 to indicate address is locally administered and may * not be globally unique. */ b91->mac_addr[0] = (b91->mac_addr[0] & ~0x01) | 0x02; #else /* Vendor Unique Identifier */ b91->mac_addr[0] = 0xC4; b91->mac_addr[1] = 0x19; b91->mac_addr[2] = 0xD1; b91->mac_addr[3] = 0x00; /* Extended Unique Identifier */ b91->mac_addr[4] = CONFIG_IEEE802154_B91_MAC4; b91->mac_addr[5] = CONFIG_IEEE802154_B91_MAC5; b91->mac_addr[6] = CONFIG_IEEE802154_B91_MAC6; b91->mac_addr[7] = CONFIG_IEEE802154_B91_MAC7; #endif return b91->mac_addr; } /* Convert RSSI to LQI */ static uint8_t b91_convert_rssi_to_lqi(int8_t rssi) { uint32_t lqi32 = 0; /* check for MIN value */ if (rssi < B91_RSSI_TO_LQI_MIN) { return 0; } /* convert RSSI to LQI */ lqi32 = B91_RSSI_TO_LQI_SCALE * (rssi - B91_RSSI_TO_LQI_MIN); /* check for MAX value */ if (lqi32 > 0xFF) { lqi32 = 0xFF; } return (uint8_t)lqi32; } /* Update RSSI and LQI parameters */ static void b91_update_rssi_and_lqi(struct net_pkt *pkt) { int8_t rssi; uint8_t lqi; rssi = ((signed char)(data.rx_buffer [data.rx_buffer[B91_LENGTH_OFFSET] + B91_RSSI_OFFSET])) - 110; lqi = b91_convert_rssi_to_lqi(rssi); net_pkt_set_ieee802154_lqi(pkt, lqi); net_pkt_set_ieee802154_rssi_dbm(pkt, rssi); } /* Prepare TX buffer */ static int b91_set_tx_payload(uint8_t *payload, uint8_t payload_len) { unsigned char rf_data_len; unsigned int rf_tx_dma_len; /* See Telink SDK Dev Handbook, AN-21010600, section 21.5.2.2. */ if (payload_len > (B91_TRX_LENGTH - B91_PAYLOAD_OFFSET - IEEE802154_FCS_LENGTH)) { return -EINVAL; } rf_data_len = payload_len + 1; rf_tx_dma_len = rf_tx_packet_dma_len(rf_data_len); data.tx_buffer[0] = rf_tx_dma_len & 0xff; data.tx_buffer[1] = (rf_tx_dma_len >> 8) & 0xff; data.tx_buffer[2] = (rf_tx_dma_len >> 16) & 0xff; data.tx_buffer[3] = (rf_tx_dma_len >> 24) & 0xff; data.tx_buffer[4] = payload_len + IEEE802154_FCS_LENGTH; memcpy(data.tx_buffer + B91_PAYLOAD_OFFSET, payload, payload_len); return 0; } /* Enable ack handler */ static void b91_handle_ack_en(void) { data.ack_handler_en = true; } /* Disable ack handler */ static void b91_handle_ack_dis(void) { data.ack_handler_en = false; } /* Handle acknowledge packet */ static void b91_handle_ack(void) { struct net_pkt *ack_pkt; /* allocate ack packet */ ack_pkt = net_pkt_rx_alloc_with_buffer(data.iface, B91_ACK_FRAME_LEN, AF_UNSPEC, 0, K_NO_WAIT); if (!ack_pkt) { LOG_ERR("No free packet available."); return; } /* update packet data */ if (net_pkt_write(ack_pkt, data.rx_buffer + B91_PAYLOAD_OFFSET, B91_ACK_FRAME_LEN) < 0) { LOG_ERR("Failed to write to a packet."); goto out; } /* update RSSI and LQI */ b91_update_rssi_and_lqi(ack_pkt); /* init net cursor */ net_pkt_cursor_init(ack_pkt); /* handle ack */ if (ieee802154_handle_ack(data.iface, ack_pkt) != NET_OK) { LOG_INF("ACK packet not handled - releasing."); } /* release ack_wait semaphore */ k_sem_give(&data.ack_wait); out: net_pkt_unref(ack_pkt); } /* Send acknowledge packet */ static void b91_send_ack(uint8_t seq_num) { uint8_t ack_buf[] = { B91_ACK_TYPE, 0, seq_num }; if (b91_set_tx_payload(ack_buf, sizeof(ack_buf))) { return; } rf_set_txmode(); delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US); rf_tx_pkt(data.tx_buffer); } /* RX IRQ handler */ static void b91_rf_rx_isr(void) { uint8_t status; uint8_t length; uint8_t *payload; struct net_pkt *pkt; /* disable DMA and clear IRQ flag */ dma_chn_dis(DMA1); rf_clr_irq_status(FLD_RF_IRQ_RX); /* check CRC */ if (rf_zigbee_packet_crc_ok(data.rx_buffer)) { /* get payload length */ if (IS_ENABLED(CONFIG_IEEE802154_RAW_MODE) || IS_ENABLED(CONFIG_NET_L2_OPENTHREAD)) { length = data.rx_buffer[B91_LENGTH_OFFSET]; } else { length = data.rx_buffer[B91_LENGTH_OFFSET] - B91_FCS_LENGTH; } /* check length */ if ((length < B91_PAYLOAD_MIN) || (length > B91_PAYLOAD_MAX)) { LOG_ERR("Invalid length\n"); goto exit; } /* get payload */ payload = (uint8_t *)(data.rx_buffer + B91_PAYLOAD_OFFSET); /* handle acknowledge packet if enabled */ if ((length == (B91_ACK_FRAME_LEN + B91_FCS_LENGTH)) && ((payload[B91_FRAME_TYPE_OFFSET] & B91_FRAME_TYPE_MASK) == B91_ACK_TYPE)) { if (data.ack_handler_en) { b91_handle_ack(); } goto exit; } /* run filter (check PAN ID and destination address) */ if (b91_run_filter(payload) == false) { LOG_DBG("Packet received is not addressed to me"); goto exit; } /* send ack if requested */ if (payload[B91_FRAME_TYPE_OFFSET] & B91_ACK_REQUEST) { b91_send_ack(payload[B91_DSN_OFFSET]); } /* get packet pointer from NET stack */ pkt = net_pkt_rx_alloc_with_buffer(data.iface, length, AF_UNSPEC, 0, K_NO_WAIT); if (!pkt) { LOG_ERR("No pkt available"); goto exit; } /* update packet data */ if (net_pkt_write(pkt, payload, length)) { LOG_ERR("Failed to write to a packet."); net_pkt_unref(pkt); goto exit; } /* update RSSI and LQI parameters */ b91_update_rssi_and_lqi(pkt); /* transfer data to NET stack */ status = net_recv_data(data.iface, pkt); if (status < 0) { LOG_ERR("RCV Packet dropped by NET stack: %d", status); net_pkt_unref(pkt); } } exit: dma_chn_en(DMA1); } /* TX IRQ handler */ static void b91_rf_tx_isr(void) { /* clear irq status */ rf_clr_irq_status(FLD_RF_IRQ_TX); /* release tx semaphore */ k_sem_give(&data.tx_wait); /* set to rx mode */ rf_set_rxmode(); } /* IRQ handler */ static void b91_rf_isr(void) { if (rf_get_irq_status(FLD_RF_IRQ_RX)) { b91_rf_rx_isr(); } else if (rf_get_irq_status(FLD_RF_IRQ_TX)) { b91_rf_tx_isr(); } else { rf_clr_irq_status(FLD_RF_IRQ_ALL); } } /* Driver initialization */ static int b91_init(const struct device *dev) { struct b91_data *b91 = dev->data; /* init semaphores */ k_sem_init(&b91->tx_wait, 0, 1); k_sem_init(&b91->ack_wait, 0, 1); /* init rf module */ rf_mode_init(); rf_set_zigbee_250K_mode(); rf_set_tx_dma(2, B91_TRX_LENGTH); rf_set_rx_dma(data.rx_buffer, 3, B91_TRX_LENGTH); rf_set_rxmode(); /* init IRQs */ IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), b91_rf_isr, 0, 0); riscv_plic_irq_enable(DT_INST_IRQN(0)); riscv_plic_set_priority(DT_INST_IRQN(0), DT_INST_IRQ(0, priority)); rf_set_irq_mask(FLD_RF_IRQ_RX | FLD_RF_IRQ_TX); /* init data variables */ data.is_started = true; data.ack_handler_en = false; data.current_channel = 0; return 0; } /* API implementation: iface_init */ static void b91_iface_init(struct net_if *iface) { const struct device *dev = net_if_get_device(iface); struct b91_data *b91 = dev->data; uint8_t *mac = b91_get_mac(dev); net_if_set_link_addr(iface, mac, B91_IEEE_ADDRESS_SIZE, NET_LINK_IEEE802154); b91->iface = iface; ieee802154_init(iface); } /* API implementation: get_capabilities */ static enum ieee802154_hw_caps b91_get_capabilities(const struct device *dev) { ARG_UNUSED(dev); return IEEE802154_HW_FCS | IEEE802154_HW_FILTER | IEEE802154_HW_TX_RX_ACK | IEEE802154_HW_RX_TX_ACK; } /* API implementation: cca */ static int b91_cca(const struct device *dev) { ARG_UNUSED(dev); unsigned int t1 = stimer_get_tick(); while (!clock_time_exceed(t1, B91_CCA_TIME_MAX_US)) { if (rf_get_rssi() < CONFIG_IEEE802154_B91_CCA_RSSI_THRESHOLD) { return 0; } } return -EBUSY; } /* API implementation: set_channel */ static int b91_set_channel(const struct device *dev, uint16_t channel) { ARG_UNUSED(dev); if (channel > 26) { return -EINVAL; } if (channel < 11) { return -ENOTSUP; } if (data.current_channel != channel) { data.current_channel = channel; rf_set_chn(B91_LOGIC_CHANNEL_TO_PHYSICAL(channel)); rf_set_rxmode(); } return 0; } /* API implementation: filter */ static int b91_filter(const struct device *dev, bool set, enum ieee802154_filter_type type, const struct ieee802154_filter *filter) { if (!set) { return -ENOTSUP; } if (type == IEEE802154_FILTER_TYPE_IEEE_ADDR) { return b91_set_ieee_addr(filter->ieee_addr); } else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) { return b91_set_short_addr(filter->short_addr); } else if (type == IEEE802154_FILTER_TYPE_PAN_ID) { return b91_set_pan_id(filter->pan_id); } return -ENOTSUP; } /* API implementation: set_txpower */ static int b91_set_txpower(const struct device *dev, int16_t dbm) { ARG_UNUSED(dev); /* check for supported Min/Max range */ if (dbm < B91_TX_POWER_MIN) { dbm = B91_TX_POWER_MIN; } else if (dbm > B91_TX_POWER_MAX) { dbm = B91_TX_POWER_MAX; } /* set TX power */ rf_set_power_level(b91_tx_pwr_lt[dbm - B91_TX_POWER_MIN]); return 0; } /* API implementation: start */ static int b91_start(const struct device *dev) { ARG_UNUSED(dev); /* check if RF is already started */ if (!data.is_started) { rf_set_rxmode(); delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US); riscv_plic_irq_enable(DT_INST_IRQN(0)); data.is_started = true; } return 0; } /* API implementation: stop */ static int b91_stop(const struct device *dev) { ARG_UNUSED(dev); /* check if RF is already stopped */ if (data.is_started) { riscv_plic_irq_disable(DT_INST_IRQN(0)); rf_set_tx_rx_off(); delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US); data.is_started = false; } return 0; } /* API implementation: tx */ static int b91_tx(const struct device *dev, enum ieee802154_tx_mode mode, struct net_pkt *pkt, struct net_buf *frag) { ARG_UNUSED(pkt); int status; struct b91_data *b91 = dev->data; /* check for supported mode */ if (mode != IEEE802154_TX_MODE_DIRECT) { LOG_DBG("TX mode %d not supported", mode); return -ENOTSUP; } /* prepare tx buffer */ status = b91_set_tx_payload(frag->data, frag->len); if (status) { return status; } /* reset semaphores */ k_sem_reset(&b91->tx_wait); k_sem_reset(&b91->ack_wait); /* start transmission */ rf_set_txmode(); delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US); rf_tx_pkt(data.tx_buffer); /* wait for tx done */ status = k_sem_take(&b91->tx_wait, K_MSEC(B91_TX_WAIT_TIME_MS)); if (status != 0) { rf_set_rxmode(); return -EIO; } /* wait for ACK if requested */ if (frag->data[B91_FRAME_TYPE_OFFSET] & B91_ACK_REQUEST) { b91_handle_ack_en(); status = k_sem_take(&b91->ack_wait, K_MSEC(B91_ACK_WAIT_TIME_MS)); b91_handle_ack_dis(); } return status; } /* API implementation: ed_scan */ static int b91_ed_scan(const struct device *dev, uint16_t duration, energy_scan_done_cb_t done_cb) { ARG_UNUSED(dev); ARG_UNUSED(duration); ARG_UNUSED(done_cb); /* ed_scan not supported */ return -ENOTSUP; } /* API implementation: configure */ static int b91_configure(const struct device *dev, enum ieee802154_config_type type, const struct ieee802154_config *config) { ARG_UNUSED(dev); ARG_UNUSED(type); ARG_UNUSED(config); /* configure not supported */ return -ENOTSUP; } /* driver-allocated attribute memory - constant across all driver instances */ IEEE802154_DEFINE_PHY_SUPPORTED_CHANNELS(drv_attr, 11, 26); /* API implementation: attr_get */ static int b91_attr_get(const struct device *dev, enum ieee802154_attr attr, struct ieee802154_attr_value *value) { ARG_UNUSED(dev); return ieee802154_attr_get_channel_page_and_range( attr, IEEE802154_ATTR_PHY_CHANNEL_PAGE_ZERO_OQPSK_2450_BPSK_868_915, &drv_attr.phy_supported_channels, value); } /* IEEE802154 driver APIs structure */ static const struct ieee802154_radio_api b91_radio_api = { .iface_api.init = b91_iface_init, .get_capabilities = b91_get_capabilities, .cca = b91_cca, .set_channel = b91_set_channel, .filter = b91_filter, .set_txpower = b91_set_txpower, .start = b91_start, .stop = b91_stop, .tx = b91_tx, .ed_scan = b91_ed_scan, .configure = b91_configure, .attr_get = b91_attr_get, }; #if defined(CONFIG_NET_L2_IEEE802154) #define L2 IEEE802154_L2 #define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(IEEE802154_L2) #define MTU 125 #elif defined(CONFIG_NET_L2_OPENTHREAD) #define L2 OPENTHREAD_L2 #define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(OPENTHREAD_L2) #define MTU 1280 #endif /* IEEE802154 driver registration */ #if defined(CONFIG_NET_L2_IEEE802154) || defined(CONFIG_NET_L2_OPENTHREAD) NET_DEVICE_DT_INST_DEFINE(0, b91_init, NULL, &data, NULL, CONFIG_IEEE802154_B91_INIT_PRIO, &b91_radio_api, L2, L2_CTX_TYPE, MTU); #else DEVICE_DT_INST_DEFINE(0, b91_init, NULL, &data, NULL, POST_KERNEL, CONFIG_IEEE802154_B91_INIT_PRIO, &b91_radio_api); #endif