File Index
Files and directories
Sign in
FILE

fbnic_tlv.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/ethernet/meta/fbnic/fbnic_tlv.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/* 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 header
 15 * @msg_id: Identifier for new message we are starting
 16 *
 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 message
 44 * @msg: Message header we are adding flag attribute to
 45 * @attr_id: ID of flag attribute we are adding to message
 46 *
 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 this
 50 * attribute can be used as a boolean value indicating true, otherwise the
 51 * 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 message
 79 * @msg: Message header we are adding flag attribute to
 80 * @attr_id: ID of flag attribute we are adding to message
 81 * @value: Pointer to data to be stored
 82 * @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. The
 87 * result is rounded up to the nearest DWORD for sizing so that the
 88 * headers remain aligned.
 89 *
 90 * The assumption is that the value field is in a format where byte
 91 * ordering can be guaranteed such as a byte array or a little endian
 92 * 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 message
128 * @msg: Message header we are adding flag attribute to
129 * @attr_id: ID of flag attribute we are adding to message
130 * @value: Data to be stored
131 * @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. Will
136 * 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 message
148 * @msg: Message header we are adding flag attribute to
149 * @attr_id: ID of flag attribute we are adding to message
150 * @mac_addr: Byte pointer to MAC address to be stored
151 *
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. Will
155 * copy the address raw so it will be in big endian with start of MAC
156 * 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 message
166 * @msg: Message header we are adding flag attribute to
167 * @attr_id: ID of flag attribute we are adding to message
168 * @string: Byte pointer to null terminated string to be stored
169 *
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. Will
173 * 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 new
182	 * attribute header. Since the message is measured in DWORDs we have
183	 * to multiply the size by 4.
184	 *
185	 * The string length doesn't include the \0 so we have to add one to
186	 * 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 result
198 * @attr: Attribute to retrieve data from
199 * @def: The default value if attr is NULL
200 *
201 * Return: unsigned 64b value containing integer value
202 **/
203u64 fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg *attr, u64 def)
204{
205	__le64 le64_value = 0;
206
207	if (!attr)
208		return def;
209
210	memcpy(&le64_value, &attr->value[0],
211	       le16_to_cpu(attr->hdr.len) - sizeof(*attr));
212
213	return le64_to_cpu(le64_value);
214}
215
216/**
217 * fbnic_tlv_attr_get_signed - Retrieve signed value from result
218 * @attr: Attribute to retrieve data from
219 * @def: The default value if attr is NULL
220 *
221 * Return: signed 64b value containing integer value
222 **/
223s64 fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg *attr, s64 def)
224{
225	__le64 le64_value = 0;
226	int shift;
227	s64 value;
228
229	if (!attr)
230		return def;
231
232	shift = (8 + sizeof(*attr) - le16_to_cpu(attr->hdr.len)) * 8;
233
234	/* Copy the value and adjust for byte ordering */
235	memcpy(&le64_value, &attr->value[0],
236	       le16_to_cpu(attr->hdr.len) - sizeof(*attr));
237	value = le64_to_cpu(le64_value);
238
239	/* Sign extend the return value by using a pair of shifts */
240	return (value << shift) >> shift;
241}
242
243/**
244 * fbnic_tlv_attr_get_string - Retrieve string value from result
245 * @attr: Attribute to retrieve data from
246 * @dst: Pointer to an allocated string to store the data
247 * @dstsize: The maximum size which can be in dst
248 *
249 * Return: the size of the string read from firmware or negative error.
250 **/
251ssize_t fbnic_tlv_attr_get_string(struct fbnic_tlv_msg *attr, char *dst,
252				  size_t dstsize)
253{
254	size_t srclen, len;
255	ssize_t ret;
256
257	if (!attr)
258		return -EINVAL;
259
260	if (dstsize == 0)
261		return -E2BIG;
262
263	srclen = le16_to_cpu(attr->hdr.len) - sizeof(*attr);
264	if (srclen > 0 && ((char *)attr->value)[srclen - 1] == '\0')
265		srclen--;
266
267	if (srclen >= dstsize) {
268		len = dstsize - 1;
269		ret = -E2BIG;
270	} else {
271		len = srclen;
272		ret = len;
273	}
274
275	memcpy(dst, &attr->value, len);
276	/* Zero pad end of dst. */
277	memset(dst + len, 0, dstsize - len);
278
279	return ret;
280}
281
282/**
283 * fbnic_tlv_attr_nest_start - Add nested attribute header to message
284 * @msg: Message header we are adding flag attribute to
285 * @attr_id: ID of flag attribute we are adding to message
286 *
287 * Return: NULL if there is no room for the attribute. Otherwise a pointer
288 * to the new attribute header.
289 *
290 * New header length is stored initially in DWORDs.
291 **/
292struct fbnic_tlv_msg *fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg *msg,
293						u16 attr_id)
294{
295	int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);
296	struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];
297	struct fbnic_tlv_hdr hdr = { 0 };
298
299	/* Make sure we have space for at least the nest header plus one more */
300	attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
301	if (attr_max_len < sizeof(*attr) * 2)
302		return NULL;
303
304	/* Record attribute type and size */
305	hdr.type = attr_id;
306
307	/* Add current message length to account for consumption within the
308	 * page and leave it as a multiple of DWORDs, we will shift to
309	 * bytes when we close it out.
310	 */
311	hdr.len = cpu_to_le16(1);
312
313	attr->hdr = hdr;
314
315	return attr;
316}
317
318/**
319 * fbnic_tlv_attr_nest_stop - Close out nested attribute and add it to message
320 * @msg: Message header we are adding flag attribute to
321 *
322 * Closes out nested attribute, adds length to message, and then bumps
323 * length from DWORDs to bytes to match other attributes.
324 **/
325void fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg *msg)
326{
327	struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];
328	u16 len = le16_to_cpu(attr->hdr.len);
329
330	/* Add attribute to message if there is more than just a header */
331	if (len <= 1)
332		return;
333
334	le16_add_cpu(&msg->hdr.len, len);
335
336	/* Convert from DWORDs to bytes */
337	attr->hdr.len = cpu_to_le16(len * sizeof(u32));
338}
339
340static int
341fbnic_tlv_attr_validate(struct fbnic_tlv_msg *attr,
342			const struct fbnic_tlv_index *tlv_index)
343{
344	u16 len = le16_to_cpu(attr->hdr.len) - sizeof(*attr);
345	u16 attr_id = attr->hdr.type;
346	__le32 *value = &attr->value[0];
347
348	if (attr->hdr.is_msg)
349		return -EINVAL;
350
351	if (attr_id >= FBNIC_TLV_RESULTS_MAX)
352		return -EINVAL;
353
354	while (tlv_index->id != attr_id) {
355		if  (tlv_index->id == FBNIC_TLV_ATTR_ID_UNKNOWN) {
356			if (attr->hdr.cannot_ignore)
357				return -ENOENT;
358			return le16_to_cpu(attr->hdr.len);
359		}
360
361		tlv_index++;
362	}
363
364	if (offset_in_page(attr) + len > PAGE_SIZE - sizeof(*attr))
365		return -E2BIG;
366
367	switch (tlv_index->type) {
368	case FBNIC_TLV_STRING:
369		if (!len || len > tlv_index->len)
370			return -EINVAL;
371		if (((char *)value)[len - 1])
372			return -EINVAL;
373		break;
374	case FBNIC_TLV_FLAG:
375		if (len)
376			return -EINVAL;
377		break;
378	case FBNIC_TLV_UNSIGNED:
379	case FBNIC_TLV_SIGNED:
380		if (tlv_index->len > sizeof(__le64))
381			return -EINVAL;
382		fallthrough;
383	case FBNIC_TLV_BINARY:
384		if (!len || len > tlv_index->len)
385			return -EINVAL;
386		break;
387	case FBNIC_TLV_NESTED:
388	case FBNIC_TLV_ARRAY:
389		if (len % 4)
390			return -EINVAL;
391		break;
392	default:
393		return -EINVAL;
394	}
395
396	return 0;
397}
398
399/**
400 * fbnic_tlv_attr_parse_array - Parse array of attributes into results array
401 * @attr: Start of attributes in the message
402 * @len: Length of attributes in the message
403 * @results: Array of pointers to store the results of parsing
404 * @tlv_index: List of TLV attributes to be parsed from message
405 * @tlv_attr_id: Specific ID that is repeated in array
406 * @array_len: Number of results to store in results array
407 *
408 * Return: zero on success, or negative value on error.
409 *
410 * Will take a list of attributes and a parser definition and will capture
411 * the results in the results array to have the data extracted later.
412 **/
413int fbnic_tlv_attr_parse_array(struct fbnic_tlv_msg *attr, int len,
414			       struct fbnic_tlv_msg **results,
415			       const struct fbnic_tlv_index *tlv_index,
416			       u16 tlv_attr_id, size_t array_len)
417{
418	int i = 0;
419
420	/* Initialize results table to NULL. */
421	memset(results, 0, array_len * sizeof(results[0]));
422
423	/* Nothing to parse if header was only thing there */
424	if (!len)
425		return 0;
426
427	/* Work through list of attributes, parsing them as necessary */
428	while (len > 0) {
429		u16 attr_id = attr->hdr.type;
430		u16 attr_len;
431		int err;
432
433		if (tlv_attr_id != attr_id)
434			return -EINVAL;
435
436		/* Stop parsing on full error */
437		err = fbnic_tlv_attr_validate(attr, tlv_index);
438		if (err < 0)
439			return err;
440
441		if (i >= array_len)
442			return -ENOSPC;
443
444		results[i++] = attr;
445
446		attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));
447		len -= attr_len;
448		attr += attr_len;
449	}
450
451	return len == 0 ? 0 : -EINVAL;
452}
453
454/**
455 * fbnic_tlv_attr_parse - Parse attributes into a list of attribute results
456 * @attr: Start of attributes in the message
457 * @len: Length of attributes in the message
458 * @results: Array of pointers to store the results of parsing
459 * @tlv_index: List of TLV attributes to be parsed from message
460 *
461 * Return: zero on success, or negative value on error.
462 *
463 * Will take a list of attributes and a parser definition and will capture
464 * the results in the results array to have the data extracted later.
465 **/
466int fbnic_tlv_attr_parse(struct fbnic_tlv_msg *attr, int len,
467			 struct fbnic_tlv_msg **results,
468			 const struct fbnic_tlv_index *tlv_index)
469{
470	/* Initialize results table to NULL. */
471	memset(results, 0, sizeof(results[0]) * FBNIC_TLV_RESULTS_MAX);
472
473	/* Nothing to parse if header was only thing there */
474	if (!len)
475		return 0;
476
477	/* Work through list of attributes, parsing them as necessary */
478	while (len > 0) {
479		int err = fbnic_tlv_attr_validate(attr, tlv_index);
480		u16 attr_id = attr->hdr.type;
481		u16 attr_len;
482
483		/* Stop parsing on full error */
484		if (err < 0)
485			return err;
486
487		/* Ignore results for unsupported values */
488		if (!err) {
489			/* Do not overwrite existing entries */
490			if (results[attr_id])
491				return -EADDRINUSE;
492
493			results[attr_id] = attr;
494		}
495
496		attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));
497		len -= attr_len;
498		attr += attr_len;
499	}
500
501	return len == 0 ? 0 : -EINVAL;
502}
503
504/**
505 * fbnic_tlv_msg_parse - Parse message and process via predetermined functions
506 * @opaque: Value passed to parser function to enable driver access
507 * @msg: Message to be parsed.
508 * @parser: TLV message parser definition.
509 *
510 * Return: zero on success, or negative value on error.
511 *
512 * Will take a message a number of message types via the attribute parsing
513 * definitions and function provided for the parser array.
514 **/
515int fbnic_tlv_msg_parse(void *opaque, struct fbnic_tlv_msg *msg,
516			const struct fbnic_tlv_parser *parser)
517{
518	struct fbnic_tlv_msg *results[FBNIC_TLV_RESULTS_MAX];
519	u16 msg_id = msg->hdr.type;
520	int err;
521
522	if (!msg->hdr.is_msg)
523		return -EINVAL;
524
525	if (le16_to_cpu(msg->hdr.len) > PAGE_SIZE / sizeof(u32))
526		return -E2BIG;
527
528	while (parser->id != msg_id) {
529		if (parser->id == FBNIC_TLV_MSG_ID_UNKNOWN)
530			return -ENOENT;
531		parser++;
532	}
533
534	err = fbnic_tlv_attr_parse(&msg[1], le16_to_cpu(msg->hdr.len) - 1,
535				   results, parser->attr);
536	if (err)
537		return err;
538
539	return parser->func(opaque, results);
540}
541
542/**
543 * fbnic_tlv_parser_error - called if message doesn't match known type
544 * @opaque: (unused)
545 * @results: (unused)
546 *
547 * Return: -EBADMSG to indicate the message is an unsupported type
548 **/
549int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results)
550{
551	return -EBADMSG;
552}
553
554void fbnic_tlv_attr_addr_copy(u8 *dest, struct fbnic_tlv_msg *src)
555{
556	u8 *mac_addr;
557
558	mac_addr = fbnic_tlv_attr_get_value_ptr(src);
559	memcpy(dest, mac_addr, ETH_ALEN);
560}
~/ Read-only file preview.