530 lines
15 KiB
C
530 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
|
|
|
|
#include <linux/gfp.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/once.h>
|
|
#include <linux/random.h>
|
|
#include <linux/string.h>
|
|
#include <uapi/linux/if_ether.h>
|
|
|
|
#include "fbnic_tlv.h"
|
|
|
|
/**
|
|
* fbnic_tlv_msg_alloc - Allocate page and initialize FW message header
|
|
* @msg_id: Identifier for new message we are starting
|
|
*
|
|
* Return: pointer to start of message, or NULL on failure.
|
|
*
|
|
* Allocates a page and initializes message header at start of page.
|
|
* Initial message size is 1 DWORD which is just the header.
|
|
**/
|
|
struct fbnic_tlv_msg *fbnic_tlv_msg_alloc(u16 msg_id)
|
|
{
|
|
struct fbnic_tlv_hdr hdr = { 0 };
|
|
struct fbnic_tlv_msg *msg;
|
|
|
|
msg = (struct fbnic_tlv_msg *)__get_free_page(GFP_KERNEL);
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
/* Start with zero filled header and then back fill with data */
|
|
hdr.type = msg_id;
|
|
hdr.is_msg = 1;
|
|
hdr.len = cpu_to_le16(1);
|
|
|
|
/* Copy header into start of message */
|
|
msg->hdr = hdr;
|
|
|
|
return msg;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_put_flag - Add flag value to message
|
|
* @msg: Message header we are adding flag attribute to
|
|
* @attr_id: ID of flag attribute we are adding to message
|
|
*
|
|
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
|
|
*
|
|
* Adds a 1 DWORD flag attribute to the message. The presence of this
|
|
* attribute can be used as a boolean value indicating true, otherwise the
|
|
* value is considered false.
|
|
**/
|
|
int fbnic_tlv_attr_put_flag(struct fbnic_tlv_msg *msg, const u16 attr_id)
|
|
{
|
|
int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);
|
|
struct fbnic_tlv_hdr hdr = { 0 };
|
|
struct fbnic_tlv_msg *attr;
|
|
|
|
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
|
|
if (attr_max_len < sizeof(*attr))
|
|
return -ENOSPC;
|
|
|
|
/* Get header pointer and bump attr to start of data */
|
|
attr = &msg[le16_to_cpu(msg->hdr.len)];
|
|
|
|
/* Record attribute type and size */
|
|
hdr.type = attr_id;
|
|
hdr.len = cpu_to_le16(sizeof(hdr));
|
|
|
|
attr->hdr = hdr;
|
|
le16_add_cpu(&msg->hdr.len,
|
|
FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_put_value - Add data to message
|
|
* @msg: Message header we are adding flag attribute to
|
|
* @attr_id: ID of flag attribute we are adding to message
|
|
* @value: Pointer to data to be stored
|
|
* @len: Size of data to be stored.
|
|
*
|
|
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
|
|
*
|
|
* Adds header and copies data pointed to by value into the message. The
|
|
* result is rounded up to the nearest DWORD for sizing so that the
|
|
* headers remain aligned.
|
|
*
|
|
* The assumption is that the value field is in a format where byte
|
|
* ordering can be guaranteed such as a byte array or a little endian
|
|
* format.
|
|
**/
|
|
int fbnic_tlv_attr_put_value(struct fbnic_tlv_msg *msg, const u16 attr_id,
|
|
const void *value, const int len)
|
|
{
|
|
int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);
|
|
struct fbnic_tlv_hdr hdr = { 0 };
|
|
struct fbnic_tlv_msg *attr;
|
|
|
|
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
|
|
if (attr_max_len < sizeof(*attr) + len)
|
|
return -ENOSPC;
|
|
|
|
/* Get header pointer and bump attr to start of data */
|
|
attr = &msg[le16_to_cpu(msg->hdr.len)];
|
|
|
|
/* Record attribute type and size */
|
|
hdr.type = attr_id;
|
|
hdr.len = cpu_to_le16(sizeof(hdr) + len);
|
|
|
|
/* Zero pad end of region to be written if we aren't aligned */
|
|
if (len % sizeof(hdr))
|
|
attr->value[len / sizeof(hdr)] = 0;
|
|
|
|
/* Copy data over */
|
|
memcpy(attr->value, value, len);
|
|
|
|
attr->hdr = hdr;
|
|
le16_add_cpu(&msg->hdr.len,
|
|
FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* __fbnic_tlv_attr_put_int - Add integer to message
|
|
* @msg: Message header we are adding flag attribute to
|
|
* @attr_id: ID of flag attribute we are adding to message
|
|
* @value: Data to be stored
|
|
* @len: Size of data to be stored, either 4 or 8 bytes.
|
|
*
|
|
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
|
|
*
|
|
* Adds header and copies data pointed to by value into the message. Will
|
|
* format the data as little endian.
|
|
**/
|
|
int __fbnic_tlv_attr_put_int(struct fbnic_tlv_msg *msg, const u16 attr_id,
|
|
s64 value, const int len)
|
|
{
|
|
__le64 le64_value = cpu_to_le64(value);
|
|
|
|
return fbnic_tlv_attr_put_value(msg, attr_id, &le64_value, len);
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_put_mac_addr - Add mac_addr to message
|
|
* @msg: Message header we are adding flag attribute to
|
|
* @attr_id: ID of flag attribute we are adding to message
|
|
* @mac_addr: Byte pointer to MAC address to be stored
|
|
*
|
|
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
|
|
*
|
|
* Adds header and copies data pointed to by mac_addr into the message. Will
|
|
* copy the address raw so it will be in big endian with start of MAC
|
|
* address at start of attribute.
|
|
**/
|
|
int fbnic_tlv_attr_put_mac_addr(struct fbnic_tlv_msg *msg, const u16 attr_id,
|
|
const u8 *mac_addr)
|
|
{
|
|
return fbnic_tlv_attr_put_value(msg, attr_id, mac_addr, ETH_ALEN);
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_put_string - Add string to message
|
|
* @msg: Message header we are adding flag attribute to
|
|
* @attr_id: ID of flag attribute we are adding to message
|
|
* @string: Byte pointer to null terminated string to be stored
|
|
*
|
|
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
|
|
*
|
|
* Adds header and copies data pointed to by string into the message. Will
|
|
* copy the address raw so it will be in byte order.
|
|
**/
|
|
int fbnic_tlv_attr_put_string(struct fbnic_tlv_msg *msg, u16 attr_id,
|
|
const char *string)
|
|
{
|
|
int attr_max_len = PAGE_SIZE - sizeof(*msg);
|
|
int str_len = 1;
|
|
|
|
/* The max length will be message minus existing message and new
|
|
* attribute header. Since the message is measured in DWORDs we have
|
|
* to multiply the size by 4.
|
|
*
|
|
* The string length doesn't include the \0 so we have to add one to
|
|
* the final value, so start with that as our initial value.
|
|
*
|
|
* We will verify if the string will fit in fbnic_tlv_attr_put_value()
|
|
*/
|
|
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
|
|
str_len += strnlen(string, attr_max_len);
|
|
|
|
return fbnic_tlv_attr_put_value(msg, attr_id, string, str_len);
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_get_unsigned - Retrieve unsigned value from result
|
|
* @attr: Attribute to retrieve data from
|
|
*
|
|
* Return: unsigned 64b value containing integer value
|
|
**/
|
|
u64 fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg *attr)
|
|
{
|
|
__le64 le64_value = 0;
|
|
|
|
memcpy(&le64_value, &attr->value[0],
|
|
le16_to_cpu(attr->hdr.len) - sizeof(*attr));
|
|
|
|
return le64_to_cpu(le64_value);
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_get_signed - Retrieve signed value from result
|
|
* @attr: Attribute to retrieve data from
|
|
*
|
|
* Return: signed 64b value containing integer value
|
|
**/
|
|
s64 fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg *attr)
|
|
{
|
|
int shift = (8 + sizeof(*attr) - le16_to_cpu(attr->hdr.len)) * 8;
|
|
__le64 le64_value = 0;
|
|
s64 value;
|
|
|
|
/* Copy the value and adjust for byte ordering */
|
|
memcpy(&le64_value, &attr->value[0],
|
|
le16_to_cpu(attr->hdr.len) - sizeof(*attr));
|
|
value = le64_to_cpu(le64_value);
|
|
|
|
/* Sign extend the return value by using a pair of shifts */
|
|
return (value << shift) >> shift;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_get_string - Retrieve string value from result
|
|
* @attr: Attribute to retrieve data from
|
|
* @str: Pointer to an allocated string to store the data
|
|
* @max_size: The maximum size which can be in str
|
|
*
|
|
* Return: the size of the string read from firmware
|
|
**/
|
|
size_t fbnic_tlv_attr_get_string(struct fbnic_tlv_msg *attr, char *str,
|
|
size_t max_size)
|
|
{
|
|
max_size = min_t(size_t, max_size,
|
|
(le16_to_cpu(attr->hdr.len) * 4) - sizeof(*attr));
|
|
memcpy(str, &attr->value, max_size);
|
|
|
|
return max_size;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_nest_start - Add nested attribute header to message
|
|
* @msg: Message header we are adding flag attribute to
|
|
* @attr_id: ID of flag attribute we are adding to message
|
|
*
|
|
* Return: NULL if there is no room for the attribute. Otherwise a pointer
|
|
* to the new attribute header.
|
|
*
|
|
* New header length is stored initially in DWORDs.
|
|
**/
|
|
struct fbnic_tlv_msg *fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg *msg,
|
|
u16 attr_id)
|
|
{
|
|
int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);
|
|
struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];
|
|
struct fbnic_tlv_hdr hdr = { 0 };
|
|
|
|
/* Make sure we have space for at least the nest header plus one more */
|
|
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
|
|
if (attr_max_len < sizeof(*attr) * 2)
|
|
return NULL;
|
|
|
|
/* Record attribute type and size */
|
|
hdr.type = attr_id;
|
|
|
|
/* Add current message length to account for consumption within the
|
|
* page and leave it as a multiple of DWORDs, we will shift to
|
|
* bytes when we close it out.
|
|
*/
|
|
hdr.len = cpu_to_le16(1);
|
|
|
|
attr->hdr = hdr;
|
|
|
|
return attr;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_nest_stop - Close out nested attribute and add it to message
|
|
* @msg: Message header we are adding flag attribute to
|
|
*
|
|
* Closes out nested attribute, adds length to message, and then bumps
|
|
* length from DWORDs to bytes to match other attributes.
|
|
**/
|
|
void fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg *msg)
|
|
{
|
|
struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];
|
|
u16 len = le16_to_cpu(attr->hdr.len);
|
|
|
|
/* Add attribute to message if there is more than just a header */
|
|
if (len <= 1)
|
|
return;
|
|
|
|
le16_add_cpu(&msg->hdr.len, len);
|
|
|
|
/* Convert from DWORDs to bytes */
|
|
attr->hdr.len = cpu_to_le16(len * sizeof(u32));
|
|
}
|
|
|
|
static int
|
|
fbnic_tlv_attr_validate(struct fbnic_tlv_msg *attr,
|
|
const struct fbnic_tlv_index *tlv_index)
|
|
{
|
|
u16 len = le16_to_cpu(attr->hdr.len) - sizeof(*attr);
|
|
u16 attr_id = attr->hdr.type;
|
|
__le32 *value = &attr->value[0];
|
|
|
|
if (attr->hdr.is_msg)
|
|
return -EINVAL;
|
|
|
|
if (attr_id >= FBNIC_TLV_RESULTS_MAX)
|
|
return -EINVAL;
|
|
|
|
while (tlv_index->id != attr_id) {
|
|
if (tlv_index->id == FBNIC_TLV_ATTR_ID_UNKNOWN) {
|
|
if (attr->hdr.cannot_ignore)
|
|
return -ENOENT;
|
|
return le16_to_cpu(attr->hdr.len);
|
|
}
|
|
|
|
tlv_index++;
|
|
}
|
|
|
|
if (offset_in_page(attr) + len > PAGE_SIZE - sizeof(*attr))
|
|
return -E2BIG;
|
|
|
|
switch (tlv_index->type) {
|
|
case FBNIC_TLV_STRING:
|
|
if (!len || len > tlv_index->len)
|
|
return -EINVAL;
|
|
if (((char *)value)[len - 1])
|
|
return -EINVAL;
|
|
break;
|
|
case FBNIC_TLV_FLAG:
|
|
if (len)
|
|
return -EINVAL;
|
|
break;
|
|
case FBNIC_TLV_UNSIGNED:
|
|
case FBNIC_TLV_SIGNED:
|
|
if (tlv_index->len > sizeof(__le64))
|
|
return -EINVAL;
|
|
fallthrough;
|
|
case FBNIC_TLV_BINARY:
|
|
if (!len || len > tlv_index->len)
|
|
return -EINVAL;
|
|
break;
|
|
case FBNIC_TLV_NESTED:
|
|
case FBNIC_TLV_ARRAY:
|
|
if (len % 4)
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_parse_array - Parse array of attributes into results array
|
|
* @attr: Start of attributes in the message
|
|
* @len: Length of attributes in the message
|
|
* @results: Array of pointers to store the results of parsing
|
|
* @tlv_index: List of TLV attributes to be parsed from message
|
|
* @tlv_attr_id: Specific ID that is repeated in array
|
|
* @array_len: Number of results to store in results array
|
|
*
|
|
* Return: zero on success, or negative value on error.
|
|
*
|
|
* Will take a list of attributes and a parser definition and will capture
|
|
* the results in the results array to have the data extracted later.
|
|
**/
|
|
int fbnic_tlv_attr_parse_array(struct fbnic_tlv_msg *attr, int len,
|
|
struct fbnic_tlv_msg **results,
|
|
const struct fbnic_tlv_index *tlv_index,
|
|
u16 tlv_attr_id, size_t array_len)
|
|
{
|
|
int i = 0;
|
|
|
|
/* Initialize results table to NULL. */
|
|
memset(results, 0, array_len * sizeof(results[0]));
|
|
|
|
/* Nothing to parse if header was only thing there */
|
|
if (!len)
|
|
return 0;
|
|
|
|
/* Work through list of attributes, parsing them as necessary */
|
|
while (len > 0) {
|
|
u16 attr_id = attr->hdr.type;
|
|
u16 attr_len;
|
|
int err;
|
|
|
|
if (tlv_attr_id != attr_id)
|
|
return -EINVAL;
|
|
|
|
/* Stop parsing on full error */
|
|
err = fbnic_tlv_attr_validate(attr, tlv_index);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (i >= array_len)
|
|
return -ENOSPC;
|
|
|
|
results[i++] = attr;
|
|
|
|
attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));
|
|
len -= attr_len;
|
|
attr += attr_len;
|
|
}
|
|
|
|
return len == 0 ? 0 : -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_attr_parse - Parse attributes into a list of attribute results
|
|
* @attr: Start of attributes in the message
|
|
* @len: Length of attributes in the message
|
|
* @results: Array of pointers to store the results of parsing
|
|
* @tlv_index: List of TLV attributes to be parsed from message
|
|
*
|
|
* Return: zero on success, or negative value on error.
|
|
*
|
|
* Will take a list of attributes and a parser definition and will capture
|
|
* the results in the results array to have the data extracted later.
|
|
**/
|
|
int fbnic_tlv_attr_parse(struct fbnic_tlv_msg *attr, int len,
|
|
struct fbnic_tlv_msg **results,
|
|
const struct fbnic_tlv_index *tlv_index)
|
|
{
|
|
/* Initialize results table to NULL. */
|
|
memset(results, 0, sizeof(results[0]) * FBNIC_TLV_RESULTS_MAX);
|
|
|
|
/* Nothing to parse if header was only thing there */
|
|
if (!len)
|
|
return 0;
|
|
|
|
/* Work through list of attributes, parsing them as necessary */
|
|
while (len > 0) {
|
|
int err = fbnic_tlv_attr_validate(attr, tlv_index);
|
|
u16 attr_id = attr->hdr.type;
|
|
u16 attr_len;
|
|
|
|
/* Stop parsing on full error */
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* Ignore results for unsupported values */
|
|
if (!err) {
|
|
/* Do not overwrite existing entries */
|
|
if (results[attr_id])
|
|
return -EADDRINUSE;
|
|
|
|
results[attr_id] = attr;
|
|
}
|
|
|
|
attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));
|
|
len -= attr_len;
|
|
attr += attr_len;
|
|
}
|
|
|
|
return len == 0 ? 0 : -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_msg_parse - Parse message and process via predetermined functions
|
|
* @opaque: Value passed to parser function to enable driver access
|
|
* @msg: Message to be parsed.
|
|
* @parser: TLV message parser definition.
|
|
*
|
|
* Return: zero on success, or negative value on error.
|
|
*
|
|
* Will take a message a number of message types via the attribute parsing
|
|
* definitions and function provided for the parser array.
|
|
**/
|
|
int fbnic_tlv_msg_parse(void *opaque, struct fbnic_tlv_msg *msg,
|
|
const struct fbnic_tlv_parser *parser)
|
|
{
|
|
struct fbnic_tlv_msg *results[FBNIC_TLV_RESULTS_MAX];
|
|
u16 msg_id = msg->hdr.type;
|
|
int err;
|
|
|
|
if (!msg->hdr.is_msg)
|
|
return -EINVAL;
|
|
|
|
if (le16_to_cpu(msg->hdr.len) > PAGE_SIZE / sizeof(u32))
|
|
return -E2BIG;
|
|
|
|
while (parser->id != msg_id) {
|
|
if (parser->id == FBNIC_TLV_MSG_ID_UNKNOWN)
|
|
return -ENOENT;
|
|
parser++;
|
|
}
|
|
|
|
err = fbnic_tlv_attr_parse(&msg[1], le16_to_cpu(msg->hdr.len) - 1,
|
|
results, parser->attr);
|
|
if (err)
|
|
return err;
|
|
|
|
return parser->func(opaque, results);
|
|
}
|
|
|
|
/**
|
|
* fbnic_tlv_parser_error - called if message doesn't match known type
|
|
* @opaque: (unused)
|
|
* @results: (unused)
|
|
*
|
|
* Return: -EBADMSG to indicate the message is an unsupported type
|
|
**/
|
|
int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results)
|
|
{
|
|
return -EBADMSG;
|
|
}
|
|
|
|
void fbnic_tlv_attr_addr_copy(u8 *dest, struct fbnic_tlv_msg *src)
|
|
{
|
|
u8 *mac_addr;
|
|
|
|
mac_addr = fbnic_tlv_attr_get_value_ptr(src);
|
|
memcpy(dest, mac_addr, ETH_ALEN);
|
|
}
|