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 * @def: The default value if attr is NULL
200 *
201 * Return: unsigned 64b value containing integer value
202 **/
fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg * attr,u64 def)203 u64 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 **/
fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg * attr,s64 def)223 s64 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 **/
fbnic_tlv_attr_get_string(struct fbnic_tlv_msg * attr,char * dst,size_t dstsize)251 ssize_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 **/
fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg * msg,u16 attr_id)292 struct 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 **/
fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg * msg)325 void 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
340 static int
fbnic_tlv_attr_validate(struct fbnic_tlv_msg * attr,const struct fbnic_tlv_index * tlv_index)341 fbnic_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 **/
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)413 int 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 **/
fbnic_tlv_attr_parse(struct fbnic_tlv_msg * attr,int len,struct fbnic_tlv_msg ** results,const struct fbnic_tlv_index * tlv_index)466 int 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 **/
fbnic_tlv_msg_parse(void * opaque,struct fbnic_tlv_msg * msg,const struct fbnic_tlv_parser * parser)515 int 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 **/
fbnic_tlv_parser_error(void * opaque,struct fbnic_tlv_msg ** results)549 int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results)
550 {
551 return -EBADMSG;
552 }
553
fbnic_tlv_attr_addr_copy(u8 * dest,struct fbnic_tlv_msg * src)554 void 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 }
561