// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) Meta Platforms, Inc. and affiliates. */ #include #include #include #include #include #include #include "fbnic_tlv.h" /** * fbnic_tlv_msg_alloc - Allocate page and initialize FW message header * @msg_id: Identifier for new message we are starting * * Return: pointer to start of message, or NULL on failure. * * Allocates a page and initializes message header at start of page. * Initial message size is 1 DWORD which is just the header. **/ struct fbnic_tlv_msg *fbnic_tlv_msg_alloc(u16 msg_id) { struct fbnic_tlv_hdr hdr = { 0 }; struct fbnic_tlv_msg *msg; msg = (struct fbnic_tlv_msg *)__get_free_page(GFP_KERNEL); if (!msg) return NULL; /* Start with zero filled header and then back fill with data */ hdr.type = msg_id; hdr.is_msg = 1; hdr.len = cpu_to_le16(1); /* Copy header into start of message */ msg->hdr = hdr; return msg; } /** * fbnic_tlv_attr_put_flag - Add flag value to message * @msg: Message header we are adding flag attribute to * @attr_id: ID of flag attribute we are adding to message * * Return: -ENOSPC if there is no room for the attribute. Otherwise 0. * * Adds a 1 DWORD flag attribute to the message. The presence of this * attribute can be used as a boolean value indicating true, otherwise the * value is considered false. **/ int fbnic_tlv_attr_put_flag(struct fbnic_tlv_msg *msg, const u16 attr_id) { int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg); struct fbnic_tlv_hdr hdr = { 0 }; struct fbnic_tlv_msg *attr; attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32); if (attr_max_len < sizeof(*attr)) return -ENOSPC; /* Get header pointer and bump attr to start of data */ attr = &msg[le16_to_cpu(msg->hdr.len)]; /* Record attribute type and size */ hdr.type = attr_id; hdr.len = cpu_to_le16(sizeof(hdr)); attr->hdr = hdr; le16_add_cpu(&msg->hdr.len, FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len))); return 0; } /** * fbnic_tlv_attr_put_value - Add data to message * @msg: Message header we are adding flag attribute to * @attr_id: ID of flag attribute we are adding to message * @value: Pointer to data to be stored * @len: Size of data to be stored. * * Return: -ENOSPC if there is no room for the attribute. Otherwise 0. * * Adds header and copies data pointed to by value into the message. The * result is rounded up to the nearest DWORD for sizing so that the * headers remain aligned. * * The assumption is that the value field is in a format where byte * ordering can be guaranteed such as a byte array or a little endian * format. **/ int fbnic_tlv_attr_put_value(struct fbnic_tlv_msg *msg, const u16 attr_id, const void *value, const int len) { int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg); struct fbnic_tlv_hdr hdr = { 0 }; struct fbnic_tlv_msg *attr; attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32); if (attr_max_len < sizeof(*attr) + len) return -ENOSPC; /* Get header pointer and bump attr to start of data */ attr = &msg[le16_to_cpu(msg->hdr.len)]; /* Record attribute type and size */ hdr.type = attr_id; hdr.len = cpu_to_le16(sizeof(hdr) + len); /* Zero pad end of region to be written if we aren't aligned */ if (len % sizeof(hdr)) attr->value[len / sizeof(hdr)] = 0; /* Copy data over */ memcpy(attr->value, value, len); attr->hdr = hdr; le16_add_cpu(&msg->hdr.len, FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len))); return 0; } /** * __fbnic_tlv_attr_put_int - Add integer to message * @msg: Message header we are adding flag attribute to * @attr_id: ID of flag attribute we are adding to message * @value: Data to be stored * @len: Size of data to be stored, either 4 or 8 bytes. * * Return: -ENOSPC if there is no room for the attribute. Otherwise 0. * * Adds header and copies data pointed to by value into the message. Will * format the data as little endian. **/ int __fbnic_tlv_attr_put_int(struct fbnic_tlv_msg *msg, const u16 attr_id, s64 value, const int len) { __le64 le64_value = cpu_to_le64(value); return fbnic_tlv_attr_put_value(msg, attr_id, &le64_value, len); } /** * fbnic_tlv_attr_put_mac_addr - Add mac_addr to message * @msg: Message header we are adding flag attribute to * @attr_id: ID of flag attribute we are adding to message * @mac_addr: Byte pointer to MAC address to be stored * * Return: -ENOSPC if there is no room for the attribute. Otherwise 0. * * Adds header and copies data pointed to by mac_addr into the message. Will * copy the address raw so it will be in big endian with start of MAC * address at start of attribute. **/ int fbnic_tlv_attr_put_mac_addr(struct fbnic_tlv_msg *msg, const u16 attr_id, const u8 *mac_addr) { return fbnic_tlv_attr_put_value(msg, attr_id, mac_addr, ETH_ALEN); } /** * fbnic_tlv_attr_put_string - Add string to message * @msg: Message header we are adding flag attribute to * @attr_id: ID of flag attribute we are adding to message * @string: Byte pointer to null terminated string to be stored * * Return: -ENOSPC if there is no room for the attribute. Otherwise 0. * * Adds header and copies data pointed to by string into the message. Will * copy the address raw so it will be in byte order. **/ int fbnic_tlv_attr_put_string(struct fbnic_tlv_msg *msg, u16 attr_id, const char *string) { int attr_max_len = PAGE_SIZE - sizeof(*msg); int str_len = 1; /* The max length will be message minus existing message and new * attribute header. Since the message is measured in DWORDs we have * to multiply the size by 4. * * The string length doesn't include the \0 so we have to add one to * the final value, so start with that as our initial value. * * We will verify if the string will fit in fbnic_tlv_attr_put_value() */ attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32); str_len += strnlen(string, attr_max_len); return fbnic_tlv_attr_put_value(msg, attr_id, string, str_len); } /** * fbnic_tlv_attr_get_unsigned - Retrieve unsigned value from result * @attr: Attribute to retrieve data from * * Return: unsigned 64b value containing integer value **/ u64 fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg *attr) { __le64 le64_value = 0; memcpy(&le64_value, &attr->value[0], le16_to_cpu(attr->hdr.len) - sizeof(*attr)); return le64_to_cpu(le64_value); } /** * fbnic_tlv_attr_get_signed - Retrieve signed value from result * @attr: Attribute to retrieve data from * * Return: signed 64b value containing integer value **/ s64 fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg *attr) { int shift = (8 + sizeof(*attr) - le16_to_cpu(attr->hdr.len)) * 8; __le64 le64_value = 0; s64 value; /* Copy the value and adjust for byte ordering */ memcpy(&le64_value, &attr->value[0], le16_to_cpu(attr->hdr.len) - sizeof(*attr)); value = le64_to_cpu(le64_value); /* Sign extend the return value by using a pair of shifts */ return (value << shift) >> shift; } /** * fbnic_tlv_attr_get_string - Retrieve string value from result * @attr: Attribute to retrieve data from * @str: Pointer to an allocated string to store the data * @max_size: The maximum size which can be in str * * Return: the size of the string read from firmware **/ size_t fbnic_tlv_attr_get_string(struct fbnic_tlv_msg *attr, char *str, size_t max_size) { max_size = min_t(size_t, max_size, (le16_to_cpu(attr->hdr.len) * 4) - sizeof(*attr)); memcpy(str, &attr->value, max_size); return max_size; } /** * fbnic_tlv_attr_nest_start - Add nested attribute header to message * @msg: Message header we are adding flag attribute to * @attr_id: ID of flag attribute we are adding to message * * Return: NULL if there is no room for the attribute. Otherwise a pointer * to the new attribute header. * * New header length is stored initially in DWORDs. **/ struct fbnic_tlv_msg *fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg *msg, u16 attr_id) { int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg); struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)]; struct fbnic_tlv_hdr hdr = { 0 }; /* Make sure we have space for at least the nest header plus one more */ attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32); if (attr_max_len < sizeof(*attr) * 2) return NULL; /* Record attribute type and size */ hdr.type = attr_id; /* Add current message length to account for consumption within the * page and leave it as a multiple of DWORDs, we will shift to * bytes when we close it out. */ hdr.len = cpu_to_le16(1); attr->hdr = hdr; return attr; } /** * fbnic_tlv_attr_nest_stop - Close out nested attribute and add it to message * @msg: Message header we are adding flag attribute to * * Closes out nested attribute, adds length to message, and then bumps * length from DWORDs to bytes to match other attributes. **/ void fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg *msg) { struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)]; u16 len = le16_to_cpu(attr->hdr.len); /* Add attribute to message if there is more than just a header */ if (len <= 1) return; le16_add_cpu(&msg->hdr.len, len); /* Convert from DWORDs to bytes */ attr->hdr.len = cpu_to_le16(len * sizeof(u32)); } static int fbnic_tlv_attr_validate(struct fbnic_tlv_msg *attr, const struct fbnic_tlv_index *tlv_index) { u16 len = le16_to_cpu(attr->hdr.len) - sizeof(*attr); u16 attr_id = attr->hdr.type; __le32 *value = &attr->value[0]; if (attr->hdr.is_msg) return -EINVAL; if (attr_id >= FBNIC_TLV_RESULTS_MAX) return -EINVAL; while (tlv_index->id != attr_id) { if (tlv_index->id == FBNIC_TLV_ATTR_ID_UNKNOWN) { if (attr->hdr.cannot_ignore) return -ENOENT; return le16_to_cpu(attr->hdr.len); } tlv_index++; } if (offset_in_page(attr) + len > PAGE_SIZE - sizeof(*attr)) return -E2BIG; switch (tlv_index->type) { case FBNIC_TLV_STRING: if (!len || len > tlv_index->len) return -EINVAL; if (((char *)value)[len - 1]) return -EINVAL; break; case FBNIC_TLV_FLAG: if (len) return -EINVAL; break; case FBNIC_TLV_UNSIGNED: case FBNIC_TLV_SIGNED: if (tlv_index->len > sizeof(__le64)) return -EINVAL; fallthrough; case FBNIC_TLV_BINARY: if (!len || len > tlv_index->len) return -EINVAL; break; case FBNIC_TLV_NESTED: case FBNIC_TLV_ARRAY: if (len % 4) return -EINVAL; break; default: return -EINVAL; } return 0; } /** * fbnic_tlv_attr_parse_array - Parse array of attributes into results array * @attr: Start of attributes in the message * @len: Length of attributes in the message * @results: Array of pointers to store the results of parsing * @tlv_index: List of TLV attributes to be parsed from message * @tlv_attr_id: Specific ID that is repeated in array * @array_len: Number of results to store in results array * * Return: zero on success, or negative value on error. * * Will take a list of attributes and a parser definition and will capture * the results in the results array to have the data extracted later. **/ int 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) { int i = 0; /* Initialize results table to NULL. */ memset(results, 0, array_len * sizeof(results[0])); /* Nothing to parse if header was only thing there */ if (!len) return 0; /* Work through list of attributes, parsing them as necessary */ while (len > 0) { u16 attr_id = attr->hdr.type; u16 attr_len; int err; if (tlv_attr_id != attr_id) return -EINVAL; /* Stop parsing on full error */ err = fbnic_tlv_attr_validate(attr, tlv_index); if (err < 0) return err; if (i >= array_len) return -ENOSPC; results[i++] = attr; attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len)); len -= attr_len; attr += attr_len; } return len == 0 ? 0 : -EINVAL; } /** * fbnic_tlv_attr_parse - Parse attributes into a list of attribute results * @attr: Start of attributes in the message * @len: Length of attributes in the message * @results: Array of pointers to store the results of parsing * @tlv_index: List of TLV attributes to be parsed from message * * Return: zero on success, or negative value on error. * * Will take a list of attributes and a parser definition and will capture * the results in the results array to have the data extracted later. **/ int fbnic_tlv_attr_parse(struct fbnic_tlv_msg *attr, int len, struct fbnic_tlv_msg **results, const struct fbnic_tlv_index *tlv_index) { /* Initialize results table to NULL. */ memset(results, 0, sizeof(results[0]) * FBNIC_TLV_RESULTS_MAX); /* Nothing to parse if header was only thing there */ if (!len) return 0; /* Work through list of attributes, parsing them as necessary */ while (len > 0) { int err = fbnic_tlv_attr_validate(attr, tlv_index); u16 attr_id = attr->hdr.type; u16 attr_len; /* Stop parsing on full error */ if (err < 0) return err; /* Ignore results for unsupported values */ if (!err) { /* Do not overwrite existing entries */ if (results[attr_id]) return -EADDRINUSE; results[attr_id] = attr; } attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len)); len -= attr_len; attr += attr_len; } return len == 0 ? 0 : -EINVAL; } /** * fbnic_tlv_msg_parse - Parse message and process via predetermined functions * @opaque: Value passed to parser function to enable driver access * @msg: Message to be parsed. * @parser: TLV message parser definition. * * Return: zero on success, or negative value on error. * * Will take a message a number of message types via the attribute parsing * definitions and function provided for the parser array. **/ int fbnic_tlv_msg_parse(void *opaque, struct fbnic_tlv_msg *msg, const struct fbnic_tlv_parser *parser) { struct fbnic_tlv_msg *results[FBNIC_TLV_RESULTS_MAX]; u16 msg_id = msg->hdr.type; int err; if (!msg->hdr.is_msg) return -EINVAL; if (le16_to_cpu(msg->hdr.len) > PAGE_SIZE / sizeof(u32)) return -E2BIG; while (parser->id != msg_id) { if (parser->id == FBNIC_TLV_MSG_ID_UNKNOWN) return -ENOENT; parser++; } err = fbnic_tlv_attr_parse(&msg[1], le16_to_cpu(msg->hdr.len) - 1, results, parser->attr); if (err) return err; return parser->func(opaque, results); } /** * fbnic_tlv_parser_error - called if message doesn't match known type * @opaque: (unused) * @results: (unused) * * Return: -EBADMSG to indicate the message is an unsupported type **/ int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results) { return -EBADMSG; } void fbnic_tlv_attr_addr_copy(u8 *dest, struct fbnic_tlv_msg *src) { u8 *mac_addr; mac_addr = fbnic_tlv_attr_get_value_ptr(src); memcpy(dest, mac_addr, ETH_ALEN); }