/** @file wlan_mac_ltg.c * @brief Local Traffic Generator * * This contains code for scheduling local traffic directly from the * board. * * @copyright Copyright 2013-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 "stdlib.h" #include "stdio.h" #include "string.h" #include "wlan_mac_dl_list.h" #include "wlan_exp_common.h" #include "wlan_mac_common.h" #include "wlan_mac_802_11_defs.h" #include "wlan_mac_eth_util.h" #include "wlan_mac_high.h" #include "wlan_mac_ltg.h" #include "wlan_mac_schedule.h" #include "wlan_platform_common.h" #include "wlan_platform_high.h" #include "wlan_mac_packet_types.h" #include "wlan_platform_intc.h" #if WLAN_SW_CONFIG_ENABLE_LTG /*************************** Constant Definitions ****************************/ platform_high_dev_info_t platform_high_dev_info; /*************************** Variable Definitions ****************************/ static dl_list tg_list; static function_ptr_t ltg_callback; static volatile u64 num_ltg_checks; static volatile u32 schedule_id; static volatile u8 schedule_running; /*************************** Functions Prototypes ****************************/ void ltg_sched_check(); int ltg_sched_start_l(dl_entry* curr_tg_dl_entry); int ltg_sched_stop_l(dl_entry* curr_tg_dl_entry); dl_entry* ltg_sched_create_l(); void ltg_sched_destroy_l(dl_entry* tg_dl_entry); void ltg_sched_destroy_params(tg_schedule* tg); /******************************** Functions **********************************/ /*****************************************************************************/ /** * @brief Initialize the LTG Framework * * This function is called by the High Framework when first starting up * @return None */ int wlan_mac_ltg_sched_init(){ platform_high_dev_info = wlan_platform_high_get_dev_info(); int return_value = 0; schedule_running = 0; schedule_id = SCHEDULE_FAILURE; num_ltg_checks = 0; ltg_sched_remove(LTG_REMOVE_ALL); dl_list_init(&tg_list); ltg_callback = (function_ptr_t)wlan_null_callback; return return_value; } /*****************************************************************************/ /** * @brief Set LTG event callback * * This function should be called by user application to configure a callback * that will be called according to an LTG schedule. * * @param callback - function pointer * @return None */ void wlan_mac_ltg_sched_set_callback(void(*callback)()){ ltg_callback = (function_ptr_t)callback; } /*****************************************************************************/ /** * @brief Create an LTG schedule * * This function will set up (but not start) an LTG. * * @param type - LTG_SCHED_TYPE_PERIODIC or LTG_SCHED_TYPE_UNIFORM_RAND * @param params - pointer to ltg_sched_periodic_params_t or ltg_sched_uniform_rand_params_t * @param callback_arg - pointer to ltg_pyld_fixed_t, ltg_pyld_all_assoc_fixed_t, or ltg_pyld_uniform_rand_t * @param cleanup_callback - optional function pointer to be called upon destruction of LTG. NULL if not needed. * @return ltg_id - ID of LTG that is created. Used as argument to stop, stop, and remove functions */ u32 ltg_sched_create(u32 type, void* params, void* callback_arg, void(*cleanup_callback)()){ static u32 id = 0; u32 return_value; tg_schedule* curr_tg; dl_entry* curr_tg_dl_entry; //Create a new tg for this id curr_tg_dl_entry = ltg_sched_create_l(); if(curr_tg_dl_entry == NULL){ return_value = LTG_ID_INVALID; return return_value; } curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); curr_tg->id = id; return_value = id; // Increment LTG ID so that it is unique per LTG id++; if(id == LTG_ID_INVALID){ id++; } curr_tg->type = type; curr_tg->cleanup_callback = (function_ptr_t)cleanup_callback; switch(type){ case LTG_SCHED_TYPE_PERIODIC: curr_tg->params = wlan_mac_high_malloc(sizeof(ltg_sched_periodic_params_t)); curr_tg->state = wlan_mac_high_malloc(sizeof(ltg_sched_periodic_state_t)); if(curr_tg->params != NULL && curr_tg->state != NULL){ bzero(curr_tg->state, sizeof(ltg_sched_periodic_state_t)); memcpy(curr_tg->params, params, sizeof(ltg_sched_periodic_params_t)); curr_tg->callback_arg = callback_arg; } else { xil_printf("LTG: ERROR: Failed to initialize LTG structs\n"); ltg_sched_destroy_l(curr_tg_dl_entry); return LTG_ID_INVALID; } break; case LTG_SCHED_TYPE_UNIFORM_RAND: curr_tg->params = wlan_mac_high_malloc(sizeof(ltg_sched_uniform_rand_params_t)); curr_tg->state = wlan_mac_high_malloc(sizeof(ltg_sched_uniform_rand_state_t)); if(curr_tg->params != NULL && curr_tg->state != NULL){ bzero(curr_tg->state, sizeof(ltg_sched_uniform_rand_state_t)); memcpy(curr_tg->params, params, sizeof(ltg_sched_uniform_rand_params_t)); curr_tg->callback_arg = callback_arg; } else { xil_printf("LTG: ERROR: Failed to initialize LTG structs\n"); ltg_sched_destroy_l(curr_tg_dl_entry); return LTG_ID_INVALID; } break; default: xil_printf("LTG: ERROR: Unknown type %d\n", type); ltg_sched_destroy_l(curr_tg_dl_entry); return LTG_ID_INVALID; break; } dl_entry_insertEnd(&tg_list,curr_tg_dl_entry); return return_value; } /*****************************************************************************/ /** * @brief Create an LTG schedule (low-level) * * Internal function. Not intended to be called by user code. * */ dl_entry* ltg_sched_create_l(){ dl_entry* curr_tg_dl_entry; tg_schedule* curr_tg; curr_tg_dl_entry = (dl_entry*)wlan_mac_high_malloc(sizeof(dl_entry)); if(curr_tg_dl_entry == NULL){ return NULL; } curr_tg = (tg_schedule*)wlan_mac_high_malloc(sizeof(tg_schedule)); if(curr_tg == NULL){ wlan_mac_high_free(curr_tg_dl_entry); return NULL; } curr_tg_dl_entry->data = (void*)curr_tg; return curr_tg_dl_entry; } /*****************************************************************************/ /** * @brief Start an existing LTG (low-lever) * * Internal function. Not intended to be called by user code. * */ int ltg_sched_start(u32 id){ dl_entry* curr_tg_dl_entry; if (id == LTG_START_ALL) { return ltg_sched_start_all(); } else { // Single ID case curr_tg_dl_entry = ltg_sched_find_tg_schedule(id); if(curr_tg_dl_entry != NULL){ return ltg_sched_start_l(curr_tg_dl_entry); } else { xil_printf("LTG: ERROR: Failed to start: %d. Please ensure LTG is configured before starting\n", id); return WLAN_FAILURE; } } } /*****************************************************************************/ /** * @brief Start all existing LTGs * This function will start all previously created schedules that have no been removed * * @return 0 if success, -1 if error */ int ltg_sched_start_all(){ int ret_val = 0; tg_schedule* curr_tg; dl_entry* next_tg_dl_entry; dl_entry* curr_tg_dl_entry; next_tg_dl_entry = tg_list.first; interrupt_state_t prev_interrupt_state; prev_interrupt_state = wlan_platform_intc_stop(); while(next_tg_dl_entry != NULL){ curr_tg_dl_entry = next_tg_dl_entry; next_tg_dl_entry = dl_entry_next(next_tg_dl_entry); curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); if(ltg_sched_start_l(curr_tg_dl_entry) == -1) { xil_printf("LTG: ERROR: Failed to start: %d. Please ensure LTG is configured before starting\n", (curr_tg->id)); ret_val = -1; } } wlan_platform_intc_set_state(prev_interrupt_state); return ret_val; } /*****************************************************************************/ /** * @brief Start an existing LTG * * Internal function, not intended to be called by user code. * */ int ltg_sched_start_l(dl_entry* curr_tg_dl_entry){ tg_schedule* curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); u64 timestamp = get_system_time_usec(); u64 random_timestamp; switch(curr_tg->type){ case LTG_SCHED_TYPE_PERIODIC: curr_tg->target = num_ltg_checks + (((ltg_sched_periodic_params_t*)(curr_tg->params))->interval_count); if(((ltg_sched_periodic_params_t*)(curr_tg->params))->duration_count != LTG_DURATION_FOREVER){ curr_tg->stop_target = num_ltg_checks + ((ltg_sched_periodic_params_t*)(curr_tg->params))->duration_count; } else { curr_tg->stop_target = LTG_DURATION_FOREVER; } ((ltg_sched_state_hdr_t*)(curr_tg->state))->start_timestamp = timestamp; ((ltg_sched_state_hdr_t*)(curr_tg->state))->enabled = 1; break; case LTG_SCHED_TYPE_UNIFORM_RAND: random_timestamp = (rand()%(((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->max_interval_count - ((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->min_interval_count))+((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->min_interval_count; curr_tg->target = num_ltg_checks + random_timestamp; if(((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->duration_count != LTG_DURATION_FOREVER){ curr_tg->stop_target = num_ltg_checks + ((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->duration_count; } else { curr_tg->stop_target = LTG_DURATION_FOREVER; } ((ltg_sched_state_hdr_t*)(curr_tg->state))->start_timestamp = timestamp; ((ltg_sched_state_hdr_t*)(curr_tg->state))->enabled = 1; break; default: xil_printf("LTG: ERROR: Unknown type %d\n", curr_tg->type); dl_entry_remove(&tg_list,curr_tg_dl_entry); ltg_sched_destroy_l(curr_tg_dl_entry); return WLAN_FAILURE; break; } if(schedule_running == 0){ schedule_running = 1; // A value of 0 here reflects that the LTG's minimum interval is whatever the scheduler's minimum interval is. // This assumption is shared among all calling contexts who set _count parameters in an LTG schedule. schedule_id = wlan_mac_schedule_add_event(SCHEDULE_ID_FINE, 0, SCHEDULE_REPEAT_FOREVER, (void*)ltg_sched_check); } //u64 start_time = ((ltg_sched_state_hdr_t*)(curr_tg->state))->start_timestamp; //xil_printf("LTG Start @ 0x%08x 0x%08x\n", (u32)(start_time >> 32), (u32)start_time ); return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Start an existing LTG * * Internal function. Not intended to be called by user code. * */ void ltg_sched_check(){ tg_schedule* curr_tg; dl_entry* curr_tg_dl_entry; u64 random_timestamp; num_ltg_checks++; if(tg_list.length > 0){ curr_tg_dl_entry = tg_list.first; while(curr_tg_dl_entry != NULL){ curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); if(((ltg_sched_state_hdr_t*)(curr_tg->state))->enabled){ if( num_ltg_checks >= ( curr_tg->target ) ){ switch(curr_tg->type){ case LTG_SCHED_TYPE_PERIODIC: curr_tg->target = num_ltg_checks + (((ltg_sched_periodic_params_t*)(curr_tg->params))->interval_count); break; case LTG_SCHED_TYPE_UNIFORM_RAND: random_timestamp = (rand()%(((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->max_interval_count - ((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->min_interval_count))+((ltg_sched_uniform_rand_params_t*)(curr_tg->params))->min_interval_count; curr_tg->target = num_ltg_checks + random_timestamp; break; default: ltg_sched_stop_l(curr_tg_dl_entry); return; break; } ltg_callback(curr_tg->id, curr_tg->callback_arg); } if( curr_tg->stop_target != LTG_DURATION_FOREVER && num_ltg_checks >= ( curr_tg->stop_target )){ ltg_sched_stop_l(curr_tg_dl_entry); } } curr_tg_dl_entry = dl_entry_next(curr_tg_dl_entry); } } } /*****************************************************************************/ /** * @brief Stop an existing LTG * * This function will stop executing callbacks for a currently-running LTG. * * @param id - ID of LTG event (provided by ltg_sched_create) * @return 0 if success, -1 if error */ int ltg_sched_stop(u32 id){ dl_entry* curr_tg_dl_entry; if (id == LTG_STOP_ALL) { return ltg_sched_stop_all(); } else { // Single ID case curr_tg_dl_entry = ltg_sched_find_tg_schedule(id); if(curr_tg_dl_entry != NULL){ return ltg_sched_stop_l(curr_tg_dl_entry); } else { xil_printf("LTG: ERROR: Failed to stop: %d. Please ensure LTG is configured before stopping\n", id); return WLAN_FAILURE; } } } /*****************************************************************************/ /** * @brief Stop all existing LTGs * * This function will stop executing callbacks for all currently-running LTGs. * Note: this does not remove the LTG schedules. They can be started again. * * @return WLAN_SUCCESS or WLAN_FAILURE */ int ltg_sched_stop_all(){ dl_entry* next_tg_dl_entry; dl_entry* curr_tg_dl_entry; interrupt_state_t prev_interrupt_state; next_tg_dl_entry = tg_list.first; prev_interrupt_state = wlan_platform_intc_stop(); while(next_tg_dl_entry != NULL){ curr_tg_dl_entry = next_tg_dl_entry; next_tg_dl_entry = dl_entry_next(curr_tg_dl_entry); ltg_sched_stop_l(curr_tg_dl_entry); } wlan_platform_intc_set_state(prev_interrupt_state); return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Stop existing LTG (low-level) * * Internal function. Not intended to be called by user code. * */ int ltg_sched_stop_l(dl_entry* curr_tg_dl_entry){ tg_schedule* curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); u64 timestamp = get_system_time_usec(); if ( ((ltg_sched_state_hdr_t*)(curr_tg->state))->enabled == 1 ) { ((ltg_sched_state_hdr_t*)(curr_tg->state))->enabled = 0; ((ltg_sched_state_hdr_t*)(curr_tg->state))->stop_timestamp = timestamp; //xil_printf("LTG Stop @ 0x%08x 0x%08x\n", (u32)(timestamp >> 32), (u32)timestamp ); } if(tg_list.length == 0 && schedule_running == 1){ wlan_mac_schedule_remove_event(schedule_id); schedule_running = 0; } return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Get the current state of an LTG * * This function will allows user code to inspect the current state of an existing LTG. This function * returns the state by filling in pointers provided by arguments. * * @param id - ID of LTG event (provided by ltg_sched_create) * @param type - pointer to a u32 that will be filled in with LTG_SCHED_TYPE_PERIODIC or LTG_SCHED_TYPE_UNIFORM_RAND * @param state - double pointer that will be filled in with a pointer to ltg_sched_periodic_state_t or ltg_sched_uniform_rand_state_t * @return WLAN_SUCCESS or WLAN_FAILURE */ int ltg_sched_get_state(u32 id, u32* type, void** state){ //This function returns the type of schedule corresponding to the id argument //It fills in the state argument with the state of the schedule tg_schedule* curr_tg; dl_entry* curr_tg_dl_entry; curr_tg_dl_entry = ltg_sched_find_tg_schedule(id); if(curr_tg_dl_entry == NULL){ return WLAN_FAILURE; } curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); if(type != NULL) *type = curr_tg->type; if(state != NULL) *state = curr_tg->state; switch(curr_tg->type){ case LTG_SCHED_TYPE_PERIODIC: if(num_ltg_checks < (curr_tg->target) ){ ((ltg_sched_periodic_state_t*)(curr_tg->state))->time_to_next_count = (u32)(curr_tg->target - num_ltg_checks); } else { ((ltg_sched_periodic_state_t*)(curr_tg->state))->time_to_next_count = 0; } break; case LTG_SCHED_TYPE_UNIFORM_RAND: if(num_ltg_checks < (curr_tg->target) ){ ((ltg_sched_uniform_rand_state_t*)(curr_tg->state))->time_to_next_count = (u32)(curr_tg->target - num_ltg_checks); } else { ((ltg_sched_uniform_rand_state_t*)(curr_tg->state))->time_to_next_count = 0; } break; default: xil_printf("LTG: ERROR: Unknown type %d\n", curr_tg->type); return WLAN_FAILURE; break; } return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Get the current params of an LTG * * This function will allows user code to inspect the current parameters of an existing LTG. This function * returns the state by filling in pointers provided by arguments. * * @param id - ID of LTG event (provided by ltg_sched_create) * @param params - double pointer that will be filled in with a pointer to ltg_sched_periodic_params_t or ltg_sched_uniform_rand_params_t * @return WLAN_SUCCESS or WLAN_FAILURE */ int ltg_sched_get_params(u32 id, void** params){ //This function returns the type of the schedule corresponding to the id argument //It fills in the current parameters of the schedule into the params argument tg_schedule* curr_tg; dl_entry* curr_tg_dl_entry; curr_tg_dl_entry = ltg_sched_find_tg_schedule(id); if(curr_tg_dl_entry == NULL){ return WLAN_FAILURE; } curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); *params = curr_tg->params; return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Get the current callback argument of an LTG * * This function will allows user code to inspect the current callback argument of an existing LTG. * * @param id - ID of LTG event (provided by ltg_sched_create) * @param params - double pointer that will be filled in with a function pointer that is the current callback * @return WLAN_SUCCESS */ int ltg_sched_get_callback_arg(u32 id, void** callback_arg){ tg_schedule* curr_tg; dl_entry* curr_tg_dl_entry; curr_tg_dl_entry = ltg_sched_find_tg_schedule(id); if(curr_tg_dl_entry == NULL){ return WLAN_FAILURE; } curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); *callback_arg = curr_tg->callback_arg; return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Remove an existing LTG * * This function will remove an existing LTG. If that LTG is currently running, it will first be stopped. * * @param id - ID of LTG event (provided by ltg_sched_create) * @return WLAN_SUCCESS or WLAN_FAILURE */ int ltg_sched_remove(u32 id){ tg_schedule* curr_tg; dl_entry* curr_tg_dl_entry; if (id == LTG_REMOVE_ALL) { return ltg_sched_remove_all(); } else { // Single ID case curr_tg_dl_entry = ltg_sched_find_tg_schedule(id); if(curr_tg_dl_entry != NULL){ curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); ltg_sched_stop_l(curr_tg_dl_entry); dl_entry_remove(&tg_list, curr_tg_dl_entry); if(curr_tg->cleanup_callback != NULL){ curr_tg->cleanup_callback(curr_tg->id, curr_tg->callback_arg); } ltg_sched_destroy_l(curr_tg_dl_entry); return WLAN_SUCCESS; } else { xil_printf("LTG: ERROR: Failed to remove: %d. Please ensure LTG is configured before removing\n", id); return WLAN_FAILURE; } } } /*****************************************************************************/ /** * @brief Remove all existing LTGs * * This function will remove all existing LTGs. The end result will be idential to a just-booted node where no LTG has been configured. * * @return WLAN_SUCCESS */ int ltg_sched_remove_all(){ tg_schedule* curr_tg; dl_entry* next_tg_dl_entry; dl_entry* curr_tg_dl_entry; interrupt_state_t prev_interrupt_state; next_tg_dl_entry = tg_list.first; prev_interrupt_state = wlan_platform_intc_stop(); // NOTE: Cannot use a for loop for this iteration b/c we are removing // elements from the list. while(next_tg_dl_entry != NULL){ curr_tg_dl_entry = next_tg_dl_entry; next_tg_dl_entry = dl_entry_next(curr_tg_dl_entry); curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); ltg_sched_stop_l(curr_tg_dl_entry); dl_entry_remove(&tg_list, curr_tg_dl_entry); if(curr_tg->cleanup_callback != NULL){ curr_tg->cleanup_callback(curr_tg->id, curr_tg->callback_arg); } ltg_sched_destroy_l(curr_tg_dl_entry); } wlan_platform_intc_set_state(prev_interrupt_state); return WLAN_SUCCESS; } /*****************************************************************************/ /** * @brief Destroy LTG parameters * * Internal function. Not intended to be called by user code. * */ void ltg_sched_destroy_params(tg_schedule* tg){ switch(tg->type){ case LTG_SCHED_TYPE_PERIODIC: case LTG_SCHED_TYPE_UNIFORM_RAND: wlan_mac_high_free(tg->params); wlan_mac_high_free(tg->state); break; } } /*****************************************************************************/ /** * @brief Destroy LTG parameters (low-level) * * Internal function. Not intended to be called by user code. * */ void ltg_sched_destroy_l(dl_entry* tg_dl_entry){ tg_schedule* curr_tg; curr_tg = (tg_schedule*)(tg_dl_entry->data); ltg_sched_destroy_params(curr_tg); wlan_mac_high_free(tg_dl_entry); wlan_mac_high_free(curr_tg); return; } /*****************************************************************************/ /** * @brief Find LTG given id * * Internal function. Not intended to be called by user code. * */ dl_entry* ltg_sched_find_tg_schedule(u32 id){ dl_entry* curr_tg_dl_entry; tg_schedule* curr_tg; int iter; curr_tg_dl_entry = tg_list.first; iter = tg_list.length; while((curr_tg_dl_entry != NULL) && (iter-- > 0)){ curr_tg = (tg_schedule*)(curr_tg_dl_entry->data); if( (curr_tg->id)==id){ return curr_tg_dl_entry; } curr_tg_dl_entry = dl_entry_next(curr_tg_dl_entry); } return NULL; } /*****************************************************************************/ /** * @brief Create LTG frame * * Function is typically called in the context of the LTG event callback. It creates a complete * 802.11 data frame with the extra LTG metadata in the payload. * */ int wlan_create_ltg_frame(u8* pkt, u8* addr1, u8* addr2, u8* addr3, u8 frame_control_2, u32 ltg_id){ u32 tx_length; ltg_packet_id_t* pkt_id; if( pkt == NULL ) return WLAN_FAILURE; tx_length = wlan_create_data_frame_header(pkt, addr1, addr2, addr3, frame_control_2); // Prepare the MPDU LLC header pkt_id = (ltg_packet_id_t*)(pkt + sizeof(mac_header_80211)); (pkt_id->llc_hdr).dsap = LLC_SNAP; (pkt_id->llc_hdr).ssap = LLC_SNAP; (pkt_id->llc_hdr).control_field = LLC_CNTRL_UNNUMBERED; bzero((void *)((pkt_id->llc_hdr).org_code), 3); // Org Code 0x000000: Encapsulated Ethernet (pkt_id->llc_hdr).type = LLC_TYPE_WLAN_LTG; pkt_id->unique_seq = 0; //make sure this is filled in via the dequeue callback pkt_id->ltg_id = ltg_id; // LTG packets always have LLC header, LTG payload id, plus any extra payload requested by user tx_length += ((sizeof(ltg_packet_id_t))); return tx_length; } #endif //WLAN_SW_CONFIG_ENABLE_LTG