| /* |
| * Copyright (c) 2018-2020 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <zephyr/types.h> |
| #include <device.h> |
| #include <drivers/entropy.h> |
| #include <drivers/clock_control.h> |
| #include <drivers/clock_control/nrf_clock_control.h> |
| |
| #include <soc.h> |
| |
| #include "hal/swi.h" |
| #include "hal/ccm.h" |
| #include "hal/radio.h" |
| #include "hal/ticker.h" |
| |
| #include "util/mem.h" |
| #include "util/memq.h" |
| #include "util/mayfly.h" |
| |
| #include "ticker/ticker.h" |
| |
| #include "lll.h" |
| #include "lll_vendor.h" |
| #include "lll_clock.h" |
| #include "lll_internal.h" |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) |
| #define LOG_MODULE_NAME bt_ctlr_lll |
| #include "common/log.h" |
| #include "hal/debug.h" |
| |
| #if defined(CONFIG_BT_CTLR_ZLI) |
| #define IRQ_CONNECT_FLAGS IRQ_ZERO_LATENCY |
| #else |
| #define IRQ_CONNECT_FLAGS 0 |
| #endif |
| |
| static struct { |
| struct { |
| void *param; |
| lll_is_abort_cb_t is_abort_cb; |
| lll_abort_cb_t abort_cb; |
| } curr; |
| } event; |
| |
| /* Entropy device */ |
| static struct device *dev_entropy; |
| |
| static int init_reset(void); |
| static int prepare(lll_is_abort_cb_t is_abort_cb, lll_abort_cb_t abort_cb, |
| lll_prepare_cb_t prepare_cb, int prio, |
| struct lll_prepare_param *prepare_param, u8_t is_resume); |
| static int resume_enqueue(lll_prepare_cb_t resume_cb, int resume_prio); |
| |
| #if !defined(CONFIG_BT_CTLR_LOW_LAT) |
| static void ticker_start_op_cb(u32_t status, void *param); |
| static void preempt_ticker_start(struct lll_prepare_param *prepare_param); |
| static void preempt_ticker_cb(u32_t ticks_at_expire, u32_t remainder, |
| u16_t lazy, void *param); |
| static void preempt(void *param); |
| #else /* CONFIG_BT_CTLR_LOW_LAT */ |
| #if (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) |
| static void ticker_op_job_disable(u32_t status, void *op_context); |
| #endif |
| #endif /* CONFIG_BT_CTLR_LOW_LAT */ |
| |
| ISR_DIRECT_DECLARE(radio_nrf5_isr) |
| { |
| DEBUG_RADIO_ISR(1); |
| |
| isr_radio(); |
| |
| ISR_DIRECT_PM(); |
| |
| DEBUG_RADIO_ISR(0); |
| return 1; |
| } |
| |
| static void rtc0_nrf5_isr(void *arg) |
| { |
| DEBUG_TICKER_ISR(1); |
| |
| /* On compare0 run ticker worker instance0 */ |
| if (NRF_RTC0->EVENTS_COMPARE[0]) { |
| NRF_RTC0->EVENTS_COMPARE[0] = 0; |
| |
| ticker_trigger(0); |
| } |
| |
| mayfly_run(TICKER_USER_ID_ULL_HIGH); |
| |
| #if !defined(CONFIG_BT_CTLR_LOW_LAT) && \ |
| (CONFIG_BT_CTLR_ULL_HIGH_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) |
| mayfly_run(TICKER_USER_ID_ULL_LOW); |
| #endif |
| |
| DEBUG_TICKER_ISR(0); |
| } |
| |
| static void swi_lll_nrf5_isr(void *arg) |
| { |
| DEBUG_RADIO_ISR(1); |
| |
| mayfly_run(TICKER_USER_ID_LLL); |
| |
| DEBUG_RADIO_ISR(0); |
| } |
| |
| #if defined(CONFIG_BT_CTLR_LOW_LAT) || \ |
| (CONFIG_BT_CTLR_ULL_HIGH_PRIO != CONFIG_BT_CTLR_ULL_LOW_PRIO) |
| static void swi_ull_low_nrf5_isr(void *arg) |
| { |
| DEBUG_TICKER_JOB(1); |
| |
| mayfly_run(TICKER_USER_ID_ULL_LOW); |
| |
| DEBUG_TICKER_JOB(0); |
| } |
| #endif |
| |
| int lll_init(void) |
| { |
| int err; |
| |
| /* Get reference to entropy device */ |
| dev_entropy = |
| device_get_binding(DT_LABEL(DT_INST(0, nordic_nrf_rng))); |
| if (!dev_entropy) { |
| return -ENODEV; |
| } |
| |
| /* Initialise LLL internals */ |
| event.curr.abort_cb = NULL; |
| |
| /* Initialize Clocks */ |
| err = lll_clock_init(); |
| if (err) { |
| return err; |
| } |
| |
| err = init_reset(); |
| if (err) { |
| return err; |
| } |
| |
| /* Initialize SW IRQ structure */ |
| hal_swi_init(); |
| |
| /* Connect ISRs */ |
| IRQ_DIRECT_CONNECT(RADIO_IRQn, CONFIG_BT_CTLR_LLL_PRIO, |
| radio_nrf5_isr, IRQ_CONNECT_FLAGS); |
| IRQ_CONNECT(RTC0_IRQn, CONFIG_BT_CTLR_ULL_HIGH_PRIO, |
| rtc0_nrf5_isr, NULL, 0); |
| IRQ_CONNECT(HAL_SWI_RADIO_IRQ, CONFIG_BT_CTLR_LLL_PRIO, |
| swi_lll_nrf5_isr, NULL, IRQ_CONNECT_FLAGS); |
| #if defined(CONFIG_BT_CTLR_LOW_LAT) || \ |
| (CONFIG_BT_CTLR_ULL_HIGH_PRIO != CONFIG_BT_CTLR_ULL_LOW_PRIO) |
| IRQ_CONNECT(HAL_SWI_JOB_IRQ, CONFIG_BT_CTLR_ULL_LOW_PRIO, |
| swi_ull_low_nrf5_isr, NULL, 0); |
| #endif |
| |
| /* Enable IRQs */ |
| irq_enable(RADIO_IRQn); |
| irq_enable(RTC0_IRQn); |
| irq_enable(HAL_SWI_RADIO_IRQ); |
| #if defined(CONFIG_BT_CTLR_LOW_LAT) || \ |
| (CONFIG_BT_CTLR_ULL_HIGH_PRIO != CONFIG_BT_CTLR_ULL_LOW_PRIO) |
| irq_enable(HAL_SWI_JOB_IRQ); |
| #endif |
| |
| return 0; |
| } |
| |
| u8_t lll_entropy_get(u8_t len, void *rand) |
| { |
| return entropy_get_entropy_isr(dev_entropy, rand, len, 0); |
| } |
| |
| int lll_reset(void) |
| { |
| int err; |
| |
| err = init_reset(); |
| if (err) { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int lll_prepare(lll_is_abort_cb_t is_abort_cb, lll_abort_cb_t abort_cb, |
| lll_prepare_cb_t prepare_cb, int prio, |
| struct lll_prepare_param *prepare_param) |
| { |
| return prepare(is_abort_cb, abort_cb, prepare_cb, prio, prepare_param, |
| 0); |
| } |
| |
| void lll_resume(void *param) |
| { |
| struct lll_event *next = param; |
| int ret; |
| |
| ret = prepare(next->is_abort_cb, next->abort_cb, next->prepare_cb, |
| next->prio, &next->prepare_param, next->is_resume); |
| LL_ASSERT(!ret || ret == -EINPROGRESS); |
| } |
| |
| void lll_disable(void *param) |
| { |
| /* LLL disable of current event, done is generated */ |
| if (!param || (param == event.curr.param)) { |
| if (event.curr.abort_cb && event.curr.param) { |
| event.curr.abort_cb(NULL, event.curr.param); |
| } else { |
| LL_ASSERT(!param); |
| } |
| } |
| { |
| struct lll_event *next; |
| u8_t idx = UINT8_MAX; |
| |
| next = ull_prepare_dequeue_iter(&idx); |
| while (next) { |
| if (!next->is_aborted && |
| (!param || (param == next->prepare_param.param))) { |
| next->is_aborted = 1; |
| next->abort_cb(&next->prepare_param, |
| next->prepare_param.param); |
| } |
| |
| next = ull_prepare_dequeue_iter(&idx); |
| } |
| } |
| } |
| |
| int lll_prepare_done(void *param) |
| { |
| #if defined(CONFIG_BT_CTLR_LOW_LAT) && \ |
| (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) |
| u32_t ret; |
| |
| /* Ticker Job Silence */ |
| ret = ticker_job_idle_get(TICKER_INSTANCE_ID_CTLR, |
| TICKER_USER_ID_LLL, |
| ticker_op_job_disable, NULL); |
| |
| return ((ret == TICKER_STATUS_SUCCESS) || |
| (ret == TICKER_STATUS_BUSY)) ? 0 : -EFAULT; |
| #else |
| return 0; |
| #endif /* CONFIG_BT_CTLR_LOW_LAT */ |
| } |
| |
| int lll_done(void *param) |
| { |
| struct lll_event *next = ull_prepare_dequeue_get(); |
| struct ull_hdr *ull = NULL; |
| void *evdone; |
| int ret = 0; |
| |
| /* Assert if param supplied without a pending prepare to cancel. */ |
| LL_ASSERT(!param || next); |
| |
| /* check if current LLL event is done */ |
| if (!param) { |
| /* Reset current event instance */ |
| LL_ASSERT(event.curr.abort_cb); |
| event.curr.abort_cb = NULL; |
| |
| param = event.curr.param; |
| event.curr.param = NULL; |
| |
| if (param) { |
| ull = HDR_ULL(((struct lll_hdr *)param)->parent); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_LOW_LAT) && |
| (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO)) { |
| mayfly_enable(TICKER_USER_ID_LLL, |
| TICKER_USER_ID_ULL_LOW, |
| 1); |
| } |
| |
| DEBUG_RADIO_CLOSE(0); |
| } else { |
| ull = HDR_ULL(((struct lll_hdr *)param)->parent); |
| } |
| |
| /* Let ULL know about LLL event done */ |
| evdone = ull_event_done(ull); |
| LL_ASSERT(evdone); |
| |
| return ret; |
| } |
| |
| bool lll_is_done(void *param) |
| { |
| /* FIXME: use param to check */ |
| return !event.curr.abort_cb; |
| } |
| |
| u32_t lll_evt_offset_get(struct evt_hdr *evt) |
| { |
| if (0) { |
| #if defined(CONFIG_BT_CTLR_XTAL_ADVANCED) |
| } else if (evt->ticks_xtal_to_start & XON_BITMASK) { |
| return MAX(evt->ticks_active_to_start, |
| evt->ticks_preempt_to_start); |
| #endif /* CONFIG_BT_CTLR_XTAL_ADVANCED */ |
| } else { |
| return MAX(evt->ticks_active_to_start, |
| evt->ticks_xtal_to_start); |
| } |
| } |
| |
| u32_t lll_preempt_calc(struct evt_hdr *evt, u8_t ticker_id, |
| u32_t ticks_at_event) |
| { |
| u32_t ticks_now = ticker_ticks_now_get(); |
| u32_t diff; |
| |
| diff = ticker_ticks_diff_get(ticks_now, ticks_at_event); |
| diff += HAL_TICKER_CNTR_CMP_OFFSET_MIN; |
| if (!(diff & BIT(HAL_TICKER_CNTR_MSBIT)) && |
| (diff > HAL_TICKER_US_TO_TICKS(EVENT_OVERHEAD_START_US))) { |
| /* TODO: for Low Latency Feature with Advanced XTAL feature. |
| * 1. Release retained HF clock. |
| * 2. Advance the radio event to accommodate normal prepare |
| * duration. |
| * 3. Increase the preempt to start ticks for future events. |
| */ |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| void lll_chan_set(u32_t chan) |
| { |
| switch (chan) { |
| case 37: |
| radio_freq_chan_set(2); |
| break; |
| |
| case 38: |
| radio_freq_chan_set(26); |
| break; |
| |
| case 39: |
| radio_freq_chan_set(80); |
| break; |
| |
| default: |
| if (chan < 11) { |
| radio_freq_chan_set(4 + (chan * 2U)); |
| } else if (chan < 40) { |
| radio_freq_chan_set(28 + ((chan - 11) * 2U)); |
| } else { |
| LL_ASSERT(0); |
| } |
| break; |
| } |
| |
| radio_whiten_iv_set(chan); |
| } |
| |
| |
| u32_t lll_radio_is_idle(void) |
| { |
| return radio_is_idle(); |
| } |
| |
| s8_t lll_radio_tx_pwr_min_get(void) |
| { |
| return radio_tx_power_min_get(); |
| } |
| |
| s8_t lll_radio_tx_pwr_max_get(void) |
| { |
| return radio_tx_power_max_get(); |
| } |
| |
| s8_t lll_radio_tx_pwr_floor(s8_t tx_pwr_lvl) |
| { |
| return radio_tx_power_floor(tx_pwr_lvl); |
| } |
| |
| static int init_reset(void) |
| { |
| return 0; |
| } |
| |
| static int prepare(lll_is_abort_cb_t is_abort_cb, lll_abort_cb_t abort_cb, |
| lll_prepare_cb_t prepare_cb, int prio, |
| struct lll_prepare_param *prepare_param, u8_t is_resume) |
| { |
| u8_t idx = UINT8_MAX; |
| struct lll_event *p; |
| int ret, err; |
| |
| /* Find the ready prepare in the pipeline */ |
| p = ull_prepare_dequeue_iter(&idx); |
| while (p && (p->is_aborted || p->is_resume)) { |
| p = ull_prepare_dequeue_iter(&idx); |
| } |
| |
| /* Current event active or another prepare is ready in the pipeline */ |
| if (event.curr.abort_cb || (p && is_resume)) { |
| #if defined(CONFIG_BT_CTLR_LOW_LAT) |
| lll_prepare_cb_t resume_cb; |
| struct lll_event *next; |
| int resume_prio; |
| #endif /* CONFIG_BT_CTLR_LOW_LAT */ |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_LOW_LAT) && event.curr.param) { |
| /* early abort */ |
| event.curr.abort_cb(NULL, event.curr.param); |
| } |
| |
| /* Store the next prepare for deferred call */ |
| ret = ull_prepare_enqueue(is_abort_cb, abort_cb, prepare_param, |
| prepare_cb, prio, is_resume); |
| LL_ASSERT(!ret); |
| |
| #if !defined(CONFIG_BT_CTLR_LOW_LAT) |
| if (is_resume) { |
| return -EINPROGRESS; |
| } |
| |
| preempt_ticker_start(prepare_param); |
| |
| #else /* CONFIG_BT_CTLR_LOW_LAT */ |
| next = NULL; |
| while (p) { |
| if (!p->is_aborted) { |
| if (event.curr.param == |
| p->prepare_param.param) { |
| p->is_aborted = 1; |
| p->abort_cb(&p->prepare_param, |
| p->prepare_param.param); |
| } else { |
| next = p; |
| } |
| } |
| |
| p = ull_prepare_dequeue_iter(&idx); |
| } |
| |
| if (next) { |
| /* check if resume requested by curr */ |
| ret = event.curr.is_abort_cb(NULL, 0, event.curr.param, |
| &resume_cb, &resume_prio); |
| LL_ASSERT(ret); |
| |
| if (ret == -EAGAIN) { |
| ret = resume_enqueue(resume_cb, resume_prio); |
| LL_ASSERT(!ret); |
| } else { |
| LL_ASSERT(ret == -ECANCELED); |
| } |
| } |
| #endif /* CONFIG_BT_CTLR_LOW_LAT */ |
| |
| return -EINPROGRESS; |
| } |
| |
| event.curr.param = prepare_param->param; |
| event.curr.is_abort_cb = is_abort_cb; |
| event.curr.abort_cb = abort_cb; |
| |
| err = prepare_cb(prepare_param); |
| |
| /* NOTE: Should the preempt timeout be stopped, check for any more |
| * in pipeline? |
| */ |
| |
| return err; |
| } |
| |
| static int resume_enqueue(lll_prepare_cb_t resume_cb, int resume_prio) |
| { |
| struct lll_prepare_param prepare_param; |
| |
| prepare_param.param = event.curr.param; |
| event.curr.param = NULL; |
| |
| return ull_prepare_enqueue(event.curr.is_abort_cb, event.curr.abort_cb, |
| &prepare_param, resume_cb, resume_prio, 1); |
| } |
| |
| #if !defined(CONFIG_BT_CTLR_LOW_LAT) |
| static void ticker_start_op_cb(u32_t status, void *param) |
| { |
| /* NOTE: this callback is present only for addition of debug messages |
| * when needed, else can be dispensed with. |
| */ |
| ARG_UNUSED(param); |
| |
| LL_ASSERT((status == TICKER_STATUS_SUCCESS) || |
| (status == TICKER_STATUS_FAILURE)); |
| } |
| |
| static void preempt_ticker_start(struct lll_prepare_param *prepare_param) |
| { |
| u32_t preempt_anchor; |
| struct evt_hdr *evt; |
| u32_t preempt_to; |
| int ret; |
| |
| /* Calc the preempt timeout */ |
| evt = HDR_LLL2EVT(prepare_param->param); |
| preempt_anchor = prepare_param->ticks_at_expire; |
| preempt_to = MAX(evt->ticks_active_to_start, |
| evt->ticks_xtal_to_start) - |
| evt->ticks_preempt_to_start; |
| |
| /* Setup pre empt timeout */ |
| ret = ticker_start(TICKER_INSTANCE_ID_CTLR, |
| TICKER_USER_ID_LLL, |
| TICKER_ID_LLL_PREEMPT, |
| preempt_anchor, |
| preempt_to, |
| TICKER_NULL_PERIOD, |
| TICKER_NULL_REMAINDER, |
| TICKER_NULL_LAZY, |
| TICKER_NULL_SLOT, |
| preempt_ticker_cb, NULL, |
| ticker_start_op_cb, NULL); |
| LL_ASSERT((ret == TICKER_STATUS_SUCCESS) || |
| (ret == TICKER_STATUS_FAILURE) || |
| (ret == TICKER_STATUS_BUSY)); |
| } |
| |
| static void preempt_ticker_cb(u32_t ticks_at_expire, u32_t remainder, |
| u16_t lazy, void *param) |
| { |
| static memq_link_t link; |
| static struct mayfly mfy = {0, 0, &link, NULL, preempt}; |
| u32_t ret; |
| |
| ret = mayfly_enqueue(TICKER_USER_ID_ULL_HIGH, TICKER_USER_ID_LLL, |
| 0, &mfy); |
| LL_ASSERT(!ret); |
| } |
| |
| static void preempt(void *param) |
| { |
| struct lll_event *next = ull_prepare_dequeue_get(); |
| lll_prepare_cb_t resume_cb; |
| u8_t idx = UINT8_MAX; |
| int resume_prio; |
| int ret; |
| |
| if (!event.curr.abort_cb || !event.curr.param) { |
| return; |
| } |
| |
| next = ull_prepare_dequeue_iter(&idx); |
| if (!next) { |
| return; |
| } |
| |
| while (next && (next->is_aborted || next->is_resume)) { |
| next = ull_prepare_dequeue_iter(&idx); |
| } |
| |
| if (!next) { |
| return; |
| } |
| |
| ret = event.curr.is_abort_cb(next->prepare_param.param, next->prio, |
| event.curr.param, |
| &resume_cb, &resume_prio); |
| if (!ret) { |
| /* Let LLL know about the cancelled prepare */ |
| next->is_aborted = 1; |
| next->abort_cb(&next->prepare_param, next->prepare_param.param); |
| |
| goto preempt_next; |
| } |
| |
| event.curr.abort_cb(NULL, event.curr.param); |
| |
| if (ret == -EAGAIN) { |
| struct lll_event *iter; |
| u8_t iter_idx = UINT8_MAX; |
| |
| iter = ull_prepare_dequeue_iter(&iter_idx); |
| while (iter) { |
| if (!iter->is_aborted && |
| event.curr.param == iter->prepare_param.param) { |
| iter->is_aborted = 1; |
| iter->abort_cb(&iter->prepare_param, |
| iter->prepare_param.param); |
| } |
| |
| iter = ull_prepare_dequeue_iter(&iter_idx); |
| } |
| |
| ret = resume_enqueue(resume_cb, resume_prio); |
| LL_ASSERT(!ret); |
| } else { |
| LL_ASSERT(ret == -ECANCELED); |
| } |
| |
| preempt_next: |
| do { |
| next = ull_prepare_dequeue_iter(&idx); |
| if (!next) { |
| return; |
| } |
| } while (next->is_aborted || next->is_resume); |
| |
| preempt_ticker_start(&next->prepare_param); |
| } |
| #else /* CONFIG_BT_CTLR_LOW_LAT */ |
| |
| #if (CONFIG_BT_CTLR_LLL_PRIO == CONFIG_BT_CTLR_ULL_LOW_PRIO) |
| static void ticker_op_job_disable(u32_t status, void *op_context) |
| { |
| ARG_UNUSED(status); |
| ARG_UNUSED(op_context); |
| |
| /* FIXME: */ |
| if (1 /* _radio.state != STATE_NONE */) { |
| mayfly_enable(TICKER_USER_ID_ULL_LOW, |
| TICKER_USER_ID_ULL_LOW, 0); |
| } |
| } |
| #endif |
| |
| #endif /* CONFIG_BT_CTLR_LOW_LAT */ |