| /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ |
| #include "array.h" |
| |
| #include <libpldm/base.h> |
| #include <libpldm/bios.h> |
| #include <libpldm/bios_table.h> |
| #include <libpldm/utils.h> |
| |
| #include <assert.h> |
| #include <endian.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #define POINTER_CHECK(pointer) \ |
| do { \ |
| if ((pointer) == NULL) \ |
| return PLDM_ERROR_INVALID_DATA; \ |
| } while (0) |
| |
| #define ATTR_TYPE_EXPECT(type, expected) \ |
| do { \ |
| if ((type) != (expected) && (type) != ((expected) | 0x80)) \ |
| return PLDM_ERROR_INVALID_DATA; \ |
| } while (0) |
| |
| #define BUFFER_SIZE_EXPECT(current_size, expected_size) \ |
| do { \ |
| if ((current_size) < (expected_size)) \ |
| return PLDM_ERROR_INVALID_LENGTH; \ |
| } while (0) |
| |
| #define MEMBER_SIZE(type, member) sizeof(((struct type *)0)->member) |
| |
| static void set_errmsg(const char **errmsg, const char *msg) |
| { |
| if (errmsg != NULL) { |
| *errmsg = msg; |
| } |
| } |
| |
| static int get_bios_string_handle(uint16_t *val) |
| { |
| static uint16_t handle = 0; |
| assert(handle != UINT16_MAX); |
| if (handle == UINT16_MAX) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| |
| *val = handle++; |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_string_entry_encode_length(uint16_t string_length) |
| { |
| return sizeof(struct pldm_bios_string_table_entry) - |
| MEMBER_SIZE(pldm_bios_string_table_entry, name) + string_length; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_string_entry_encode(void *entry, size_t entry_length, |
| const char *str, uint16_t str_length) |
| { |
| if (str_length == 0) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| POINTER_CHECK(entry); |
| POINTER_CHECK(str); |
| size_t length = pldm_bios_table_string_entry_encode_length(str_length); |
| BUFFER_SIZE_EXPECT(entry_length, length); |
| struct pldm_bios_string_table_entry *string_entry = entry; |
| uint16_t handle; |
| int rc = get_bios_string_handle(&handle); |
| if (rc != PLDM_SUCCESS) { |
| return rc; |
| } |
| string_entry->string_handle = htole16(handle); |
| string_entry->string_length = htole16(str_length); |
| memcpy(string_entry->name, str, str_length); |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_string_entry_decode_handle( |
| const struct pldm_bios_string_table_entry *entry) |
| { |
| return le16toh(entry->string_handle); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_string_entry_decode_string_length( |
| const struct pldm_bios_string_table_entry *entry) |
| { |
| return le16toh(entry->string_length); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_string_entry_decode_string( |
| const struct pldm_bios_string_table_entry *entry, char *buffer, |
| size_t size) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(buffer); |
| if (size == 0) { |
| return PLDM_ERROR_INVALID_LENGTH; |
| } |
| size_t length = |
| pldm_bios_table_string_entry_decode_string_length(entry); |
| length = length < (size - 1) ? length : (size - 1); |
| memcpy(buffer, entry->name, length); |
| buffer[length] = 0; |
| return PLDM_SUCCESS; |
| } |
| |
| static ssize_t string_table_entry_length(const void *table_entry) |
| { |
| const struct pldm_bios_string_table_entry *entry = table_entry; |
| size_t len = sizeof(*entry) - sizeof(entry->name) + |
| pldm_bios_table_string_entry_decode_string_length(entry); |
| if (len > SSIZE_MAX) { |
| return -1; |
| } |
| return (ssize_t)len; |
| } |
| |
| static int get_bios_attr_handle(uint16_t *val) |
| { |
| static uint16_t handle = 0; |
| assert(handle != UINT16_MAX); |
| if (handle == UINT16_MAX) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| |
| *val = handle++; |
| return PLDM_SUCCESS; |
| } |
| |
| static int attr_table_entry_encode_header(void *entry, size_t length, |
| uint8_t attr_type, |
| uint16_t string_handle) |
| { |
| struct pldm_bios_attr_table_entry *attr_entry = entry; |
| |
| assert(sizeof(*attr_entry) <= length); |
| if (sizeof(*attr_entry) > length) { |
| return PLDM_ERROR_INVALID_LENGTH; |
| } |
| |
| uint16_t handle; |
| int rc = get_bios_attr_handle(&handle); |
| if (rc != PLDM_SUCCESS) { |
| return rc; |
| } |
| |
| attr_entry->attr_handle = htole16(handle); |
| attr_entry->attr_type = attr_type; |
| attr_entry->string_handle = htole16(string_handle); |
| |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_entry_decode_attribute_handle( |
| const struct pldm_bios_attr_table_entry *entry) |
| { |
| return le16toh(entry->attr_handle); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint8_t pldm_bios_table_attr_entry_decode_attribute_type( |
| const struct pldm_bios_attr_table_entry *entry) |
| { |
| return entry->attr_type; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_entry_decode_string_handle( |
| const struct pldm_bios_attr_table_entry *entry) |
| { |
| return le16toh(entry->string_handle); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_attr_entry_enum_encode_length(uint8_t pv_num, |
| uint8_t def_num) |
| { |
| return sizeof(struct pldm_bios_attr_table_entry) - |
| MEMBER_SIZE(pldm_bios_attr_table_entry, metadata) + |
| sizeof(pv_num) + pv_num * sizeof(uint16_t) + sizeof(def_num) + |
| def_num; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_enum_encode( |
| void *entry, size_t entry_length, |
| const struct pldm_bios_table_attr_entry_enum_info *info) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(info); |
| size_t length = pldm_bios_table_attr_entry_enum_encode_length( |
| info->pv_num, info->def_num); |
| BUFFER_SIZE_EXPECT(entry_length, length); |
| uint8_t attr_type = info->read_only ? PLDM_BIOS_ENUMERATION_READ_ONLY : |
| PLDM_BIOS_ENUMERATION; |
| int rc = attr_table_entry_encode_header(entry, entry_length, attr_type, |
| info->name_handle); |
| if (rc != PLDM_SUCCESS) { |
| return rc; |
| } |
| struct pldm_bios_attr_table_entry *attr_entry = entry; |
| attr_entry->metadata[0] = info->pv_num; |
| uint16_t *pv_hdls = |
| (uint16_t *)(attr_entry->metadata + 1 /* sizeof(pv num) */); |
| size_t i; |
| for (i = 0; i < info->pv_num; i++) { |
| pv_hdls[i] = htole16(info->pv_handle[i]); |
| } |
| attr_entry->metadata[1 + info->pv_num * sizeof(uint16_t)] = |
| info->def_num; |
| memcpy(attr_entry->metadata + 1 /* sizeof(pv num) */ + |
| info->pv_num * sizeof(uint16_t) + 1 /* sizeof(def num)*/, |
| info->def_index, info->def_num); |
| return PLDM_SUCCESS; |
| } |
| |
| #define ATTR_TYPE_EXPECT(type, expected) \ |
| do { \ |
| if ((type) != (expected) && (type) != ((expected) | 0x80)) \ |
| return PLDM_ERROR_INVALID_DATA; \ |
| } while (0) |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_enum_decode_pv_num( |
| const struct pldm_bios_attr_table_entry *entry, uint8_t *pv_num) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(pv_num); |
| ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_ENUMERATION); |
| *pv_num = entry->metadata[0]; |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_enum_decode_def_num( |
| const struct pldm_bios_attr_table_entry *entry, uint8_t *def_num) |
| { |
| uint8_t pv_num; |
| |
| POINTER_CHECK(entry); |
| POINTER_CHECK(def_num); |
| ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_ENUMERATION); |
| pv_num = entry->metadata[0]; |
| *def_num = entry->metadata[sizeof(uint8_t) + sizeof(uint16_t) * pv_num]; |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_enum_decode_pv_hdls( |
| const struct pldm_bios_attr_table_entry *entry, uint16_t *pv_hdls, |
| uint8_t pv_num) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(pv_hdls); |
| ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_ENUMERATION); |
| uint8_t num = entry->metadata[0]; |
| num = num < pv_num ? num : pv_num; |
| size_t i; |
| for (i = 0; i < num; i++) { |
| uint16_t *hdl = (uint16_t *)(entry->metadata + sizeof(uint8_t) + |
| i * sizeof(uint16_t)); |
| pv_hdls[i] = le16toh(*hdl); |
| } |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint8_t pldm_bios_table_attr_entry_enum_decode_def_indices( |
| const struct pldm_bios_attr_table_entry *entry, uint8_t *def_indices, |
| uint8_t def_num) |
| { |
| uint8_t num = 0; |
| |
| /* TODO: Fix the API to propagate errors */ |
| pldm_bios_table_attr_entry_enum_decode_def_num(entry, &num); |
| num = num < def_num ? num : def_num; |
| uint8_t pv_num = entry->metadata[0]; |
| const uint8_t *p = entry->metadata + |
| sizeof(uint8_t) /* number of possible values*/ |
| + pv_num * sizeof(uint16_t) /* possible values */ |
| + sizeof(uint8_t); /* number of default values */ |
| memcpy(def_indices, p, num); |
| return num; |
| } |
| |
| /** @brief Get length of an enum attribute entry |
| */ |
| static ssize_t attr_table_entry_length_enum(const void *arg) |
| { |
| const struct pldm_bios_attr_table_entry *entry = arg; |
| uint8_t pv_num = entry->metadata[0]; |
| uint8_t def_num = 0; |
| |
| /* TODO: Fix the API to propagate errors */ |
| pldm_bios_table_attr_entry_enum_decode_def_num(entry, &def_num); |
| size_t len = |
| pldm_bios_table_attr_entry_enum_encode_length(pv_num, def_num); |
| if (len > SSIZE_MAX) { |
| return -1; |
| } |
| return (ssize_t)len; |
| } |
| |
| struct attr_table_string_entry_fields { |
| uint8_t string_type; |
| uint16_t min_length; |
| uint16_t max_length; |
| uint16_t def_length; |
| uint8_t def_string[1]; |
| } __attribute__((packed)); |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_attr_entry_string_encode_length(uint16_t def_str_len) |
| { |
| return sizeof(struct pldm_bios_attr_table_entry) - |
| MEMBER_SIZE(pldm_bios_attr_table_entry, metadata) + |
| sizeof(struct attr_table_string_entry_fields) - |
| MEMBER_SIZE(attr_table_string_entry_fields, def_string) + |
| def_str_len; |
| } |
| |
| #define PLDM_STRING_TYPE_MAX 5 |
| #define PLDM_STRING_TYPE_VENDOR 0xff |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_string_info_check( |
| const struct pldm_bios_table_attr_entry_string_info *info, |
| const char **errmsg) |
| { |
| if (info->min_length > info->max_length) { |
| set_errmsg(errmsg, "MinimumStingLength should not be greater " |
| "than MaximumStringLength"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if (info->min_length == info->max_length && |
| info->def_length != info->min_length) { |
| set_errmsg(errmsg, "Wrong DefaultStringLength"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if (info->def_length > info->max_length || |
| info->def_length < info->min_length) { |
| set_errmsg(errmsg, "Wrong DefaultStringLength"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if (info->string_type > PLDM_STRING_TYPE_MAX && |
| info->string_type != PLDM_STRING_TYPE_VENDOR) { |
| set_errmsg(errmsg, "Wrong StringType"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if (info->def_string && info->def_length != strlen(info->def_string)) { |
| set_errmsg(errmsg, "Length of DefaultString should be equal to " |
| "DefaultStringLength"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_string_encode( |
| void *entry, size_t entry_length, |
| const struct pldm_bios_table_attr_entry_string_info *info) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(info); |
| size_t length = pldm_bios_table_attr_entry_string_encode_length( |
| info->def_length); |
| BUFFER_SIZE_EXPECT(entry_length, length); |
| if (pldm_bios_table_attr_entry_string_info_check(info, NULL) != |
| PLDM_SUCCESS) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| uint8_t attr_type = info->read_only ? PLDM_BIOS_STRING_READ_ONLY : |
| PLDM_BIOS_STRING; |
| int rc = attr_table_entry_encode_header(entry, entry_length, attr_type, |
| info->name_handle); |
| if (rc != PLDM_SUCCESS) { |
| return rc; |
| } |
| struct pldm_bios_attr_table_entry *attr_entry = entry; |
| struct attr_table_string_entry_fields *attr_fields = |
| (struct attr_table_string_entry_fields *)attr_entry->metadata; |
| attr_fields->string_type = info->string_type; |
| attr_fields->min_length = htole16(info->min_length); |
| attr_fields->max_length = htole16(info->max_length); |
| attr_fields->def_length = htole16(info->def_length); |
| if (info->def_length != 0 && info->def_string != NULL) { |
| memcpy(attr_fields->def_string, info->def_string, |
| info->def_length); |
| } |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_string_decode_def_string_length( |
| const struct pldm_bios_attr_table_entry *entry, |
| uint16_t *def_string_length) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(def_string_length); |
| ATTR_TYPE_EXPECT(entry->attr_type, PLDM_BIOS_STRING); |
| struct attr_table_string_entry_fields *fields = |
| (struct attr_table_string_entry_fields *)entry->metadata; |
| *def_string_length = le16toh(fields->def_length); |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint8_t pldm_bios_table_attr_entry_string_decode_string_type( |
| const struct pldm_bios_attr_table_entry *entry) |
| { |
| struct attr_table_string_entry_fields *fields = |
| (struct attr_table_string_entry_fields *)entry->metadata; |
| return fields->string_type; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_entry_string_decode_max_length( |
| const struct pldm_bios_attr_table_entry *entry) |
| { |
| struct attr_table_string_entry_fields *fields = |
| (struct attr_table_string_entry_fields *)entry->metadata; |
| return le16toh(fields->max_length); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_entry_string_decode_min_length( |
| const struct pldm_bios_attr_table_entry *entry) |
| { |
| struct attr_table_string_entry_fields *fields = |
| (struct attr_table_string_entry_fields *)entry->metadata; |
| return le16toh(fields->min_length); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_entry_string_decode_def_string( |
| const struct pldm_bios_attr_table_entry *entry, char *buffer, |
| size_t size) |
| { |
| uint16_t length = 0; |
| int rc; |
| |
| if (!entry || !buffer || size == 0) { |
| return 0; |
| } |
| |
| /* TODO: Rework the API to propagate the error */ |
| rc = pldm_bios_table_attr_entry_string_decode_def_string_length( |
| entry, &length); |
| if (rc != PLDM_SUCCESS) { |
| return 0; |
| } |
| |
| length = length < (size - 1) ? length : (size - 1); |
| struct attr_table_string_entry_fields *fields = |
| (struct attr_table_string_entry_fields *)entry->metadata; |
| memcpy(buffer, fields->def_string, length); |
| buffer[length] = 0; |
| return length; |
| } |
| |
| /** @brief Get length of a string attribute entry |
| */ |
| static ssize_t attr_table_entry_length_string(const void *entry) |
| { |
| uint16_t def_str_len = 0; |
| |
| /* TODO: Rework the API to propagate the error */ |
| pldm_bios_table_attr_entry_string_decode_def_string_length( |
| entry, &def_str_len); |
| size_t len = |
| pldm_bios_table_attr_entry_string_encode_length(def_str_len); |
| if (len > SSIZE_MAX) { |
| return -1; |
| } |
| return (ssize_t)len; |
| } |
| |
| struct attr_table_integer_entry_fields { |
| uint64_t lower_bound; |
| uint64_t upper_bound; |
| uint32_t scalar_increment; |
| uint64_t default_value; |
| } __attribute__((packed)); |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_attr_entry_integer_encode_length(void) |
| { |
| return sizeof(struct pldm_bios_attr_table_entry) - 1 + |
| sizeof(struct attr_table_integer_entry_fields); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_integer_info_check( |
| const struct pldm_bios_table_attr_entry_integer_info *info, |
| const char **errmsg) |
| { |
| if (info->lower_bound == info->upper_bound) { |
| if (info->default_value != info->lower_bound) { |
| set_errmsg(errmsg, "Wrong DefaultValue"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if (info->scalar_increment != 0) { |
| set_errmsg(errmsg, "Wrong ScalarIncrement"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| return PLDM_SUCCESS; |
| } |
| if (info->lower_bound > info->upper_bound) { |
| set_errmsg(errmsg, |
| "LowerBound should not be greater than UpperBound"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if (info->default_value > info->upper_bound || |
| info->default_value < info->lower_bound) { |
| set_errmsg(errmsg, "Wrong DefaultValue"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if (info->scalar_increment == 0) { |
| set_errmsg(errmsg, "ScalarIncrement should not be zero when " |
| "lower_bound != upper_bound"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| if ((info->default_value - info->lower_bound) % |
| info->scalar_increment != |
| 0) { |
| set_errmsg(errmsg, "Wrong DefaultValue or ScalarIncrement"); |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_entry_integer_encode( |
| void *entry, size_t entry_length, |
| const struct pldm_bios_table_attr_entry_integer_info *info) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(info); |
| size_t length = pldm_bios_table_attr_entry_integer_encode_length(); |
| BUFFER_SIZE_EXPECT(entry_length, length); |
| if (pldm_bios_table_attr_entry_integer_info_check(info, NULL) != |
| PLDM_SUCCESS) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| uint8_t attr_type = info->read_only ? PLDM_BIOS_INTEGER_READ_ONLY : |
| PLDM_BIOS_INTEGER; |
| int rc = attr_table_entry_encode_header(entry, entry_length, attr_type, |
| info->name_handle); |
| if (rc != PLDM_SUCCESS) { |
| return rc; |
| } |
| struct pldm_bios_attr_table_entry *attr_entry = entry; |
| struct attr_table_integer_entry_fields *attr_fields = |
| (struct attr_table_integer_entry_fields *)attr_entry->metadata; |
| attr_fields->lower_bound = htole64(info->lower_bound); |
| attr_fields->upper_bound = htole64(info->upper_bound); |
| attr_fields->scalar_increment = htole32(info->scalar_increment); |
| attr_fields->default_value = htole64(info->default_value); |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| void pldm_bios_table_attr_entry_integer_decode( |
| const struct pldm_bios_attr_table_entry *entry, uint64_t *lower, |
| uint64_t *upper, uint32_t *scalar, uint64_t *def) |
| { |
| struct attr_table_integer_entry_fields *fields = |
| (struct attr_table_integer_entry_fields *)entry->metadata; |
| *lower = le64toh(fields->lower_bound); |
| *upper = le64toh(fields->upper_bound); |
| *scalar = le32toh(fields->scalar_increment); |
| *def = le64toh(fields->default_value); |
| } |
| |
| static ssize_t attr_table_entry_length_integer(const void *entry) |
| { |
| (void)entry; |
| size_t len = pldm_bios_table_attr_entry_integer_encode_length(); |
| if (len > SSIZE_MAX) { |
| return -1; |
| } |
| return (ssize_t)len; |
| } |
| |
| struct table_entry_length { |
| uint8_t attr_type; |
| ssize_t (*entry_length_handler)(const void *); |
| }; |
| |
| static const struct table_entry_length * |
| find_table_entry_length_by_type(uint8_t attr_type, |
| const struct table_entry_length *handlers, |
| size_t count) |
| { |
| size_t i; |
| for (i = 0; i < count; i++) { |
| if (attr_type == handlers[i].attr_type) { |
| return &handlers[i]; |
| } |
| } |
| return NULL; |
| } |
| |
| static const struct table_entry_length attr_table_entries[] = { |
| { .attr_type = PLDM_BIOS_ENUMERATION, |
| .entry_length_handler = attr_table_entry_length_enum }, |
| { .attr_type = PLDM_BIOS_ENUMERATION_READ_ONLY, |
| .entry_length_handler = attr_table_entry_length_enum }, |
| { .attr_type = PLDM_BIOS_STRING, |
| .entry_length_handler = attr_table_entry_length_string }, |
| { .attr_type = PLDM_BIOS_STRING_READ_ONLY, |
| .entry_length_handler = attr_table_entry_length_string }, |
| { .attr_type = PLDM_BIOS_INTEGER, |
| .entry_length_handler = attr_table_entry_length_integer }, |
| { .attr_type = PLDM_BIOS_INTEGER_READ_ONLY, |
| .entry_length_handler = attr_table_entry_length_integer }, |
| }; |
| |
| static ssize_t attr_table_entry_length(const void *table_entry) |
| { |
| const struct pldm_bios_attr_table_entry *entry = table_entry; |
| const struct table_entry_length *attr_table_entry = |
| find_table_entry_length_by_type(entry->attr_type, |
| attr_table_entries, |
| ARRAY_SIZE(attr_table_entries)); |
| assert(attr_table_entry != NULL); |
| if (!attr_table_entry) { |
| return -1; |
| } |
| assert(attr_table_entry->entry_length_handler != NULL); |
| if (!attr_table_entry->entry_length_handler) { |
| return -1; |
| } |
| |
| return attr_table_entry->entry_length_handler(entry); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_value_entry_decode_attribute_handle( |
| const struct pldm_bios_attr_val_table_entry *entry) |
| { |
| return le16toh(entry->attr_handle); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint8_t pldm_bios_table_attr_value_entry_decode_attribute_type( |
| const struct pldm_bios_attr_val_table_entry *entry) |
| { |
| return entry->attr_type; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_attr_value_entry_encode_enum_length(uint8_t count) |
| { |
| return sizeof(struct pldm_bios_attr_val_table_entry) - 1 + |
| sizeof(count) + count; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint8_t pldm_bios_table_attr_value_entry_enum_decode_number( |
| const struct pldm_bios_attr_val_table_entry *entry) |
| { |
| return entry->value[0]; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint8_t pldm_bios_table_attr_value_entry_enum_decode_handles( |
| const struct pldm_bios_attr_val_table_entry *entry, uint8_t *handles, |
| uint8_t number) |
| { |
| uint8_t curr_num = |
| pldm_bios_table_attr_value_entry_enum_decode_number(entry); |
| curr_num = number < curr_num ? number : curr_num; |
| memcpy(handles, &entry->value[1], curr_num); |
| |
| return curr_num; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_value_entry_encode_enum( |
| void *entry, size_t entry_length, uint16_t attr_handle, |
| uint8_t attr_type, uint8_t count, const uint8_t *handles) |
| { |
| POINTER_CHECK(entry); |
| POINTER_CHECK(handles); |
| if (count != 0 && handles == NULL) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| ATTR_TYPE_EXPECT(attr_type, PLDM_BIOS_ENUMERATION); |
| size_t length = |
| pldm_bios_table_attr_value_entry_encode_enum_length(count); |
| BUFFER_SIZE_EXPECT(entry_length, length); |
| struct pldm_bios_attr_val_table_entry *table_entry = entry; |
| table_entry->attr_handle = htole16(attr_handle); |
| table_entry->attr_type = attr_type; |
| table_entry->value[0] = count; |
| if (count != 0) { |
| memcpy(&table_entry->value[1], handles, count); |
| } |
| return PLDM_SUCCESS; |
| } |
| |
| static ssize_t attr_value_table_entry_length_enum(const void *entry) |
| { |
| uint8_t number = |
| pldm_bios_table_attr_value_entry_enum_decode_number(entry); |
| size_t len = |
| pldm_bios_table_attr_value_entry_encode_enum_length(number); |
| if (len > SSIZE_MAX) { |
| return -1; |
| } |
| return (ssize_t)len; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| size_t |
| pldm_bios_table_attr_value_entry_encode_string_length(uint16_t string_length) |
| { |
| return sizeof(struct pldm_bios_attr_val_table_entry) - 1 + |
| sizeof(string_length) + string_length; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_value_entry_string_decode_length( |
| const struct pldm_bios_attr_val_table_entry *entry) |
| { |
| uint16_t str_length = 0; |
| memcpy(&str_length, entry->value, sizeof(str_length)); |
| return le16toh(str_length); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| void pldm_bios_table_attr_value_entry_string_decode_string( |
| const struct pldm_bios_attr_val_table_entry *entry, |
| struct variable_field *current_string) |
| { |
| current_string->length = |
| pldm_bios_table_attr_value_entry_string_decode_length(entry); |
| current_string->ptr = |
| entry->value + sizeof(uint16_t); // sizeof(CurrentStringLength) |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_value_entry_encode_string_check( |
| void *entry, size_t entry_length, uint16_t attr_handle, |
| uint8_t attr_type, uint16_t str_length, const char *str) |
| { |
| POINTER_CHECK(entry); |
| if (str_length != 0 && str == NULL) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| ATTR_TYPE_EXPECT(attr_type, PLDM_BIOS_STRING); |
| size_t length = pldm_bios_table_attr_value_entry_encode_string_length( |
| str_length); |
| BUFFER_SIZE_EXPECT(entry_length, length); |
| struct pldm_bios_attr_val_table_entry *table_entry = entry; |
| table_entry->attr_handle = htole16(attr_handle); |
| table_entry->attr_type = attr_type; |
| if (str_length != 0) { |
| memcpy(table_entry->value + sizeof(str_length), str, |
| str_length); |
| } |
| str_length = htole16(str_length); |
| memcpy(table_entry->value, &str_length, sizeof(str_length)); |
| return PLDM_SUCCESS; |
| } |
| |
| static ssize_t attr_value_table_entry_length_string(const void *entry) |
| { |
| uint16_t str_length = |
| pldm_bios_table_attr_value_entry_string_decode_length(entry); |
| size_t len = pldm_bios_table_attr_value_entry_encode_string_length( |
| str_length); |
| if (len > SSIZE_MAX) { |
| return -1; |
| } |
| return (ssize_t)len; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_attr_value_entry_encode_integer_length(void) |
| { |
| return sizeof(struct pldm_bios_attr_val_table_entry) - 1 + |
| sizeof(uint64_t); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_value_entry_encode_integer_check(void *entry, |
| size_t entry_length, |
| uint16_t attr_handle, |
| uint8_t attr_type, |
| uint64_t cv) |
| { |
| POINTER_CHECK(entry); |
| size_t length = |
| pldm_bios_table_attr_value_entry_encode_integer_length(); |
| ATTR_TYPE_EXPECT(attr_type, PLDM_BIOS_INTEGER); |
| BUFFER_SIZE_EXPECT(entry_length, length); |
| struct pldm_bios_attr_val_table_entry *table_entry = entry; |
| table_entry->attr_handle = htole16(attr_handle); |
| table_entry->attr_type = attr_type; |
| cv = htole64(cv); |
| memcpy(table_entry->value, &cv, sizeof(uint64_t)); |
| return PLDM_SUCCESS; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint64_t pldm_bios_table_attr_value_entry_integer_decode_cv( |
| const struct pldm_bios_attr_val_table_entry *entry) |
| { |
| uint64_t cv = 0; |
| memcpy(&cv, entry->value, sizeof(cv)); |
| cv = le64toh(cv); |
| return cv; |
| } |
| |
| static ssize_t attr_value_table_entry_length_integer(const void *entry) |
| { |
| (void)entry; |
| size_t len = pldm_bios_table_attr_value_entry_encode_integer_length(); |
| if (len > SSIZE_MAX) { |
| return -1; |
| } |
| return (ssize_t)len; |
| } |
| |
| static const struct table_entry_length attr_value_table_entries[] = { |
| { .attr_type = PLDM_BIOS_ENUMERATION, |
| .entry_length_handler = attr_value_table_entry_length_enum }, |
| { .attr_type = PLDM_BIOS_ENUMERATION_READ_ONLY, |
| .entry_length_handler = attr_value_table_entry_length_enum }, |
| { .attr_type = PLDM_BIOS_STRING, |
| .entry_length_handler = attr_value_table_entry_length_string }, |
| { .attr_type = PLDM_BIOS_STRING_READ_ONLY, |
| .entry_length_handler = attr_value_table_entry_length_string }, |
| { .attr_type = PLDM_BIOS_INTEGER, |
| .entry_length_handler = attr_value_table_entry_length_integer }, |
| { .attr_type = PLDM_BIOS_INTEGER_READ_ONLY, |
| .entry_length_handler = attr_value_table_entry_length_integer }, |
| }; |
| |
| static ssize_t attr_value_table_entry_length(const void *table_entry) |
| { |
| const struct pldm_bios_attr_val_table_entry *entry = table_entry; |
| const struct table_entry_length *entry_length = |
| find_table_entry_length_by_type( |
| entry->attr_type, attr_value_table_entries, |
| ARRAY_SIZE(attr_value_table_entries)); |
| assert(entry_length != NULL); |
| if (!entry_length) { |
| return -1; |
| } |
| assert(entry_length->entry_length_handler != NULL); |
| if (!entry_length->entry_length_handler) { |
| return -1; |
| } |
| |
| return entry_length->entry_length_handler(entry); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_attr_value_entry_length( |
| const struct pldm_bios_attr_val_table_entry *entry) |
| { |
| return attr_value_table_entry_length(entry); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| uint16_t pldm_bios_table_attr_value_entry_decode_handle( |
| const struct pldm_bios_attr_val_table_entry *entry) |
| { |
| return le16toh(entry->attr_handle); |
| } |
| |
| static size_t pad_size_get(size_t size_without_pad) |
| { |
| return ((size_without_pad % 4) ? (4 - size_without_pad % 4) : 0); |
| } |
| |
| static uint8_t *pad_append(uint8_t *table_end, size_t pad_size) |
| { |
| while (pad_size--) { |
| *table_end++ = 0; |
| } |
| |
| return table_end; |
| } |
| |
| static uint8_t *checksum_append(uint8_t *table_end, uint32_t checksum) |
| { |
| checksum = htole32(checksum); |
| memcpy(table_end, &checksum, sizeof(checksum)); |
| |
| return table_end + sizeof(checksum); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| size_t pldm_bios_table_pad_checksum_size(size_t size_without_pad) |
| { |
| size_t size = pad_size_get(size_without_pad) + |
| sizeof(uint32_t) /*sizeof(checksum)*/; |
| return size; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_append_pad_checksum_check(void *table, size_t capacity, |
| size_t *size) |
| { |
| if (!table || !size) { |
| return PLDM_ERROR_INVALID_DATA; |
| } |
| |
| size_t pad_checksum_size = pldm_bios_table_pad_checksum_size(*size); |
| size_t total_length = *size + pad_checksum_size; |
| if (capacity < total_length) { |
| return PLDM_ERROR_INVALID_LENGTH; |
| } |
| |
| uint8_t *table_end = (uint8_t *)table + *size; |
| size_t pad_size = pad_size_get(*size); |
| table_end = pad_append(table_end, pad_size); |
| |
| uint32_t checksum = crc32(table, *size + pad_size); |
| checksum_append(table_end, checksum); |
| *size = total_length; |
| |
| return PLDM_SUCCESS; |
| } |
| |
| struct pldm_bios_table_iter { |
| const uint8_t *table_data; |
| size_t table_len; |
| size_t current_pos; |
| ssize_t (*entry_length_handler)(const void *table_entry); |
| }; |
| |
| LIBPLDM_ABI_STABLE |
| struct pldm_bios_table_iter * |
| pldm_bios_table_iter_create(const void *table, size_t length, |
| enum pldm_bios_table_types type) |
| { |
| struct pldm_bios_table_iter *iter = malloc(sizeof(*iter)); |
| assert(iter != NULL); |
| if (!iter) { |
| return NULL; |
| } |
| iter->table_data = table; |
| iter->table_len = length; |
| iter->current_pos = 0; |
| iter->entry_length_handler = NULL; |
| switch (type) { |
| case PLDM_BIOS_STRING_TABLE: |
| iter->entry_length_handler = string_table_entry_length; |
| break; |
| case PLDM_BIOS_ATTR_TABLE: |
| iter->entry_length_handler = attr_table_entry_length; |
| break; |
| case PLDM_BIOS_ATTR_VAL_TABLE: |
| iter->entry_length_handler = attr_value_table_entry_length; |
| break; |
| } |
| |
| return iter; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| void pldm_bios_table_iter_free(struct pldm_bios_table_iter *iter) |
| { |
| free(iter); |
| } |
| |
| #define pad_and_check_max 7 |
| LIBPLDM_ABI_STABLE |
| bool pldm_bios_table_iter_is_end(const struct pldm_bios_table_iter *iter) |
| { |
| ssize_t len; |
| |
| if (iter->table_len - iter->current_pos <= pad_and_check_max) { |
| return true; |
| } |
| |
| len = iter->entry_length_handler(iter->table_data + iter->current_pos); |
| |
| return len < 0; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| void pldm_bios_table_iter_next(struct pldm_bios_table_iter *iter) |
| { |
| if (pldm_bios_table_iter_is_end(iter)) { |
| return; |
| } |
| const void *entry = iter->table_data + iter->current_pos; |
| ssize_t rc = iter->entry_length_handler(entry); |
| /* Prevent bad behaviour by acting as if we've hit the end of the iterator */ |
| if (rc < 0) { |
| return; |
| } |
| iter->current_pos += rc; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| const void *pldm_bios_table_iter_value(struct pldm_bios_table_iter *iter) |
| { |
| return iter->table_data + iter->current_pos; |
| } |
| |
| typedef bool (*equal_handler)(const void *entry, const void *key); |
| |
| static const void * |
| pldm_bios_table_entry_find_by_iter(struct pldm_bios_table_iter *iter, |
| const void *key, equal_handler equal) |
| { |
| const void *entry; |
| while (!pldm_bios_table_iter_is_end(iter)) { |
| entry = pldm_bios_table_iter_value(iter); |
| if (equal(entry, key)) { |
| return entry; |
| } |
| pldm_bios_table_iter_next(iter); |
| } |
| return NULL; |
| } |
| |
| static const void * |
| pldm_bios_table_entry_find_from_table(const void *table, size_t length, |
| enum pldm_bios_table_types type, |
| equal_handler equal, const void *key) |
| { |
| struct pldm_bios_table_iter *iter = |
| pldm_bios_table_iter_create(table, length, type); |
| const void *entry = |
| pldm_bios_table_entry_find_by_iter(iter, key, equal); |
| pldm_bios_table_iter_free(iter); |
| return entry; |
| } |
| |
| static bool string_table_handle_equal(const void *entry, const void *key) |
| { |
| const struct pldm_bios_string_table_entry *string_entry = entry; |
| uint16_t handle = *(uint16_t *)key; |
| if (pldm_bios_table_string_entry_decode_handle(string_entry) == |
| handle) { |
| return true; |
| } |
| return false; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| const struct pldm_bios_string_table_entry * |
| pldm_bios_table_string_find_by_handle(const void *table, size_t length, |
| uint16_t handle) |
| { |
| return pldm_bios_table_entry_find_from_table(table, length, |
| PLDM_BIOS_STRING_TABLE, |
| string_table_handle_equal, |
| &handle); |
| } |
| |
| struct string_equal_arg { |
| uint16_t str_length; |
| const char *str; |
| }; |
| |
| static bool string_table_string_equal(const void *entry, const void *key) |
| { |
| const struct pldm_bios_string_table_entry *string_entry = entry; |
| const struct string_equal_arg *arg = key; |
| if (arg->str_length != |
| pldm_bios_table_string_entry_decode_string_length(string_entry)) { |
| return false; |
| } |
| if (memcmp(string_entry->name, arg->str, arg->str_length) != 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| const struct pldm_bios_string_table_entry * |
| pldm_bios_table_string_find_by_string(const void *table, size_t length, |
| const char *str) |
| { |
| uint16_t str_length = strlen(str); |
| struct string_equal_arg arg = { str_length, str }; |
| return pldm_bios_table_entry_find_from_table(table, length, |
| PLDM_BIOS_STRING_TABLE, |
| string_table_string_equal, |
| &arg); |
| } |
| |
| static bool attr_table_handle_equal(const void *entry, const void *key) |
| { |
| uint16_t handle = *(uint16_t *)key; |
| return pldm_bios_table_attr_entry_decode_attribute_handle(entry) == |
| handle; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| const struct pldm_bios_attr_table_entry * |
| pldm_bios_table_attr_find_by_handle(const void *table, size_t length, |
| uint16_t handle) |
| { |
| return pldm_bios_table_entry_find_from_table(table, length, |
| PLDM_BIOS_ATTR_TABLE, |
| attr_table_handle_equal, |
| &handle); |
| } |
| |
| static bool attr_table_string_handle_equal(const void *entry, const void *key) |
| { |
| uint16_t handle = *(uint16_t *)key; |
| return pldm_bios_table_attr_entry_decode_string_handle(entry) == handle; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| const struct pldm_bios_attr_table_entry * |
| pldm_bios_table_attr_find_by_string_handle(const void *table, size_t length, |
| uint16_t handle) |
| { |
| return pldm_bios_table_entry_find_from_table( |
| table, length, PLDM_BIOS_ATTR_TABLE, |
| attr_table_string_handle_equal, &handle); |
| } |
| |
| static bool attr_value_table_handle_equal(const void *entry, const void *key) |
| { |
| uint16_t handle = *(uint16_t *)key; |
| return pldm_bios_table_attr_value_entry_decode_handle(entry) == handle; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| const struct pldm_bios_attr_val_table_entry * |
| pldm_bios_table_attr_value_find_by_handle(const void *table, size_t length, |
| uint16_t handle) |
| { |
| return pldm_bios_table_entry_find_from_table( |
| table, length, PLDM_BIOS_ATTR_VAL_TABLE, |
| attr_value_table_handle_equal, &handle); |
| } |
| |
| LIBPLDM_ABI_STABLE |
| int pldm_bios_table_attr_value_copy_and_update( |
| const void *src_table, size_t src_length, void *dest_table, |
| size_t *dest_length, const void *entry, size_t entry_length) |
| { |
| struct pldm_bios_table_iter *iter = pldm_bios_table_iter_create( |
| src_table, src_length, PLDM_BIOS_ATTR_VAL_TABLE); |
| |
| int rc = PLDM_SUCCESS; |
| const struct pldm_bios_attr_val_table_entry *tmp; |
| const struct pldm_bios_attr_val_table_entry *to_update = entry; |
| size_t buffer_length = *dest_length; |
| size_t copied_length = 0; |
| size_t length = 0; |
| while (!pldm_bios_table_iter_is_end(iter)) { |
| tmp = pldm_bios_table_iter_attr_value_entry_value(iter); |
| length = attr_value_table_entry_length(tmp); |
| |
| /* we need the tmp's entry_length here, iter_next will calculate |
| * it too, use current_pos directly to avoid calculating it |
| * twice */ |
| iter->current_pos += length; |
| if (tmp->attr_handle == to_update->attr_handle) { |
| if (tmp->attr_type != to_update->attr_type) { |
| rc = PLDM_ERROR_INVALID_DATA; |
| goto out; |
| } |
| length = entry_length; |
| tmp = entry; |
| } |
| if (copied_length + length > buffer_length) { |
| rc = PLDM_ERROR_INVALID_LENGTH; |
| goto out; |
| } |
| memcpy((uint8_t *)dest_table + copied_length, tmp, length); |
| copied_length += length; |
| } |
| |
| size_t pad_checksum_size = |
| pldm_bios_table_pad_checksum_size(copied_length); |
| if ((pad_checksum_size + copied_length) > buffer_length) { |
| rc = PLDM_ERROR_INVALID_LENGTH; |
| goto out; |
| } |
| |
| rc = pldm_bios_table_append_pad_checksum_check( |
| dest_table, buffer_length, &copied_length); |
| if (rc == PLDM_SUCCESS) { |
| *dest_length = copied_length; |
| } |
| out: |
| pldm_bios_table_iter_free(iter); |
| return rc; |
| } |
| |
| LIBPLDM_ABI_STABLE |
| bool pldm_bios_table_checksum(const uint8_t *table, size_t size) |
| { |
| if (table == NULL) { |
| return false; |
| } |
| |
| // 12: BIOSStringHandle(uint16) + BIOSStringLength(uint16) + |
| // Variable(4) + checksum(uint32) |
| if (size < 12) { |
| return false; |
| } |
| |
| uint32_t src_crc = le32toh(*(uint32_t *)(table + size - 4)); |
| uint32_t dst_crc = crc32(table, size - 4); |
| |
| return src_crc == dst_crc; |
| } |