1 | /** @file wlan_mac_schedule.c |
---|
2 | * @brief Scheduler subsystem for high MAC framework |
---|
3 | * |
---|
4 | * This set of functions allows upper-level MAC implementations |
---|
5 | * to schedule the execution of a provided callback for some point |
---|
6 | * in the future. |
---|
7 | * |
---|
8 | * @copyright Copyright 2014-2019, Mango Communications. All rights reserved. |
---|
9 | * Distributed under the Mango Communications Reference Design License |
---|
10 | * See LICENSE.txt included in the design archive or |
---|
11 | * at http://mangocomm.com/802.11/license |
---|
12 | * |
---|
13 | * This file is part of the Mango 802.11 Reference Design (https://mangocomm.com/802.11) |
---|
14 | */ |
---|
15 | |
---|
16 | /***************************** Include Files *********************************/ |
---|
17 | #include "wlan_mac_high_sw_config.h" |
---|
18 | |
---|
19 | #include "xil_types.h" |
---|
20 | #include "xil_exception.h" |
---|
21 | |
---|
22 | #include "wlan_platform_high.h" |
---|
23 | #include "wlan_platform_common.h" |
---|
24 | #include "wlan_platform_timer.h" |
---|
25 | |
---|
26 | #include "wlan_mac_high.h" |
---|
27 | #include "wlan_mac_dl_list.h" |
---|
28 | #include "wlan_mac_schedule.h" |
---|
29 | |
---|
30 | |
---|
31 | /*************************** Variable Definitions ****************************/ |
---|
32 | // Global counter for assigning unique event IDs |
---|
33 | static volatile u32 sched_event_id_cntr; |
---|
34 | |
---|
35 | // Global to count number of currently-defined scheduled events |
---|
36 | static int total_num_scheduled_events; |
---|
37 | |
---|
38 | // Global array of schedules; reference code defines two by default |
---|
39 | #define NUM_SCHEDULES 2 |
---|
40 | static wlan_schedule_t wlan_schedules[NUM_SCHEDULES]; |
---|
41 | |
---|
42 | /******************************** Functions **********************************/ |
---|
43 | |
---|
44 | /*****************************************************************************/ |
---|
45 | /** |
---|
46 | * Initializes the scheduler subsystem. |
---|
47 | * |
---|
48 | * @param None |
---|
49 | * |
---|
50 | * @return WLAN_SUCCESS |
---|
51 | * |
---|
52 | *****************************************************************************/ |
---|
53 | int wlan_mac_schedule_init(){ |
---|
54 | int i; |
---|
55 | |
---|
56 | // Initialize globals |
---|
57 | total_num_scheduled_events = 0; |
---|
58 | |
---|
59 | // Counter increments as events are assigned unique IDs upon creation |
---|
60 | // Event IDs are globally unique and do not need to be densely packed |
---|
61 | // First event assigned ID 1 by convention |
---|
62 | sched_event_id_cntr = 1; |
---|
63 | |
---|
64 | // Initialize each schedule's event list |
---|
65 | for(i=0; i<NUM_SCHEDULES; i++) { |
---|
66 | dl_list_init(&(wlan_schedules[i].event_list)); |
---|
67 | } |
---|
68 | |
---|
69 | // Define the default schedules |
---|
70 | // By convention schedules are ordered most-frequent (#0) to least-frequent |
---|
71 | |
---|
72 | // Fine schedule - executes on every timer ISR |
---|
73 | wlan_schedules[SCHEDULE_ID_FINE].exec_interval = 0; |
---|
74 | wlan_schedules[SCHEDULE_ID_FINE].polls_to_next_exec = 0; |
---|
75 | |
---|
76 | // Coarse schedule - executes every 4096 timer ISRs (2.6msec for 64usec timer interval) |
---|
77 | wlan_schedules[SCHEDULE_ID_COARSE].exec_interval = 4096; |
---|
78 | wlan_schedules[SCHEDULE_ID_COARSE].polls_to_next_exec = 4095; |
---|
79 | |
---|
80 | return WLAN_SUCCESS; |
---|
81 | } |
---|
82 | |
---|
83 | /*****************************************************************************/ |
---|
84 | /** |
---|
85 | * Schedules the execution of a callback for some time in the future |
---|
86 | * |
---|
87 | * @param scheduler_sel - SCHEDULE_ID_COARSE or SCHEDULE_ID_FINE |
---|
88 | * @param interval_us - Interval (in microseconds) until callback should be called |
---|
89 | * @param num_calls - Number of repetitions or SCHEDULE_REPEAT_FOREVER for permanent periodic |
---|
90 | * @param callback - Function pointer to callback |
---|
91 | * |
---|
92 | * @return id - ID of scheduled event or SCHEDULE_FAILURE if error |
---|
93 | * |
---|
94 | *****************************************************************************/ |
---|
95 | u32 wlan_mac_schedule_add_event(u8 schedule_sel, u32 interval_us, u32 num_calls, void(*callback)()) { |
---|
96 | u32 id; |
---|
97 | dl_entry* event_entry_ptr; |
---|
98 | wlan_sched_event_t* event_ptr; |
---|
99 | u64 curr_system_time; |
---|
100 | |
---|
101 | if(schedule_sel >= NUM_SCHEDULES) { |
---|
102 | xil_printf("WARNING: invalid schedule selection %d\n", schedule_sel); |
---|
103 | return SCHEDULE_FAILURE; |
---|
104 | } |
---|
105 | |
---|
106 | // Allocate memory for data structures |
---|
107 | event_entry_ptr = wlan_mac_high_malloc(sizeof(dl_entry)); |
---|
108 | if (event_entry_ptr == NULL) { |
---|
109 | return SCHEDULE_FAILURE; |
---|
110 | } |
---|
111 | |
---|
112 | event_ptr = wlan_mac_high_malloc(sizeof(wlan_sched_event_t)); |
---|
113 | if (event_ptr == NULL) { |
---|
114 | wlan_mac_high_free(event_entry_ptr); |
---|
115 | return SCHEDULE_FAILURE; |
---|
116 | } |
---|
117 | |
---|
118 | // Attach the schedule struct to this dl_entry |
---|
119 | event_entry_ptr->data = event_ptr; |
---|
120 | |
---|
121 | // Get unique scheduled event ID from global counter |
---|
122 | // TODO: handle (very unlikely) case of exhausting all scheduled event IDs; this while loop would run forever as-is |
---|
123 | do{ |
---|
124 | id = (sched_event_id_cntr++); |
---|
125 | |
---|
126 | // Check if we hit the section of reserved IDs; Wrap back to 0 and start again |
---|
127 | if ((id >= SCHEDULE_ID_RESERVED_MIN) && (id <= SCHEDULE_ID_RESERVED_MAX)) { id = 1; } |
---|
128 | |
---|
129 | // Check if this candidate id is already allocated |
---|
130 | } while(wlan_mac_schedule_find_event(id, NULL) != NULL); |
---|
131 | |
---|
132 | // Get the current system time |
---|
133 | curr_system_time = get_system_time_usec(); |
---|
134 | |
---|
135 | // Initialize the scheduled event struct |
---|
136 | event_ptr->enabled = 1; |
---|
137 | event_ptr->id = id; |
---|
138 | event_ptr->interval_us = interval_us; |
---|
139 | event_ptr->num_calls = num_calls; |
---|
140 | event_ptr->target_us = curr_system_time + (u64)(interval_us); |
---|
141 | event_ptr->callback = (function_ptr_t)callback; |
---|
142 | |
---|
143 | // Add new scheduled event to the schedule's list of events |
---|
144 | total_num_scheduled_events++; |
---|
145 | dl_entry_insertEnd(&(wlan_schedules[schedule_sel].event_list), event_entry_ptr); |
---|
146 | |
---|
147 | // Check if this new scheduled event will be the only scheduled event |
---|
148 | // If so the timer must be started - the timer is stopped when there are |
---|
149 | // no scheduled events to avoid unnecessary interrupts |
---|
150 | if(total_num_scheduled_events == 1) { |
---|
151 | wlan_timer_start(); |
---|
152 | } |
---|
153 | |
---|
154 | return id; |
---|
155 | } |
---|
156 | |
---|
157 | |
---|
158 | int wlan_mac_schedule_disable_event(u32 event_id) { |
---|
159 | dl_entry* event_entry; |
---|
160 | |
---|
161 | // Check if event_id is valid |
---|
162 | event_entry = wlan_mac_schedule_find_event(event_id, NULL); |
---|
163 | |
---|
164 | if(event_entry == NULL) { |
---|
165 | xil_printf("WARNING: attempted to disable unknown event ID %d\n", event_id); |
---|
166 | return WLAN_FAILURE; |
---|
167 | } |
---|
168 | |
---|
169 | ((wlan_sched_event_t*)(event_entry->data))->enabled = 0; |
---|
170 | |
---|
171 | return WLAN_SUCCESS; |
---|
172 | } |
---|
173 | |
---|
174 | int wlan_mac_schedule_enable_event(u32 event_id) { |
---|
175 | dl_entry* event_entry; |
---|
176 | |
---|
177 | // Check if event_id is valid |
---|
178 | event_entry = wlan_mac_schedule_find_event(event_id, NULL); |
---|
179 | |
---|
180 | if(event_entry == NULL) { |
---|
181 | xil_printf("WARNING: attempted to enable unknown event ID %d\n", event_id); |
---|
182 | return WLAN_FAILURE; |
---|
183 | } |
---|
184 | |
---|
185 | // Enable the event and schedule its next execution |
---|
186 | ((wlan_sched_event_t*)(event_entry->data))->enabled = 1; |
---|
187 | ((wlan_sched_event_t*)(event_entry->data))->target_us = get_system_time_usec() + ((wlan_sched_event_t*)(event_entry->data))->interval_us; |
---|
188 | |
---|
189 | return WLAN_SUCCESS; |
---|
190 | } |
---|
191 | |
---|
192 | |
---|
193 | /*****************************************************************************/ |
---|
194 | /** |
---|
195 | * @brief Deletes a scheduled event |
---|
196 | * |
---|
197 | * @param event_id - ID of scheduled event to remove |
---|
198 | * |
---|
199 | * @return int status - WLAN SUCCESS or WLAN_FAILURE |
---|
200 | * |
---|
201 | *****************************************************************************/ |
---|
202 | int wlan_mac_schedule_remove_event(u32 event_id) { |
---|
203 | wlan_sched_event_t* event_ptr = NULL; |
---|
204 | dl_entry* event_entry_ptr = NULL; |
---|
205 | dl_list* event_list = NULL; |
---|
206 | |
---|
207 | // Lookup the event list entry from the event ID |
---|
208 | event_entry_ptr = wlan_mac_schedule_find_event(event_id, &event_list); |
---|
209 | |
---|
210 | if(event_entry_ptr == NULL) { |
---|
211 | xil_printf("WARNING: attempted to remove unknown schedule ID %d\n", event_id); |
---|
212 | return WLAN_FAILURE; |
---|
213 | } |
---|
214 | |
---|
215 | if(event_list == NULL) { |
---|
216 | xil_printf("WARNING: wlan_mac_schedule_find_event() returned NULL list for event ID %d\n", event_id); |
---|
217 | return WLAN_FAILURE; |
---|
218 | } |
---|
219 | |
---|
220 | // Extract the scheduled event struct from the dl_entry |
---|
221 | event_ptr = (wlan_sched_event_t*)(event_entry_ptr->data); |
---|
222 | |
---|
223 | if(event_ptr == NULL) { |
---|
224 | xil_printf("WARNING: schedule entry for ID %d had NULL data\n", event_id); |
---|
225 | return WLAN_FAILURE; |
---|
226 | } |
---|
227 | |
---|
228 | // Remove the list entry and free the entry/data memory allocations |
---|
229 | dl_entry_remove(event_list, event_entry_ptr); |
---|
230 | wlan_mac_high_free(event_entry_ptr); |
---|
231 | wlan_mac_high_free(event_ptr); |
---|
232 | |
---|
233 | total_num_scheduled_events--; |
---|
234 | |
---|
235 | // Stop the timer if there are now zero scheduled events |
---|
236 | if(total_num_scheduled_events == 0) { |
---|
237 | wlan_timer_stop(); |
---|
238 | } |
---|
239 | |
---|
240 | return WLAN_SUCCESS; |
---|
241 | } |
---|
242 | |
---|
243 | |
---|
244 | /*****************************************************************************/ |
---|
245 | /** |
---|
246 | * This function is called periodically (typically by a timer ISR) to |
---|
247 | * evaluate all schedules and scheduled events. |
---|
248 | * |
---|
249 | * @return Returns WLAN_SUCCESS |
---|
250 | * |
---|
251 | *****************************************************************************/ |
---|
252 | int wlan_mac_schedule_poll() { |
---|
253 | |
---|
254 | wlan_schedule_t* sched; |
---|
255 | dl_list* event_list; |
---|
256 | |
---|
257 | dl_entry* event_entry_ptr; |
---|
258 | wlan_sched_event_t* event_ptr; |
---|
259 | int event_id; |
---|
260 | function_ptr_t event_callback; |
---|
261 | |
---|
262 | int sched_id; |
---|
263 | int l_iter; |
---|
264 | |
---|
265 | // Record the current system time once here to avoid race conditions |
---|
266 | // when evaluating multiple schedules/events below |
---|
267 | u64 curr_system_time = get_system_time_usec(); |
---|
268 | |
---|
269 | // Evaluate every schedule |
---|
270 | for(sched_id=0; sched_id < NUM_SCHEDULES; sched_id++) { |
---|
271 | sched = &(wlan_schedules[sched_id]); |
---|
272 | event_list = &(wlan_schedules[sched_id].event_list); |
---|
273 | |
---|
274 | // Check if this schedule is due for evaluation |
---|
275 | // Scheduled events are only considered (via the while loop below) |
---|
276 | // when their parent schedule is evaluated. This saves loop iterations |
---|
277 | // and u64 comparisons for groups of infrequently-executed events |
---|
278 | if(sched->polls_to_next_exec > 0) { |
---|
279 | // Schedule not due for evaluation - decrement its poll counter and continue |
---|
280 | sched->polls_to_next_exec--; |
---|
281 | |
---|
282 | } else { |
---|
283 | // Schedule is due for evaluation - iterate over all its events |
---|
284 | |
---|
285 | // Initialize the dl_entry pointer to the head of the event_list |
---|
286 | event_entry_ptr = event_list->first; |
---|
287 | l_iter = event_list->length; |
---|
288 | |
---|
289 | while((event_entry_ptr != NULL) && (l_iter-- > 0)) { |
---|
290 | event_ptr = (wlan_sched_event_t*)event_entry_ptr->data; |
---|
291 | |
---|
292 | // Update the dl_entry pointer for the next iteration |
---|
293 | // This happens before the callback in case the callback removes the |
---|
294 | // event (callbacks shouldn't do this, but just in case) |
---|
295 | // TODO: consider making this even safer - what happens if callback N |
---|
296 | // removes scheduled event N+1? This pointer would be bogus. Perhaps |
---|
297 | // record the next pointer here then again below, punting if they differ? |
---|
298 | event_entry_ptr = dl_entry_next(event_entry_ptr); |
---|
299 | |
---|
300 | // Call the event's callback if it's enabled and its next execution time has passed |
---|
301 | // Evaluate 'enabled' first to avoid unnecessary u64 comparisons |
---|
302 | if((event_ptr->enabled == 1) && (curr_system_time >= (event_ptr->target_us))) { |
---|
303 | // Decrement the number of remaining calls for this event only if event |
---|
304 | // has finite and non-zero number of remaining calls |
---|
305 | if ((event_ptr->num_calls != SCHEDULE_REPEAT_FOREVER) && |
---|
306 | (event_ptr->num_calls != 0)) { |
---|
307 | |
---|
308 | (event_ptr->num_calls)--; |
---|
309 | } |
---|
310 | |
---|
311 | // Call the scheduled event's callback |
---|
312 | event_id = event_ptr->id; |
---|
313 | event_callback = event_ptr->callback; |
---|
314 | event_callback(event_id); |
---|
315 | |
---|
316 | // Update or remove the scheduled event *after* the callback in case the |
---|
317 | // callback changed the event's parameters. For example the callback might |
---|
318 | // reset the num_calls value, indicating the event should not be removed |
---|
319 | if(event_ptr->num_calls == 0) { |
---|
320 | // Remove event after its final execution |
---|
321 | wlan_mac_schedule_remove_event(event_id); |
---|
322 | } else { |
---|
323 | // Update target time for next execution |
---|
324 | event_ptr->target_us = curr_system_time + (u64)(event_ptr->interval_us); |
---|
325 | } |
---|
326 | } //END if(curr_system_time > event.target) |
---|
327 | } |
---|
328 | |
---|
329 | // Update the schedule after evaluating all its scheduled events |
---|
330 | // The fastest schedule always has exec_interval=polls_to_next_exec=0, |
---|
331 | if(sched->exec_interval != 0) { |
---|
332 | sched->polls_to_next_exec = sched->exec_interval; |
---|
333 | } |
---|
334 | |
---|
335 | } //END else of if(polls_to_next_exec > 0) |
---|
336 | } //END for(sched_id in wlan_schedules) |
---|
337 | |
---|
338 | return WLAN_SUCCESS; |
---|
339 | } |
---|
340 | |
---|
341 | |
---|
342 | |
---|
343 | /*****************************************************************************/ |
---|
344 | /** |
---|
345 | * Find schedule that corresponds to a given ID |
---|
346 | * |
---|
347 | * @param event_id - ID of the scheduled event to find |
---|
348 | * @param dl_list** - Return param for list containing the scheduled event, |
---|
349 | * if event_id is found. Pass NULL if list pointer is |
---|
350 | * not required by calling context. |
---|
351 | * |
---|
352 | * @return dl_entry* - Pointer to the list entry for the scheduled event |
---|
353 | * |
---|
354 | *****************************************************************************/ |
---|
355 | dl_entry* wlan_mac_schedule_find_event(u32 event_id, dl_list** sched_list_ret) { |
---|
356 | int i, iter; |
---|
357 | |
---|
358 | dl_list* event_list; |
---|
359 | dl_entry* curr_dl_entry; |
---|
360 | dl_entry* next_dl_entry; |
---|
361 | wlan_sched_event_t* curr_event; |
---|
362 | |
---|
363 | // Check all event lists for the matching ID |
---|
364 | for(i=0; i < NUM_SCHEDULES; i++) { |
---|
365 | event_list = &(wlan_schedules[i].event_list); |
---|
366 | |
---|
367 | // Initialize the loop variables |
---|
368 | iter = event_list->length; |
---|
369 | next_dl_entry = event_list->first; |
---|
370 | |
---|
371 | // Search the event_list for this event_id |
---|
372 | while ((next_dl_entry != NULL) && (iter-- > 0)) { |
---|
373 | curr_dl_entry = next_dl_entry; |
---|
374 | next_dl_entry = dl_entry_next(next_dl_entry); |
---|
375 | curr_event = (wlan_sched_event_t*)(curr_dl_entry->data); |
---|
376 | |
---|
377 | if(curr_event->id == event_id) { |
---|
378 | // Found a matching event - populate the return list argument |
---|
379 | // if the caller asked for it |
---|
380 | if(sched_list_ret != NULL) { |
---|
381 | *sched_list_ret = event_list; |
---|
382 | } |
---|
383 | return curr_dl_entry; |
---|
384 | } |
---|
385 | } |
---|
386 | } |
---|
387 | |
---|
388 | // Matching event not found |
---|
389 | return NULL; |
---|
390 | } |
---|