/* SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include #include #include #include #define LEGACY_BYTES_PER_GEO_OFFSET 6 #define LEGACY_BYTES_PER_SAR_LIMIT 10 #define LEGACY_NUM_SAR_LIMITS 4 #define LEGACY_SAR_BIN_SIZE 81 #define LEGACY_SAR_WGDS_BIN_SIZE 119 #define LEGACY_SAR_NUM_WGDS_GROUPS 3 static uint8_t *wifi_hextostr(const char *sar_str, size_t str_len, size_t *sar_bin_len, bool legacy_hex_format) { uint8_t *sar_bin = NULL; size_t bin_len; if (!legacy_hex_format) { sar_bin = malloc(str_len); if (!sar_bin) { printk(BIOS_ERR, "Failed to allocate space for SAR binary!\n"); return NULL; } memcpy(sar_bin, sar_str, str_len); *sar_bin_len = str_len; } else { bin_len = ((str_len - 1) / 2); sar_bin = malloc(bin_len); if (!sar_bin) { printk(BIOS_ERR, "Failed to allocate space for SAR binary!\n"); return NULL; } if (hexstrtobin(sar_str, (uint8_t *)sar_bin, bin_len) != bin_len) { printk(BIOS_ERR, "sar_limits contains non-hex value!\n"); free(sar_bin); return NULL; } *sar_bin_len = bin_len; } return sar_bin; } static int sar_table_size(const struct sar_profile *sar) { if (sar == NULL) return 0; return (sizeof(struct sar_profile) + ((1 + sar->dsar_set_count) * sar->chains_count * sar->subbands_count)); } static int wgds_table_size(const struct geo_profile *geo) { if (geo == NULL) return 0; return sizeof(struct geo_profile) + (geo->chains_count * geo->bands_count); } static int gain_table_size(const struct gain_profile *gain) { if (gain == NULL) return 0; return sizeof(struct gain_profile) + (gain->chains_count * gain->bands_count); } static int sar_avg_table_size(const struct avg_profile *sar_avg) { if (sar_avg == NULL) return 0; return sizeof(struct avg_profile); } static int dsm_table_size(const struct dsm_profile *dsm) { if (dsm == NULL) return 0; return sizeof(struct dsm_profile); } static int bsar_table_size(const struct bsar_profile *bsar) { if (bsar == NULL) return 0; return sizeof(struct bsar_profile); } static int wbem_table_size(const struct wbem_profile *wbem) { if (wbem == NULL) return 0; return sizeof(struct wbem_profile); } static bool valid_legacy_length(size_t bin_len) { if (bin_len == LEGACY_SAR_WGDS_BIN_SIZE) return true; if (bin_len == LEGACY_SAR_BIN_SIZE && !CONFIG(GEO_SAR_ENABLE)) return true; return false; } static int sar_header_size(void) { return (MAX_PROFILE_COUNT * sizeof(uint16_t)) + sizeof(struct sar_header); } static int fill_wifi_sar_limits(union wifi_sar_limits *sar_limits, const uint8_t *sar_bin, size_t sar_bin_size) { struct sar_header *header; size_t i = 0, expected_sar_bin_size; size_t header_size = sar_header_size(); if (sar_bin_size < header_size) { printk(BIOS_ERR, "Invalid SAR format!\n"); return -1; } header = (struct sar_header *)sar_bin; if (header->version != SAR_FILE_REVISION) { printk(BIOS_ERR, "Invalid SAR file version: %d!\n", header->version); return -1; } for (i = 0; i < MAX_PROFILE_COUNT; i++) { if (header->offsets[i] > sar_bin_size) { printk(BIOS_ERR, "Offset is outside the file size!\n"); return -1; } if (header->offsets[i]) sar_limits->profile[i] = (void *) (sar_bin + header->offsets[i]); } expected_sar_bin_size = header_size; expected_sar_bin_size += sar_table_size(sar_limits->sar); expected_sar_bin_size += wgds_table_size(sar_limits->wgds); expected_sar_bin_size += gain_table_size(sar_limits->ppag); expected_sar_bin_size += sar_avg_table_size(sar_limits->wtas); expected_sar_bin_size += dsm_table_size(sar_limits->dsm); expected_sar_bin_size += bsar_table_size(sar_limits->bsar); expected_sar_bin_size += wbem_table_size(sar_limits->wbem); if (sar_bin_size != expected_sar_bin_size) { printk(BIOS_ERR, "Invalid SAR size, expected: %zu, obtained: %zu\n", expected_sar_bin_size, sar_bin_size); return -1; } return 0; } static int fill_wifi_sar_limits_legacy(union wifi_sar_limits *sar_limits, const uint8_t *sar_bin, size_t sar_bin_size) { uint8_t *new_sar_bin; size_t size = sar_bin_size + sizeof(struct sar_profile); if (CONFIG(GEO_SAR_ENABLE)) size += sizeof(struct geo_profile); new_sar_bin = malloc(size); if (!new_sar_bin) { printk(BIOS_ERR, "Failed to allocate space for SAR binary!\n"); return -1; } sar_limits->sar = (struct sar_profile *) new_sar_bin; sar_limits->sar->revision = 0; sar_limits->sar->dsar_set_count = CONFIG_DSAR_SET_NUM; sar_limits->sar->chains_count = SAR_REV0_CHAINS_COUNT; sar_limits->sar->subbands_count = SAR_REV0_SUBBANDS_COUNT; memcpy(&sar_limits->sar->sar_table, sar_bin, LEGACY_BYTES_PER_SAR_LIMIT * LEGACY_NUM_SAR_LIMITS); if (!CONFIG(GEO_SAR_ENABLE)) return 0; sar_limits->wgds = (struct geo_profile *)(new_sar_bin + sar_table_size(sar_limits->sar)); sar_limits->wgds->revision = 0; sar_limits->wgds->chains_count = LEGACY_SAR_NUM_WGDS_GROUPS; sar_limits->wgds->bands_count = LEGACY_BYTES_PER_GEO_OFFSET; memcpy(&sar_limits->wgds->wgds_table, sar_bin + LEGACY_BYTES_PER_SAR_LIMIT * LEGACY_NUM_SAR_LIMITS + REVISION_SIZE, LEGACY_BYTES_PER_GEO_OFFSET * LEGACY_SAR_NUM_WGDS_GROUPS); return 0; } /* * Retrieve WiFi SAR limits data from CBFS and decode it * Legacy WiFi SAR data is expected in the format: [][WGDS] * * [] = NUM_SAR_LIMITS * BYTES_PER_SAR_LIMIT bytes. * [WGDS]=[WGDS_REVISION][WGDS_DATA] * * Current SAR configuration data is expected in the format: * "$SAR" Marker * Version * Offset count * Offsets * [SAR_REVISION,DSAR_SET_COUNT,CHAINS_COUNT,SUBBANDS_COUNT [EWRD]] * [WGDS_REVISION,CHAINS_COUNT,SUBBANDS_COUNT] * [PPAG_REVISION,MODE,CHAINS_COUNT,SUBBANDS_COUNT] * [WTAS_REVISION, WTAS_DATA] * [DSM_RETURN_VALUES] * [BSAR_REVISION,IPML,LB,BR,EDR2,EDR3,LE,LE2,LE_LR] * [WBEM_REVISION, WBEM_DATA] * * The configuration data will always have the revision added in the file for each of the * block, based on the revision number and validity, size of the specific block will be * calculated. * * [WGDS_DATA] = [GROUP#0][GROUP#1][GROUP#2] * * [GROUP#] = * Supported by Revision 0, 1 and 2 * [2.4Ghz - Max Allowed][2.4Ghz - Chain A Offset][2.4Ghz - Chain B Offset] * [5Ghz - Max Allowed][5Ghz - Chain A Offset][5Ghz - Chain B Offset] * Supported by Revision 1 and 2 * [6Ghz - Max Allowed][6Ghz - Chain A Offset][6Ghz - Chain B Offset] * * [GROUP#0] is for FCC * [GROUP#1] is for Europe/Japan * [GROUP#2] is for ROW * * [PPAG_DATA] = [ANT_gain Table Chain A] [ANT_gain Table Chain A] * * [ANT_gain Table] = * Supported by Revision 0, 1 and 2 * [Antenna gain used for 2400MHz frequency] * [Antenna gain used for 5150-5350MHz frequency] * [Antenna gain used for 5350-5470MHz frequency] * [Antenna gain used for 5470-5725MHz frequency] * [Antenna gain used for 5725-5945MHz frequency] * Supported by Revision 1 and 2 * [Antenna gain used for 5945-6165MHz frequency] * [Antenna gain used for 6165-6405MHz frequency] * [Antenna gain used for 6405-6525MHz frequency] * [Antenna gain used for 6525-6705MHz frequency] * [Antenna gain used for 6705-6865MHz frequency] * [Antenna gain used for 6865-7105MHz frequency] * * [WTAS_DATA] = * [Enable/disable the TAS feature] * [Number of blocked countries that are not approved by the OEM to support this feature] * [deny_list_entry_<1-16>: ISO country code to block] * [WBEM_DATA] = * [Enable or disable 320MHZ Bandwidth for Japan, SouthKorea] */ int get_wifi_sar_limits(union wifi_sar_limits *sar_limits) { const char *filename; size_t sar_bin_len, sar_str_len; uint8_t *sar_bin; char *sar_str; int ret = -1; bool legacy_hex_format = false; filename = get_wifi_sar_cbfs_filename(); if (filename == NULL) { printk(BIOS_ERR, "Filename missing for CBFS SAR file!\n"); return ret; } sar_str = cbfs_map(filename, &sar_str_len); if (!sar_str) { printk(BIOS_ERR, "Failed to get the %s file size!\n", filename); return ret; } if (strncmp(sar_str, SAR_STR_PREFIX, SAR_STR_PREFIX_SIZE) == 0) { legacy_hex_format = false; } else if (valid_legacy_length(sar_str_len)) { legacy_hex_format = true; } else { printk(BIOS_ERR, "Invalid SAR format!\n"); goto error; } sar_bin = wifi_hextostr(sar_str, sar_str_len, &sar_bin_len, legacy_hex_format); if (sar_bin == NULL) { printk(BIOS_ERR, "Failed to parse SAR file %s\n", filename); goto error; } memset(sar_limits, 0, sizeof(*sar_limits)); if (legacy_hex_format) { ret = fill_wifi_sar_limits_legacy(sar_limits, sar_bin, sar_bin_len); free(sar_bin); } else { ret = fill_wifi_sar_limits(sar_limits, sar_bin, sar_bin_len); if (ret < 0) free(sar_bin); } error: cbfs_unmap(sar_str); return ret; } __weak const char *get_wifi_sar_cbfs_filename(void) { return WIFI_SAR_CBFS_DEFAULT_FILENAME; } char *get_wifi_sar_fw_config_filename(const struct fw_config_field *field) { uint64_t sar_id = fw_config_get_field(field); if (sar_id == UNDEFINED_FW_CONFIG) { printk(BIOS_WARNING, "fw_config unprovisioned, set sar filename to NULL\n"); return NULL; } static char filename[20]; printk(BIOS_INFO, "Use wifi_sar_%lld.hex.\n", sar_id); if (snprintf(filename, sizeof(filename), "wifi_sar_%lld.hex", sar_id) < 0) { printk(BIOS_ERR, "Error occurred with snprintf, set sar filename to NULL\n"); return NULL; } return filename; }