/* * Copyright (c) 2016 - 2019 Nordic Semiconductor ASA * Copyright (c) 2016 Vinayak Kariappa Chettimada * Copyright 2019 - 2020 NXP * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include "util/mem.h" #include "hal/ccm.h" #include "hal/radio.h" #include "lll/pdu_vendor.h" #include "ll_sw/pdu.h" #include "fsl_xcvr.h" #include "hal/cntr.h" #include "hal/ticker.h" #include "hal/swi.h" #include "fsl_cau3_ble.h" /* must be after irq.h */ #include "common/assert.h" #include #include "hal/debug.h" #define LOG_LEVEL CONFIG_BT_HCI_DRIVER_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_openisa_radio); static radio_isr_cb_t isr_cb; static void *isr_cb_param; #define RADIO_AESCCM_HDR_MASK 0xE3 /* AES-CCM: NESN, SN, MD bits masked to 0 */ #define RADIO_PDU_LEN_MAX (BIT(8) - 1) #define BYTES_TO_USEC(bytes, bits_per_usec) \ ((bytes) * 8 >> (__builtin_ffs(bits_per_usec) - 1)) /* us values */ #define MIN_CMD_TIME 10 /* Minimum interval for a delayed radio cmd */ #define RX_MARGIN 8 #define TX_MARGIN 0 #define RX_WTMRK 5 /* (AA + PDU header) - 1 */ #define AA_OVHD_1MBPS 27 /* AA playback overhead for 1 Mbps PHY */ #define AA_OVHD_2MBPS 10 /* AA playback overhead for 2 Mbps PHY */ #define RX_OVHD 32 /* Rx overhead */ #define PB_RX 544 /* half of PB (packet buffer) */ /* The PDU in packet buffer starts after the Access Address which is 4 octets */ #define PB_RX_PDU (PB_RX + 2) /* Rx PDU offset (in halfwords) in PB */ #define PB_TX_PDU 2 /* Tx PDU offset (in halfwords) in packet * buffer */ #define RADIO_ACTIVE_MASK 0x1fff #define RADIO_DISABLE_TMR 4 /* us */ /* Delay needed in order to enter Manual DSM. * Must be at least 4 ticks ahead of DSM_TIMER (from RM). */ #define DSM_ENTER_DELAY_TICKS 6 /* Delay needed in order to exit Manual DSM. * Should be after radio_tmr_start() (2 ticks after lll_clk_on()). * But less that 1.5ms (EVENT_OVERHEAD_XTAL_US) (ULL to LLL time offset). * Must be at least 4 ticks ahead of DSM_TIMER (from RM). */ #define DSM_EXIT_DELAY_TICKS 30 /* Mask to determine the state of DSM machine */ #define MAN_DSM_ON (RSIM_DSM_CONTROL_MAN_DEEP_SLEEP_STATUS_MASK \ | RSIM_DSM_CONTROL_DSM_MAN_READY_MASK \ | RSIM_DSM_CONTROL_MAN_SLEEP_REQUEST_MASK) \ static uint32_t dsm_ref; /* DSM reference counter */ static uint8_t delayed_radio_start; static uint8_t delayed_trx; static uint32_t delayed_ticks_start; static uint32_t delayed_remainder; static uint8_t delayed_radio_stop; static uint32_t delayed_hcto; static uint32_t rtc_start; static uint32_t rtc_diff_start_us; static uint32_t tmr_aa; /* AA (Access Address) timestamp saved value */ static uint32_t tmr_aa_save; /* save AA timestamp */ static uint32_t tmr_ready; /* radio ready for Tx/Rx timestamp */ static uint32_t tmr_end; /* Tx/Rx end timestamp saved value */ static uint32_t tmr_end_save; /* save Tx/Rx end timestamp */ static uint32_t tmr_tifs; static uint32_t rx_wu; static uint32_t tx_wu; static uint8_t phy_mode; /* Current PHY mode (DR_1MBPS or DR_2MBPS) */ static uint8_t bits_per_usec; /* This saves the # of bits per usec, * depending on the PHY mode */ static uint8_t phy_aa_ovhd; /* This saves the AA overhead, depending on the * PHY mode */ static uint32_t isr_tmr_aa; static uint32_t isr_tmr_end; static uint32_t isr_latency; static uint32_t next_wu; static uint32_t next_radio_cmd; static uint32_t radio_trx; static uint32_t force_bad_crc; static uint32_t skip_hcto; static uint8_t *rx_pkt_ptr; static uint32_t payload_max_size; static uint8_t MALIGN(4) _pkt_empty[PDU_EM_LL_SIZE_MAX]; static uint8_t MALIGN(4) _pkt_scratch[ ((RADIO_PDU_LEN_MAX + 3) > PDU_AC_LL_SIZE_MAX) ? (RADIO_PDU_LEN_MAX + 3) : PDU_AC_LL_SIZE_MAX]; static int8_t rssi; static struct { union { uint64_t counter; uint8_t bytes[CAU3_AES_BLOCK_SIZE - 1 - 2]; } nonce; /* used by the B0 format but not in-situ */ struct pdu_data *rx_pkt_out; struct pdu_data *rx_pkt_in; uint8_t auth_mic_valid; uint8_t empty_pdu_rxed; } ctx_ccm; #if defined(CONFIG_BT_CTLR_PRIVACY) #define RPA_NO_IRK_MATCH 0xFF /* No IRK match in AR table */ static struct { uint8_t ar_enable; uint32_t irk_idx; } radio_ar_ctx = {0U, RPA_NO_IRK_MATCH}; #endif /* CONFIG_BT_CTLR_PRIVACY */ static void tmp_cb(void *param) { uint32_t tmr = GENFSK->EVENT_TMR & GENFSK_EVENT_TMR_EVENT_TMR_MASK; uint32_t t2 = GENFSK->T2_CMP & GENFSK_T2_CMP_T2_CMP_MASK; isr_latency = (tmr - t2) & GENFSK_EVENT_TMR_EVENT_TMR_MASK; /* 24bit */ /* Mark as done */ *(uint32_t *)param = 1; } static void get_isr_latency(void) { volatile uint32_t tmp = 0; radio_isr_set(tmp_cb, (void *)&tmp); /* Reset TMR to zero */ GENFSK->EVENT_TMR = 0x1000000; radio_disable(); while (!tmp) { } irq_disable(LL_RADIO_IRQn_2nd_lvl); } static uint32_t radio_tmr_start_hlp(uint8_t trx, uint32_t ticks_start, uint32_t remainder); static void radio_tmr_hcto_configure_hlp(uint32_t hcto); static void radio_config_after_wake(void) { if (!delayed_radio_start) { delayed_radio_stop = 0; return; } delayed_radio_start = 0; radio_tmr_start_hlp(delayed_trx, delayed_ticks_start, delayed_remainder); if (delayed_radio_stop) { delayed_radio_stop = 0; /* Adjust time out as remainder was in radio_tmr_start_hlp() */ delayed_hcto += rtc_diff_start_us; radio_tmr_hcto_configure_hlp(delayed_hcto); } } #if defined(CONFIG_BT_CTLR_PRIVACY) static void ar_execute(void *pkt) { struct pdu_adv *pdu_adv = (struct pdu_adv *)pkt; bt_addr_t *rpa = (bt_addr_t *)&pdu_adv->payload[0]; /* Perform address resolution when TxAdd=1 and address is resolvable */ if (pdu_adv->tx_addr && BT_ADDR_IS_RPA(rpa)) { uint32_t *hash, *prand; status_t status; /* Use pointers to avoid breaking strict aliasing */ hash = (uint32_t *)(&rpa->val[0]); prand = (uint32_t *)(&rpa->val[3]); /* CAUv3 needs hash & prand in le format, right-justified */ status = CAU3_RPAtableSearch(CAU3, (*prand & 0xFFFFFF), (*hash & 0xFFFFFF), &radio_ar_ctx.irk_idx, kCAU3_TaskDoneEvent); if (status != kStatus_Success) { radio_ar_ctx.irk_idx = RPA_NO_IRK_MATCH; LOG_ERR("CAUv3 RPA table search failed %d", status); return; } } radio_ar_ctx.ar_enable = 0U; } #endif /* CONFIG_BT_CTLR_PRIVACY */ static void pkt_rx(void) { uint32_t len, idx; uint16_t *rxb = (uint16_t *)rx_pkt_ptr, tmp; volatile uint16_t *pb = &GENFSK->PACKET_BUFFER[PB_RX_PDU]; volatile const uint32_t *sts = &GENFSK->XCVR_STS; /* payload length */ len = (GENFSK->XCVR_CTRL & GENFSK_XCVR_CTRL_LENGTH_EXT_MASK) >> GENFSK_XCVR_CTRL_LENGTH_EXT_SHIFT; if (len > payload_max_size) { /* Unexpected size */ force_bad_crc = 1; next_radio_cmd = 0; while (*sts & GENFSK_XCVR_STS_RX_IN_PROGRESS_MASK) { } return; } /* For Data Physical Channel PDU, * assume no CTEInfo field, CP=0 => 2 bytes header */ /* Add PDU header size */ len += 2; /* Add to AA time, PDU + CRC time */ isr_tmr_end = isr_tmr_aa + BYTES_TO_USEC(len + 3, bits_per_usec); /* If not enough time for warmup after we copy the PDU from * packet buffer, send delayed command now */ if (next_radio_cmd) { /* Start Rx/Tx in TIFS */ idx = isr_tmr_end + tmr_tifs - next_wu; GENFSK->XCVR_CTRL = next_radio_cmd; GENFSK->T1_CMP = GENFSK_T1_CMP_T1_CMP(idx) | GENFSK_T1_CMP_T1_CMP_EN(1); next_radio_cmd = 0; } /* Can't rely on data read from packet buffer while in Rx */ /* Wait for Rx to finish */ while (*sts & GENFSK_XCVR_STS_RX_IN_PROGRESS_MASK) { } if (ctx_ccm.rx_pkt_out) { *(uint16_t *)ctx_ccm.rx_pkt_out = pb[0]; if (len < CAU3_BLE_MIC_SIZE) { ctx_ccm.rx_pkt_out = 0; ctx_ccm.rx_pkt_in = 0; ctx_ccm.empty_pdu_rxed = 1; } } /* Copy the PDU */ for (idx = 0; idx < len / 2; idx++) { rxb[idx] = pb[idx]; } /* Copy last byte */ if (len & 0x1) { tmp = pb[len / 2]; rx_pkt_ptr[len - 1] = ((uint8_t *)&tmp)[0]; } #if defined(CONFIG_BT_CTLR_PRIVACY) /* Perform address resolution on this Rx */ if (radio_ar_ctx.ar_enable) { ar_execute(rxb); } #endif /* CONFIG_BT_CTLR_PRIVACY */ force_bad_crc = 0; } #define IRQ_MASK ~(GENFSK_IRQ_CTRL_T2_IRQ_MASK | \ GENFSK_IRQ_CTRL_RX_WATERMARK_IRQ_MASK | \ GENFSK_IRQ_CTRL_TX_IRQ_MASK | \ GENFSK_IRQ_CTRL_WAKE_IRQ_MASK) void isr_radio(void *arg) { ARG_UNUSED(arg); uint32_t tmr = GENFSK->EVENT_TMR & GENFSK_EVENT_TMR_EVENT_TMR_MASK; uint32_t irq = GENFSK->IRQ_CTRL; uint32_t valid = 0; /* We need to check for a valid IRQ source. * In theory, we could get to this ISR after the IRQ source was cleared. * This could happen due to the way LLL interacts with IRQs * (radio_isr_set(), radio_disable()) and their propagation path: * GENFSK -> INTMUX(does not latch pending source interrupts) * INTMUX -> EVENT_UNIT */ if (irq & GENFSK_IRQ_CTRL_WAKE_IRQ_MASK) { /* Clear pending interrupts */ GENFSK->IRQ_CTRL &= 0xffffffff; /* Disable DSM_TIMER */ RSIM->DSM_CONTROL &= ~RSIM_DSM_CONTROL_DSM_TIMER_EN(1); radio_config_after_wake(); return; } if (irq & GENFSK_IRQ_CTRL_TX_IRQ_MASK) { valid = 1; GENFSK->IRQ_CTRL &= (IRQ_MASK | GENFSK_IRQ_CTRL_TX_IRQ_MASK); GENFSK->T1_CMP &= ~GENFSK_T1_CMP_T1_CMP_EN_MASK; isr_tmr_end = tmr - isr_latency; if (tmr_end_save) { tmr_end = isr_tmr_end; } radio_trx = 1; } if (irq & GENFSK_IRQ_CTRL_RX_WATERMARK_IRQ_MASK) { valid = 1; /* Disable Rx timeout */ /* 0b1010..RX Cancel -- Cancels pending RX events but do not * abort a RX-in-progress */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0xa); GENFSK->T2_CMP &= ~GENFSK_T2_CMP_T2_CMP_EN_MASK; GENFSK->IRQ_CTRL &= (IRQ_MASK | GENFSK_IRQ_CTRL_RX_WATERMARK_IRQ_MASK); GENFSK->T1_CMP &= ~GENFSK_T1_CMP_T1_CMP_EN_MASK; /* Fix reported AA time */ isr_tmr_aa = GENFSK->TIMESTAMP - phy_aa_ovhd; if (tmr_aa_save) { tmr_aa = isr_tmr_aa; } /* Copy the PDU as it arrives, calculates Rx end */ pkt_rx(); if (tmr_end_save) { tmr_end = isr_tmr_end; /* from pkt_rx() */ } radio_trx = 1; rssi = (GENFSK->XCVR_STS & GENFSK_XCVR_STS_RSSI_MASK) >> GENFSK_XCVR_STS_RSSI_SHIFT; } if (irq & GENFSK_IRQ_CTRL_T2_IRQ_MASK) { valid = 1; GENFSK->IRQ_CTRL &= (IRQ_MASK | GENFSK_IRQ_CTRL_T2_IRQ_MASK); /* Disable both comparators */ GENFSK->T1_CMP &= ~GENFSK_T1_CMP_T1_CMP_EN_MASK; GENFSK->T2_CMP &= ~GENFSK_T2_CMP_T2_CMP_EN_MASK; } if (radio_trx && next_radio_cmd) { /* Start Rx/Tx in TIFS */ tmr = isr_tmr_end + tmr_tifs - next_wu; GENFSK->XCVR_CTRL = next_radio_cmd; GENFSK->T1_CMP = GENFSK_T1_CMP_T1_CMP(tmr) | GENFSK_T1_CMP_T1_CMP_EN(1); next_radio_cmd = 0; } if (valid) { isr_cb(isr_cb_param); } } void radio_isr_set(radio_isr_cb_t cb, void *param) { irq_disable(LL_RADIO_IRQn_2nd_lvl); irq_disable(LL_RADIO_IRQn); isr_cb_param = param; isr_cb = cb; /* Clear pending interrupts */ GENFSK->IRQ_CTRL &= 0xffffffff; EVENT_UNIT->INTPTPENDCLEAR = (uint32_t)(1U << LL_RADIO_IRQn); irq_enable(LL_RADIO_IRQn); irq_enable(LL_RADIO_IRQn_2nd_lvl); } #define DISABLE_HPMCAL #ifdef DISABLE_HPMCAL #define WU_OPTIM 26 /* 34: quite ok, 36 few ok */ #define USE_FIXED_HPMCAL 563 static void hpmcal_disable(void) { #ifdef USE_FIXED_HPMCAL uint32_t hpmcal = USE_FIXED_HPMCAL; #else uint32_t hpmcal_vals[40]; uint32_t hpmcal; int i; GENFSK->TX_POWER = GENFSK_TX_POWER_TX_POWER(1); /* TX warm-up at Channel Frequency = 2.44GHz */ for (i = 0; i < ARRAY_SIZE(hpmcal_vals); i++) { GENFSK->CHANNEL_NUM = 2402 - 2360 + 2 * i; /* Reset TMR to zero */ GENFSK->EVENT_TMR = 0x1000000; /* TX Start Now */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0x1); while ((GENFSK->EVENT_TMR & 0xffffff) < 1000) ; /* Abort All */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0xb); /* Wait for XCVR to become idle. */ while (GENFSK->XCVR_CTRL & GENFSK_XCVR_CTRL_XCVR_BUSY_MASK) ; hpmcal_vals[i] = (XCVR_PLL_DIG->HPMCAL_CTRL & XCVR_PLL_DIG_HPMCAL_CTRL_HPM_CAL_FACTOR_MASK) >> XCVR_PLL_DIG_HPMCAL_CTRL_HPM_CAL_FACTOR_SHIFT; } hpmcal = hpmcal_vals[20]; #endif XCVR_PLL_DIG->HPMCAL_CTRL = (XCVR_PLL_DIG->HPMCAL_CTRL & ~XCVR_PLL_DIG_HPMCAL_CTRL_HPM_CAL_FACTOR_MANUAL_MASK) + XCVR_PLL_DIG_HPMCAL_CTRL_HPM_CAL_FACTOR_MANUAL(hpmcal) + XCVR_PLL_DIG_HPMCAL_CTRL_HP_CAL_DISABLE_MASK + 0x00000000; /* Move the sigma_delta_en signal to be 1us after pll_dig_en */ int pll_dig_en = (XCVR_TSM->TIMING34 & XCVR_TSM_TIMING34_PLL_DIG_EN_TX_HI_MASK) >> XCVR_TSM_TIMING34_PLL_DIG_EN_TX_HI_SHIFT; XCVR_TSM->TIMING38 = (XCVR_TSM->TIMING38 & ~XCVR_TSM_TIMING38_SIGMA_DELTA_EN_TX_HI_MASK) + XCVR_TSM_TIMING38_SIGMA_DELTA_EN_TX_HI(pll_dig_en+1); /* sigma_delta_en */ XCVR_TSM->TIMING19 -= B1(WU_OPTIM); /* sy_pd_filter_charge_en */ XCVR_TSM->TIMING24 -= B1(WU_OPTIM); /* sy_divn_cal_en */ XCVR_TSM->TIMING13 -= B1(WU_OPTIM); /* sy_vco_autotune_en */ XCVR_TSM->TIMING17 -= B0(WU_OPTIM); /* sy_lo_tx_buf_en */ XCVR_TSM->TIMING26 -= B0(WU_OPTIM); /* tx_pa_en */ XCVR_TSM->TIMING35 -= B0(WU_OPTIM); /* tx_dig_en */ XCVR_TSM->TIMING14 -= B0(WU_OPTIM); /* sy_pd_cycle_slip_ld_ft_en */ XCVR_TSM->END_OF_SEQ -= B1(WU_OPTIM) + B0(WU_OPTIM); } #endif void radio_setup(void) { XCVR_Reset(); #if defined(CONFIG_BT_CTLR_PRIVACY) || defined(CONFIG_BT_CTLR_LE_ENC) CAU3_Init(CAU3); #endif /* CONFIG_BT_CTLR_PRIVACY || CONFIG_BT_CTLR_LE_ENC */ XCVR_Init(GFSK_BT_0p5_h_0p5, DR_1MBPS); XCVR_SetXtalTrim(41); #ifdef DISABLE_HPMCAL hpmcal_disable(); #endif /* Enable Deep Sleep Mode for GENFSK */ XCVR_MISC->XCVR_CTRL |= XCVR_CTRL_XCVR_CTRL_MAN_DSM_SEL(2); /* Enable the CRC as it is disabled by default after reset */ XCVR_MISC->CRCW_CFG |= XCVR_CTRL_CRCW_CFG_CRCW_EN(1); /* Assign Radio #0 Interrupt to GENERIC_FSK */ XCVR_MISC->XCVR_CTRL |= XCVR_CTRL_XCVR_CTRL_RADIO0_IRQ_SEL(3); phy_mode = GENFSK->BITRATE = DR_1MBPS; bits_per_usec = 1; phy_aa_ovhd = AA_OVHD_1MBPS; /* * Split the buffer in equal parts: first half for Tx, * second half for Rx */ GENFSK->PB_PARTITION = GENFSK_PB_PARTITION_PB_PARTITION(PB_RX); /* Get warmup times. They are used in TIFS calculations */ rx_wu = (XCVR_TSM->END_OF_SEQ & XCVR_TSM_END_OF_SEQ_END_OF_RX_WU_MASK) >> XCVR_TSM_END_OF_SEQ_END_OF_RX_WU_SHIFT; tx_wu = (XCVR_TSM->END_OF_SEQ & XCVR_TSM_END_OF_SEQ_END_OF_TX_WU_MASK) >> XCVR_TSM_END_OF_SEQ_END_OF_TX_WU_SHIFT; /* IRQ config, clear pending interrupts */ irq_disable(LL_RADIO_IRQn_2nd_lvl); GENFSK->IRQ_CTRL = 0xffffffff; GENFSK->IRQ_CTRL = GENFSK_IRQ_CTRL_GENERIC_FSK_IRQ_EN(1) | GENFSK_IRQ_CTRL_RX_WATERMARK_IRQ_EN(1) | GENFSK_IRQ_CTRL_TX_IRQ_EN(1) | GENFSK_IRQ_CTRL_T2_IRQ_EN(1) | GENFSK_IRQ_CTRL_WAKE_IRQ_EN(1); /* Disable Rx recycle */ GENFSK->IRQ_CTRL |= GENFSK_IRQ_CTRL_CRC_IGNORE(1); GENFSK->WHITEN_SZ_THR |= GENFSK_WHITEN_SZ_THR_REC_BAD_PKT(1); /* Turn radio on to measure ISR latency */ if (radio_is_off()) { dsm_ref = 0; radio_wake(); while (radio_is_off()) { } } else { dsm_ref = 1; } get_isr_latency(); /* Turn radio off */ radio_sleep(); } void radio_reset(void) { irq_disable(LL_RADIO_IRQn_2nd_lvl); /* Vega radio is never disabled therefore doesn't require resetting */ } void radio_phy_set(uint8_t phy, uint8_t flags) { int err = 0; ARG_UNUSED(flags); /* This function sets one of two modes: * - BLE 1 Mbps * - BLE 2 Mbps * Coded BLE is not supported by VEGA */ switch (phy) { case BIT(0): default: if (phy_mode == DR_1MBPS) { break; } err = XCVR_ChangeMode(GFSK_BT_0p5_h_0p5, DR_1MBPS); if (err) { LOG_ERR("Failed to change PHY to 1 Mbps"); BT_ASSERT(0); } phy_mode = GENFSK->BITRATE = DR_1MBPS; bits_per_usec = 1; phy_aa_ovhd = AA_OVHD_1MBPS; break; case BIT(1): if (phy_mode == DR_2MBPS) { break; } err = XCVR_ChangeMode(GFSK_BT_0p5_h_0p5, DR_2MBPS); if (err) { LOG_ERR("Failed to change PHY to 2 Mbps"); BT_ASSERT(0); } phy_mode = GENFSK->BITRATE = DR_2MBPS; bits_per_usec = 2; phy_aa_ovhd = AA_OVHD_2MBPS; break; } } void radio_tx_power_set(uint32_t power) { ARG_UNUSED(power); /* tx_power_level should only have the values 1, 2, 4, 6, ... , 62. * Any value odd value (1, 3, ...) is not permitted and will result in * undefined behavior. * Those value have an associated db level that was not found in the * documentation. * Because of these inconsistencies for the moment this * function sets the power level to a known value. */ uint32_t tx_power_level = 62; GENFSK->TX_POWER = GENFSK_TX_POWER_TX_POWER(tx_power_level); } void radio_tx_power_max_set(void) { printk("%s\n", __func__); } void radio_freq_chan_set(uint32_t chan) { /* * The channel number for vega radio is computed * as 2360 + ch_num [in MHz] * The LLL expects the channel number to be computed as 2400 + ch_num. * Therefore a compensation of 40 MHz has been provided. */ GENFSK->CHANNEL_NUM = GENFSK_CHANNEL_NUM_CHANNEL_NUM(40 + chan); } #define GENFSK_BLE_WHITEN_START 1 /* after H0 */ #define GENFSK_BLE_WHITEN_END 1 /* at the end of CRC */ #define GENFSK_BLE_WHITEN_POLY_TYPE 0 /* Galois poly type */ #define GENFSK_BLE_WHITEN_SIZE 7 /* poly order */ #define GENFSK_BLE_WHITEN_POLY 0x04 void radio_whiten_iv_set(uint32_t iv) { GENFSK->WHITEN_CFG &= ~(GENFSK_WHITEN_CFG_WHITEN_START_MASK | GENFSK_WHITEN_CFG_WHITEN_END_MASK | GENFSK_WHITEN_CFG_WHITEN_B4_CRC_MASK | GENFSK_WHITEN_CFG_WHITEN_POLY_TYPE_MASK | GENFSK_WHITEN_CFG_WHITEN_REF_IN_MASK | GENFSK_WHITEN_CFG_WHITEN_PAYLOAD_REINIT_MASK | GENFSK_WHITEN_CFG_WHITEN_SIZE_MASK | GENFSK_WHITEN_CFG_MANCHESTER_EN_MASK | GENFSK_WHITEN_CFG_MANCHESTER_INV_MASK | GENFSK_WHITEN_CFG_MANCHESTER_START_MASK | GENFSK_WHITEN_CFG_WHITEN_INIT_MASK); GENFSK->WHITEN_CFG |= GENFSK_WHITEN_CFG_WHITEN_START(GENFSK_BLE_WHITEN_START) | GENFSK_WHITEN_CFG_WHITEN_END(GENFSK_BLE_WHITEN_END) | GENFSK_WHITEN_CFG_WHITEN_B4_CRC(0) | GENFSK_WHITEN_CFG_WHITEN_POLY_TYPE(GENFSK_BLE_WHITEN_POLY_TYPE) | GENFSK_WHITEN_CFG_WHITEN_REF_IN(0) | GENFSK_WHITEN_CFG_WHITEN_PAYLOAD_REINIT(0) | GENFSK_WHITEN_CFG_WHITEN_SIZE(GENFSK_BLE_WHITEN_SIZE) | GENFSK_WHITEN_CFG_MANCHESTER_EN(0) | GENFSK_WHITEN_CFG_MANCHESTER_INV(0) | GENFSK_WHITEN_CFG_MANCHESTER_START(0) | GENFSK_WHITEN_CFG_WHITEN_INIT(iv | 0x40); GENFSK->WHITEN_POLY = GENFSK_WHITEN_POLY_WHITEN_POLY(GENFSK_BLE_WHITEN_POLY); GENFSK->WHITEN_SZ_THR &= ~GENFSK_WHITEN_SZ_THR_WHITEN_SZ_THR_MASK; GENFSK->WHITEN_SZ_THR |= GENFSK_WHITEN_SZ_THR_WHITEN_SZ_THR(0); } void radio_aa_set(const uint8_t *aa) { /* Configure Access Address detection using NETWORK ADDRESS 0 */ GENFSK->NTW_ADR_0 = *((uint32_t *)aa); GENFSK->NTW_ADR_CTRL &= ~(GENFSK_NTW_ADR_CTRL_NTW_ADR0_SZ_MASK | GENFSK_NTW_ADR_CTRL_NTW_ADR_THR0_MASK); GENFSK->NTW_ADR_CTRL |= GENFSK_NTW_ADR_CTRL_NTW_ADR0_SZ(3) | GENFSK_NTW_ADR_CTRL_NTW_ADR_THR0(0); GENFSK->NTW_ADR_CTRL |= (uint32_t) ((1 << 0) << GENFSK_NTW_ADR_CTRL_NTW_ADR_EN_SHIFT); /* * The Access Address must be written in the packet buffer * in front of the PDU */ GENFSK->PACKET_BUFFER[0] = (aa[1] << 8) + aa[0]; GENFSK->PACKET_BUFFER[1] = (aa[3] << 8) + aa[2]; } #define GENFSK_BLE_CRC_SZ 3 /* 3 bytes */ #define GENFSK_BLE_PREAMBLE_SZ 0 /* 1 byte of preamble, depends on PHY type */ #define GENFSK_BLE_LEN_BIT_ORD 0 /* LSB */ #define GENFSK_BLE_SYNC_ADDR_SZ 3 /* 4 bytes, Access Address */ #define GENFSK_BLE_LEN_ADJ_SZ GENFSK_BLE_CRC_SZ /* adjust length with CRC * size */ #define GENFSK_BLE_H0_SZ 8 /* 8 bits */ void radio_pkt_configure(uint8_t bits_len, uint8_t max_len, uint8_t flags) { ARG_UNUSED(flags); payload_max_size = max_len; GENFSK->XCVR_CFG &= ~GENFSK_XCVR_CFG_PREAMBLE_SZ_MASK; GENFSK->XCVR_CFG |= GENFSK_XCVR_CFG_PREAMBLE_SZ(GENFSK_BLE_PREAMBLE_SZ); GENFSK->PACKET_CFG &= ~(GENFSK_PACKET_CFG_LENGTH_SZ_MASK | GENFSK_PACKET_CFG_LENGTH_BIT_ORD_MASK | GENFSK_PACKET_CFG_SYNC_ADDR_SZ_MASK | GENFSK_PACKET_CFG_LENGTH_ADJ_MASK | GENFSK_PACKET_CFG_H0_SZ_MASK | GENFSK_PACKET_CFG_H1_SZ_MASK); GENFSK->PACKET_CFG |= GENFSK_PACKET_CFG_LENGTH_SZ(bits_len) | GENFSK_PACKET_CFG_LENGTH_BIT_ORD(GENFSK_BLE_LEN_BIT_ORD) | GENFSK_PACKET_CFG_SYNC_ADDR_SZ(GENFSK_BLE_SYNC_ADDR_SZ) | GENFSK_PACKET_CFG_LENGTH_ADJ(GENFSK_BLE_LEN_ADJ_SZ) | GENFSK_PACKET_CFG_H0_SZ(GENFSK_BLE_H0_SZ) | GENFSK_PACKET_CFG_H1_SZ((8 - bits_len)); GENFSK->H0_CFG &= ~(GENFSK_H0_CFG_H0_MASK_MASK | GENFSK_H0_CFG_H0_MATCH_MASK); GENFSK->H0_CFG |= GENFSK_H0_CFG_H0_MASK(0) | GENFSK_H0_CFG_H0_MATCH(0); GENFSK->H1_CFG &= ~(GENFSK_H1_CFG_H1_MASK_MASK | GENFSK_H1_CFG_H1_MATCH_MASK); GENFSK->H1_CFG |= GENFSK_H1_CFG_H1_MASK(0) | GENFSK_H1_CFG_H1_MATCH(0); /* set Rx watermark to AA + PDU header */ GENFSK->RX_WATERMARK = GENFSK_RX_WATERMARK_RX_WATERMARK(RX_WTMRK); } void radio_pkt_rx_set(void *rx_packet) { rx_pkt_ptr = rx_packet; } void radio_pkt_tx_set(void *tx_packet) { /* * The GENERIC_FSK software must program the TX buffer * before commanding a TX operation, and must not access the RAM during * the transmission. */ uint16_t *pkt = tx_packet; uint32_t cnt = 0, pkt_len = (((uint8_t *)tx_packet)[1] + 1) / 2 + 1; volatile uint16_t *pkt_buffer = &GENFSK->PACKET_BUFFER[PB_TX_PDU]; for (; cnt < pkt_len; cnt++) { pkt_buffer[cnt] = pkt[cnt]; } } uint32_t radio_tx_ready_delay_get(uint8_t phy, uint8_t flags) { return tx_wu; } uint32_t radio_tx_chain_delay_get(uint8_t phy, uint8_t flags) { return 0; } uint32_t radio_rx_ready_delay_get(uint8_t phy, uint8_t flags) { return rx_wu; } uint32_t radio_rx_chain_delay_get(uint8_t phy, uint8_t flags) { /* RX_WTMRK = AA + PDU header, but AA time is already accounted for */ /* PDU header (assume 2 bytes) => 16us, depends on PHY type */ /* 2 * RX_OVHD = RX_WATERMARK_IRQ time - TIMESTAMP - isr_latency */ /* The rest is Rx margin that for now isn't well defined */ return BYTES_TO_USEC(2, bits_per_usec) + 2 * RX_OVHD + RX_MARGIN + isr_latency + RX_OVHD; } void radio_rx_enable(void) { /* Wait for idle state */ while (GENFSK->XCVR_STS & RADIO_ACTIVE_MASK) { } /* 0b0101..RX Start Now */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0x5); } void radio_tx_enable(void) { /* Wait for idle state */ while (GENFSK->XCVR_STS & RADIO_ACTIVE_MASK) { } /* 0b0001..TX Start Now */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0x1); } void radio_disable(void) { /* Disable both comparators */ GENFSK->T1_CMP = 0; GENFSK->T2_CMP = 0; /* * 0b1011..Abort All - Cancels all pending events and abort any * sequence-in-progress */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0xb); /* Wait for idle state */ while (GENFSK->XCVR_STS & RADIO_ACTIVE_MASK) { } /* Clear pending interrupts */ GENFSK->IRQ_CTRL &= 0xffffffff; EVENT_UNIT->INTPTPENDCLEAR = (uint32_t)(1U << LL_RADIO_IRQn); next_radio_cmd = 0; radio_trx = 0; /* generate T2 interrupt to get into isr_radio() */ uint32_t tmr = GENFSK->EVENT_TMR + RADIO_DISABLE_TMR; GENFSK->T2_CMP = GENFSK_T2_CMP_T2_CMP(tmr) | GENFSK_T2_CMP_T2_CMP_EN(1); } void radio_status_reset(void) { radio_trx = 0; } uint32_t radio_is_ready(void) { /* Always false. LLL expects the radio not to be in idle/Tx/Rx state */ return 0; } uint32_t radio_is_done(void) { return radio_trx; } uint32_t radio_has_disabled(void) { /* Not used */ return 0; } uint32_t radio_is_idle(void) { /* Vega radio is never disabled */ return 1; } #define GENFSK_BLE_CRC_START_BYTE 4 /* After Access Address */ #define GENFSK_BLE_CRC_BYTE_ORD 0 /* LSB */ void radio_crc_configure(uint32_t polynomial, uint32_t iv) { /* printk("%s poly: %08x, iv: %08x\n", __func__, polynomial, iv); */ GENFSK->CRC_CFG &= ~(GENFSK_CRC_CFG_CRC_SZ_MASK | GENFSK_CRC_CFG_CRC_START_BYTE_MASK | GENFSK_CRC_CFG_CRC_REF_IN_MASK | GENFSK_CRC_CFG_CRC_REF_OUT_MASK | GENFSK_CRC_CFG_CRC_BYTE_ORD_MASK); GENFSK->CRC_CFG |= GENFSK_CRC_CFG_CRC_SZ(GENFSK_BLE_CRC_SZ) | GENFSK_CRC_CFG_CRC_START_BYTE(GENFSK_BLE_CRC_START_BYTE) | GENFSK_CRC_CFG_CRC_REF_IN(0) | GENFSK_CRC_CFG_CRC_REF_OUT(0) | GENFSK_CRC_CFG_CRC_BYTE_ORD(GENFSK_BLE_CRC_BYTE_ORD); GENFSK->CRC_INIT = (iv << ((4U - GENFSK_BLE_CRC_SZ) << 3)); GENFSK->CRC_POLY = (polynomial << ((4U - GENFSK_BLE_CRC_SZ) << 3)); GENFSK->CRC_XOR_OUT = 0; /* * Enable CRC in hardware; this is already done at startup but we do it * here anyways just to be sure. */ GENFSK->XCVR_CFG &= ~GENFSK_XCVR_CFG_SW_CRC_EN_MASK; } uint32_t radio_crc_is_valid(void) { if (force_bad_crc) { return 0; } uint32_t radio_crc = (GENFSK->XCVR_STS & GENFSK_XCVR_STS_CRC_VALID_MASK) >> GENFSK_XCVR_STS_CRC_VALID_SHIFT; return radio_crc; } void *radio_pkt_empty_get(void) { return _pkt_empty; } void *radio_pkt_scratch_get(void) { return _pkt_scratch; } void radio_switch_complete_and_rx(uint8_t phy_rx) { /* 0b0110..RX Start @ T1 Timer Compare Match (EVENT_TMR = T1_CMP) */ next_radio_cmd = GENFSK_XCVR_CTRL_SEQCMD(0x6); /* the margin is used to account for any overhead in radio switching */ next_wu = rx_wu + RX_MARGIN; } void radio_switch_complete_and_tx(uint8_t phy_rx, uint8_t flags_rx, uint8_t phy_tx, uint8_t flags_tx) { /* 0b0010..TX Start @ T1 Timer Compare Match (EVENT_TMR = T1_CMP) */ next_radio_cmd = GENFSK_XCVR_CTRL_SEQCMD(0x2); /* the margin is used to account for any overhead in radio switching */ next_wu = tx_wu + TX_MARGIN; } void radio_switch_complete_and_disable(void) { next_radio_cmd = 0; } void radio_rssi_measure(void) { rssi = 0; } uint32_t radio_rssi_get(void) { return (uint32_t)-rssi; } void radio_rssi_status_reset(void) { } uint32_t radio_rssi_is_ready(void) { return (rssi != 0); } void radio_filter_configure(uint8_t bitmask_enable, uint8_t bitmask_addr_type, uint8_t *bdaddr) { /* printk("%s\n", __func__); */ } void radio_filter_disable(void) { /* Nothing to do here */ } void radio_filter_status_reset(void) { /* printk("%s\n", __func__); */ } uint32_t radio_filter_has_match(void) { /* printk("%s\n", __func__); */ return 0; } uint32_t radio_filter_match_get(void) { /* printk("%s\n", __func__); */ return 0; } void radio_bc_configure(uint32_t n) { printk("%s\n", __func__); } void radio_bc_status_reset(void) { printk("%s\n", __func__); } uint32_t radio_bc_has_match(void) { printk("%s\n", __func__); return 0; } void radio_tmr_status_reset(void) { tmr_aa_save = 0; tmr_end_save = 0; } void radio_tmr_tifs_set(uint32_t tifs) { tmr_tifs = tifs; } /* Start the radio after ticks_start (ticks) + remainder (us) time */ static uint32_t radio_tmr_start_hlp(uint8_t trx, uint32_t ticks_start, uint32_t remainder) { uint32_t radio_start_now_cmd = 0; /* Disable both comparators */ GENFSK->T1_CMP = 0; GENFSK->T2_CMP = 0; /* Save it for later */ rtc_start = ticks_start; /* Convert ticks to us and use just EVENT_TMR */ rtc_diff_start_us = HAL_TICKER_TICKS_TO_US(rtc_start - cntr_cnt_get()); skip_hcto = 0; if (rtc_diff_start_us > GENFSK_T1_CMP_T1_CMP_MASK) { /* ticks_start already passed. Don't start the radio */ rtc_diff_start_us = 0; /* Ignore time out as well */ skip_hcto = 1; return remainder; } remainder += rtc_diff_start_us; if (trx) { if (remainder <= MIN_CMD_TIME) { /* 0b0001..TX Start Now */ radio_start_now_cmd = GENFSK_XCVR_CTRL_SEQCMD(0x1); remainder = 0; } else { /* * 0b0010..TX Start @ T1 Timer Compare Match * (EVENT_TMR = T1_CMP) */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0x2); GENFSK->T1_CMP = GENFSK_T1_CMP_T1_CMP(remainder); } tmr_ready = remainder + tx_wu; } else { if (remainder <= MIN_CMD_TIME) { /* 0b0101..RX Start Now */ radio_start_now_cmd = GENFSK_XCVR_CTRL_SEQCMD(0x5); remainder = 0; } else { /* * 0b0110..RX Start @ T1 Timer Compare Match * (EVENT_TMR = T1_CMP) */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0x6); GENFSK->T1_CMP = GENFSK_T1_CMP_T1_CMP(remainder); } tmr_ready = remainder + rx_wu; } /* * reset EVENT_TMR should be done after ticks_start. * We converted ticks to us, so we reset it now. * All tmr_* times are relative to EVENT_TMR reset. * rtc_diff_start_us is used to adjust them */ GENFSK->EVENT_TMR = GENFSK_EVENT_TMR_EVENT_TMR_LD(1); if (radio_start_now_cmd) { /* trigger Rx/Tx Start Now */ GENFSK->XCVR_CTRL = radio_start_now_cmd; } else { /* enable T1_CMP to trigger the SEQCMD */ GENFSK->T1_CMP |= GENFSK_T1_CMP_T1_CMP_EN(1); } return remainder; } uint32_t radio_tmr_start(uint8_t trx, uint32_t ticks_start, uint32_t remainder) { if ((!(remainder / 1000000UL)) || (remainder & 0x80000000)) { ticks_start--; remainder += 30517578UL; } remainder /= 1000000UL; if (radio_is_off()) { delayed_radio_start = 1; delayed_trx = trx; delayed_ticks_start = ticks_start; delayed_remainder = remainder; return remainder; } delayed_radio_start = 0; return radio_tmr_start_hlp(trx, ticks_start, remainder); } uint32_t radio_tmr_start_tick(uint8_t trx, uint32_t tick) { /* Setup compare event with min. 1 us offset */ uint32_t remainder_us = 1; return radio_tmr_start_hlp(trx, tick, remainder_us); } void radio_tmr_start_us(uint8_t trx, uint32_t us) { printk("%s\n", __func__); } uint32_t radio_tmr_start_now(uint8_t trx) { printk("%s\n", __func__); return 0; } uint32_t radio_tmr_start_get(void) { return rtc_start; } void radio_tmr_stop(void) { } static void radio_tmr_hcto_configure_hlp(uint32_t hcto) { if (skip_hcto) { skip_hcto = 0; return; } /* 0b1001..RX Stop @ T2 Timer Compare Match (EVENT_TMR = T2_CMP) */ GENFSK->XCVR_CTRL = GENFSK_XCVR_CTRL_SEQCMD(0x9); GENFSK->T2_CMP = GENFSK_T2_CMP_T2_CMP(hcto) | GENFSK_T2_CMP_T2_CMP_EN(1); } /* Header completion time out */ void radio_tmr_hcto_configure(uint32_t hcto) { if (delayed_radio_start) { delayed_radio_stop = 1; delayed_hcto = hcto; return; } delayed_radio_stop = 0; radio_tmr_hcto_configure_hlp(hcto); } void radio_tmr_aa_capture(void) { tmr_aa_save = 1; } uint32_t radio_tmr_aa_get(void) { return tmr_aa - rtc_diff_start_us; } static uint32_t radio_tmr_aa; void radio_tmr_aa_save(uint32_t aa) { radio_tmr_aa = aa; } uint32_t radio_tmr_aa_restore(void) { return radio_tmr_aa; } uint32_t radio_tmr_ready_get(void) { return tmr_ready - rtc_diff_start_us; } void radio_tmr_end_capture(void) { tmr_end_save = 1; } uint32_t radio_tmr_end_get(void) { return tmr_end - rtc_diff_start_us; } uint32_t radio_tmr_tifs_base_get(void) { return radio_tmr_end_get() + rtc_diff_start_us; } void radio_tmr_sample(void) { printk("%s\n", __func__); } uint32_t radio_tmr_sample_get(void) { printk("%s\n", __func__); return 0; } void *radio_ccm_rx_pkt_set_ut(struct ccm *ccm, uint8_t phy, void *pkt) { /* Saved by LL as MSO to LSO in the ccm->key * SK (LSO to MSO) * :0x66:0xC6:0xC2:0x27:0x8E:0x3B:0x8E:0x05 * :0x3E:0x7E:0xA3:0x26:0x52:0x1B:0xAD:0x99 */ uint8_t key_local[16] __aligned(4) = { 0x99, 0xad, 0x1b, 0x52, 0x26, 0xa3, 0x7e, 0x3e, 0x05, 0x8e, 0x3b, 0x8e, 0x27, 0xc2, 0xc6, 0x66 }; void *result; /* ccm.key[16] is stored in MSO format, as retrieved from e function */ memcpy(ccm->key, key_local, sizeof(key_local)); /* Input std sample data, vol 6, part C, ch 1 */ _pkt_scratch[0] = 0x0f; _pkt_scratch[1] = 0x05; _pkt_scratch[2] = 0x9f; /* cleartext = 0x06*/ _pkt_scratch[3] = 0xcd; _pkt_scratch[4] = 0xa7; _pkt_scratch[5] = 0xf4; _pkt_scratch[6] = 0x48; /* IV std sample data, vol 6, part C, ch 1, stored in LL in LSO format * IV (LSO to MSO) :0x24:0xAB:0xDC:0xBA:0xBE:0xBA:0xAF:0xDE */ ccm->iv[0] = 0x24; ccm->iv[1] = 0xAB; ccm->iv[2] = 0xDC; ccm->iv[3] = 0xBA; ccm->iv[4] = 0xBE; ccm->iv[5] = 0xBA; ccm->iv[6] = 0xAF; ccm->iv[7] = 0xDE; result = radio_ccm_rx_pkt_set(ccm, phy, pkt); radio_ccm_is_done(); if (ctx_ccm.auth_mic_valid == 1 && ((uint8_t *)pkt)[2] == 0x06) { LOG_INF("Passed decrypt\n"); } else { LOG_INF("Failed decrypt\n"); } return result; } void *radio_ccm_rx_pkt_set(struct ccm *ccm, uint8_t phy, void *pkt) { uint8_t key_local[16] __aligned(4); status_t status; cau3_handle_t handle = { .keySlot = kCAU3_KeySlot2, .taskDone = kCAU3_TaskDonePoll }; ARG_UNUSED(phy); /* ccm.key[16] is stored in MSO format, as retrieved from e function */ memcpy(key_local, ccm->key, sizeof(key_local)); ctx_ccm.auth_mic_valid = 0; ctx_ccm.empty_pdu_rxed = 0; ctx_ccm.rx_pkt_in = (struct pdu_data *)_pkt_scratch; ctx_ccm.rx_pkt_out = (struct pdu_data *)pkt; ctx_ccm.nonce.counter = ccm->counter; /* LSO to MSO, counter is LE */ /* The directionBit set to 1 for Data Physical Chan PDUs sent by * the central and set to 0 for Data Physical Chan PDUs sent by the * peripheral */ ctx_ccm.nonce.bytes[4] |= ccm->direction << 7; memcpy(&ctx_ccm.nonce.bytes[5], ccm->iv, 8); /* LSO to MSO */ /* Loads the key into CAU3's DMEM and expands the AES key schedule. */ status = CAU3_AES_SetKey(CAU3, &handle, key_local, 16); if (status != kStatus_Success) { LOG_ERR("CAUv3 AES key set failed %d", status); return NULL; } return _pkt_scratch; } void *radio_ccm_tx_pkt_set_ut(struct ccm *ccm, void *pkt) { /* Clear: * 06 1b 17 00 37 36 35 34 33 32 31 30 41 42 43 * 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 */ uint8_t data_in[29] = { 0x06, 0x1b, 0x17, 0x00, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51 }; /* LL_DATA2: * 06 1f f3 88 81 e7 bd 94 c9 c3 69 b9 a6 68 46 * dd 47 86 aa 8c 39 ce 54 0d 0d ae 3a dc df 89 b9 60 88 */ uint8_t data_ref_out[33] = { 0x06, 0x1f, 0xf3, 0x88, 0x81, 0xe7, 0xbd, 0x94, 0xc9, 0xc3, 0x69, 0xb9, 0xa6, 0x68, 0x46, 0xdd, 0x47, 0x86, 0xaa, 0x8c, 0x39, 0xce, 0x54, 0x0d, 0x0d, 0xae, 0x3a, 0xdc, 0xdf, 0x89, 0xb9, 0x60, 0x88 }; /* Saved by LL as MSO to LSO in the ccm->key * SK (LSO to MSO) * :0x66:0xC6:0xC2:0x27:0x8E:0x3B:0x8E:0x05 * :0x3E:0x7E:0xA3:0x26:0x52:0x1B:0xAD:0x99 */ uint8_t key_local[16] __aligned(4) = { 0x99, 0xad, 0x1b, 0x52, 0x26, 0xa3, 0x7e, 0x3e, 0x05, 0x8e, 0x3b, 0x8e, 0x27, 0xc2, 0xc6, 0x66 }; void *result; /* ccm.key[16] is stored in MSO format, as retrieved from e function */ memcpy(ccm->key, key_local, sizeof(key_local)); memcpy(pkt, data_in, sizeof(data_in)); /* IV std sample data, vol 6, part C, ch 1, stored in LL in LSO format * IV (LSO to MSO) :0x24:0xAB:0xDC:0xBA:0xBE:0xBA:0xAF:0xDE */ ccm->iv[0] = 0x24; ccm->iv[1] = 0xAB; ccm->iv[2] = 0xDC; ccm->iv[3] = 0xBA; ccm->iv[4] = 0xBE; ccm->iv[5] = 0xBA; ccm->iv[6] = 0xAF; ccm->iv[7] = 0xDE; /* 4. Data packet2 (packet 1, S --> M) */ ccm->counter = 1; ccm->direction = 0; result = radio_ccm_tx_pkt_set(ccm, pkt); if (memcmp(result, data_ref_out, sizeof(data_ref_out))) { LOG_INF("Failed encrypt\n"); } else { LOG_INF("Passed encrypt\n"); } return result; } void *radio_ccm_tx_pkt_set(struct ccm *ccm, void *pkt) { uint8_t key_local[16] __aligned(4); uint8_t aad; uint8_t *auth_mic; status_t status; cau3_handle_t handle = { .keySlot = kCAU3_KeySlot2, .taskDone = kCAU3_TaskDonePoll }; /* Test for Empty PDU and bypass encryption */ if (((struct pdu_data *)pkt)->len == 0) { return pkt; } /* ccm.key[16] is stored in MSO format, as retrieved from e function */ memcpy(key_local, ccm->key, sizeof(key_local)); ctx_ccm.nonce.counter = ccm->counter; /* LSO to MSO, counter is LE */ /* The directionBit set to 1 for Data Physical Chan PDUs sent by * the central and set to 0 for Data Physical Chan PDUs sent by the * peripheral */ ctx_ccm.nonce.bytes[4] |= ccm->direction << 7; memcpy(&ctx_ccm.nonce.bytes[5], ccm->iv, 8); /* LSO to MSO */ /* Loads the key into CAU3's DMEM and expands the AES key schedule. */ status = CAU3_AES_SetKey(CAU3, &handle, key_local, 16); if (status != kStatus_Success) { LOG_ERR("CAUv3 AES key set failed %d", status); return NULL; } auth_mic = _pkt_scratch + 2 + ((struct pdu_data *)pkt)->len; aad = *(uint8_t *)pkt & RADIO_AESCCM_HDR_MASK; status = CAU3_AES_CCM_EncryptTag(CAU3, &handle, (uint8_t *)pkt + 2, ((struct pdu_data *)pkt)->len, _pkt_scratch + 2, ctx_ccm.nonce.bytes, 13, &aad, 1, auth_mic, CAU3_BLE_MIC_SIZE); if (status != kStatus_Success) { LOG_ERR("CAUv3 AES CCM decrypt failed %d", status); return 0; } _pkt_scratch[0] = *(uint8_t *)pkt; _pkt_scratch[1] = ((struct pdu_data *)pkt)->len + CAU3_BLE_MIC_SIZE; return _pkt_scratch; } uint32_t radio_ccm_is_done(void) { status_t status; uint8_t *auth_mic; uint8_t aad; cau3_handle_t handle = { .keySlot = kCAU3_KeySlot2, .taskDone = kCAU3_TaskDonePoll }; if (ctx_ccm.rx_pkt_in->len > CAU3_BLE_MIC_SIZE) { auth_mic = (uint8_t *)ctx_ccm.rx_pkt_in + 2 + ctx_ccm.rx_pkt_in->len - CAU3_BLE_MIC_SIZE; aad = *(uint8_t *)ctx_ccm.rx_pkt_in & RADIO_AESCCM_HDR_MASK; status = CAU3_AES_CCM_DecryptTag(CAU3, &handle, (uint8_t *)ctx_ccm.rx_pkt_in + 2, (uint8_t *)ctx_ccm.rx_pkt_out + 2, ctx_ccm.rx_pkt_in->len - CAU3_BLE_MIC_SIZE, ctx_ccm.nonce.bytes, 13, &aad, 1, auth_mic, CAU3_BLE_MIC_SIZE); if (status != kStatus_Success) { LOG_ERR("CAUv3 AES CCM decrypt failed %d", status); return 0; } ctx_ccm.auth_mic_valid = handle.micPassed; ctx_ccm.rx_pkt_out->len -= 4; } else if (ctx_ccm.rx_pkt_in->len == 0) { /* Just copy input into output */ *ctx_ccm.rx_pkt_out = *ctx_ccm.rx_pkt_in; ctx_ccm.auth_mic_valid = 1; ctx_ccm.empty_pdu_rxed = 1; } else { return 0; /* length only allowed 0, not 1,2,3,4 */ } return 1; } uint32_t radio_ccm_mic_is_valid(void) { return ctx_ccm.auth_mic_valid; } uint32_t radio_ccm_is_available(void) { return ctx_ccm.empty_pdu_rxed; } #if defined(CONFIG_BT_CTLR_PRIVACY) void radio_ar_configure(uint32_t nirk, void *irk) { status_t status; uint8_t pirk[16]; /* Initialize CAUv3 RPA table */ status = CAU3_RPAtableInit(CAU3, kCAU3_TaskDoneEvent); if (kStatus_Success != status) { LOG_ERR("CAUv3 RPA table init failed"); return; } /* CAUv3 RPA table is limited to CONFIG_BT_CTLR_RL_SIZE entries */ if (nirk > CONFIG_BT_CTLR_RL_SIZE) { LOG_WRN("Max CAUv3 RPA table size is %d", CONFIG_BT_CTLR_RL_SIZE); nirk = CONFIG_BT_CTLR_RL_SIZE; } /* Insert RPA keys(IRK) in table */ for (int i = 0; i < nirk; i++) { /* CAUv3 needs IRK in le format */ sys_memcpy_swap(pirk, (uint8_t *)irk + i * 16, 16); status = CAU3_RPAtableInsertKey(CAU3, (uint32_t *)&pirk, kCAU3_TaskDoneEvent); if (kStatus_Success != status) { LOG_ERR("CAUv3 RPA table insert failed"); return; } } /* RPA Table was configured, it can be used for next Rx */ radio_ar_ctx.ar_enable = 1U; } uint32_t radio_ar_match_get(void) { return radio_ar_ctx.irk_idx; } void radio_ar_status_reset(void) { radio_ar_ctx.ar_enable = 0U; radio_ar_ctx.irk_idx = RPA_NO_IRK_MATCH; } uint32_t radio_ar_has_match(void) { return (radio_ar_ctx.irk_idx != RPA_NO_IRK_MATCH); } #endif /* CONFIG_BT_CTLR_PRIVACY */ uint32_t radio_sleep(void) { if (dsm_ref == 0) { return -EALREADY; } uint32_t localref = --dsm_ref; #if (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_HIGH_PRIO) && \ (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) /* TODO: * These operations (decrement, check) should be atomic/critical section * since we turn radio on/off from different contexts/ISRs (LLL, radio). * It's fine for now as all the contexts have the same priority and * don't preempt each other. */ #else #error "Missing atomic operation in radio_sleep()" #endif if (localref == 0) { uint32_t status = (RSIM->DSM_CONTROL & MAN_DSM_ON); if (status) { /* Already in sleep mode */ return 0; } /* Disable DSM_TIMER */ RSIM->DSM_CONTROL &= ~RSIM_DSM_CONTROL_DSM_TIMER_EN(1); /* Get current DSM_TIMER value */ uint32_t dsm_timer = RSIM->DSM_TIMER; /* Set Sleep time after DSM_ENTER_DELAY */ RSIM->MAN_SLEEP = RSIM_MAN_SLEEP_MAN_SLEEP_TIME(dsm_timer + DSM_ENTER_DELAY_TICKS); /* Set Wake time to max, we use DSM early exit */ RSIM->MAN_WAKE = RSIM_MAN_WAKE_MAN_WAKE_TIME(dsm_timer - 1); /* MAN wakeup request enable */ RSIM->DSM_CONTROL |= RSIM_DSM_CONTROL_MAN_WAKEUP_REQUEST_EN(1); /* Enable DSM, sending a sleep request */ GENFSK->DSM_CTRL |= GENFSK_DSM_CTRL_GEN_SLEEP_REQUEST(1); /* Enable DSM_TIMER */ RSIM->DSM_CONTROL |= RSIM_DSM_CONTROL_DSM_TIMER_EN(1); } return 0; } uint32_t radio_wake(void) { uint32_t localref = ++dsm_ref; #if (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_HIGH_PRIO) && \ (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) /* TODO: * These operations (increment, check) should be atomic/critical section * since we turn radio on/off from different contexts/ISRs (LLL, radio). * It's fine for now as all the contexts have the same priority and * don't preempt each other. */ #else #error "Missing atomic operation in radio_wake()" #endif if (localref == 1) { uint32_t status = (RSIM->DSM_CONTROL & MAN_DSM_ON); if (!status) { /* Not in sleep mode */ return 0; } /* Get current DSM_TIMER value */ uint32_t dsm_timer = RSIM->DSM_TIMER; /* Set Wake time after DSM_ENTER_DELAY */ RSIM->MAN_WAKE = RSIM_MAN_WAKE_MAN_WAKE_TIME(dsm_timer + DSM_EXIT_DELAY_TICKS); } return 0; } uint32_t radio_is_off(void) { uint32_t status = (RSIM->DSM_CONTROL & MAN_DSM_ON); return status; }