/* SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include /* * This file provides functions looking in the VPD for WiFi calibration data, * and if found, copying the calibration blobs into CBMEM. * * Per interface calibration data is stored in the VPD in opaque blobs. The * keys of the blobs follow one of two possible patterns: * "wifi_base64_calibration" or "wifi_calibration", where is the * interface number. * * This function accommodates up to 4 interfaces. All calibration blobs found * in the VPD are packed into a single CBMEM entry as describe by the * structures below: */ /* This structure describes a single calibration data blob */ struct calibration_blob { uint32_t blob_size; /* Total size. rounded up to fall on a 4 byte boundary. */ uint32_t key_size; /* Size of the name of this entry, \0 included. */ uint32_t value_size; /* Size of the value of this entry */ /* Zero terminated name(key) goes here, immediately followed by value */ }; /* * This is the structure of the CBMEM entry containing WiFi calibration blobs. * It starts with the total size (header size included) followed by an * arbitrary number of concatenated 4 byte aligned calibration blobs. */ struct calibration_entry { uint32_t size; struct calibration_blob entries[0]; /* A varialble size container. */ }; #define MAX_WIFI_INTERFACE_COUNT 4 /* * Structure of the cache to keep information about calibration blobs present * in the VPD, one cache entry per VPD blob. * * Maintaing the cache allows to scan the VPD once, determine the CBMEM entry * memory requirements, then allocate as much room as necessary and fill it * up. */ struct vpd_blob_cache_t { /* The longest name template must fit with an extra character. */ char key_name[40]; void *value_pointer; unsigned blob_size; unsigned key_size; unsigned value_size; }; static const char * const templates[] = { "wifi_base64_calibrationX", "wifi_calibrationX" }; /* * Scan the VPD for WiFi calibration data, checking for all possible key names * and caching discovered blobs. * * Return the sum of sizes of all blobs, as stored in CBMEM. */ static size_t fill_up_entries_cache(struct vpd_blob_cache_t *cache, size_t max_entries, size_t *filled_entries) { int i; int cbmem_entry_size = 0; size_t used_entries = 0; for (i = 0; (i < ARRAY_SIZE(templates)) && (used_entries < max_entries); i++) { int j; const int index_location = strlen(templates[i]) - 1; const int key_length = index_location + 2; if (key_length > sizeof(cache->key_name)) continue; for (j = 0; j < MAX_WIFI_INTERFACE_COUNT; j++) { const void *payload; void *decoded_payload; int payload_size; size_t decoded_size; strcpy(cache->key_name, templates[i]); cache->key_name[index_location] = j + '0'; payload = vpd_find(cache->key_name, &payload_size, VPD_RO_THEN_RW); if (!payload) continue; decoded_size = B64_DECODED_SIZE(payload_size); decoded_payload = malloc(decoded_size); if (!decoded_payload) { printk(BIOS_ERR, "%s: failed allocating %zd bytes\n", __func__, decoded_size); continue; } decoded_size = b64_decode(payload, payload_size, decoded_payload); if (!decoded_size) { free(decoded_payload); printk(BIOS_ERR, "%s: failed decoding %s\n", __func__, cache->key_name); continue; } cache->value_pointer = decoded_payload; cache->key_size = key_length; cache->value_size = decoded_size; cache->blob_size = ALIGN(sizeof(struct calibration_blob) + cache->key_size + cache->value_size, 4); cbmem_entry_size += cache->blob_size; used_entries++; if (used_entries == max_entries) break; cache++; } } *filled_entries = used_entries; return cbmem_entry_size; } void cbmem_add_vpd_calibration_data(void) { size_t cbmem_entry_size, filled_entries; struct calibration_entry *cbmem_entry; struct calibration_blob *cal_blob; int i; /* * Allocate one more cache entry than max required, to make sure that * the last entry can be identified by the key size of zero. */ struct vpd_blob_cache_t vpd_blob_cache[ARRAY_SIZE(templates) * MAX_WIFI_INTERFACE_COUNT]; cbmem_entry_size = fill_up_entries_cache(vpd_blob_cache, ARRAY_SIZE(vpd_blob_cache), &filled_entries); if (!cbmem_entry_size) return; /* No calibration data found in the VPD. */ cbmem_entry_size += sizeof(struct calibration_entry); cbmem_entry = cbmem_add(CBMEM_ID_WIFI_CALIBRATION, cbmem_entry_size); if (!cbmem_entry) { printk(BIOS_ERR, "%s: no room in cbmem to add %zd bytes\n", __func__, cbmem_entry_size); return; } cbmem_entry->size = cbmem_entry_size; /* Copy cached data into the CBMEM entry. */ cal_blob = cbmem_entry->entries; for (i = 0; i < filled_entries; i++) { /* Use this as a pointer to the current cache entry. */ struct vpd_blob_cache_t *cache = vpd_blob_cache + i; char *pointer; cal_blob->blob_size = cache->blob_size; cal_blob->key_size = cache->key_size; cal_blob->value_size = cache->value_size; /* copy the key */ pointer = (char *)(cal_blob + 1); memcpy(pointer, cache->key_name, cache->key_size); /* and the value */ pointer += cache->key_size; memcpy(pointer, cache->value_pointer, cache->value_size); free(cache->value_pointer); printk(BIOS_INFO, "%s: added %s to CBMEM\n", __func__, cache->key_name); cal_blob = (struct calibration_blob *) ((char *)cal_blob + cal_blob->blob_size); } }