/* * Copyright (c) 2017 Nordic Semiconductor ASA * Copyright (c) 2017 Exati Tecnologia Ltda. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include struct entropy_nrf5_dev_data { atomic_t user_count; }; #define DEV_DATA(dev) \ ((struct entropy_nrf5_dev_data *)(dev)->driver_data) static inline u8_t entropy_nrf5_get_u8(void) { while (!nrf_rng_event_get(NRF_RNG_EVENT_VALRDY)) { __WFE(); __SEV(); __WFE(); } nrf_rng_event_clear(NRF_RNG_EVENT_VALRDY); /* Clear the Pending status of the RNG interrupt so that it could * wake up the core when the VALRDY event occurs again. */ NVIC_ClearPendingIRQ(RNG_IRQn); return nrf_rng_random_value_get(); } static int entropy_nrf5_get_entropy(struct device *device, u8_t *buf, u16_t len) { /* Mark the peripheral as being used */ atomic_inc(&DEV_DATA(device)->user_count); /* Disable the shortcut that stops the task after a byte is generated */ nrf_rng_shorts_disable(NRF_RNG_SHORT_VALRDY_STOP_MASK); /* Start the RNG generator peripheral */ nrf_rng_task_trigger(NRF_RNG_TASK_START); while (len) { *buf = entropy_nrf5_get_u8(); buf++; len--; } /* Only stop the RNG generator peripheral if we're the last user */ if (atomic_dec(&DEV_DATA(device)->user_count) == 1) { /* Disable the peripheral on the next VALRDY event */ nrf_rng_shorts_enable(NRF_RNG_SHORT_VALRDY_STOP_MASK); if (atomic_get(&DEV_DATA(device)->user_count) != 0) { /* Race condition: another thread started to use * the peripheral while we were disabling it. * Enable the peripheral again */ nrf_rng_shorts_disable(NRF_RNG_SHORT_VALRDY_STOP_MASK); nrf_rng_task_trigger(NRF_RNG_TASK_START); } } return 0; } static int entropy_nrf5_init(struct device *device) { /* Enable the RNG interrupt to be generated on the VALRDY event, * but do not enable this interrupt in NVIC to be serviced. * When the interrupt enters the Pending state it will set internal * event (SEVONPEND is activated by kernel) and wake up the core * if it was suspended by WFE. And that's enough. */ nrf_rng_int_enable(NRF_RNG_INT_VALRDY_MASK); NVIC_ClearPendingIRQ(RNG_IRQn); /* Enable or disable bias correction */ if (IS_ENABLED(CONFIG_ENTROPY_NRF5_BIAS_CORRECTION)) { nrf_rng_error_correction_enable(); } else { nrf_rng_error_correction_disable(); } /* Initialize the user count with zero */ atomic_clear(&DEV_DATA(device)->user_count); return 0; } static struct entropy_nrf5_dev_data entropy_nrf5_data; static const struct entropy_driver_api entropy_nrf5_api_funcs = { .get_entropy = entropy_nrf5_get_entropy }; DEVICE_AND_API_INIT(entropy_nrf5, CONFIG_ENTROPY_NAME, entropy_nrf5_init, &entropy_nrf5_data, NULL, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &entropy_nrf5_api_funcs);