blob: 98196d8a6058266edad13a84b8aed9786977cc12 [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 <asm/unaligned.h>
#include <linux/bitfield.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/ieee802154.h>
#include <linux/module.h>
#include <net/mcps802154_frame.h>
/**
* enum mcps802154_ie_state - State of information element writing.
* @MCPS802154_IE_STATE_INIT: Initial state, no IE written.
* @MCPS802154_IE_STATE_HEADER_IE: At least one header element written.
* @MCPS802154_IE_STATE_PAYLOAD_IE: At least one payload element written.
* @MCPS802154_IE_STATE_NESTED_MLME_IE: At least one payload element written,
* the last one is a MLME IE containing at least one nested IE.
*/
enum mcps802154_ie_state {
MCPS802154_IE_STATE_INIT,
MCPS802154_IE_STATE_HEADER_IE,
MCPS802154_IE_STATE_PAYLOAD_IE,
MCPS802154_IE_STATE_NESTED_MLME_IE,
};
/**
* struct mcps802154_ie_cb - Control buffer used in sk_buff to store information
* while building IE.
*/
struct mcps802154_ie_cb {
/**
* @ie_state: State of IEs writing, used to know whether a terminator is
* needed or not.
*/
enum mcps802154_ie_state ie_state;
/**
* @mlme_ie_index: Index in buffer of the MLME IE header. Valid in the
* corresponding state only, used to increment IE payload length.
*/
u16 mlme_ie_index;
/**
* @header_len: Length of frame header. Can be used for frame
* encryption, the header is not encrypted but only used for
* authentication.
*/
u16 header_len;
};
void mcps802154_ie_put_begin(struct sk_buff *skb)
{
struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
cb->ie_state = MCPS802154_IE_STATE_INIT;
cb->header_len = skb->len;
}
EXPORT_SYMBOL(mcps802154_ie_put_begin);
int mcps802154_ie_put_end(struct sk_buff *skb, bool data_payload)
{
struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
if (data_payload) {
bool err = false;
if (cb->ie_state == MCPS802154_IE_STATE_HEADER_IE)
err = !mcps802154_ie_put_header_ie(
skb, IEEE802154_IE_HEADER_TERMINATION_2_ID, 0);
else if (cb->ie_state >= MCPS802154_IE_STATE_PAYLOAD_IE)
err = !mcps802154_ie_put_payload_ie(
skb, IEEE802154_IE_PAYLOAD_TERMINATION_GID, 0);
if (unlikely(err))
return -ENOBUFS;
}
return cb->header_len;
}
EXPORT_SYMBOL(mcps802154_ie_put_end);
void *mcps802154_ie_put_header_ie(struct sk_buff *skb, int element_id,
unsigned int len)
{
u16 ie_header;
u8 *ie;
struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
if (unlikely(len > FIELD_MAX(IEEE802154_HEADER_IE_HEADER_LENGTH)))
return NULL;
if (unlikely(skb_tailroom(skb) < IEEE802154_IE_HEADER_LEN + len))
return NULL;
if (cb->ie_state == MCPS802154_IE_STATE_INIT)
skb->data[1] |= IEEE802154_FC_IE_PRESENT >> 8;
cb->ie_state = MCPS802154_IE_STATE_HEADER_IE;
ie_header =
FIELD_PREP(IEEE802154_HEADER_IE_HEADER_LENGTH, len) |
FIELD_PREP(IEEE802154_HEADER_IE_HEADER_ELEMENT_ID, element_id) |
IEEE802154_HEADER_IE_HEADER_TYPE;
ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len);
put_unaligned_le16(ie_header, ie);
cb->header_len = skb->len;
return ie + IEEE802154_IE_HEADER_LEN;
}
EXPORT_SYMBOL(mcps802154_ie_put_header_ie);
void *mcps802154_ie_put_payload_ie(struct sk_buff *skb, int group_id,
unsigned int len)
{
u16 ie_header;
u8 *ie;
struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
bool need_terminator = false;
if (cb->ie_state < MCPS802154_IE_STATE_PAYLOAD_IE)
need_terminator = true;
if (unlikely(len > FIELD_MAX(IEEE802154_PAYLOAD_IE_HEADER_LENGTH)))
return NULL;
if (unlikely(skb_tailroom(skb) <
(need_terminator ? IEEE802154_IE_HEADER_LEN : 0) +
IEEE802154_IE_HEADER_LEN + len))
return NULL;
if (cb->ie_state == MCPS802154_IE_STATE_INIT)
skb->data[1] |= IEEE802154_FC_IE_PRESENT >> 8;
if (need_terminator)
mcps802154_ie_put_header_ie(
skb, IEEE802154_IE_HEADER_TERMINATION_1_ID, 0);
cb->ie_state = MCPS802154_IE_STATE_PAYLOAD_IE;
ie_header =
FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_LENGTH, len) |
FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID, group_id) |
IEEE802154_PAYLOAD_IE_HEADER_TYPE;
ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len);
put_unaligned_le16(ie_header, ie);
return ie + IEEE802154_IE_HEADER_LEN;
}
EXPORT_SYMBOL(mcps802154_ie_put_payload_ie);
void *mcps802154_ie_put_nested_mlme_ie(struct sk_buff *skb, int sub_id,
unsigned int len)
{
u16 ie_header;
u8 *ie;
struct mcps802154_ie_cb *cb = (struct mcps802154_ie_cb *)&skb->cb;
if (sub_id < IEEE802154_IE_NESTED_SHORT_MIN_SID) {
if (unlikely(
len >
FIELD_MAX(IEEE802154_LONG_NESTED_IE_HEADER_LENGTH)))
return NULL;
ie_header = FIELD_PREP(IEEE802154_LONG_NESTED_IE_HEADER_LENGTH,
len) |
FIELD_PREP(IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID,
sub_id) |
IEEE802154_LONG_NESTED_IE_HEADER_TYPE;
} else {
if (unlikely(len >
FIELD_MAX(
IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH)))
return NULL;
ie_header = FIELD_PREP(IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH,
len) |
FIELD_PREP(IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID,
sub_id) |
IEEE802154_SHORT_NESTED_IE_HEADER_TYPE;
}
if (cb->ie_state != MCPS802154_IE_STATE_NESTED_MLME_IE) {
ie = mcps802154_ie_put_payload_ie(
skb, IEEE802154_IE_PAYLOAD_MLME_GID,
IEEE802154_IE_HEADER_LEN + len);
if (unlikely(!ie))
return NULL;
cb->ie_state = MCPS802154_IE_STATE_NESTED_MLME_IE;
cb->mlme_ie_index = ie - IEEE802154_IE_HEADER_LEN - skb->data;
} else {
u8 *mlme_ie = skb->data + cb->mlme_ie_index;
u16 mlme_ie_header = get_unaligned_le16(mlme_ie);
int mlme_len = FIELD_GET(IEEE802154_PAYLOAD_IE_HEADER_LENGTH,
mlme_ie_header);
mlme_len += IEEE802154_IE_HEADER_LEN + len;
if (unlikely(mlme_len >
FIELD_MAX(IEEE802154_PAYLOAD_IE_HEADER_LENGTH)))
return NULL;
mlme_ie_header = (mlme_ie_header &
~IEEE802154_PAYLOAD_IE_HEADER_LENGTH) |
FIELD_PREP(IEEE802154_PAYLOAD_IE_HEADER_LENGTH,
mlme_len);
put_unaligned_le16(mlme_ie_header, mlme_ie);
ie = skb_put(skb, IEEE802154_IE_HEADER_LEN + len);
}
put_unaligned_le16(ie_header, ie);
return ie + IEEE802154_IE_HEADER_LEN;
}
EXPORT_SYMBOL(mcps802154_ie_put_nested_mlme_ie);
int mcps802154_ie_get(struct sk_buff *skb,
struct mcps802154_ie_get_context *context)
{
u16 ie_header;
bool header;
bool last = false;
if (skb->len < IEEE802154_IE_HEADER_LEN) {
if (context->mlme_len)
/* This could only happen if caller made a mistake. */
return -EINVAL;
if (skb->len > 0)
/* Not enough for a header, but too much for nothing. */
return -EBADMSG;
context->in_payload = true;
context->kind = MCPS802154_IE_GET_KIND_NONE;
context->id = 0;
context->len = 0;
return 1;
}
ie_header = get_unaligned_le16(skb->data);
skb_pull(skb, sizeof(ie_header));
if (context->mlme_len) {
context->kind = MCPS802154_IE_GET_KIND_MLME_NESTED;
if ((ie_header & IEEE802154_IE_HEADER_TYPE) ==
IEEE802154_LONG_NESTED_IE_HEADER_TYPE) {
context->id = FIELD_GET(
IEEE802154_LONG_NESTED_IE_HEADER_SUB_ID,
ie_header);
context->len = FIELD_GET(
IEEE802154_LONG_NESTED_IE_HEADER_LENGTH,
ie_header);
} else {
context->id = FIELD_GET(
IEEE802154_SHORT_NESTED_IE_HEADER_SUB_ID,
ie_header);
context->len = FIELD_GET(
IEEE802154_SHORT_NESTED_IE_HEADER_LENGTH,
ie_header);
}
if (context->mlme_len < IEEE802154_IE_HEADER_LEN + context->len)
return -EBADMSG;
context->mlme_len -= IEEE802154_IE_HEADER_LEN + context->len;
} else {
header = (ie_header & IEEE802154_IE_HEADER_TYPE) ==
IEEE802154_HEADER_IE_HEADER_TYPE;
if (header != !context->in_payload)
return -EBADMSG;
if (header) {
context->kind = MCPS802154_IE_GET_KIND_HEADER;
context->id = FIELD_GET(
IEEE802154_HEADER_IE_HEADER_ELEMENT_ID,
ie_header);
context->len = FIELD_GET(
IEEE802154_HEADER_IE_HEADER_LENGTH, ie_header);
if (context->id ==
IEEE802154_IE_HEADER_TERMINATION_1_ID)
context->in_payload = true;
if (context->id ==
IEEE802154_IE_HEADER_TERMINATION_2_ID) {
context->in_payload = true;
last = true;
}
} else {
context->kind = MCPS802154_IE_GET_KIND_PAYLOAD;
context->id =
FIELD_GET(IEEE802154_PAYLOAD_IE_HEADER_GROUP_ID,
ie_header);
context->len = FIELD_GET(
IEEE802154_PAYLOAD_IE_HEADER_LENGTH, ie_header);
if (context->id ==
IEEE802154_IE_PAYLOAD_TERMINATION_GID)
last = true;
else if (context->id == IEEE802154_IE_PAYLOAD_MLME_GID)
context->mlme_len = context->len;
}
if (skb->len < context->len)
return -EBADMSG;
}
return last ? 1 : 0;
}
EXPORT_SYMBOL(mcps802154_ie_get);