blob: 6f673ad1564f5a6702afea75072db24f9e790581 [file] [log] [blame]
/*
* 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);
}
}