Skip to content

Commit

Permalink
Add new async_context abstraction and refactor cyw43_arch to use it (#…
Browse files Browse the repository at this point in the history
…1177)

* Extract all poll/threadsafe_background/freertos from cyw43_arch into new abstraction async_context:
* provides support for asynchronous events (timers/IRQ notifications) to be handled in a safe context.
* now guarantees all callbacks happen on a single core.
* is reusable by multiple different libraries (stdio_usb can now be ported to this but hasn't been yet).
* supports multiple independent instances (independent instances will not block each other).
* cyw43_arch libraries cleaned up to use the new abstraction. Note each distinct cyw43_arch type is now a very thin layer that creates the right type of context and adds cyw43_driver and lwip support as appropriate.

Additionally,

* Add new pico_time and hardware_alarm APIs
* Add from_us_since_boot()
* Add alarm_pool_create_with_unused_hardware_alarm()
* Add alarm_pool_add_alarm_at_force_in_context()
* Add hardware_alarm_claim_unused()
* Add hardware_alarm_force_irq()
* Added panic_compact() and some minor comment cleanup; moved FIRST_USER_IRQ define to platform_defs.h
  • Loading branch information
kilograham authored Jan 24, 2023
1 parent c578422 commit a540ca9
Show file tree
Hide file tree
Showing 47 changed files with 2,680 additions and 869 deletions.
2 changes: 2 additions & 0 deletions docs/index.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
* This group of libraries provide higher level functionality that isn't hardware related or provides a richer
* set of functionality above the basic hardware interfaces
* @{
* \defgroup pico_async_context pico_async_context
* \defgroup pico_multicore pico_multicore
* \defgroup pico_stdlib pico_stdlib
* \defgroup pico_sync pico_sync
Expand All @@ -59,6 +60,7 @@
* \defgroup networking Networking Libraries
* Functions for implementing networking
* @{
* \defgroup pico_cyw43_driver pico_cyw43_driver
* \defgroup pico_lwip pico_lwip
* \defgroup pico_cyw43_arch pico_cyw43_arch
* @}
Expand Down
12 changes: 12 additions & 0 deletions src/common/pico_base/include/pico/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ static inline void update_us_since_boot(absolute_time_t *t, uint64_t us_since_bo
#endif
}

/*! fn from_us_since_boot
* \brief convert a number of microseconds since boot to an absolute_time_t
* \param us_since_boot number of microseconds since boot
* \return an absolute time equivalent to us_since_boot
* \ingroup timestamp
*/
static inline absolute_time_t from_us_since_boot(uint64_t us_since_boot) {
absolute_time_t t;
update_us_since_boot(&t, us_since_boot);
return t;
}

#ifdef NDEBUG
#define ABSOLUTE_TIME_INITIALIZED_VAR(name, value) name = value
#else
Expand Down
49 changes: 49 additions & 0 deletions src/common/pico_time/include/pico/time.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ static inline int64_t absolute_time_diff_us(absolute_time_t from, absolute_time_
return (int64_t)(to_us_since_boot(to) - to_us_since_boot(from));
}

/*! \brief Return the earlier of two timestamps
* \ingroup timestamp
*
* \param a the first timestamp
* \param b the second timestamp
* \return the earlier of the two timestamps
*/
static inline absolute_time_t absolute_time_min(absolute_time_t a, absolute_time_t b) {
return to_us_since_boot(a) < to_us_since_boot(b) ? a : b;
}

/*! \brief The timestamp representing the end of time; this is actually not the maximum possible
* timestamp, but is set to 0x7fffffff_ffffffff microseconds to avoid sign overflows with time
* arithmetic. This is almost 300,000 years, so should be sufficient.
Expand Down Expand Up @@ -397,6 +408,25 @@ alarm_pool_t *alarm_pool_get_default(void);
*/
alarm_pool_t *alarm_pool_create(uint hardware_alarm_num, uint max_timers);

/**
* \brief Create an alarm pool, claiming an used hardware alarm to back it.
*
* The alarm pool will call callbacks from an alarm IRQ Handler on the core of this function is called from.
*
* In many situations there is never any need for anything other than the default alarm pool, however you
* might want to create another if you want alarm callbacks on core 1 or require alarm pools of
* different priority (IRQ priority based preemption of callbacks)
*
* \note This method will hard assert if the there is no free hardware to claim.
*
* \ingroup alarm
* \param max_timers the maximum number of timers
* \note For implementation reasons this is limited to PICO_PHEAP_MAX_ENTRIES which defaults to 255
* \sa alarm_pool_get_default()
* \sa hardware_claiming
*/
alarm_pool_t *alarm_pool_create_with_unused_hardware_alarm(uint max_timers);

/**
* \brief Return the hardware alarm used by an alarm pool
* \ingroup alarm
Expand Down Expand Up @@ -446,6 +476,25 @@ void alarm_pool_destroy(alarm_pool_t *pool);
*/
alarm_id_t alarm_pool_add_alarm_at(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback, void *user_data, bool fire_if_past);

/*!
* \brief Add an alarm callback to be called at or after a specific time
* \ingroup alarm
*
* The callback is called as soon as possible after the time specified from an IRQ handler
* on the core the alarm pool was created on. Unlike \ref alarm_pool_add_alarm_at, this method
* guarantees to call the callback from that core even if the time is during this method call or in the past.
*
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
*
* @param pool the alarm pool to use for scheduling the callback (this determines which hardware alarm is used, and which core calls the callback)
* @param time the timestamp when (after which) the callback should fire
* @param callback the callback function
* @param user_data user data to pass to the callback function
* @return >0 the alarm id for an active (at the time of return) alarm
* @return -1 if there were no alarm slots available
*/
alarm_id_t alarm_pool_add_alarm_at_force_in_context(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
void *user_data);
/*!
* \brief Add an alarm callback to be called after a delay specified in microseconds
* \ingroup alarm
Expand Down
32 changes: 31 additions & 1 deletion src/common/pico_time/time.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ void alarm_pool_init_default() {
if (!default_alarm_pool_initialized()) {
ph_post_alloc_init(default_alarm_pool.heap, PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS,
timer_pool_entry_comparator, &default_alarm_pool);
hardware_alarm_claim(PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM);
alarm_pool_post_alloc_init(&default_alarm_pool,
PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM);
}
Expand Down Expand Up @@ -181,12 +182,21 @@ alarm_pool_t *alarm_pool_create(uint hardware_alarm_num, uint max_timers) {
pool->heap = ph_create(max_timers, timer_pool_entry_comparator, pool);
pool->entries = (alarm_pool_entry_t *)calloc(max_timers, sizeof(alarm_pool_entry_t));
pool->entry_ids_high = (uint8_t *)calloc(max_timers, sizeof(uint8_t));
hardware_alarm_claim(hardware_alarm_num);
alarm_pool_post_alloc_init(pool, hardware_alarm_num);
return pool;
}

alarm_pool_t *alarm_pool_create_with_unused_hardware_alarm(uint max_timers) {
alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t));
pool->heap = ph_create(max_timers, timer_pool_entry_comparator, pool);
pool->entries = (alarm_pool_entry_t *)calloc(max_timers, sizeof(alarm_pool_entry_t));
pool->entry_ids_high = (uint8_t *)calloc(max_timers, sizeof(uint8_t));
alarm_pool_post_alloc_init(pool, (uint)hardware_alarm_claim_unused(true));
return pool;
}

void alarm_pool_post_alloc_init(alarm_pool_t *pool, uint hardware_alarm_num) {
hardware_alarm_claim(hardware_alarm_num);
hardware_alarm_cancel(hardware_alarm_num);
hardware_alarm_set_callback(hardware_alarm_num, alarm_pool_alarm_callback);
pool->lock = spin_lock_instance(next_striped_spin_lock_num());
Expand Down Expand Up @@ -260,6 +270,26 @@ alarm_id_t alarm_pool_add_alarm_at(alarm_pool_t *pool, absolute_time_t time, ala
return public_id;
}

alarm_id_t alarm_pool_add_alarm_at_force_in_context(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
void *user_data) {
bool missed = false;

uint8_t id_high = 0;
uint32_t save = spin_lock_blocking(pool->lock);

pheap_node_id_t id = add_alarm_under_lock(pool, time, callback, user_data, 0, true, &missed);
if (id) id_high = *get_entry_id_high(pool, id);
spin_unlock(pool->lock, save);
if (!id) return -1;
if (missed) {
// we want to fire the timer forcibly because it is in the past. Note that we do
// not care about racing with other timers, as it is harmless to have the IRQ
// wake up one time too many, we just need to make sure it does wake up
hardware_alarm_force_irq(pool->hardware_alarm_num);
}
return make_public_id(id_high, id);
}

bool alarm_pool_cancel_alarm(alarm_pool_t *pool, alarm_id_t alarm_id) {
bool rc = false;
uint32_t save = spin_lock_blocking(pool->lock);
Expand Down
3 changes: 2 additions & 1 deletion src/host/hardware_timer/include/hardware/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ bool time_reached(absolute_time_t t);
typedef void (*hardware_alarm_callback_t)(uint alarm_num);
void hardware_alarm_claim(uint alarm_num);
void hardware_alarm_unclaim(uint alarm_num);
int hardware_alarm_claim_unused(bool required);
void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback);
bool hardware_alarm_set_target(uint alarm_num, absolute_time_t t);
void hardware_alarm_cancel(uint alarm_num);

void hardware_alarm_force_irq(uint alarm_num);
#ifdef __cplusplus
}
#endif
Expand Down
12 changes: 12 additions & 0 deletions src/host/hardware_timer/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ void hardware_alarm_unclaim(uint alarm_num) {
claimed_alarms &= ~(1u <<alarm_num);
}

int hardware_alarm_claim_unused(bool required) {
int alarm_id = claimed_alarms ? __builtin_clz(~claimed_alarms) : 1;
if (alarm_id >= NUM_TIMERS) return -1;
claimed_alarms |= 1u << alarm_id;
return alarm_id;
}

PICO_WEAK_FUNCTION_DEF(hardware_alarm_set_callback)
void PICO_WEAK_FUNCTION_IMPL_NAME(hardware_alarm_set_callback)(uint alarm_num, hardware_alarm_callback_t callback) {
panic_unsupported();
Expand All @@ -101,3 +108,8 @@ PICO_WEAK_FUNCTION_DEF(hardware_alarm_cancel)
void PICO_WEAK_FUNCTION_IMPL_NAME(hardware_alarm_cancel)(uint alarm_num) {
panic_unsupported();
}

PICO_WEAK_FUNCTION_DEF(hardware_alarm_force_irq)
void PICO_WEAK_FUNCTION_IMPL_NAME(hardware_alarm_force_irq)(uint alarm_num) {
panic_unsupported();
}
2 changes: 2 additions & 0 deletions src/rp2040/hardware_regs/include/hardware/platform_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@
#define XOSC_MHZ _u(12)
#endif

#define FIRST_USER_IRQ (NUM_IRQS - NUM_USER_IRQS)

#endif

3 changes: 2 additions & 1 deletion src/rp2_common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ if (NOT PICO_BARE_METAL)
pico_add_subdirectory(tinyusb)
pico_add_subdirectory(pico_stdio_usb)

pico_add_subdirectory(cyw43_driver)
pico_add_subdirectory(pico_async_context)
pico_add_subdirectory(pico_cyw43_driver)
pico_add_subdirectory(pico_lwip)
pico_add_subdirectory(pico_cyw43_arch)
pico_add_subdirectory(pico_mbedtls)
Expand Down
9 changes: 5 additions & 4 deletions src/rp2_common/hardware_flash/include/hardware/flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
*
* Low level flash programming and erase API
*
* Note these functions are *unsafe* if you have two cores concurrently
* executing from flash. In this case you must perform your own
* synchronisation to make sure no XIP accesses take place during flash
* programming.
* Note these functions are *unsafe* if you are using both cores, and the other
* is executing from flash concurrently with the operation. In this could be the
* case, you must perform your own synchronisation to make sure that no XIP
* accesses take place during flash programming. One option is to use the
* \ref multicore_lockout functions.
*
* Likewise they are *unsafe* if you have interrupt handlers or an interrupt
* vector table in flash, so you must disable interrupts before calling in
Expand Down
2 changes: 0 additions & 2 deletions src/rp2_common/hardware_irq/irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,6 @@ void irq_init_priorities() {
#endif
}

#define FIRST_USER_IRQ (NUM_IRQS - NUM_USER_IRQS)

static uint get_user_irq_claim_index(uint irq_num) {
invalid_params_if(IRQ, irq_num < FIRST_USER_IRQ || irq_num >= NUM_IRQS);
// we count backwards from the last, to match the existing hard coded uses of user IRQs in the SDK which were previously using 31
Expand Down
26 changes: 24 additions & 2 deletions src/rp2_common/hardware_timer/include/hardware/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ typedef void (*hardware_alarm_callback_t)(uint alarm_num);
*/
void hardware_alarm_claim(uint alarm_num);

/*! \brief cooperatively claim the use of this hardware alarm_num
* \ingroup hardware_timer
*
* This method attempts to claim an unused hardware alarm
*
* \return alarm_num the hardware alarm claimed or -1 if requires was false, and none are available
* \sa hardware_claiming
*/
int hardware_alarm_claim_unused(bool required);

/*! \brief cooperatively release the claim on use of this hardware alarm_num
* \ingroup hardware_timer
*
Expand Down Expand Up @@ -187,11 +197,23 @@ bool hardware_alarm_set_target(uint alarm_num, absolute_time_t t);
* \brief Cancel an existing target (if any) for a given hardware_alarm
* \ingroup hardware_timer
*
* @param alarm_num
* @param alarm_num the hardware alarm number
*/

void hardware_alarm_cancel(uint alarm_num);

/**
* \brief Force and IRQ for a specific hardware alarm
* \ingroup hardware_timer
*
* This method will forcibly make sure the current alarm callback (if present) for the hardware
* alarm is called from an IRQ context after this call. If an actual callback is due at the same
* time then the callback may only be called once.
*
* Calling this method does not otherwise interfere with regular callback operations.
*
* @param alarm_num the hardware alarm number
*/
void hardware_alarm_force_irq(uint alarm_num);
#ifdef __cplusplus
}
#endif
Expand Down
19 changes: 15 additions & 4 deletions src/rp2_common/hardware_timer/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ bool hardware_alarm_is_claimed(uint alarm_num) {
return hw_is_claimed(&claimed, alarm_num);
}

int hardware_alarm_claim_unused(bool required) {
return hw_claim_unused_from_range(&claimed, required, 0, NUM_TIMERS - 1, "No timers available");
}

/// tag::time_us_64[]
uint64_t time_us_64() {
// Need to make sure that the upper 32 bits of the timer
Expand Down Expand Up @@ -108,9 +112,7 @@ static inline uint harware_alarm_irq_number(uint alarm_num) {

static void hardware_alarm_irq_handler(void) {
// Determine which timer this IRQ is for
uint32_t ipsr;
__asm volatile ("mrs %0, ipsr" : "=r" (ipsr)::);
uint alarm_num = (ipsr & 0x3fu) - 16 - TIMER_IRQ_0;
uint alarm_num = __get_current_exception() - 16 - TIMER_IRQ_0;
check_hardware_alarm_num_param(alarm_num);

hardware_alarm_callback_t callback = NULL;
Expand All @@ -119,6 +121,8 @@ static void hardware_alarm_irq_handler(void) {
uint32_t save = spin_lock_blocking(lock);
// Clear the timer IRQ (inside lock, because we check whether we have handled the IRQ yet in alarm_set by looking at the interrupt status
timer_hw->intr = 1u << alarm_num;
// Clear any forced IRQ
hw_clear_bits(&timer_hw->intf, 1u << alarm_num);

// make sure the IRQ is still valid
if (timer_callbacks_pending & (1u << alarm_num)) {
Expand Down Expand Up @@ -227,4 +231,11 @@ void hardware_alarm_cancel(uint alarm_num) {
spin_unlock(lock, save);
}


void hardware_alarm_force_irq(uint alarm_num) {
check_hardware_alarm_num_param(alarm_num);
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
uint32_t save = spin_lock_blocking(lock);
timer_callbacks_pending |= (uint8_t)(1u << alarm_num);
spin_unlock(lock, save);
hw_set_bits(&timer_hw->intf, 1u << alarm_num);
}
24 changes: 24 additions & 0 deletions src/rp2_common/pico_async_context/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
add_library(pico_async_context_base INTERFACE)
target_include_directories(pico_async_context_base INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_sources(pico_async_context_base INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_base.c
)
target_link_libraries(pico_async_context_base INTERFACE pico_platform)

pico_add_impl_library(pico_async_context_poll INTERFACE)
target_sources(pico_async_context_poll INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_poll.c
)
target_link_libraries(pico_async_context_poll INTERFACE pico_async_context_base)

pico_add_impl_library(pico_async_context_threadsafe_background INTERFACE)
target_sources(pico_async_context_threadsafe_background INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_threadsafe_background.c
)
target_link_libraries(pico_async_context_threadsafe_background INTERFACE pico_async_context_base)

pico_add_impl_library(pico_async_context_freertos INTERFACE)
target_sources(pico_async_context_freertos INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_freertos.c
)
target_link_libraries(pico_async_context_freertos INTERFACE pico_async_context_base)
Loading

0 comments on commit a540ca9

Please sign in to comment.