1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) Meta Platforms, Inc. and affiliates. */
6 #include <linux/once.h>
7 #include <linux/random.h>
8 #include <linux/string.h>
9 #include <uapi/linux/if_ether.h>
11 #include "fbnic_tlv.h"
14 * fbnic_tlv_msg_alloc - Allocate page and initialize FW message header
15 * @msg_id: Identifier for new message we are starting
17 * Return: pointer to start of message, or NULL on failure.
19 * Allocates a page and initializes message header at start of page.
20 * Initial message size is 1 DWORD which is just the header.
22 struct fbnic_tlv_msg
*fbnic_tlv_msg_alloc(u16 msg_id
)
24 struct fbnic_tlv_hdr hdr
= { 0 };
25 struct fbnic_tlv_msg
*msg
;
27 msg
= (struct fbnic_tlv_msg
*)__get_free_page(GFP_KERNEL
);
31 /* Start with zero filled header and then back fill with data */
34 hdr
.len
= cpu_to_le16(1);
36 /* Copy header into start of message */
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
47 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
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.
53 int fbnic_tlv_attr_put_flag(struct fbnic_tlv_msg
*msg
, const u16 attr_id
)
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
;
59 attr_max_len
-= le16_to_cpu(msg
->hdr
.len
) * sizeof(u32
);
60 if (attr_max_len
< sizeof(*attr
))
63 /* Get header pointer and bump attr to start of data */
64 attr
= &msg
[le16_to_cpu(msg
->hdr
.len
)];
66 /* Record attribute type and size */
68 hdr
.len
= cpu_to_le16(sizeof(hdr
));
71 le16_add_cpu(&msg
->hdr
.len
,
72 FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr
.len
)));
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.
84 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
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.
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
94 int fbnic_tlv_attr_put_value(struct fbnic_tlv_msg
*msg
, const u16 attr_id
,
95 const void *value
, const int len
)
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
;
101 attr_max_len
-= le16_to_cpu(msg
->hdr
.len
) * sizeof(u32
);
102 if (attr_max_len
< sizeof(*attr
) + len
)
105 /* Get header pointer and bump attr to start of data */
106 attr
= &msg
[le16_to_cpu(msg
->hdr
.len
)];
108 /* Record attribute type and size */
110 hdr
.len
= cpu_to_le16(sizeof(hdr
) + len
);
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;
117 memcpy(attr
->value
, value
, len
);
120 le16_add_cpu(&msg
->hdr
.len
,
121 FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr
.len
)));
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.
133 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
135 * Adds header and copies data pointed to by value into the message. Will
136 * format the data as little endian.
138 int __fbnic_tlv_attr_put_int(struct fbnic_tlv_msg
*msg
, const u16 attr_id
,
139 s64 value
, const int len
)
141 __le64 le64_value
= cpu_to_le64(value
);
143 return fbnic_tlv_attr_put_value(msg
, attr_id
, &le64_value
, len
);
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
152 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
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.
158 int fbnic_tlv_attr_put_mac_addr(struct fbnic_tlv_msg
*msg
, const u16 attr_id
,
161 return fbnic_tlv_attr_put_value(msg
, attr_id
, mac_addr
, ETH_ALEN
);
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
170 * Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
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.
175 int fbnic_tlv_attr_put_string(struct fbnic_tlv_msg
*msg
, u16 attr_id
,
178 int attr_max_len
= PAGE_SIZE
- sizeof(*msg
);
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.
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.
188 * We will verify if the string will fit in fbnic_tlv_attr_put_value()
190 attr_max_len
-= le16_to_cpu(msg
->hdr
.len
) * sizeof(u32
);
191 str_len
+= strnlen(string
, attr_max_len
);
193 return fbnic_tlv_attr_put_value(msg
, attr_id
, string
, str_len
);
197 * fbnic_tlv_attr_get_unsigned - Retrieve unsigned value from result
198 * @attr: Attribute to retrieve data from
200 * Return: unsigned 64b value containing integer value
202 u64
fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg
*attr
)
204 __le64 le64_value
= 0;
206 memcpy(&le64_value
, &attr
->value
[0],
207 le16_to_cpu(attr
->hdr
.len
) - sizeof(*attr
));
209 return le64_to_cpu(le64_value
);
213 * fbnic_tlv_attr_get_signed - Retrieve signed value from result
214 * @attr: Attribute to retrieve data from
216 * Return: signed 64b value containing integer value
218 s64
fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg
*attr
)
220 int shift
= (8 + sizeof(*attr
) - le16_to_cpu(attr
->hdr
.len
)) * 8;
221 __le64 le64_value
= 0;
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
);
229 /* Sign extend the return value by using a pair of shifts */
230 return (value
<< shift
) >> shift
;
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
239 * Return: the size of the string read from firmware
241 size_t fbnic_tlv_attr_get_string(struct fbnic_tlv_msg
*attr
, char *str
,
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
);
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
256 * Return: NULL if there is no room for the attribute. Otherwise a pointer
257 * to the new attribute header.
259 * New header length is stored initially in DWORDs.
261 struct fbnic_tlv_msg
*fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg
*msg
,
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 };
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)
273 /* Record attribute type and size */
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.
280 hdr
.len
= cpu_to_le16(1);
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
291 * Closes out nested attribute, adds length to message, and then bumps
292 * length from DWORDs to bytes to match other attributes.
294 void fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg
*msg
)
296 struct fbnic_tlv_msg
*attr
= &msg
[le16_to_cpu(msg
->hdr
.len
)];
297 u16 len
= le16_to_cpu(attr
->hdr
.len
);
299 /* Add attribute to message if there is more than just a header */
303 le16_add_cpu(&msg
->hdr
.len
, len
);
305 /* Convert from DWORDs to bytes */
306 attr
->hdr
.len
= cpu_to_le16(len
* sizeof(u32
));
310 fbnic_tlv_attr_validate(struct fbnic_tlv_msg
*attr
,
311 const struct fbnic_tlv_index
*tlv_index
)
313 u16 len
= le16_to_cpu(attr
->hdr
.len
) - sizeof(*attr
);
314 u16 attr_id
= attr
->hdr
.type
;
315 __le32
*value
= &attr
->value
[0];
317 if (attr
->hdr
.is_msg
)
320 if (attr_id
>= FBNIC_TLV_RESULTS_MAX
)
323 while (tlv_index
->id
!= attr_id
) {
324 if (tlv_index
->id
== FBNIC_TLV_ATTR_ID_UNKNOWN
) {
325 if (attr
->hdr
.cannot_ignore
)
327 return le16_to_cpu(attr
->hdr
.len
);
333 if (offset_in_page(attr
) + len
> PAGE_SIZE
- sizeof(*attr
))
336 switch (tlv_index
->type
) {
337 case FBNIC_TLV_STRING
:
338 if (!len
|| len
> tlv_index
->len
)
340 if (((char *)value
)[len
- 1])
347 case FBNIC_TLV_UNSIGNED
:
348 case FBNIC_TLV_SIGNED
:
349 if (tlv_index
->len
> sizeof(__le64
))
352 case FBNIC_TLV_BINARY
:
353 if (!len
|| len
> tlv_index
->len
)
356 case FBNIC_TLV_NESTED
:
357 case FBNIC_TLV_ARRAY
:
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
377 * Return: zero on success, or negative value on error.
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.
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
)
389 /* Initialize results table to NULL. */
390 memset(results
, 0, array_len
* sizeof(results
[0]));
392 /* Nothing to parse if header was only thing there */
396 /* Work through list of attributes, parsing them as necessary */
398 u16 attr_id
= attr
->hdr
.type
;
402 if (tlv_attr_id
!= attr_id
)
405 /* Stop parsing on full error */
406 err
= fbnic_tlv_attr_validate(attr
, tlv_index
);
415 attr_len
= FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr
->hdr
.len
));
420 return len
== 0 ? 0 : -EINVAL
;
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
430 * Return: zero on success, or negative value on error.
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.
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
)
439 /* Initialize results table to NULL. */
440 memset(results
, 0, sizeof(results
[0]) * FBNIC_TLV_RESULTS_MAX
);
442 /* Nothing to parse if header was only thing there */
446 /* Work through list of attributes, parsing them as necessary */
448 int err
= fbnic_tlv_attr_validate(attr
, tlv_index
);
449 u16 attr_id
= attr
->hdr
.type
;
452 /* Stop parsing on full error */
456 /* Ignore results for unsupported values */
458 /* Do not overwrite existing entries */
459 if (results
[attr_id
])
462 results
[attr_id
] = attr
;
465 attr_len
= FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr
->hdr
.len
));
470 return len
== 0 ? 0 : -EINVAL
;
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.
479 * Return: zero on success, or negative value on error.
481 * Will take a message a number of message types via the attribute parsing
482 * definitions and function provided for the parser array.
484 int fbnic_tlv_msg_parse(void *opaque
, struct fbnic_tlv_msg
*msg
,
485 const struct fbnic_tlv_parser
*parser
)
487 struct fbnic_tlv_msg
*results
[FBNIC_TLV_RESULTS_MAX
];
488 u16 msg_id
= msg
->hdr
.type
;
491 if (!msg
->hdr
.is_msg
)
494 if (le16_to_cpu(msg
->hdr
.len
) > PAGE_SIZE
/ sizeof(u32
))
497 while (parser
->id
!= msg_id
) {
498 if (parser
->id
== FBNIC_TLV_MSG_ID_UNKNOWN
)
503 err
= fbnic_tlv_attr_parse(&msg
[1], le16_to_cpu(msg
->hdr
.len
) - 1,
504 results
, parser
->attr
);
508 return parser
->func(opaque
, results
);
512 * fbnic_tlv_parser_error - called if message doesn't match known type
516 * Return: -EBADMSG to indicate the message is an unsupported type
518 int fbnic_tlv_parser_error(void *opaque
, struct fbnic_tlv_msg
**results
)
523 void fbnic_tlv_attr_addr_copy(u8
*dest
, struct fbnic_tlv_msg
*src
)
527 mac_addr
= fbnic_tlv_attr_get_value_ptr(src
);
528 memcpy(dest
, mac_addr
, ETH_ALEN
);