| /* |
| * 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 <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/errno.h> |
| |
| #include "mcps802154_i.h" |
| #include "trace.h" |
| |
| void mcps802154_schedule_clear(struct mcps802154_local *local) |
| { |
| if (local->ca.schedule.regions) { |
| local->ca.schedule.n_regions = 0; |
| kfree(local->ca.schedule.regions); |
| local->ca.schedule.regions = NULL; |
| } |
| } |
| |
| int mcps802154_schedule_update(struct mcps802154_local *local, |
| u32 next_timestamp_dtu) |
| { |
| struct mcps802154_schedule_update_local sulocal; |
| struct mcps802154_schedule_update *su = &sulocal.schedule_update; |
| struct mcps802154_schedule *sched = &local->ca.schedule; |
| struct mcps802154_scheduler *scheduler; |
| u32 expected_start_timestamp_dtu; |
| int r; |
| |
| trace_schedule_update(local, next_timestamp_dtu); |
| |
| /* If no schedule at all, set sane values. */ |
| if (sched->n_regions == 0) { |
| sched->start_timestamp_dtu = next_timestamp_dtu; |
| sched->duration_dtu = 0; |
| expected_start_timestamp_dtu = next_timestamp_dtu; |
| } else { |
| expected_start_timestamp_dtu = |
| sched->start_timestamp_dtu + sched->duration_dtu; |
| } |
| sched->current_index = 0; |
| |
| /* Call scheduler. */ |
| su->expected_start_timestamp_dtu = expected_start_timestamp_dtu; |
| su->start_timestamp_dtu = sched->start_timestamp_dtu; |
| su->duration_dtu = sched->duration_dtu; |
| su->n_regions = sched->n_regions; |
| sulocal.local = local; |
| scheduler = local->ca.scheduler; |
| r = scheduler->ops->update_schedule(scheduler, su, next_timestamp_dtu); |
| if (r) { |
| mcps802154_schedule_clear(local); |
| return r; |
| } |
| |
| /* Check we have a valid schedule. */ |
| if (sched->n_regions == 0 || |
| is_before_dtu(sched->start_timestamp_dtu, |
| expected_start_timestamp_dtu)) { |
| mcps802154_schedule_clear(local); |
| return -EOPNOTSUPP; |
| } |
| |
| trace_schedule_update_done(local, sched); |
| |
| return 0; |
| } |
| |
| int mcps802154_schedule_set_start( |
| const struct mcps802154_schedule_update *schedule_update, |
| u32 start_timestamp_dtu) |
| { |
| struct mcps802154_schedule_update_local *sulocal = |
| schedule_update_to_local(schedule_update); |
| struct mcps802154_schedule_update *su = &sulocal->schedule_update; |
| struct mcps802154_schedule *sched = &sulocal->local->ca.schedule; |
| |
| if (is_before_dtu(start_timestamp_dtu, |
| schedule_update->expected_start_timestamp_dtu)) |
| return -EINVAL; |
| |
| su->start_timestamp_dtu = sched->start_timestamp_dtu = |
| start_timestamp_dtu; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(mcps802154_schedule_set_start); |
| |
| int mcps802154_schedule_recycle( |
| const struct mcps802154_schedule_update *schedule_update, |
| size_t n_keeps, int last_region_duration_dtu) |
| { |
| struct mcps802154_schedule_update_local *sulocal = |
| schedule_update_to_local(schedule_update); |
| struct mcps802154_schedule_update *su = &sulocal->schedule_update; |
| struct mcps802154_schedule *sched = &sulocal->local->ca.schedule; |
| struct mcps802154_schedule_region *last_sched_region; |
| |
| if (n_keeps > sched->n_regions) |
| return -EINVAL; |
| |
| if (n_keeps == 0 && |
| last_region_duration_dtu != MCPS802154_DURATION_NO_CHANGE) |
| return -EINVAL; |
| |
| /* Change the number of currently used regions. */ |
| su->n_regions = sched->n_regions = n_keeps; |
| |
| /* Update last region. */ |
| last_sched_region = |
| sched->n_regions ? &sched->regions[sched->n_regions - 1] : NULL; |
| if (last_region_duration_dtu != MCPS802154_DURATION_NO_CHANGE) |
| last_sched_region->duration_dtu = last_region_duration_dtu; |
| |
| /* Update schedule duration. */ |
| if (!last_sched_region || last_sched_region->duration_dtu == 0) { |
| su->duration_dtu = sched->duration_dtu = 0; |
| } else { |
| su->duration_dtu = sched->duration_dtu = |
| last_sched_region->start_dtu + |
| last_sched_region->duration_dtu; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(mcps802154_schedule_recycle); |
| |
| int mcps802154_schedule_add_region( |
| const struct mcps802154_schedule_update *schedule_update, |
| struct mcps802154_region *region, int start_dtu, int duration_dtu) |
| { |
| struct mcps802154_schedule_update_local *sulocal = |
| schedule_update_to_local(schedule_update); |
| struct mcps802154_schedule_update *su = &sulocal->schedule_update; |
| struct mcps802154_schedule *sched = &sulocal->local->ca.schedule; |
| struct mcps802154_schedule_region *last_sched_region = |
| sched->n_regions ? &sched->regions[sched->n_regions - 1] : NULL; |
| struct mcps802154_schedule_region *sched_region, *new_sched_regions; |
| |
| if (start_dtu < 0 || duration_dtu < 0) |
| return -EINVAL; |
| |
| /* Can not add a region after an endless region. */ |
| if (last_sched_region && last_sched_region->duration_dtu == 0) |
| return -EINVAL; |
| |
| /* Regions can not overlap. */ |
| if (last_sched_region && |
| start_dtu < last_sched_region->start_dtu + |
| last_sched_region->duration_dtu) |
| return -EINVAL; |
| |
| if (!region || !region->ops || !region->ops->get_access) |
| return -EINVAL; |
| |
| /* Add to schedule. */ |
| new_sched_regions = krealloc( |
| sched->regions, |
| sizeof(sched->regions[0]) * (sched->n_regions + 1), GFP_KERNEL); |
| if (!new_sched_regions) |
| return -ENOMEM; |
| |
| /* Fill new added schedule region. */ |
| sched_region = &new_sched_regions[sched->n_regions]; |
| sched_region->start_dtu = start_dtu; |
| sched_region->duration_dtu = duration_dtu; |
| sched_region->region = region; |
| |
| sched->regions = new_sched_regions; |
| su->n_regions = sched->n_regions = sched->n_regions + 1; |
| |
| /* Update schedule duration. */ |
| if (duration_dtu == 0) { |
| su->duration_dtu = sched->duration_dtu = 0; |
| } else { |
| su->duration_dtu = sched->duration_dtu = |
| start_dtu + duration_dtu; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(mcps802154_schedule_add_region); |
| |
| void mcps802154_reschedule(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| mcps802154_ca_may_reschedule(local); |
| } |
| EXPORT_SYMBOL(mcps802154_reschedule); |
| |
| void mcps802154_schedule_invalidate(struct mcps802154_llhw *llhw) |
| { |
| struct mcps802154_local *local = llhw_to_local(llhw); |
| |
| if (likely(local->started)) |
| mcps802154_ca_invalidate_schedule(local); |
| } |
| EXPORT_SYMBOL(mcps802154_schedule_invalidate); |