| /* |
| * This file is part of the UWB stack for linux. |
| * |
| * Copyright (c) 2020-2021 Qorvo US, Inc. |
| * |
| * This software is provided under the GNU General Public License, version 2 |
| * (GPLv2), as well as under a Qorvo commercial license. |
| * |
| * You may choose to use this software under the terms of the GPLv2 License, |
| * version 2 ("GPLv2"), as published by the Free Software Foundation. |
| * You should have received a copy of the GPLv2 along with this program. If |
| * not, see <http://www.gnu.org/licenses/>. |
| * |
| * This program is distributed under the GPLv2 in the hope that it will be |
| * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more |
| * details. |
| * |
| * If you cannot meet the requirements of the GPLv2, you may not use this |
| * software for any purpose without first obtaining a commercial license from |
| * Qorvo. Please contact Qorvo to inquire about licensing terms. |
| */ |
| |
| #include "fira_session.h" |
| #include "fira_crypto.h" |
| #include "fira_round_hopping_sequence.h" |
| #include "fira_access.h" |
| #include "mcps802154_i.h" |
| |
| #include <linux/errno.h> |
| #include <linux/ieee802154.h> |
| #include <linux/string.h> |
| #include <linux/limits.h> |
| |
| #define FIRA_DRIFT_TOLERANCE_PPM 30 |
| |
| /** |
| * fira_session_controlees_max() - Calculate the maximum number of controlees |
| * for current session. |
| * @params: Session params. |
| * |
| * Return: Maximum number of controlees possible with current parameters. |
| */ |
| static size_t fira_session_controlees_max(struct fira_session_params *params) |
| { |
| /* TODO: use parameters (embedded mode, ranging mode, device type...) |
| to calculate the size of frames, number of messages... |
| Currently using default parameters configuration. */ |
| static const u8 mrm_size_without_delays = 49; |
| static const u8 delay_size_per_controlee = 6; |
| static const u8 rcm_size_without_slots = 45; |
| static const u8 slots_size = 4; |
| static const u8 controller_messages = 4; |
| static const u8 controlee_messages = 2; |
| static const u8 frame_size_max = 125; |
| |
| static const size_t mrm_max_controlees = |
| (frame_size_max - mrm_size_without_delays) / |
| delay_size_per_controlee; |
| |
| static const size_t rcm_max_controlees = |
| (frame_size_max - rcm_size_without_slots - |
| slots_size * controller_messages) / |
| (slots_size * controlee_messages); |
| |
| const size_t controlees_max = |
| min(mrm_max_controlees, rcm_max_controlees); |
| |
| return controlees_max; |
| } |
| |
| struct fira_session *fira_session_new(struct fira_local *local, u32 session_id) |
| { |
| struct fira_session *session; |
| |
| session = kzalloc(sizeof(*session), GFP_KERNEL); |
| if (!session) |
| return NULL; |
| |
| /* Notify session init before setting default parameters*/ |
| session->state = SESSION_STATE_INIT; |
| fira_session_notify_state_change(local, session_id, SESSION_STATE_INIT); |
| |
| session->id = session_id; |
| session->params.ranging_round_usage = FIRA_RANGING_ROUND_USAGE_DSTWR; |
| session->params.short_addr = IEEE802154_ADDR_SHORT_BROADCAST; |
| session->params.controller_short_addr = IEEE802154_ADDR_SHORT_BROADCAST; |
| session->params.slot_duration_dtu = |
| FIRA_SLOT_DURATION_RSTU_DEFAULT * local->llhw->rstu_dtu; |
| session->params.block_duration_dtu = FIRA_BLOCK_DURATION_MS_DEFAULT * |
| (local->llhw->dtu_freq_hz / 1000); |
| session->params.round_duration_slots = |
| FIRA_ROUND_DURATION_SLOTS_DEFAULT; |
| session->params.max_rr_retry = FIRA_MAX_RR_RETRY_DEFAULT; |
| session->params.round_hopping = false; |
| session->params.priority = FIRA_PRIORITY_DEFAULT; |
| session->params.result_report_phase = true; |
| session->params.rframe_config = FIRA_RFRAME_CONFIG_SP3; |
| session->params.preamble_duration = FIRA_PREAMBULE_DURATION_64; |
| session->params.sfd_id = FIRA_SFD_ID_2; |
| |
| /* Antenna parameters which have a default value not equal to zero. */ |
| session->params.rx_antenna_pair_azimuth = FIRA_RX_ANTENNA_PAIR_INVALID; |
| session->params.rx_antenna_pair_elevation = |
| FIRA_RX_ANTENNA_PAIR_INVALID; |
| session->params.tx_antenna_selection = 0x01; |
| /* Report parameters. */ |
| session->params.aoa_result_req = true; |
| session->params.report_tof = true; |
| session->params.n_controlees_max = FIRA_CONTROLEES_MAX; |
| |
| if (fira_round_hopping_sequence_init(session)) |
| goto failed; |
| |
| list_add(&session->entry, &local->inactive_sessions); |
| |
| return session; |
| failed: |
| kfree(session); |
| return NULL; |
| } |
| |
| void fira_session_free(struct fira_local *local, struct fira_session *session) |
| { |
| fira_round_hopping_sequence_destroy(session); |
| list_del(&session->entry); |
| fira_aead_destroy(&session->crypto.aead); |
| /* The session structure contains the Crypto context. This needs to be |
| * cleared. */ |
| kfree_sensitive(session); |
| } |
| |
| struct fira_session *fira_session_get(struct fira_local *local, u32 session_id, |
| bool *active) |
| { |
| struct fira_session *session; |
| |
| list_for_each_entry (session, &local->inactive_sessions, entry) { |
| if (session->id == session_id) { |
| *active = false; |
| return session; |
| } |
| } |
| |
| list_for_each_entry (session, &local->active_sessions, entry) { |
| if (session->id == session_id) { |
| *active = true; |
| return session; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void fira_session_copy_controlees(struct fira_controlees_array *to, |
| const struct fira_controlees_array *from) |
| { |
| /* Copy only valid entries. */ |
| memcpy(to->data, from->data, from->size * sizeof(from->data[0])); |
| to->size = from->size; |
| } |
| |
| int fira_session_new_controlees(struct fira_local *local, |
| struct fira_session *session, |
| struct fira_controlees_array *controlees_array, |
| const struct fira_controlee *controlees, |
| size_t n_controlees) |
| { |
| int i, j; |
| |
| /* On inactive session, the max is the size of the array. |
| * And on active session, the size depend to the config. */ |
| if (controlees_array->size + n_controlees > |
| session->params.n_controlees_max) |
| return -EINVAL; |
| |
| for (i = 0; i < n_controlees; i++) { |
| for (j = 0; j < controlees_array->size; j++) { |
| if (controlees[i].short_addr == |
| controlees_array->data[j].short_addr) |
| return -EINVAL; |
| } |
| } |
| |
| for (i = 0; i < n_controlees; i++) |
| controlees_array->data[controlees_array->size++] = |
| controlees[i]; |
| |
| return 0; |
| } |
| |
| static void |
| fira_session_update_stopping_controlees(struct fira_local *local, |
| struct fira_session *session) |
| { |
| size_t ii, io; |
| struct fira_controlees_array *controlees_array = |
| &session->current_controlees; |
| |
| for (ii = 0, io = 0; ii < controlees_array->size; ii++) { |
| struct fira_controlee *c = &controlees_array->data[ii]; |
| |
| if (c->state != FIRA_CONTROLEE_STATE_PENDING_DEL) { |
| if (io != ii) |
| controlees_array->data[io] = *c; |
| controlees_array->data[io].state = |
| FIRA_CONTROLEE_STATE_RUNNING; |
| io++; |
| } |
| } |
| controlees_array->size = io; |
| } |
| |
| int fira_session_del_controlees(struct fira_local *local, |
| struct fira_session *session, |
| struct fira_controlees_array *controlees_array, |
| const struct fira_controlee *controlees, |
| size_t n_controlees) |
| { |
| size_t i, j; |
| |
| for (i = 0; i < controlees_array->size; i++) { |
| struct fira_controlee *c = &controlees_array->data[i]; |
| enum fira_controlee_state state = FIRA_CONTROLEE_STATE_RUNNING; |
| |
| for (j = 0; j < n_controlees; j++) { |
| if (c->short_addr == controlees[j].short_addr) { |
| state = FIRA_CONTROLEE_STATE_PENDING_DEL; |
| break; |
| } |
| } |
| c->state = state; |
| } |
| |
| return 0; |
| } |
| |
| void fira_session_stop_controlees(struct fira_local *local, |
| struct fira_session *session, |
| struct fira_controlees_array *controlees_array) |
| { |
| size_t i; |
| |
| for (i = 0; i < controlees_array->size; i++) { |
| controlees_array->data[i].state = |
| FIRA_CONTROLEE_STATE_PENDING_STOP; |
| } |
| } |
| |
| bool fira_session_is_ready(struct fira_local *local, |
| struct fira_session *session) |
| { |
| int round_duration_dtu; |
| struct fira_session_params *params = &session->params; |
| |
| if (params->multi_node_mode == FIRA_MULTI_NODE_MODE_UNICAST) { |
| if (session->current_controlees.size > 1) |
| return false; |
| } else { |
| params->n_controlees_max = fira_session_controlees_max(params); |
| if (session->current_controlees.size > params->n_controlees_max) |
| return false; |
| } |
| /* RFRAME (INITIATION and FINAL) reception on different antenna is |
| * not implemented on CONTROLLER. */ |
| if (params->rx_antenna_switch == FIRA_RX_ANTENNA_SWITCH_DURING_ROUND && |
| params->device_type == FIRA_DEVICE_TYPE_CONTROLLER) |
| return false; |
| |
| round_duration_dtu = |
| params->slot_duration_dtu * params->round_duration_slots; |
| return params->slot_duration_dtu != 0 && |
| params->block_duration_dtu != 0 && |
| params->round_duration_slots != 0 && |
| round_duration_dtu < params->block_duration_dtu; |
| } |
| |
| void fira_session_round_hopping(struct fira_session *session) |
| { |
| if (session->hopping_sequence_generation) { |
| session->round_index = fira_round_hopping_sequence_get( |
| session, session->block_index); |
| session->next_round_index = fira_round_hopping_sequence_get( |
| session, session->block_index + 1); |
| } else { |
| session->round_index = session->next_round_index; |
| session->hopping_sequence_generation = |
| session->params.round_hopping; |
| } |
| } |
| |
| static void fira_session_update(struct fira_local *local, |
| struct fira_session *session, |
| u32 next_timestamp_dtu) |
| { |
| s32 diff_dtu; |
| int block_duration_margin_dtu = 0; |
| |
| if (session->params.device_type == FIRA_DEVICE_TYPE_CONTROLEE) |
| block_duration_margin_dtu = |
| fira_session_get_block_duration_margin(local, session); |
| diff_dtu = session->block_start_dtu + |
| fira_session_get_round_slot(session) * |
| session->params.slot_duration_dtu - |
| block_duration_margin_dtu - next_timestamp_dtu; |
| |
| if (diff_dtu < 0) { |
| int block_duration_dtu = session->params.block_duration_dtu; |
| int block_duration_slots = |
| block_duration_dtu / session->params.slot_duration_dtu; |
| int add_blocks; |
| |
| add_blocks = (-diff_dtu + block_duration_dtu - 1) / |
| block_duration_dtu; |
| session->block_start_dtu += add_blocks * block_duration_dtu; |
| session->block_index += add_blocks; |
| session->sts_index += add_blocks * block_duration_slots; |
| if (add_blocks != 1) |
| session->hopping_sequence_generation = |
| session->params.round_hopping; |
| fira_session_round_hopping(session); |
| } |
| } |
| |
| static inline bool |
| fira_session_has_higher_priority(const struct fira_session *session, |
| const struct fira_session *selected_session) |
| { |
| return session->params.priority > selected_session->params.priority || |
| (session->params.priority == selected_session->params.priority && |
| is_before_dtu(session->last_access_timestamp_dtu, |
| selected_session->last_access_timestamp_dtu)); |
| } |
| |
| static struct fira_session *fira_session_find_next(struct fira_local *local, |
| u32 next_timestamp_dtu, |
| u32 max_access_duration_dtu, |
| u32 *timestamp_dtu, |
| u32 *duration_dtu) |
| { |
| struct fira_session *selected_session = NULL; |
| struct fira_session *session; |
| u32 selected_timestamp_dtu = 0; |
| u32 selected_duration_dtu = 0; |
| u32 access_timestamp_dtu; |
| u32 access_duration_dtu; |
| u32 unsync_access_duration_dtu; |
| u32 selected_unsync_access_duration_dtu = 0; |
| u32 max_unsync_access_duration_dtu = 0; |
| bool found_sync_session = false; |
| struct mcps802154_region_demand demand; |
| |
| /* Select the next synchronised session that can be scheduled without |
| * overlapping any other synchronised sessions or if they are |
| * overlapping, the session with the highest priority. */ |
| list_for_each_entry (session, &local->active_sessions, entry) { |
| if (session->params.device_type == FIRA_DEVICE_TYPE_CONTROLEE && |
| !session->synchronised) |
| continue; |
| fira_session_update(local, session, next_timestamp_dtu); |
| fira_session_get_demand(local, session, &demand); |
| access_timestamp_dtu = demand.timestamp_dtu; |
| access_duration_dtu = demand.duration_dtu; |
| if ((!selected_session || |
| is_before_dtu(access_timestamp_dtu + access_duration_dtu + |
| local->llhw->anticip_dtu, |
| selected_timestamp_dtu + 1) || |
| (is_before_dtu(access_timestamp_dtu, |
| selected_timestamp_dtu + |
| selected_duration_dtu + |
| local->llhw->anticip_dtu) && |
| fira_session_has_higher_priority(session, |
| selected_session))) && |
| (!max_access_duration_dtu || |
| access_duration_dtu <= max_access_duration_dtu)) { |
| found_sync_session = true; |
| selected_session = session; |
| selected_timestamp_dtu = access_timestamp_dtu; |
| selected_duration_dtu = access_duration_dtu; |
| } |
| } |
| |
| if (found_sync_session) |
| max_unsync_access_duration_dtu = |
| max((s32)(selected_timestamp_dtu - next_timestamp_dtu - |
| local->llhw->anticip_dtu), |
| 0); |
| |
| /* Select a session that is not synchronised if there is enough time to |
| * schedule it before the synchronised session currently selected. */ |
| list_for_each_entry (session, &local->active_sessions, entry) { |
| if (session->params.device_type != FIRA_DEVICE_TYPE_CONTROLEE || |
| session->synchronised) |
| continue; |
| fira_session_update(local, session, next_timestamp_dtu); |
| fira_session_get_demand(local, session, &demand); |
| access_duration_dtu = demand.duration_dtu; |
| unsync_access_duration_dtu = U32_MAX; |
| if (session->params.max_rr_retry) { |
| int nb_blocks = session->params.max_rr_retry + |
| session->last_block_index - |
| session->block_index; |
| |
| unsync_access_duration_dtu = |
| min((u32)session->params.block_duration_dtu * |
| max(nb_blocks, 1), |
| unsync_access_duration_dtu); |
| } |
| if (found_sync_session) |
| unsync_access_duration_dtu = |
| min(max_unsync_access_duration_dtu, |
| unsync_access_duration_dtu); |
| if (max_access_duration_dtu) |
| unsync_access_duration_dtu = |
| min(max_access_duration_dtu, |
| unsync_access_duration_dtu); |
| /* Among the sessions that are not synchronised, select the one for which the |
| * shortest access needs to be generated. */ |
| if (access_duration_dtu <= unsync_access_duration_dtu && |
| (!selected_unsync_access_duration_dtu || |
| unsync_access_duration_dtu < |
| selected_unsync_access_duration_dtu)) { |
| selected_session = session; |
| selected_timestamp_dtu = next_timestamp_dtu; |
| if (unsync_access_duration_dtu != U32_MAX) { |
| selected_unsync_access_duration_dtu = |
| selected_duration_dtu = |
| unsync_access_duration_dtu; |
| } else { |
| selected_unsync_access_duration_dtu = |
| selected_duration_dtu = 0; |
| } |
| } |
| } |
| |
| *timestamp_dtu = selected_timestamp_dtu; |
| *duration_dtu = selected_duration_dtu; |
| return selected_session; |
| } |
| |
| static void |
| fira_session_check_max_number_of_measurements(struct fira_local *local, |
| struct fira_session *session) |
| { |
| if (!session->max_number_of_measurements_reached && |
| session->params.max_number_of_measurements && |
| ((s32)(session->params.max_number_of_measurements - |
| session->number_of_measurements) <= 0)) { |
| session->max_number_of_measurements_reached = true; |
| session->controlee_management_flags = |
| FIRA_SESSION_CONTROLEE_MANAGEMENT_FLAG_STOP; |
| fira_session_stop_controlees(local, session, |
| &session->current_controlees); |
| } |
| } |
| |
| static bool fira_session_check_max_rr_retry(struct fira_session *session) |
| { |
| if (session->params.max_rr_retry && |
| !((s32)(session->block_index - session->last_block_index - |
| session->params.max_rr_retry) < 0)) { |
| session->stop_no_response = true; |
| return true; |
| } |
| return false; |
| } |
| |
| static void |
| fira_session_send_collision_reports(struct fira_local *local, |
| struct fira_session *selected_session, |
| u32 selected_end_dtu) |
| { |
| struct fira_session *session; |
| struct fira_session *tmp_session; |
| struct mcps802154_region_demand demand; |
| int i; |
| |
| list_for_each_entry_safe (session, tmp_session, &local->active_sessions, |
| entry) { |
| if (session == selected_session || |
| (session->params.device_type == |
| FIRA_DEVICE_TYPE_CONTROLEE && |
| !session->synchronised)) |
| continue; |
| fira_session_get_demand(local, session, &demand); |
| if (is_before_dtu(demand.timestamp_dtu, selected_end_dtu)) { |
| fira_compute_access(local, session); |
| for (i = 0; i < local->n_ranging_info; i++) { |
| local->ranging_info[i].status = |
| FIRA_STATUS_RANGING_TX_FAILED; |
| } |
| fira_session_access_done(local, session, true); |
| } |
| } |
| } |
| |
| static void fira_session_stop_expired_sessions(struct fira_local *local) |
| { |
| struct fira_session *session; |
| struct fira_session *tmp_session; |
| int i; |
| |
| list_for_each_entry_safe (session, tmp_session, &local->active_sessions, |
| entry) { |
| if (session == local->current_session || |
| !fira_session_check_max_rr_retry(session)) |
| continue; |
| fira_compute_access(local, session); |
| for (i = 0; i < local->n_ranging_info; i++) { |
| local->ranging_info[i].status = |
| FIRA_STATUS_RANGING_RX_TIMEOUT; |
| } |
| fira_session_access_done(local, session, true); |
| } |
| } |
| |
| static void fira_session_check_unsync(struct fira_local *local, |
| struct fira_session *session) |
| { |
| int nb_blocks; |
| int unsync_drift_dtu; |
| int block_margin_dtu; |
| |
| if ((session->params.device_type != FIRA_DEVICE_TYPE_CONTROLEE) || |
| !session->synchronised) |
| return; |
| |
| nb_blocks = session->block_index - session->last_block_index; |
| unsync_drift_dtu = (long long)nb_blocks * |
| session->params.block_duration_dtu * |
| FIRA_DRIFT_TOLERANCE_PPM / 1000000; |
| block_margin_dtu = |
| fira_session_get_block_duration_margin(local, session); |
| if (unsync_drift_dtu >= block_margin_dtu) |
| session->synchronised = false; |
| } |
| |
| struct fira_session *fira_session_next(struct fira_local *local, |
| u32 next_timestamp_dtu, |
| u32 max_access_duration_dtu) |
| { |
| struct fira_session *selected_session; |
| u32 selected_timestamp_dtu = 0; |
| u32 selected_duration_dtu = 0; |
| u32 selected_end_dtu; |
| |
| if (list_empty(&local->active_sessions)) |
| return NULL; |
| |
| selected_session = fira_session_find_next(local, next_timestamp_dtu, |
| max_access_duration_dtu, |
| &selected_timestamp_dtu, |
| &selected_duration_dtu); |
| if (!selected_session) |
| return NULL; |
| selected_end_dtu = selected_timestamp_dtu + selected_duration_dtu + |
| local->llhw->anticip_dtu; |
| fira_session_send_collision_reports(local, selected_session, |
| selected_end_dtu); |
| selected_session->last_access_timestamp_dtu = selected_timestamp_dtu; |
| selected_session->last_access_duration_dtu = selected_duration_dtu; |
| return selected_session; |
| } |
| |
| void fira_session_resync(struct fira_local *local, struct fira_session *session, |
| u32 sts_index, u32 timestamp_dtu) |
| { |
| int block_duration_slots = session->params.block_duration_dtu / |
| session->params.slot_duration_dtu; |
| int slot_index = sts_index - session->crypto.sts_index_init; |
| int block_index = slot_index / block_duration_slots; |
| int round_slot_index = slot_index - block_index * block_duration_slots; |
| |
| session->block_index = block_index; |
| session->block_start_dtu = |
| timestamp_dtu - |
| round_slot_index * session->params.slot_duration_dtu; |
| session->sts_index = sts_index - round_slot_index; |
| session->round_index = |
| round_slot_index / session->params.round_duration_slots; |
| session->synchronised = true; |
| session->last_access_timestamp_dtu = timestamp_dtu; |
| } |
| |
| void fira_session_access_done(struct fira_local *local, |
| struct fira_session *session, |
| bool add_measurements) |
| { |
| if (session->controlee_management_flags == |
| FIRA_SESSION_CONTROLEE_MANAGEMENT_FLAG_STOP) { |
| fira_session_update_stopping_controlees(local, session); |
| session->controlee_management_flags = 0; |
| } |
| |
| if (session == local->current_session) { |
| if (!(session->params.device_type == |
| FIRA_DEVICE_TYPE_CONTROLEE && |
| local->ranging_info[0].status) && |
| !(session->params.device_type == |
| FIRA_DEVICE_TYPE_CONTROLLER && |
| local->n_ranging_valid != local->n_ranging_info)) { |
| session->last_block_index = session->block_index; |
| } else { |
| fira_session_check_unsync(local, session); |
| } |
| session->number_of_measurements++; |
| } |
| |
| fira_session_check_max_number_of_measurements(local, session); |
| fira_session_check_max_rr_retry(session); |
| fira_report(local, session, add_measurements); |
| |
| if (session->controlee_management_flags & |
| FIRA_SESSION_CONTROLEE_MANAGEMENT_FLAG_UPDATE) { |
| fira_session_copy_controlees(&session->current_controlees, |
| &session->new_controlees); |
| session->controlee_management_flags &= |
| ~FIRA_SESSION_CONTROLEE_MANAGEMENT_FLAG_UPDATE; |
| } |
| |
| if (((session->stop_request || |
| session->max_number_of_measurements_reached) && |
| !(session->controlee_management_flags & |
| FIRA_SESSION_CONTROLEE_MANAGEMENT_FLAG_STOP)) || |
| session->stop_inband || session->stop_no_response) { |
| list_move(&session->entry, &local->inactive_sessions); |
| session->stop_request = false; |
| session->stop_inband = false; |
| session->stop_no_response = false; |
| session->max_number_of_measurements_reached = false; |
| /* Reset to max value as it will be recomputed on session start |
| * with fira_session_is_ready call. */ |
| session->params.n_controlees_max = FIRA_CONTROLEES_MAX; |
| /* Reset data parameters. */ |
| session->params.data_payload_seq = 0; |
| session->params.data_payload_len = 0; |
| } |
| |
| if (session == local->current_session) { |
| fira_session_stop_expired_sessions(local); |
| } |
| } |