/** @file wlan_mac_schedule.c * @brief Scheduler subsystem for high MAC framework * * This set of functions allows upper-level MAC implementations * to schedule the execution of a provided callback for some point * in the future. * * @copyright Copyright 2014-2019, Mango Communications. All rights reserved. * Distributed under the Mango Communications Reference Design License * See LICENSE.txt included in the design archive or * at http://mangocomm.com/802.11/license * * This file is part of the Mango 802.11 Reference Design (https://mangocomm.com/802.11) */ /***************************** Include Files *********************************/ #include "wlan_mac_high_sw_config.h" #include "xil_types.h" #include "xil_exception.h" #include "wlan_platform_high.h" #include "wlan_platform_common.h" #include "wlan_platform_timer.h" #include "wlan_mac_high.h" #include "wlan_mac_dl_list.h" #include "wlan_mac_schedule.h" /*************************** Variable Definitions ****************************/ // Global counter for assigning unique event IDs static volatile u32 sched_event_id_cntr; // Global to count number of currently-defined scheduled events static int total_num_scheduled_events; // Global array of schedules; reference code defines two by default #define NUM_SCHEDULES 2 static wlan_schedule_t wlan_schedules[NUM_SCHEDULES]; /******************************** Functions **********************************/ /*****************************************************************************/ /** * Initializes the scheduler subsystem. * * @param None * * @return WLAN_SUCCESS * *****************************************************************************/ int wlan_mac_schedule_init(){ int i; // Initialize globals total_num_scheduled_events = 0; // Counter increments as events are assigned unique IDs upon creation // Event IDs are globally unique and do not need to be densely packed // First event assigned ID 1 by convention sched_event_id_cntr = 1; // Initialize each schedule's event list for(i=0; i= NUM_SCHEDULES) { xil_printf("WARNING: invalid schedule selection %d\n", schedule_sel); return SCHEDULE_FAILURE; } // Allocate memory for data structures event_entry_ptr = wlan_mac_high_malloc(sizeof(dl_entry)); if (event_entry_ptr == NULL) { return SCHEDULE_FAILURE; } event_ptr = wlan_mac_high_malloc(sizeof(wlan_sched_event_t)); if (event_ptr == NULL) { wlan_mac_high_free(event_entry_ptr); return SCHEDULE_FAILURE; } // Attach the schedule struct to this dl_entry event_entry_ptr->data = event_ptr; // Get unique scheduled event ID from global counter // TODO: handle (very unlikely) case of exhausting all scheduled event IDs; this while loop would run forever as-is do{ id = (sched_event_id_cntr++); // Check if we hit the section of reserved IDs; Wrap back to 0 and start again if ((id >= SCHEDULE_ID_RESERVED_MIN) && (id <= SCHEDULE_ID_RESERVED_MAX)) { id = 1; } // Check if this candidate id is already allocated } while(wlan_mac_schedule_find_event(id, NULL) != NULL); // Get the current system time curr_system_time = get_system_time_usec(); // Initialize the scheduled event struct event_ptr->enabled = 1; event_ptr->id = id; event_ptr->interval_us = interval_us; event_ptr->num_calls = num_calls; event_ptr->target_us = curr_system_time + (u64)(interval_us); event_ptr->callback = (function_ptr_t)callback; // Add new scheduled event to the schedule's list of events total_num_scheduled_events++; dl_entry_insertEnd(&(wlan_schedules[schedule_sel].event_list), event_entry_ptr); // Check if this new scheduled event will be the only scheduled event // If so the timer must be started - the timer is stopped when there are // no scheduled events to avoid unnecessary interrupts if(total_num_scheduled_events == 1) { wlan_timer_start(); } return id; } int wlan_mac_schedule_disable_event(u32 event_id) { dl_entry* event_entry; // Check if event_id is valid event_entry = wlan_mac_schedule_find_event(event_id, NULL); if(event_entry == NULL) { xil_printf("WARNING: attempted to disable unknown event ID %d\n", event_id); return WLAN_FAILURE; } ((wlan_sched_event_t*)(event_entry->data))->enabled = 0; return WLAN_SUCCESS; } int wlan_mac_schedule_enable_event(u32 event_id) { dl_entry* event_entry; // Check if event_id is valid event_entry = wlan_mac_schedule_find_event(event_id, NULL); if(event_entry == NULL) { xil_printf("WARNING: attempted to enable unknown event ID %d\n", event_id); return WLAN_FAILURE; } // Enable the event and schedule its next execution ((wlan_sched_event_t*)(event_entry->data))->enabled = 1; ((wlan_sched_event_t*)(event_entry->data))->target_us = get_system_time_usec() + ((wlan_sched_event_t*)(event_entry->data))->interval_us; return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Deletes a scheduled event * * @param event_id - ID of scheduled event to remove * * @return int status - WLAN SUCCESS or WLAN_FAILURE * *****************************************************************************/ int wlan_mac_schedule_remove_event(u32 event_id) { wlan_sched_event_t* event_ptr = NULL; dl_entry* event_entry_ptr = NULL; dl_list* event_list = NULL; // Lookup the event list entry from the event ID event_entry_ptr = wlan_mac_schedule_find_event(event_id, &event_list); if(event_entry_ptr == NULL) { xil_printf("WARNING: attempted to remove unknown schedule ID %d\n", event_id); return WLAN_FAILURE; } if(event_list == NULL) { xil_printf("WARNING: wlan_mac_schedule_find_event() returned NULL list for event ID %d\n", event_id); return WLAN_FAILURE; } // Extract the scheduled event struct from the dl_entry event_ptr = (wlan_sched_event_t*)(event_entry_ptr->data); if(event_ptr == NULL) { xil_printf("WARNING: schedule entry for ID %d had NULL data\n", event_id); return WLAN_FAILURE; } // Remove the list entry and free the entry/data memory allocations dl_entry_remove(event_list, event_entry_ptr); wlan_mac_high_free(event_entry_ptr); wlan_mac_high_free(event_ptr); total_num_scheduled_events--; // Stop the timer if there are now zero scheduled events if(total_num_scheduled_events == 0) { wlan_timer_stop(); } return WLAN_SUCCESS; } /*****************************************************************************/ /** * This function is called periodically (typically by a timer ISR) to * evaluate all schedules and scheduled events. * * @return Returns WLAN_SUCCESS * *****************************************************************************/ int wlan_mac_schedule_poll() { wlan_schedule_t* sched; dl_list* event_list; dl_entry* event_entry_ptr; wlan_sched_event_t* event_ptr; int event_id; function_ptr_t event_callback; int sched_id; int l_iter; // Record the current system time once here to avoid race conditions // when evaluating multiple schedules/events below u64 curr_system_time = get_system_time_usec(); // Evaluate every schedule for(sched_id=0; sched_id < NUM_SCHEDULES; sched_id++) { sched = &(wlan_schedules[sched_id]); event_list = &(wlan_schedules[sched_id].event_list); // Check if this schedule is due for evaluation // Scheduled events are only considered (via the while loop below) // when their parent schedule is evaluated. This saves loop iterations // and u64 comparisons for groups of infrequently-executed events if(sched->polls_to_next_exec > 0) { // Schedule not due for evaluation - decrement its poll counter and continue sched->polls_to_next_exec--; } else { // Schedule is due for evaluation - iterate over all its events // Initialize the dl_entry pointer to the head of the event_list event_entry_ptr = event_list->first; l_iter = event_list->length; while((event_entry_ptr != NULL) && (l_iter-- > 0)) { event_ptr = (wlan_sched_event_t*)event_entry_ptr->data; // Update the dl_entry pointer for the next iteration // This happens before the callback in case the callback removes the // event (callbacks shouldn't do this, but just in case) // TODO: consider making this even safer - what happens if callback N // removes scheduled event N+1? This pointer would be bogus. Perhaps // record the next pointer here then again below, punting if they differ? event_entry_ptr = dl_entry_next(event_entry_ptr); // Call the event's callback if it's enabled and its next execution time has passed // Evaluate 'enabled' first to avoid unnecessary u64 comparisons if((event_ptr->enabled == 1) && (curr_system_time >= (event_ptr->target_us))) { // Decrement the number of remaining calls for this event only if event // has finite and non-zero number of remaining calls if ((event_ptr->num_calls != SCHEDULE_REPEAT_FOREVER) && (event_ptr->num_calls != 0)) { (event_ptr->num_calls)--; } // Call the scheduled event's callback event_id = event_ptr->id; event_callback = event_ptr->callback; event_callback(event_id); // Update or remove the scheduled event *after* the callback in case the // callback changed the event's parameters. For example the callback might // reset the num_calls value, indicating the event should not be removed if(event_ptr->num_calls == 0) { // Remove event after its final execution wlan_mac_schedule_remove_event(event_id); } else { // Update target time for next execution event_ptr->target_us = curr_system_time + (u64)(event_ptr->interval_us); } } //END if(curr_system_time > event.target) } // Update the schedule after evaluating all its scheduled events // The fastest schedule always has exec_interval=polls_to_next_exec=0, if(sched->exec_interval != 0) { sched->polls_to_next_exec = sched->exec_interval; } } //END else of if(polls_to_next_exec > 0) } //END for(sched_id in wlan_schedules) return WLAN_SUCCESS; } /*****************************************************************************/ /** * Find schedule that corresponds to a given ID * * @param event_id - ID of the scheduled event to find * @param dl_list** - Return param for list containing the scheduled event, * if event_id is found. Pass NULL if list pointer is * not required by calling context. * * @return dl_entry* - Pointer to the list entry for the scheduled event * *****************************************************************************/ dl_entry* wlan_mac_schedule_find_event(u32 event_id, dl_list** sched_list_ret) { int i, iter; dl_list* event_list; dl_entry* curr_dl_entry; dl_entry* next_dl_entry; wlan_sched_event_t* curr_event; // Check all event lists for the matching ID for(i=0; i < NUM_SCHEDULES; i++) { event_list = &(wlan_schedules[i].event_list); // Initialize the loop variables iter = event_list->length; next_dl_entry = event_list->first; // Search the event_list for this event_id while ((next_dl_entry != NULL) && (iter-- > 0)) { curr_dl_entry = next_dl_entry; next_dl_entry = dl_entry_next(next_dl_entry); curr_event = (wlan_sched_event_t*)(curr_dl_entry->data); if(curr_event->id == event_id) { // Found a matching event - populate the return list argument // if the caller asked for it if(sched_list_ret != NULL) { *sched_list_ret = event_list; } return curr_dl_entry; } } } // Matching event not found return NULL; }