blob: bf8fb9bd218af1fd88fcf37584601c3f621313ef [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 <linux/errno.h>
#include <linux/string.h>
#include <linux/limits.h>
#include <net/nfcc_coex_region_nl.h>
#include <net/mcps802154_frame.h>
#include "mcps802154_qorvo.h"
#include "nfcc_coex_session.h"
#include "nfcc_coex_region_call.h"
#include "nfcc_coex_trace.h"
static const struct nla_policy nfcc_coex_call_nla_policy[NFCC_COEX_CALL_ATTR_MAX +
1] = {
[NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS] = { .type = NLA_NESTED },
};
static const struct nla_policy nfcc_coex_session_param_nla_policy
[NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX + 1] = {
[NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS] = { .type = NLA_U64 },
[NFCC_COEX_CCC_SESSION_PARAM_ATTR_CHANNEL_NUMBER] = { .type = NLA_U8 },
[NFCC_COEX_CCC_SESSION_PARAM_ATTR_VERSION] =
NLA_POLICY_RANGE(NLA_U8, 2, 3),
};
/**
* nfcc_coex_session_set_parameters() - Set NFCC coexistence session parameters.
* @local: NFCC coexistence context.
* @params: Nested attribute containing session parameters.
* @info: Request information.
* @now_ns: current kernel time.
*
* Return: 0 or error.
*/
static int nfcc_coex_session_set_parameters(struct nfcc_coex_local *local,
const struct nlattr *params,
const struct genl_info *info,
u64 now_ns)
{
struct nlattr *attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX + 1];
struct nfcc_coex_session *session = &local->session;
struct nfcc_coex_session_params *p = &session->params;
/* Maximum dtu duration is INT32_MAX. */
const s64 max_time0_ns =
(S32_MAX * NS_PER_SECOND) / local->llhw->dtu_freq_hz;
int r;
if (!params)
return -EINVAL;
r = nla_parse_nested(attrs, NFCC_COEX_CCC_SESSION_PARAM_ATTR_MAX,
params, nfcc_coex_session_param_nla_policy,
info->extack);
if (r)
return r;
#define P(attr, member, type, conv) \
do { \
type x; \
if (attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_##attr]) { \
x = nla_get_##type( \
attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_##attr]); \
p->member = conv; \
} \
} while (0)
P(TIME0_NS, time0_ns, u64, x);
P(CHANNEL_NUMBER, channel_number, u8, x);
P(VERSION, version, u8, x);
#undef P
if (!attrs[NFCC_COEX_CCC_SESSION_PARAM_ATTR_TIME0_NS]) {
p->time0_ns = (NS_PER_SECOND * local->llhw->anticip_dtu) /
local->llhw->dtu_freq_hz + now_ns;
}
if ((s64)(p->time0_ns - now_ns) > max_time0_ns)
return -ERANGE;
return 0;
}
/**
* nfcc_coex_session_start() - Start NFCC coex session.
* @local: NFCC coexistence context.
* @info: Request information.
* @now_ns: current kernel time.
*
* Return: 0 or error.
*/
static int nfcc_coex_session_start(struct nfcc_coex_local *local,
const struct genl_info *info, u64 now_ns)
{
struct nfcc_coex_session *session = &local->session;
const struct nfcc_coex_session_params *p = &session->params;
u32 now_dtu;
s64 diff_ns;
s64 diff_dtu;
int r;
WARN_ON(session->state == NFCC_COEX_STATE_STARTED);
trace_region_nfcc_coex_session_start(local, p);
r = mcps802154_get_current_timestamp_dtu(local->llhw, &now_dtu);
if (r)
return r;
diff_ns = p->time0_ns - now_ns;
diff_dtu = div64_s64(diff_ns * local->llhw->dtu_freq_hz, NS_PER_SECOND);
/* If the requested start date is in the past, start immediately */
if (diff_dtu < local->llhw->anticip_dtu) {
pr_warn("dw3000: Computed start date is in the past, scheduling"
" to anticip_dtu instead");
diff_dtu = local->llhw->anticip_dtu;
}
session->region_demand.timestamp_dtu = now_dtu + diff_dtu;
session->region_demand.max_duration_dtu = 0;
session->event_portid = info->snd_portid;
session->first_access = true;
nfcc_coex_set_state(local, NFCC_COEX_STATE_STARTED);
mcps802154_reschedule(local->llhw);
return 0;
}
/**
* nfcc_coex_session_start_all() - Start all for a NFCC coex session.
* @local: NFCC coexistence context.
* @params: Call parameters.
* @info: Request information.
*
* Return: 0 or error.
*/
static int nfcc_coex_session_start_all(struct nfcc_coex_local *local,
const struct nlattr *params,
const struct genl_info *info)
{
struct nlattr *attrs[NFCC_COEX_CALL_ATTR_MAX + 1];
int r;
u64 now_ns;
if (!params)
return -EINVAL;
r = nla_parse_nested(attrs, NFCC_COEX_CALL_ATTR_MAX, params,
nfcc_coex_call_nla_policy, info->extack);
if (r)
return r;
if (local->session.state == NFCC_COEX_STATE_STARTED)
return -EBUSY;
nfcc_coex_session_init(local);
now_ns = ktime_to_ns(ktime_get_boottime());
r = nfcc_coex_session_set_parameters(
local, attrs[NFCC_COEX_CALL_ATTR_CCC_SESSION_PARAMS], info,
now_ns);
if (r)
return r;
r = nfcc_coex_session_start(local, info, now_ns);
if (r)
return r;
return 0;
}
/**
* nfcc_coex_session_stop() - Stop NFCC coex session.
* @local: NFCC coexistence context.
*
* Return: 0 or error.
*/
static int nfcc_coex_session_stop(struct nfcc_coex_local *local)
{
struct nfcc_coex_session *session = &local->session;
trace_region_nfcc_coex_session_stop(local);
if (session->state == NFCC_COEX_STATE_STARTED) {
nfcc_coex_set_state(local, NFCC_COEX_STATE_STOPPING);
mcps802154_schedule_invalidate(local->llhw);
}
return 0;
}
int nfcc_coex_session_control(struct nfcc_coex_local *local, u32 call_id,
const struct nlattr *params,
const struct genl_info *info)
{
switch (call_id) {
case NFCC_COEX_CALL_CCC_SESSION_START:
return nfcc_coex_session_start_all(local, params, info);
default:
case NFCC_COEX_CALL_CCC_SESSION_STOP:
return nfcc_coex_session_stop(local);
}
}