xref: /linux/drivers/net/ethernet/meta/fbnic/fbnic_tlv.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
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  **/
fbnic_tlv_msg_alloc(u16 msg_id)22 struct 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  **/
fbnic_tlv_attr_put_flag(struct fbnic_tlv_msg * msg,const u16 attr_id)53 int 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  **/
fbnic_tlv_attr_put_value(struct fbnic_tlv_msg * msg,const u16 attr_id,const void * value,const int len)94 int 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  **/
__fbnic_tlv_attr_put_int(struct fbnic_tlv_msg * msg,const u16 attr_id,s64 value,const int len)138 int __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  **/
fbnic_tlv_attr_put_mac_addr(struct fbnic_tlv_msg * msg,const u16 attr_id,const u8 * mac_addr)158 int 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  **/
fbnic_tlv_attr_put_string(struct fbnic_tlv_msg * msg,u16 attr_id,const char * string)175 int 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  *
200  * Return: unsigned 64b value containing integer value
201  **/
fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg * attr)202 u64 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 result
214  * @attr: Attribute to retrieve data from
215  *
216  * Return: signed 64b value containing integer value
217  **/
fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg * attr)218 s64 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 result
235  * @attr: Attribute to retrieve data from
236  * @str: Pointer to an allocated string to store the data
237  * @max_size: The maximum size which can be in str
238  *
239  * Return: the size of the string read from firmware
240  **/
fbnic_tlv_attr_get_string(struct fbnic_tlv_msg * attr,char * str,size_t max_size)241 size_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 message
253  * @msg: Message header we are adding flag attribute to
254  * @attr_id: ID of flag attribute we are adding to message
255  *
256  * Return: NULL if there is no room for the attribute. Otherwise a pointer
257  * to the new attribute header.
258  *
259  * New header length is stored initially in DWORDs.
260  **/
fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg * msg,u16 attr_id)261 struct 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 the
277 	 * page and leave it as a multiple of DWORDs, we will shift to
278 	 * 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 message
289  * @msg: Message header we are adding flag attribute to
290  *
291  * Closes out nested attribute, adds length to message, and then bumps
292  * length from DWORDs to bytes to match other attributes.
293  **/
fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg * msg)294 void 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 
309 static int
fbnic_tlv_attr_validate(struct fbnic_tlv_msg * attr,const struct fbnic_tlv_index * tlv_index)310 fbnic_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 array
370  * @attr: Start of attributes in the message
371  * @len: Length of attributes in the message
372  * @results: Array of pointers to store the results of parsing
373  * @tlv_index: List of TLV attributes to be parsed from message
374  * @tlv_attr_id: Specific ID that is repeated in array
375  * @array_len: Number of results to store in results array
376  *
377  * Return: zero on success, or negative value on error.
378  *
379  * Will take a list of attributes and a parser definition and will capture
380  * the results in the results array to have the data extracted later.
381  **/
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)382 int 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 results
425  * @attr: Start of attributes in the message
426  * @len: Length of attributes in the message
427  * @results: Array of pointers to store the results of parsing
428  * @tlv_index: List of TLV attributes to be parsed from message
429  *
430  * Return: zero on success, or negative value on error.
431  *
432  * Will take a list of attributes and a parser definition and will capture
433  * the results in the results array to have the data extracted later.
434  **/
fbnic_tlv_attr_parse(struct fbnic_tlv_msg * attr,int len,struct fbnic_tlv_msg ** results,const struct fbnic_tlv_index * tlv_index)435 int 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 functions
475  * @opaque: Value passed to parser function to enable driver access
476  * @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 parsing
482  * definitions and function provided for the parser array.
483  **/
fbnic_tlv_msg_parse(void * opaque,struct fbnic_tlv_msg * msg,const struct fbnic_tlv_parser * parser)484 int 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 type
513  * @opaque: (unused)
514  * @results: (unused)
515  *
516  * Return: -EBADMSG to indicate the message is an unsupported type
517  **/
fbnic_tlv_parser_error(void * opaque,struct fbnic_tlv_msg ** results)518 int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results)
519 {
520 	return -EBADMSG;
521 }
522 
fbnic_tlv_attr_addr_copy(u8 * dest,struct fbnic_tlv_msg * src)523 void 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