/* * Copyright (c) 2016-2018, Arm Limited and affiliates. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "nsconfig.h" #include "ns_types.h" #include "eventOS_event.h" #include "eventOS_scheduler.h" #include "eventOS_callback_timer.h" #include "load_balance_api.h" #include "string.h" #include "ns_trace.h" #include "nsdynmemLIB.h" #include "randLIB.h" #include "mlme.h" typedef enum { LB_NWK_SWITCH_IDLE = 0, LB_NWK_SWITCH_ROUTER_LEAVE, LB_NWK_SWITCH_NEIGHBOUR_LEAVE } lb_nwk_switch_state_t; typedef enum { LB_IDLE_STATE = 0, LB_BLOCK_STATE, LB_ACCEPT_STATE, LB_BLOCK_COMPARE, LB_BLOCK_NETWORK_SELECT, } lb_state_t; /** * @brief struct lb_network_s Load balance network information * */ typedef struct lb_network_s { struct mlme_pan_descriptor_s PANDescriptor; uint8_t priority; bool network_switch_accepted; lb_nwk_switch_state_t state; uint16_t state_timer; uint16_t beacon_data_length; uint8_t beacon_data[]; } lb_network_t; /** get pointer to Mac header start point*/ #define beacon_payload_start_pointer(x) (&(x)->beacon_data[0]) typedef struct lb_monitor_internal_s { load_balance_api_get_node_count *get_count_cb; load_balance_api_set_load_level *set_new_load_cb; uint16_t expected_node_count; uint8_t network_load_scaler; uint8_t last_load_level; uint16_t timer2update; } lb_monitor_internal_t; typedef struct lb_internal_s { load_balance_api_t *lb_api; lb_monitor_internal_t *lb_border_router; //user spesific callback load_balance_beacon_tx *load_balance_beacon_tx_cb; load_balance_priority_get *lb_priority_get_cb; load_balance_network_switch_req *lb_nwk_switch_cb; load_balance_network_switch_notify *lb_nwk_switch_notify; //Can be NULL if app not want to control //APP defined nwk switch callback net_load_balance_network_switch_notify *lb_access_switch_cb; void *lb_user_parent_id; lb_network_t *notified_network; uint32_t triggle_period; uint32_t lb_state_timer; uint32_t time_to_next_beacon; uint16_t beacon_max_payload_length; uint8_t nwk_switch_threshold_min; uint8_t nwk_switch_threshold_max; uint8_t nwk_maX_P; lb_state_t lb_state; bool load_balance_activate; bool periodic_beacon_activated; } lb_internal_t; /** * Load balance internal variables */ static load_balance_api_t lb_api; static lb_internal_t *lb_store = NULL; /** * Load balance internal used functions */ static lb_internal_t *load_balance_class_allocate(void); static void load_balance_class_free(lb_internal_t *api); static lb_internal_t *lb_api_get(const load_balance_api_t *api); static bool load_balance_network_class_allocate(lb_internal_t *lb_store_ptr, uint16_t beacon_max_payload_length); static void lb_network_switch_handle(lb_internal_t *this); static void lb_load_level_poll(lb_internal_t *this, uint32_t trigle_period); /** * Load balance shared functions to user */ static void lb_beacon_notify(const load_balance_api_t *api, const struct mlme_beacon_ind_s *beacon_ind, uint8_t priority); static void lb_enable(const load_balance_api_t *api, bool active_state, uint32_t network_triggle_max_period, uint32_t network_route_life_time); static int8_t lb_api_initialize(load_balance_api_t *api, load_balance_beacon_tx *lb_beacon_tx, load_balance_priority_get *priority_get_cb, load_balance_network_switch_req *lb_nwk_switch_cb, uint16_t baecon_max_payload_length, void *lb_user); static void lb_second_ticks(const load_balance_api_t *api); #define TRACE_GROUP "lba" /** * Load balance border router class allocate */ static lb_monitor_internal_t *lb_border_router_api_allocate(lb_internal_t *api) { if (!api->lb_border_router) { api->lb_border_router = ns_dyn_mem_alloc(sizeof(lb_monitor_internal_t)); } return api->lb_border_router; } /** * Load balance border router class free */ static int8_t lb_border_router_api_free(lb_internal_t *api) { if (api->lb_border_router) { ns_dyn_mem_free(api->lb_border_router); api->lb_border_router = NULL; return 0; } return -1; } /** * Allocate Load balance class base */ static lb_internal_t *load_balance_class_allocate(void) { if (lb_store) { if (lb_store->lb_user_parent_id) { return NULL; } return lb_store; } lb_internal_t *store = ns_dyn_mem_alloc(sizeof(lb_internal_t)); if (store) { store->lb_api = &lb_api; lb_api.lb_beacon_notify = lb_beacon_notify; lb_api.lb_enable = lb_enable; lb_api.lb_initialize = lb_api_initialize; lb_api.lb_seconds_tick_update = lb_second_ticks; store->lb_border_router = NULL; store->load_balance_beacon_tx_cb = NULL; store->lb_nwk_switch_cb = NULL; store->lb_priority_get_cb = NULL; store->lb_user_parent_id = NULL; store->notified_network = NULL; store->lb_access_switch_cb = NULL; store->beacon_max_payload_length = 0; store->nwk_switch_threshold_max = 0; store->nwk_switch_threshold_min = 0; store->nwk_maX_P = 25; store->load_balance_activate = false; store->time_to_next_beacon = 0; store->triggle_period = 0; lb_store = store; } return store; } /** * Free allocated load balance class */ static void load_balance_class_free(lb_internal_t *api) { //Clean heared networks ns_dyn_mem_free(api->notified_network); lb_border_router_api_free(api); ns_dyn_mem_free(api); } /** * Load balance class get by user API pointer */ static lb_internal_t *lb_api_get(const load_balance_api_t *api) { if (!api || !lb_store || lb_store->lb_api != api) { return NULL; } return lb_store; } /** * Allocate notified network class */ static bool load_balance_network_class_allocate(lb_internal_t *lb_store_ptr, uint16_t beacon_max_payload_length) { ns_dyn_mem_free(lb_store_ptr->notified_network); lb_store_ptr->beacon_max_payload_length = 0; if (beacon_max_payload_length) { lb_store_ptr->notified_network = ns_dyn_mem_alloc(sizeof(lb_network_t) + beacon_max_payload_length); if (!lb_store_ptr->notified_network) { return false; } lb_store_ptr->notified_network->network_switch_accepted = false; lb_store_ptr->notified_network->priority = 0xff; lb_store_ptr->notified_network->state = LB_NWK_SWITCH_IDLE; lb_store_ptr->notified_network->state_timer = 0; lb_store_ptr->beacon_max_payload_length = beacon_max_payload_length; } return true; } /** * Switch to notified and selected network */ static void lb_network_switch_handle(lb_internal_t *this) { lb_network_t *network_class = this->notified_network; if (network_class->state_timer) { network_class->state_timer--; return; } switch (network_class->state) { case LB_NWK_SWITCH_IDLE: if (!this->notified_network->network_switch_accepted) { if (this->lb_access_switch_cb && !this->lb_access_switch_cb()) { return; } this->notified_network->network_switch_accepted = true; } tr_info("Start NWK switch statemachine!"); this->lb_nwk_switch_notify(this->lb_user_parent_id, LB_ROUTER_LEAVE, &network_class->state_timer); network_class->state = LB_NWK_SWITCH_ROUTER_LEAVE; break; case LB_NWK_SWITCH_ROUTER_LEAVE: tr_info("Router leave state!"); this->lb_nwk_switch_notify(this->lb_user_parent_id, LB_NEIGHBOUR_LEAVE, &network_class->state_timer); network_class->state = LB_NWK_SWITCH_NEIGHBOUR_LEAVE; break; case LB_NWK_SWITCH_NEIGHBOUR_LEAVE: tr_info("Neighbour leave state!"); //Disable beacon send lb_enable(this->lb_api, false, 0, 0); this->lb_nwk_switch_cb(this->lb_user_parent_id, &network_class->PANDescriptor, beacon_payload_start_pointer(network_class), network_class->beacon_data_length); break; } } static bool lb_accept_beacon_state(lb_state_t lb_state) { //Check state switch (lb_state) { case LB_IDLE_STATE: case LB_BLOCK_STATE: case LB_BLOCK_COMPARE: return false; default: break; } return true; } /** * Beacon notify handler */ static void lb_beacon_notify(const load_balance_api_t *api, const struct mlme_beacon_ind_s *beacon_ind, uint8_t priority) { lb_internal_t *this = lb_api_get(api); if (!beacon_ind || !this || !this->nwk_switch_threshold_max || !this->nwk_switch_threshold_min) { return; } else if (!lb_accept_beacon_state(this->lb_state)) { return; } else if (beacon_ind->beacon_data_length > this->beacon_max_payload_length) { return; } lb_network_t *nwk = this->notified_network; //Get current global part joining priority uint8_t current_priority = this->lb_priority_get_cb(this->lb_user_parent_id); if (this->lb_state == LB_ACCEPT_STATE) { //Compare new network priority if (current_priority < priority) { return; } uint16_t diff = current_priority - priority; uint16_t switch_prob; // 256 * percent (0-25600) if (diff >= this->nwk_switch_threshold_max) { switch_prob = this->nwk_maX_P * 256; tr_debug("diff %"PRIu16", tmax %"PRIu8" %"PRIu16, diff, this->nwk_switch_threshold_max, switch_prob); } else if (diff <= this->nwk_switch_threshold_min) { tr_debug("diff %"PRIu16", tmin %"PRIu8, diff, this->nwk_switch_threshold_min); switch_prob = 0; } else { // The multiplication could overflow 16-bit, even though the final result will be 16-bit switch_prob = (uint32_t) this->nwk_maX_P * 256 * (diff - this->nwk_switch_threshold_min) / (this->nwk_switch_threshold_max - this->nwk_switch_threshold_min); tr_debug("diff between min & max, diff %"PRIu16", tmax %"PRIu8" prob%"PRIu16, diff, this->nwk_switch_threshold_max, switch_prob); } if (switch_prob > randLIB_get_random_in_range(0, 25599)) { this->lb_state_timer = randLIB_get_random_in_range(1, 32); this->lb_state = LB_BLOCK_NETWORK_SELECT; } else { //Enter Block state this->lb_state_timer = this->triggle_period; this->lb_state = LB_BLOCK_COMPARE; return; } } else { //Compare priority to saved network if (priority > nwk->priority) { return; } else if (priority == nwk->priority && nwk->PANDescriptor.LinkQuality > beacon_ind->PANDescriptor.LinkQuality) { return; } } //Copy network info nwk->priority = priority; nwk->PANDescriptor = beacon_ind->PANDescriptor; memcpy(beacon_payload_start_pointer(nwk), beacon_ind->beacon_data, beacon_ind->beacon_data_length); nwk->beacon_data_length = beacon_ind->beacon_data_length; } /** * Load balance activate or disable */ static void lb_enable(const load_balance_api_t *api, bool active_state, uint32_t network_triggle_max_period, uint32_t network_route_life_time) { lb_internal_t *this = lb_api_get(api); if (!this || !this->lb_user_parent_id) { return; } this->load_balance_activate = active_state; if (active_state) { //Start Block_period this->triggle_period = network_triggle_max_period; this->lb_state = LB_BLOCK_STATE; tr_debug("Enable Network Triggle max time %"PRIu32" %"PRIu32" route life time", network_triggle_max_period, network_route_life_time); if (this->nwk_switch_threshold_min && this->nwk_switch_threshold_max) { this->lb_state_timer = network_route_life_time > network_triggle_max_period ? network_route_life_time : network_triggle_max_period; } else { this->lb_state_timer = 10; if (this->lb_border_router) { this->lb_border_router->timer2update = 0; lb_load_level_poll(this, this->triggle_period); } } } else { this->lb_state = LB_IDLE_STATE; } if (this->notified_network) { this->notified_network->priority = 0xff; this->notified_network->network_switch_accepted = false; this->notified_network->state = LB_NWK_SWITCH_IDLE; this->notified_network->state_timer = 0; } } /** * Load balance activate or disable */ static int8_t lb_api_initialize(load_balance_api_t *api, load_balance_beacon_tx *lb_beacon_tx, load_balance_priority_get *priority_get_cb, load_balance_network_switch_req *lb_nwk_switch_cb, uint16_t beacon_max_payload_length, void *lb_user) { lb_internal_t *this = lb_api_get(api); if (!this || !lb_beacon_tx || !priority_get_cb || !lb_nwk_switch_cb || !lb_user) { return -1; } //Allocate beacon payload here if (!load_balance_network_class_allocate(this, beacon_max_payload_length)) { return -2; } this->lb_user_parent_id = lb_user; this->load_balance_beacon_tx_cb = lb_beacon_tx; this->lb_nwk_switch_cb = lb_nwk_switch_cb; this->lb_priority_get_cb = priority_get_cb; return 0; } static void lb_load_level_poll(lb_internal_t *this, uint32_t trigle_period) { if (!this->lb_border_router || !this->lb_user_parent_id) { return; } if (this->lb_border_router->timer2update) { this->lb_border_router->timer2update--; return; } uint16_t node_count; if (this->lb_border_router->get_count_cb(this->lb_user_parent_id, &node_count) != 0) { this->lb_border_router->last_load_level = 0xff; this->lb_border_router->timer2update = 10; return; } //Calculate uint8_t new_load; if (node_count >= this->lb_border_router->expected_node_count) { new_load = this->lb_border_router->network_load_scaler - 1; } else { uint16_t scaled_level = this->lb_border_router->expected_node_count / this->lb_border_router->network_load_scaler; new_load = node_count / scaled_level; } if (this->lb_border_router->last_load_level != new_load) { if (this->lb_border_router->set_new_load_cb(this->lb_user_parent_id, new_load) == 0) { this->lb_border_router->last_load_level = new_load; } } this->lb_border_router->timer2update = trigle_period >> 1; //Update Every block period / 2 } static void lb_second_ticks(const load_balance_api_t *api) { lb_internal_t *this = lb_api_get(api); if (!this || this->lb_state == LB_IDLE_STATE) { return; } lb_load_level_poll(this, this->triggle_period); //Update Next beacon generation timer if (this->periodic_beacon_activated && this->lb_state != LB_BLOCK_STATE && --this->time_to_next_beacon == 0) { this->time_to_next_beacon = this->triggle_period; this->load_balance_beacon_tx_cb(this->lb_user_parent_id); } if (this->lb_state_timer == 0) { switch (this->lb_state) { case LB_BLOCK_STATE: //Enter block state to accept state this->lb_state = LB_ACCEPT_STATE; this->time_to_next_beacon = randLIB_get_random_in_range(1, 32); break; case LB_BLOCK_COMPARE: this->lb_state = LB_ACCEPT_STATE; break; case LB_BLOCK_NETWORK_SELECT: lb_network_switch_handle(this); break; default: break; } } else { this->lb_state_timer--; } } load_balance_api_t *load_balance_create(load_balance_network_switch_notify *lb_notify_cb, bool activate_periodic_beacon) { //validate beacon period & beacon_notify if (!lb_notify_cb) { return NULL; } //allocate load balance class lb_internal_t *this = load_balance_class_allocate(); if (!this) { return NULL; } this->lb_state = LB_IDLE_STATE; this->lb_nwk_switch_notify = lb_notify_cb; this->periodic_beacon_activated = activate_periodic_beacon; return this->lb_api; } int load_balance_delete(load_balance_api_t *api) { if (!lb_store || lb_store->lb_api != api) { return -1; } load_balance_class_free(lb_store); lb_store = NULL; return 0; } int load_balance_network_threshold_set(load_balance_api_t *api, uint8_t threshold_min, uint8_t threshold_max) { lb_internal_t *this = lb_api_get(api); if (!this) { return -1; } this->nwk_switch_threshold_min = threshold_min; this->nwk_switch_threshold_max = threshold_max; return 0; } int load_balance_network_load_monitor_enable(load_balance_api_t *api, uint16_t expected_node_count, uint8_t network_load_scaler, load_balance_api_get_node_count *get_count_cb, load_balance_api_set_load_level *set_new_load_cb) { lb_internal_t *this = lb_api_get(api); if (!this) { return -1; } if (expected_node_count == 0 || network_load_scaler == 0 || expected_node_count < network_load_scaler) { return -1; } if (!get_count_cb || !set_new_load_cb) { return -1; } lb_monitor_internal_t *border_router = lb_border_router_api_allocate(this); if (!border_router) { return -2; } border_router->expected_node_count = expected_node_count; border_router->network_load_scaler = network_load_scaler; border_router->get_count_cb = get_count_cb; border_router->last_load_level = 0xff; border_router->set_new_load_cb = set_new_load_cb; border_router->timer2update = 10; return 0; } int load_balance_network_load_monitor_disable(load_balance_api_t *api) { lb_internal_t *this = lb_api_get(api); if (!this) { return -1; } return lb_border_router_api_free(this); } int load_balance_set_max_probability(load_balance_api_t *api, uint8_t max_p) { lb_internal_t *this = lb_api_get(api); if (!this) { return -1; } if (max_p > 100 || max_p == 0) { return -1; } this->nwk_maX_P = max_p; return 0; } int load_balance_network_switch_cb_set(load_balance_api_t *api, net_load_balance_network_switch_notify *network_switch_notify) { lb_internal_t *this = lb_api_get(api); if (!this) { return -1; } this->lb_access_switch_cb = network_switch_notify; return 0; }