brintos

brintos / linux-shallow public Read only

0
0
Text · 14.7 KiB · 2a174ab Raw
530 lines · c
1// SPDX-License-Identifier: GPL-2.02/* Copyright (c) Meta Platforms, Inc. and affiliates. */3 4#include <linux/gfp.h>5#include <linux/mm.h>6#include <linux/once.h>7#include <linux/random.h>8#include <linux/string.h>9#include <uapi/linux/if_ether.h>10 11#include "fbnic_tlv.h"12 13/**14 * fbnic_tlv_msg_alloc - Allocate page and initialize FW message header15 * @msg_id: Identifier for new message we are starting16 *17 * Return: pointer to start of message, or NULL on failure.18 *19 * Allocates a page and initializes message header at start of page.20 * Initial message size is 1 DWORD which is just the header.21 **/22struct fbnic_tlv_msg *fbnic_tlv_msg_alloc(u16 msg_id)23{24	struct fbnic_tlv_hdr hdr = { 0 };25	struct fbnic_tlv_msg *msg;26 27	msg = (struct fbnic_tlv_msg *)__get_free_page(GFP_KERNEL);28	if (!msg)29		return NULL;30 31	/* Start with zero filled header and then back fill with data */32	hdr.type = msg_id;33	hdr.is_msg = 1;34	hdr.len = cpu_to_le16(1);35 36	/* Copy header into start of message */37	msg->hdr = hdr;38 39	return msg;40}41 42/**43 * fbnic_tlv_attr_put_flag - Add flag value to message44 * @msg: Message header we are adding flag attribute to45 * @attr_id: ID of flag attribute we are adding to message46 *47 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.48 *49 * Adds a 1 DWORD flag attribute to the message. The presence of this50 * attribute can be used as a boolean value indicating true, otherwise the51 * value is considered false.52 **/53int fbnic_tlv_attr_put_flag(struct fbnic_tlv_msg *msg, const u16 attr_id)54{55	int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);56	struct fbnic_tlv_hdr hdr = { 0 };57	struct fbnic_tlv_msg *attr;58 59	attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);60	if (attr_max_len < sizeof(*attr))61		return -ENOSPC;62 63	/* Get header pointer and bump attr to start of data */64	attr = &msg[le16_to_cpu(msg->hdr.len)];65 66	/* Record attribute type and size */67	hdr.type = attr_id;68	hdr.len = cpu_to_le16(sizeof(hdr));69 70	attr->hdr = hdr;71	le16_add_cpu(&msg->hdr.len,72		     FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len)));73 74	return 0;75}76 77/**78 * fbnic_tlv_attr_put_value - Add data to message79 * @msg: Message header we are adding flag attribute to80 * @attr_id: ID of flag attribute we are adding to message81 * @value: Pointer to data to be stored82 * @len: Size of data to be stored.83 *84 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.85 *86 * Adds header and copies data pointed to by value into the message. The87 * result is rounded up to the nearest DWORD for sizing so that the88 * headers remain aligned.89 *90 * The assumption is that the value field is in a format where byte91 * ordering can be guaranteed such as a byte array or a little endian92 * format.93 **/94int fbnic_tlv_attr_put_value(struct fbnic_tlv_msg *msg, const u16 attr_id,95			     const void *value, const int len)96{97	int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);98	struct fbnic_tlv_hdr hdr = { 0 };99	struct fbnic_tlv_msg *attr;100 101	attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);102	if (attr_max_len < sizeof(*attr) + len)103		return -ENOSPC;104 105	/* Get header pointer and bump attr to start of data */106	attr = &msg[le16_to_cpu(msg->hdr.len)];107 108	/* Record attribute type and size */109	hdr.type = attr_id;110	hdr.len = cpu_to_le16(sizeof(hdr) + len);111 112	/* Zero pad end of region to be written if we aren't aligned */113	if (len % sizeof(hdr))114		attr->value[len / sizeof(hdr)] = 0;115 116	/* Copy data over */117	memcpy(attr->value, value, len);118 119	attr->hdr = hdr;120	le16_add_cpu(&msg->hdr.len,121		     FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len)));122 123	return 0;124}125 126/**127 * __fbnic_tlv_attr_put_int - Add integer to message128 * @msg: Message header we are adding flag attribute to129 * @attr_id: ID of flag attribute we are adding to message130 * @value: Data to be stored131 * @len: Size of data to be stored, either 4 or 8 bytes.132 *133 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.134 *135 * Adds header and copies data pointed to by value into the message. Will136 * format the data as little endian.137 **/138int __fbnic_tlv_attr_put_int(struct fbnic_tlv_msg *msg, const u16 attr_id,139			     s64 value, const int len)140{141	__le64 le64_value = cpu_to_le64(value);142 143	return fbnic_tlv_attr_put_value(msg, attr_id, &le64_value, len);144}145 146/**147 * fbnic_tlv_attr_put_mac_addr - Add mac_addr to message148 * @msg: Message header we are adding flag attribute to149 * @attr_id: ID of flag attribute we are adding to message150 * @mac_addr: Byte pointer to MAC address to be stored151 *152 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.153 *154 * Adds header and copies data pointed to by mac_addr into the message. Will155 * copy the address raw so it will be in big endian with start of MAC156 * address at start of attribute.157 **/158int fbnic_tlv_attr_put_mac_addr(struct fbnic_tlv_msg *msg, const u16 attr_id,159				const u8 *mac_addr)160{161	return fbnic_tlv_attr_put_value(msg, attr_id, mac_addr, ETH_ALEN);162}163 164/**165 * fbnic_tlv_attr_put_string - Add string to message166 * @msg: Message header we are adding flag attribute to167 * @attr_id: ID of flag attribute we are adding to message168 * @string: Byte pointer to null terminated string to be stored169 *170 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.171 *172 * Adds header and copies data pointed to by string into the message. Will173 * copy the address raw so it will be in byte order.174 **/175int fbnic_tlv_attr_put_string(struct fbnic_tlv_msg *msg, u16 attr_id,176			      const char *string)177{178	int attr_max_len = PAGE_SIZE - sizeof(*msg);179	int str_len = 1;180 181	/* The max length will be message minus existing message and new182	 * attribute header. Since the message is measured in DWORDs we have183	 * to multiply the size by 4.184	 *185	 * The string length doesn't include the \0 so we have to add one to186	 * the final value, so start with that as our initial value.187	 *188	 * We will verify if the string will fit in fbnic_tlv_attr_put_value()189	 */190	attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);191	str_len += strnlen(string, attr_max_len);192 193	return fbnic_tlv_attr_put_value(msg, attr_id, string, str_len);194}195 196/**197 * fbnic_tlv_attr_get_unsigned - Retrieve unsigned value from result198 * @attr: Attribute to retrieve data from199 *200 * Return: unsigned 64b value containing integer value201 **/202u64 fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg *attr)203{204	__le64 le64_value = 0;205 206	memcpy(&le64_value, &attr->value[0],207	       le16_to_cpu(attr->hdr.len) - sizeof(*attr));208 209	return le64_to_cpu(le64_value);210}211 212/**213 * fbnic_tlv_attr_get_signed - Retrieve signed value from result214 * @attr: Attribute to retrieve data from215 *216 * Return: signed 64b value containing integer value217 **/218s64 fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg *attr)219{220	int shift = (8 + sizeof(*attr) - le16_to_cpu(attr->hdr.len)) * 8;221	__le64 le64_value = 0;222	s64 value;223 224	/* Copy the value and adjust for byte ordering */225	memcpy(&le64_value, &attr->value[0],226	       le16_to_cpu(attr->hdr.len) - sizeof(*attr));227	value = le64_to_cpu(le64_value);228 229	/* Sign extend the return value by using a pair of shifts */230	return (value << shift) >> shift;231}232 233/**234 * fbnic_tlv_attr_get_string - Retrieve string value from result235 * @attr: Attribute to retrieve data from236 * @str: Pointer to an allocated string to store the data237 * @max_size: The maximum size which can be in str238 *239 * Return: the size of the string read from firmware240 **/241size_t fbnic_tlv_attr_get_string(struct fbnic_tlv_msg *attr, char *str,242				 size_t max_size)243{244	max_size = min_t(size_t, max_size,245			 (le16_to_cpu(attr->hdr.len) * 4) - sizeof(*attr));246	memcpy(str, &attr->value, max_size);247 248	return max_size;249}250 251/**252 * fbnic_tlv_attr_nest_start - Add nested attribute header to message253 * @msg: Message header we are adding flag attribute to254 * @attr_id: ID of flag attribute we are adding to message255 *256 * Return: NULL if there is no room for the attribute. Otherwise a pointer257 * to the new attribute header.258 *259 * New header length is stored initially in DWORDs.260 **/261struct fbnic_tlv_msg *fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg *msg,262						u16 attr_id)263{264	int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);265	struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];266	struct fbnic_tlv_hdr hdr = { 0 };267 268	/* Make sure we have space for at least the nest header plus one more */269	attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);270	if (attr_max_len < sizeof(*attr) * 2)271		return NULL;272 273	/* Record attribute type and size */274	hdr.type = attr_id;275 276	/* Add current message length to account for consumption within the277	 * page and leave it as a multiple of DWORDs, we will shift to278	 * bytes when we close it out.279	 */280	hdr.len = cpu_to_le16(1);281 282	attr->hdr = hdr;283 284	return attr;285}286 287/**288 * fbnic_tlv_attr_nest_stop - Close out nested attribute and add it to message289 * @msg: Message header we are adding flag attribute to290 *291 * Closes out nested attribute, adds length to message, and then bumps292 * length from DWORDs to bytes to match other attributes.293 **/294void fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg *msg)295{296	struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];297	u16 len = le16_to_cpu(attr->hdr.len);298 299	/* Add attribute to message if there is more than just a header */300	if (len <= 1)301		return;302 303	le16_add_cpu(&msg->hdr.len, len);304 305	/* Convert from DWORDs to bytes */306	attr->hdr.len = cpu_to_le16(len * sizeof(u32));307}308 309static int310fbnic_tlv_attr_validate(struct fbnic_tlv_msg *attr,311			const struct fbnic_tlv_index *tlv_index)312{313	u16 len = le16_to_cpu(attr->hdr.len) - sizeof(*attr);314	u16 attr_id = attr->hdr.type;315	__le32 *value = &attr->value[0];316 317	if (attr->hdr.is_msg)318		return -EINVAL;319 320	if (attr_id >= FBNIC_TLV_RESULTS_MAX)321		return -EINVAL;322 323	while (tlv_index->id != attr_id) {324		if  (tlv_index->id == FBNIC_TLV_ATTR_ID_UNKNOWN) {325			if (attr->hdr.cannot_ignore)326				return -ENOENT;327			return le16_to_cpu(attr->hdr.len);328		}329 330		tlv_index++;331	}332 333	if (offset_in_page(attr) + len > PAGE_SIZE - sizeof(*attr))334		return -E2BIG;335 336	switch (tlv_index->type) {337	case FBNIC_TLV_STRING:338		if (!len || len > tlv_index->len)339			return -EINVAL;340		if (((char *)value)[len - 1])341			return -EINVAL;342		break;343	case FBNIC_TLV_FLAG:344		if (len)345			return -EINVAL;346		break;347	case FBNIC_TLV_UNSIGNED:348	case FBNIC_TLV_SIGNED:349		if (tlv_index->len > sizeof(__le64))350			return -EINVAL;351		fallthrough;352	case FBNIC_TLV_BINARY:353		if (!len || len > tlv_index->len)354			return -EINVAL;355		break;356	case FBNIC_TLV_NESTED:357	case FBNIC_TLV_ARRAY:358		if (len % 4)359			return -EINVAL;360		break;361	default:362		return -EINVAL;363	}364 365	return 0;366}367 368/**369 * fbnic_tlv_attr_parse_array - Parse array of attributes into results array370 * @attr: Start of attributes in the message371 * @len: Length of attributes in the message372 * @results: Array of pointers to store the results of parsing373 * @tlv_index: List of TLV attributes to be parsed from message374 * @tlv_attr_id: Specific ID that is repeated in array375 * @array_len: Number of results to store in results array376 *377 * Return: zero on success, or negative value on error.378 *379 * Will take a list of attributes and a parser definition and will capture380 * the results in the results array to have the data extracted later.381 **/382int fbnic_tlv_attr_parse_array(struct fbnic_tlv_msg *attr, int len,383			       struct fbnic_tlv_msg **results,384			       const struct fbnic_tlv_index *tlv_index,385			       u16 tlv_attr_id, size_t array_len)386{387	int i = 0;388 389	/* Initialize results table to NULL. */390	memset(results, 0, array_len * sizeof(results[0]));391 392	/* Nothing to parse if header was only thing there */393	if (!len)394		return 0;395 396	/* Work through list of attributes, parsing them as necessary */397	while (len > 0) {398		u16 attr_id = attr->hdr.type;399		u16 attr_len;400		int err;401 402		if (tlv_attr_id != attr_id)403			return -EINVAL;404 405		/* Stop parsing on full error */406		err = fbnic_tlv_attr_validate(attr, tlv_index);407		if (err < 0)408			return err;409 410		if (i >= array_len)411			return -ENOSPC;412 413		results[i++] = attr;414 415		attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));416		len -= attr_len;417		attr += attr_len;418	}419 420	return len == 0 ? 0 : -EINVAL;421}422 423/**424 * fbnic_tlv_attr_parse - Parse attributes into a list of attribute results425 * @attr: Start of attributes in the message426 * @len: Length of attributes in the message427 * @results: Array of pointers to store the results of parsing428 * @tlv_index: List of TLV attributes to be parsed from message429 *430 * Return: zero on success, or negative value on error.431 *432 * Will take a list of attributes and a parser definition and will capture433 * the results in the results array to have the data extracted later.434 **/435int fbnic_tlv_attr_parse(struct fbnic_tlv_msg *attr, int len,436			 struct fbnic_tlv_msg **results,437			 const struct fbnic_tlv_index *tlv_index)438{439	/* Initialize results table to NULL. */440	memset(results, 0, sizeof(results[0]) * FBNIC_TLV_RESULTS_MAX);441 442	/* Nothing to parse if header was only thing there */443	if (!len)444		return 0;445 446	/* Work through list of attributes, parsing them as necessary */447	while (len > 0) {448		int err = fbnic_tlv_attr_validate(attr, tlv_index);449		u16 attr_id = attr->hdr.type;450		u16 attr_len;451 452		/* Stop parsing on full error */453		if (err < 0)454			return err;455 456		/* Ignore results for unsupported values */457		if (!err) {458			/* Do not overwrite existing entries */459			if (results[attr_id])460				return -EADDRINUSE;461 462			results[attr_id] = attr;463		}464 465		attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));466		len -= attr_len;467		attr += attr_len;468	}469 470	return len == 0 ? 0 : -EINVAL;471}472 473/**474 * fbnic_tlv_msg_parse - Parse message and process via predetermined functions475 * @opaque: Value passed to parser function to enable driver access476 * @msg: Message to be parsed.477 * @parser: TLV message parser definition.478 *479 * Return: zero on success, or negative value on error.480 *481 * Will take a message a number of message types via the attribute parsing482 * definitions and function provided for the parser array.483 **/484int fbnic_tlv_msg_parse(void *opaque, struct fbnic_tlv_msg *msg,485			const struct fbnic_tlv_parser *parser)486{487	struct fbnic_tlv_msg *results[FBNIC_TLV_RESULTS_MAX];488	u16 msg_id = msg->hdr.type;489	int err;490 491	if (!msg->hdr.is_msg)492		return -EINVAL;493 494	if (le16_to_cpu(msg->hdr.len) > PAGE_SIZE / sizeof(u32))495		return -E2BIG;496 497	while (parser->id != msg_id) {498		if (parser->id == FBNIC_TLV_MSG_ID_UNKNOWN)499			return -ENOENT;500		parser++;501	}502 503	err = fbnic_tlv_attr_parse(&msg[1], le16_to_cpu(msg->hdr.len) - 1,504				   results, parser->attr);505	if (err)506		return err;507 508	return parser->func(opaque, results);509}510 511/**512 * fbnic_tlv_parser_error - called if message doesn't match known type513 * @opaque: (unused)514 * @results: (unused)515 *516 * Return: -EBADMSG to indicate the message is an unsupported type517 **/518int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results)519{520	return -EBADMSG;521}522 523void fbnic_tlv_attr_addr_copy(u8 *dest, struct fbnic_tlv_msg *src)524{525	u8 *mac_addr;526 527	mac_addr = fbnic_tlv_attr_get_value_ptr(src);528	memcpy(dest, mac_addr, ETH_ALEN);529}530