162306a36Sopenharmony_ci// SPDX-License-Identifier: ISC 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2013 Broadcom Corporation 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/efi.h> 762306a36Sopenharmony_ci#include <linux/kernel.h> 862306a36Sopenharmony_ci#include <linux/slab.h> 962306a36Sopenharmony_ci#include <linux/device.h> 1062306a36Sopenharmony_ci#include <linux/firmware.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/bcm47xx_nvram.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "debug.h" 1562306a36Sopenharmony_ci#include "firmware.h" 1662306a36Sopenharmony_ci#include "core.h" 1762306a36Sopenharmony_ci#include "common.h" 1862306a36Sopenharmony_ci#include "chip.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define BRCMF_FW_MAX_NVRAM_SIZE 64000 2162306a36Sopenharmony_ci#define BRCMF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */ 2262306a36Sopenharmony_ci#define BRCMF_FW_NVRAM_PCIEDEV_LEN 10 /* pcie/1/4/ + \0 */ 2362306a36Sopenharmony_ci#define BRCMF_FW_DEFAULT_BOARDREV "boardrev=0xff" 2462306a36Sopenharmony_ci#define BRCMF_FW_MACADDR_FMT "macaddr=%pM" 2562306a36Sopenharmony_ci#define BRCMF_FW_MACADDR_LEN (7 + ETH_ALEN * 3) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cienum nvram_parser_state { 2862306a36Sopenharmony_ci IDLE, 2962306a36Sopenharmony_ci KEY, 3062306a36Sopenharmony_ci VALUE, 3162306a36Sopenharmony_ci COMMENT, 3262306a36Sopenharmony_ci END 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/** 3662306a36Sopenharmony_ci * struct nvram_parser - internal info for parser. 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * @state: current parser state. 3962306a36Sopenharmony_ci * @data: input buffer being parsed. 4062306a36Sopenharmony_ci * @nvram: output buffer with parse result. 4162306a36Sopenharmony_ci * @nvram_len: length of parse result. 4262306a36Sopenharmony_ci * @line: current line. 4362306a36Sopenharmony_ci * @column: current column in line. 4462306a36Sopenharmony_ci * @pos: byte offset in input buffer. 4562306a36Sopenharmony_ci * @entry: start position of key,value entry. 4662306a36Sopenharmony_ci * @multi_dev_v1: detect pcie multi device v1 (compressed). 4762306a36Sopenharmony_ci * @multi_dev_v2: detect pcie multi device v2. 4862306a36Sopenharmony_ci * @boardrev_found: nvram contains boardrev information. 4962306a36Sopenharmony_ci * @strip_mac: strip the MAC address. 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_cistruct nvram_parser { 5262306a36Sopenharmony_ci enum nvram_parser_state state; 5362306a36Sopenharmony_ci const u8 *data; 5462306a36Sopenharmony_ci u8 *nvram; 5562306a36Sopenharmony_ci u32 nvram_len; 5662306a36Sopenharmony_ci u32 line; 5762306a36Sopenharmony_ci u32 column; 5862306a36Sopenharmony_ci u32 pos; 5962306a36Sopenharmony_ci u32 entry; 6062306a36Sopenharmony_ci bool multi_dev_v1; 6162306a36Sopenharmony_ci bool multi_dev_v2; 6262306a36Sopenharmony_ci bool boardrev_found; 6362306a36Sopenharmony_ci bool strip_mac; 6462306a36Sopenharmony_ci}; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci/* 6762306a36Sopenharmony_ci * is_nvram_char() - check if char is a valid one for NVRAM entry 6862306a36Sopenharmony_ci * 6962306a36Sopenharmony_ci * It accepts all printable ASCII chars except for '#' which opens a comment. 7062306a36Sopenharmony_ci * Please note that ' ' (space) while accepted is not a valid key name char. 7162306a36Sopenharmony_ci */ 7262306a36Sopenharmony_cistatic bool is_nvram_char(char c) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci /* comment marker excluded */ 7562306a36Sopenharmony_ci if (c == '#') 7662306a36Sopenharmony_ci return false; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci /* key and value may have any other readable character */ 7962306a36Sopenharmony_ci return (c >= 0x20 && c < 0x7f); 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic bool is_whitespace(char c) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci return (c == ' ' || c == '\r' || c == '\n' || c == '\t'); 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic enum nvram_parser_state brcmf_nvram_handle_idle(struct nvram_parser *nvp) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci char c; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci c = nvp->data[nvp->pos]; 9262306a36Sopenharmony_ci if (c == '\n') 9362306a36Sopenharmony_ci return COMMENT; 9462306a36Sopenharmony_ci if (is_whitespace(c) || c == '\0') 9562306a36Sopenharmony_ci goto proceed; 9662306a36Sopenharmony_ci if (c == '#') 9762306a36Sopenharmony_ci return COMMENT; 9862306a36Sopenharmony_ci if (is_nvram_char(c)) { 9962306a36Sopenharmony_ci nvp->entry = nvp->pos; 10062306a36Sopenharmony_ci return KEY; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci brcmf_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n", 10362306a36Sopenharmony_ci nvp->line, nvp->column); 10462306a36Sopenharmony_ciproceed: 10562306a36Sopenharmony_ci nvp->column++; 10662306a36Sopenharmony_ci nvp->pos++; 10762306a36Sopenharmony_ci return IDLE; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci enum nvram_parser_state st = nvp->state; 11362306a36Sopenharmony_ci char c; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci c = nvp->data[nvp->pos]; 11662306a36Sopenharmony_ci if (c == '=') { 11762306a36Sopenharmony_ci /* ignore RAW1 by treating as comment */ 11862306a36Sopenharmony_ci if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0) 11962306a36Sopenharmony_ci st = COMMENT; 12062306a36Sopenharmony_ci else 12162306a36Sopenharmony_ci st = VALUE; 12262306a36Sopenharmony_ci if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0) 12362306a36Sopenharmony_ci nvp->multi_dev_v1 = true; 12462306a36Sopenharmony_ci if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0) 12562306a36Sopenharmony_ci nvp->multi_dev_v2 = true; 12662306a36Sopenharmony_ci if (strncmp(&nvp->data[nvp->entry], "boardrev", 8) == 0) 12762306a36Sopenharmony_ci nvp->boardrev_found = true; 12862306a36Sopenharmony_ci /* strip macaddr if platform MAC overrides */ 12962306a36Sopenharmony_ci if (nvp->strip_mac && 13062306a36Sopenharmony_ci strncmp(&nvp->data[nvp->entry], "macaddr", 7) == 0) 13162306a36Sopenharmony_ci st = COMMENT; 13262306a36Sopenharmony_ci } else if (!is_nvram_char(c) || c == ' ') { 13362306a36Sopenharmony_ci brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n", 13462306a36Sopenharmony_ci nvp->line, nvp->column); 13562306a36Sopenharmony_ci return COMMENT; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci nvp->column++; 13962306a36Sopenharmony_ci nvp->pos++; 14062306a36Sopenharmony_ci return st; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic enum nvram_parser_state 14462306a36Sopenharmony_cibrcmf_nvram_handle_value(struct nvram_parser *nvp) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci char c; 14762306a36Sopenharmony_ci char *skv; 14862306a36Sopenharmony_ci char *ekv; 14962306a36Sopenharmony_ci u32 cplen; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci c = nvp->data[nvp->pos]; 15262306a36Sopenharmony_ci if (!is_nvram_char(c)) { 15362306a36Sopenharmony_ci /* key,value pair complete */ 15462306a36Sopenharmony_ci ekv = (u8 *)&nvp->data[nvp->pos]; 15562306a36Sopenharmony_ci skv = (u8 *)&nvp->data[nvp->entry]; 15662306a36Sopenharmony_ci cplen = ekv - skv; 15762306a36Sopenharmony_ci if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE) 15862306a36Sopenharmony_ci return END; 15962306a36Sopenharmony_ci /* copy to output buffer */ 16062306a36Sopenharmony_ci memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen); 16162306a36Sopenharmony_ci nvp->nvram_len += cplen; 16262306a36Sopenharmony_ci nvp->nvram[nvp->nvram_len] = '\0'; 16362306a36Sopenharmony_ci nvp->nvram_len++; 16462306a36Sopenharmony_ci return IDLE; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci nvp->pos++; 16762306a36Sopenharmony_ci nvp->column++; 16862306a36Sopenharmony_ci return VALUE; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic enum nvram_parser_state 17262306a36Sopenharmony_cibrcmf_nvram_handle_comment(struct nvram_parser *nvp) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci char *eoc, *sol; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci sol = (char *)&nvp->data[nvp->pos]; 17762306a36Sopenharmony_ci eoc = strchr(sol, '\n'); 17862306a36Sopenharmony_ci if (!eoc) { 17962306a36Sopenharmony_ci eoc = strchr(sol, '\0'); 18062306a36Sopenharmony_ci if (!eoc) 18162306a36Sopenharmony_ci return END; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci /* eat all moving to next line */ 18562306a36Sopenharmony_ci nvp->line++; 18662306a36Sopenharmony_ci nvp->column = 1; 18762306a36Sopenharmony_ci nvp->pos += (eoc - sol) + 1; 18862306a36Sopenharmony_ci return IDLE; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic enum nvram_parser_state brcmf_nvram_handle_end(struct nvram_parser *nvp) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci /* final state */ 19462306a36Sopenharmony_ci return END; 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic enum nvram_parser_state 19862306a36Sopenharmony_ci(*nv_parser_states[])(struct nvram_parser *nvp) = { 19962306a36Sopenharmony_ci brcmf_nvram_handle_idle, 20062306a36Sopenharmony_ci brcmf_nvram_handle_key, 20162306a36Sopenharmony_ci brcmf_nvram_handle_value, 20262306a36Sopenharmony_ci brcmf_nvram_handle_comment, 20362306a36Sopenharmony_ci brcmf_nvram_handle_end 20462306a36Sopenharmony_ci}; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic int brcmf_init_nvram_parser(struct nvram_parser *nvp, 20762306a36Sopenharmony_ci const u8 *data, size_t data_len) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci size_t size; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci memset(nvp, 0, sizeof(*nvp)); 21262306a36Sopenharmony_ci nvp->data = data; 21362306a36Sopenharmony_ci /* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */ 21462306a36Sopenharmony_ci if (data_len > BRCMF_FW_MAX_NVRAM_SIZE) 21562306a36Sopenharmony_ci size = BRCMF_FW_MAX_NVRAM_SIZE; 21662306a36Sopenharmony_ci else 21762306a36Sopenharmony_ci size = data_len; 21862306a36Sopenharmony_ci /* Add space for properties we may add */ 21962306a36Sopenharmony_ci size += strlen(BRCMF_FW_DEFAULT_BOARDREV) + 1; 22062306a36Sopenharmony_ci size += BRCMF_FW_MACADDR_LEN + 1; 22162306a36Sopenharmony_ci /* Alloc for extra 0 byte + roundup by 4 + length field */ 22262306a36Sopenharmony_ci size += 1 + 3 + sizeof(u32); 22362306a36Sopenharmony_ci nvp->nvram = kzalloc(size, GFP_KERNEL); 22462306a36Sopenharmony_ci if (!nvp->nvram) 22562306a36Sopenharmony_ci return -ENOMEM; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci nvp->line = 1; 22862306a36Sopenharmony_ci nvp->column = 1; 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci/* brcmf_fw_strip_multi_v1 :Some nvram files contain settings for multiple 23362306a36Sopenharmony_ci * devices. Strip it down for one device, use domain_nr/bus_nr to determine 23462306a36Sopenharmony_ci * which data is to be returned. v1 is the version where nvram is stored 23562306a36Sopenharmony_ci * compressed and "devpath" maps to index for valid entries. 23662306a36Sopenharmony_ci */ 23762306a36Sopenharmony_cistatic void brcmf_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr, 23862306a36Sopenharmony_ci u16 bus_nr) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci /* Device path with a leading '=' key-value separator */ 24162306a36Sopenharmony_ci char pci_path[] = "=pci/?/?"; 24262306a36Sopenharmony_ci size_t pci_len; 24362306a36Sopenharmony_ci char pcie_path[] = "=pcie/?/?"; 24462306a36Sopenharmony_ci size_t pcie_len; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci u32 i, j; 24762306a36Sopenharmony_ci bool found; 24862306a36Sopenharmony_ci u8 *nvram; 24962306a36Sopenharmony_ci u8 id; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL); 25262306a36Sopenharmony_ci if (!nvram) 25362306a36Sopenharmony_ci goto fail; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* min length: devpath0=pcie/1/4/ + 0:x=y */ 25662306a36Sopenharmony_ci if (nvp->nvram_len < BRCMF_FW_NVRAM_DEVPATH_LEN + 6) 25762306a36Sopenharmony_ci goto fail; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* First search for the devpathX and see if it is the configuration 26062306a36Sopenharmony_ci * for domain_nr/bus_nr. Search complete nvp 26162306a36Sopenharmony_ci */ 26262306a36Sopenharmony_ci snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr, 26362306a36Sopenharmony_ci bus_nr); 26462306a36Sopenharmony_ci pci_len = strlen(pci_path); 26562306a36Sopenharmony_ci snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr, 26662306a36Sopenharmony_ci bus_nr); 26762306a36Sopenharmony_ci pcie_len = strlen(pcie_path); 26862306a36Sopenharmony_ci found = false; 26962306a36Sopenharmony_ci i = 0; 27062306a36Sopenharmony_ci while (i < nvp->nvram_len - BRCMF_FW_NVRAM_DEVPATH_LEN) { 27162306a36Sopenharmony_ci /* Format: devpathX=pcie/Y/Z/ 27262306a36Sopenharmony_ci * Y = domain_nr, Z = bus_nr, X = virtual ID 27362306a36Sopenharmony_ci */ 27462306a36Sopenharmony_ci if (strncmp(&nvp->nvram[i], "devpath", 7) == 0 && 27562306a36Sopenharmony_ci (!strncmp(&nvp->nvram[i + 8], pci_path, pci_len) || 27662306a36Sopenharmony_ci !strncmp(&nvp->nvram[i + 8], pcie_path, pcie_len))) { 27762306a36Sopenharmony_ci id = nvp->nvram[i + 7] - '0'; 27862306a36Sopenharmony_ci found = true; 27962306a36Sopenharmony_ci break; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci while (nvp->nvram[i] != 0) 28262306a36Sopenharmony_ci i++; 28362306a36Sopenharmony_ci i++; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci if (!found) 28662306a36Sopenharmony_ci goto fail; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci /* Now copy all valid entries, release old nvram and assign new one */ 28962306a36Sopenharmony_ci i = 0; 29062306a36Sopenharmony_ci j = 0; 29162306a36Sopenharmony_ci while (i < nvp->nvram_len) { 29262306a36Sopenharmony_ci if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) { 29362306a36Sopenharmony_ci i += 2; 29462306a36Sopenharmony_ci if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0) 29562306a36Sopenharmony_ci nvp->boardrev_found = true; 29662306a36Sopenharmony_ci while (nvp->nvram[i] != 0) { 29762306a36Sopenharmony_ci nvram[j] = nvp->nvram[i]; 29862306a36Sopenharmony_ci i++; 29962306a36Sopenharmony_ci j++; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci nvram[j] = 0; 30262306a36Sopenharmony_ci j++; 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci while (nvp->nvram[i] != 0) 30562306a36Sopenharmony_ci i++; 30662306a36Sopenharmony_ci i++; 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci kfree(nvp->nvram); 30962306a36Sopenharmony_ci nvp->nvram = nvram; 31062306a36Sopenharmony_ci nvp->nvram_len = j; 31162306a36Sopenharmony_ci return; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_cifail: 31462306a36Sopenharmony_ci kfree(nvram); 31562306a36Sopenharmony_ci nvp->nvram_len = 0; 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci/* brcmf_fw_strip_multi_v2 :Some nvram files contain settings for multiple 31962306a36Sopenharmony_ci * devices. Strip it down for one device, use domain_nr/bus_nr to determine 32062306a36Sopenharmony_ci * which data is to be returned. v2 is the version where nvram is stored 32162306a36Sopenharmony_ci * uncompressed, all relevant valid entries are identified by 32262306a36Sopenharmony_ci * pcie/domain_nr/bus_nr: 32362306a36Sopenharmony_ci */ 32462306a36Sopenharmony_cistatic void brcmf_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr, 32562306a36Sopenharmony_ci u16 bus_nr) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci char prefix[BRCMF_FW_NVRAM_PCIEDEV_LEN]; 32862306a36Sopenharmony_ci size_t len; 32962306a36Sopenharmony_ci u32 i, j; 33062306a36Sopenharmony_ci u8 *nvram; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL); 33362306a36Sopenharmony_ci if (!nvram) { 33462306a36Sopenharmony_ci nvp->nvram_len = 0; 33562306a36Sopenharmony_ci return; 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci /* Copy all valid entries, release old nvram and assign new one. 33962306a36Sopenharmony_ci * Valid entries are of type pcie/X/Y/ where X = domain_nr and 34062306a36Sopenharmony_ci * Y = bus_nr. 34162306a36Sopenharmony_ci */ 34262306a36Sopenharmony_ci snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr); 34362306a36Sopenharmony_ci len = strlen(prefix); 34462306a36Sopenharmony_ci i = 0; 34562306a36Sopenharmony_ci j = 0; 34662306a36Sopenharmony_ci while (i < nvp->nvram_len - len) { 34762306a36Sopenharmony_ci if (strncmp(&nvp->nvram[i], prefix, len) == 0) { 34862306a36Sopenharmony_ci i += len; 34962306a36Sopenharmony_ci if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0) 35062306a36Sopenharmony_ci nvp->boardrev_found = true; 35162306a36Sopenharmony_ci while (nvp->nvram[i] != 0) { 35262306a36Sopenharmony_ci nvram[j] = nvp->nvram[i]; 35362306a36Sopenharmony_ci i++; 35462306a36Sopenharmony_ci j++; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci nvram[j] = 0; 35762306a36Sopenharmony_ci j++; 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci while (nvp->nvram[i] != 0) 36062306a36Sopenharmony_ci i++; 36162306a36Sopenharmony_ci i++; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci kfree(nvp->nvram); 36462306a36Sopenharmony_ci nvp->nvram = nvram; 36562306a36Sopenharmony_ci nvp->nvram_len = j; 36662306a36Sopenharmony_ci} 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_cistatic void brcmf_fw_add_defaults(struct nvram_parser *nvp) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci if (nvp->boardrev_found) 37162306a36Sopenharmony_ci return; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci memcpy(&nvp->nvram[nvp->nvram_len], &BRCMF_FW_DEFAULT_BOARDREV, 37462306a36Sopenharmony_ci strlen(BRCMF_FW_DEFAULT_BOARDREV)); 37562306a36Sopenharmony_ci nvp->nvram_len += strlen(BRCMF_FW_DEFAULT_BOARDREV); 37662306a36Sopenharmony_ci nvp->nvram[nvp->nvram_len] = '\0'; 37762306a36Sopenharmony_ci nvp->nvram_len++; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic void brcmf_fw_add_macaddr(struct nvram_parser *nvp, u8 *mac) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci int len; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci len = scnprintf(&nvp->nvram[nvp->nvram_len], BRCMF_FW_MACADDR_LEN + 1, 38562306a36Sopenharmony_ci BRCMF_FW_MACADDR_FMT, mac); 38662306a36Sopenharmony_ci WARN_ON(len != BRCMF_FW_MACADDR_LEN); 38762306a36Sopenharmony_ci nvp->nvram_len += len + 1; 38862306a36Sopenharmony_ci} 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci/* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil 39162306a36Sopenharmony_ci * and ending in a NUL. Removes carriage returns, empty lines, comment lines, 39262306a36Sopenharmony_ci * and converts newlines to NULs. Shortens buffer as needed and pads with NULs. 39362306a36Sopenharmony_ci * End of buffer is completed with token identifying length of buffer. 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_cistatic void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len, 39662306a36Sopenharmony_ci u32 *new_length, u16 domain_nr, u16 bus_nr, 39762306a36Sopenharmony_ci struct device *dev) 39862306a36Sopenharmony_ci{ 39962306a36Sopenharmony_ci struct nvram_parser nvp; 40062306a36Sopenharmony_ci u32 pad; 40162306a36Sopenharmony_ci u32 token; 40262306a36Sopenharmony_ci __le32 token_le; 40362306a36Sopenharmony_ci u8 mac[ETH_ALEN]; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0) 40662306a36Sopenharmony_ci return NULL; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci if (eth_platform_get_mac_address(dev, mac) == 0) 40962306a36Sopenharmony_ci nvp.strip_mac = true; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci while (nvp.pos < data_len) { 41262306a36Sopenharmony_ci nvp.state = nv_parser_states[nvp.state](&nvp); 41362306a36Sopenharmony_ci if (nvp.state == END) 41462306a36Sopenharmony_ci break; 41562306a36Sopenharmony_ci } 41662306a36Sopenharmony_ci if (nvp.multi_dev_v1) { 41762306a36Sopenharmony_ci nvp.boardrev_found = false; 41862306a36Sopenharmony_ci brcmf_fw_strip_multi_v1(&nvp, domain_nr, bus_nr); 41962306a36Sopenharmony_ci } else if (nvp.multi_dev_v2) { 42062306a36Sopenharmony_ci nvp.boardrev_found = false; 42162306a36Sopenharmony_ci brcmf_fw_strip_multi_v2(&nvp, domain_nr, bus_nr); 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci if (nvp.nvram_len == 0) { 42562306a36Sopenharmony_ci kfree(nvp.nvram); 42662306a36Sopenharmony_ci return NULL; 42762306a36Sopenharmony_ci } 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci brcmf_fw_add_defaults(&nvp); 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci if (nvp.strip_mac) 43262306a36Sopenharmony_ci brcmf_fw_add_macaddr(&nvp, mac); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci pad = nvp.nvram_len; 43562306a36Sopenharmony_ci *new_length = roundup(nvp.nvram_len + 1, 4); 43662306a36Sopenharmony_ci while (pad != *new_length) { 43762306a36Sopenharmony_ci nvp.nvram[pad] = 0; 43862306a36Sopenharmony_ci pad++; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci token = *new_length / 4; 44262306a36Sopenharmony_ci token = (~token << 16) | (token & 0x0000FFFF); 44362306a36Sopenharmony_ci token_le = cpu_to_le32(token); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le)); 44662306a36Sopenharmony_ci *new_length += sizeof(token_le); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci return nvp.nvram; 44962306a36Sopenharmony_ci} 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_civoid brcmf_fw_nvram_free(void *nvram) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci kfree(nvram); 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistruct brcmf_fw { 45762306a36Sopenharmony_ci struct device *dev; 45862306a36Sopenharmony_ci struct brcmf_fw_request *req; 45962306a36Sopenharmony_ci u32 curpos; 46062306a36Sopenharmony_ci unsigned int board_index; 46162306a36Sopenharmony_ci void (*done)(struct device *dev, int err, struct brcmf_fw_request *req); 46262306a36Sopenharmony_ci}; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci#ifdef CONFIG_EFI 46562306a36Sopenharmony_ci/* In some cases the EFI-var stored nvram contains "ccode=ALL" or "ccode=XV" 46662306a36Sopenharmony_ci * to specify "worldwide" compatible settings, but these 2 ccode-s do not work 46762306a36Sopenharmony_ci * properly. "ccode=ALL" causes channels 12 and 13 to not be available, 46862306a36Sopenharmony_ci * "ccode=XV" causes all 5GHz channels to not be available. So we replace both 46962306a36Sopenharmony_ci * with "ccode=X2" which allows channels 12+13 and 5Ghz channels in 47062306a36Sopenharmony_ci * no-Initiate-Radiation mode. This means that we will never send on these 47162306a36Sopenharmony_ci * channels without first having received valid wifi traffic on the channel. 47262306a36Sopenharmony_ci */ 47362306a36Sopenharmony_cistatic void brcmf_fw_fix_efi_nvram_ccode(char *data, unsigned long data_len) 47462306a36Sopenharmony_ci{ 47562306a36Sopenharmony_ci char *ccode; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci ccode = strnstr((char *)data, "ccode=ALL", data_len); 47862306a36Sopenharmony_ci if (!ccode) 47962306a36Sopenharmony_ci ccode = strnstr((char *)data, "ccode=XV\r", data_len); 48062306a36Sopenharmony_ci if (!ccode) 48162306a36Sopenharmony_ci return; 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci ccode[6] = 'X'; 48462306a36Sopenharmony_ci ccode[7] = '2'; 48562306a36Sopenharmony_ci ccode[8] = '\r'; 48662306a36Sopenharmony_ci} 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_cistatic u8 *brcmf_fw_nvram_from_efi(size_t *data_len_ret) 48962306a36Sopenharmony_ci{ 49062306a36Sopenharmony_ci efi_guid_t guid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61, 0xb5, 0x1f, 49162306a36Sopenharmony_ci 0x43, 0x26, 0x81, 0x23, 0xd1, 0x13); 49262306a36Sopenharmony_ci unsigned long data_len = 0; 49362306a36Sopenharmony_ci efi_status_t status; 49462306a36Sopenharmony_ci u8 *data = NULL; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) 49762306a36Sopenharmony_ci return NULL; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci status = efi.get_variable(L"nvram", &guid, NULL, &data_len, NULL); 50062306a36Sopenharmony_ci if (status != EFI_BUFFER_TOO_SMALL) 50162306a36Sopenharmony_ci goto fail; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci data = kmalloc(data_len, GFP_KERNEL); 50462306a36Sopenharmony_ci if (!data) 50562306a36Sopenharmony_ci goto fail; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci status = efi.get_variable(L"nvram", &guid, NULL, &data_len, data); 50862306a36Sopenharmony_ci if (status != EFI_SUCCESS) 50962306a36Sopenharmony_ci goto fail; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci brcmf_fw_fix_efi_nvram_ccode(data, data_len); 51262306a36Sopenharmony_ci brcmf_info("Using nvram EFI variable\n"); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci *data_len_ret = data_len; 51562306a36Sopenharmony_ci return data; 51662306a36Sopenharmony_cifail: 51762306a36Sopenharmony_ci kfree(data); 51862306a36Sopenharmony_ci return NULL; 51962306a36Sopenharmony_ci} 52062306a36Sopenharmony_ci#else 52162306a36Sopenharmony_cistatic inline u8 *brcmf_fw_nvram_from_efi(size_t *data_len) { return NULL; } 52262306a36Sopenharmony_ci#endif 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_cistatic void brcmf_fw_free_request(struct brcmf_fw_request *req) 52562306a36Sopenharmony_ci{ 52662306a36Sopenharmony_ci struct brcmf_fw_item *item; 52762306a36Sopenharmony_ci int i; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) { 53062306a36Sopenharmony_ci if (item->type == BRCMF_FW_TYPE_BINARY) 53162306a36Sopenharmony_ci release_firmware(item->binary); 53262306a36Sopenharmony_ci else if (item->type == BRCMF_FW_TYPE_NVRAM) 53362306a36Sopenharmony_ci brcmf_fw_nvram_free(item->nv_data.data); 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci kfree(req); 53662306a36Sopenharmony_ci} 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_cistatic int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx) 53962306a36Sopenharmony_ci{ 54062306a36Sopenharmony_ci struct brcmf_fw *fwctx = ctx; 54162306a36Sopenharmony_ci struct brcmf_fw_item *cur; 54262306a36Sopenharmony_ci bool free_bcm47xx_nvram = false; 54362306a36Sopenharmony_ci bool kfree_nvram = false; 54462306a36Sopenharmony_ci u32 nvram_length = 0; 54562306a36Sopenharmony_ci void *nvram = NULL; 54662306a36Sopenharmony_ci u8 *data = NULL; 54762306a36Sopenharmony_ci size_t data_len; 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev)); 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci cur = &fwctx->req->items[fwctx->curpos]; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci if (fw && fw->data) { 55462306a36Sopenharmony_ci data = (u8 *)fw->data; 55562306a36Sopenharmony_ci data_len = fw->size; 55662306a36Sopenharmony_ci } else { 55762306a36Sopenharmony_ci if ((data = bcm47xx_nvram_get_contents(&data_len))) 55862306a36Sopenharmony_ci free_bcm47xx_nvram = true; 55962306a36Sopenharmony_ci else if ((data = brcmf_fw_nvram_from_efi(&data_len))) 56062306a36Sopenharmony_ci kfree_nvram = true; 56162306a36Sopenharmony_ci else if (!(cur->flags & BRCMF_FW_REQF_OPTIONAL)) 56262306a36Sopenharmony_ci goto fail; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci if (data) 56662306a36Sopenharmony_ci nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length, 56762306a36Sopenharmony_ci fwctx->req->domain_nr, 56862306a36Sopenharmony_ci fwctx->req->bus_nr, 56962306a36Sopenharmony_ci fwctx->dev); 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci if (free_bcm47xx_nvram) 57262306a36Sopenharmony_ci bcm47xx_nvram_release_contents(data); 57362306a36Sopenharmony_ci if (kfree_nvram) 57462306a36Sopenharmony_ci kfree(data); 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci release_firmware(fw); 57762306a36Sopenharmony_ci if (!nvram && !(cur->flags & BRCMF_FW_REQF_OPTIONAL)) 57862306a36Sopenharmony_ci goto fail; 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci brcmf_dbg(TRACE, "nvram %p len %d\n", nvram, nvram_length); 58162306a36Sopenharmony_ci cur->nv_data.data = nvram; 58262306a36Sopenharmony_ci cur->nv_data.len = nvram_length; 58362306a36Sopenharmony_ci return 0; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_cifail: 58662306a36Sopenharmony_ci return -ENOENT; 58762306a36Sopenharmony_ci} 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_cistatic int brcmf_fw_complete_request(const struct firmware *fw, 59062306a36Sopenharmony_ci struct brcmf_fw *fwctx) 59162306a36Sopenharmony_ci{ 59262306a36Sopenharmony_ci struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; 59362306a36Sopenharmony_ci int ret = 0; 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci brcmf_dbg(TRACE, "firmware %s %sfound\n", cur->path, fw ? "" : "not "); 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci switch (cur->type) { 59862306a36Sopenharmony_ci case BRCMF_FW_TYPE_NVRAM: 59962306a36Sopenharmony_ci ret = brcmf_fw_request_nvram_done(fw, fwctx); 60062306a36Sopenharmony_ci break; 60162306a36Sopenharmony_ci case BRCMF_FW_TYPE_BINARY: 60262306a36Sopenharmony_ci if (fw) 60362306a36Sopenharmony_ci cur->binary = fw; 60462306a36Sopenharmony_ci else 60562306a36Sopenharmony_ci ret = -ENOENT; 60662306a36Sopenharmony_ci break; 60762306a36Sopenharmony_ci default: 60862306a36Sopenharmony_ci /* something fishy here so bail out early */ 60962306a36Sopenharmony_ci brcmf_err("unknown fw type: %d\n", cur->type); 61062306a36Sopenharmony_ci release_firmware(fw); 61162306a36Sopenharmony_ci ret = -EINVAL; 61262306a36Sopenharmony_ci } 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci return (cur->flags & BRCMF_FW_REQF_OPTIONAL) ? 0 : ret; 61562306a36Sopenharmony_ci} 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_cistatic char *brcm_alt_fw_path(const char *path, const char *board_type) 61862306a36Sopenharmony_ci{ 61962306a36Sopenharmony_ci char base[BRCMF_FW_NAME_LEN]; 62062306a36Sopenharmony_ci const char *suffix; 62162306a36Sopenharmony_ci char *ret; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci if (!board_type) 62462306a36Sopenharmony_ci return NULL; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci suffix = strrchr(path, '.'); 62762306a36Sopenharmony_ci if (!suffix || suffix == path) 62862306a36Sopenharmony_ci return NULL; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci /* strip extension at the end */ 63162306a36Sopenharmony_ci strscpy(base, path, BRCMF_FW_NAME_LEN); 63262306a36Sopenharmony_ci base[suffix - path] = 0; 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci ret = kasprintf(GFP_KERNEL, "%s.%s%s", base, board_type, suffix); 63562306a36Sopenharmony_ci if (!ret) 63662306a36Sopenharmony_ci brcmf_err("out of memory allocating firmware path for '%s'\n", 63762306a36Sopenharmony_ci path); 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci brcmf_dbg(TRACE, "FW alt path: %s\n", ret); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci return ret; 64262306a36Sopenharmony_ci} 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cistatic int brcmf_fw_request_firmware(const struct firmware **fw, 64562306a36Sopenharmony_ci struct brcmf_fw *fwctx) 64662306a36Sopenharmony_ci{ 64762306a36Sopenharmony_ci struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; 64862306a36Sopenharmony_ci unsigned int i; 64962306a36Sopenharmony_ci int ret; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci /* Files can be board-specific, first try board-specific paths */ 65262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(fwctx->req->board_types); i++) { 65362306a36Sopenharmony_ci char *alt_path; 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci if (!fwctx->req->board_types[i]) 65662306a36Sopenharmony_ci goto fallback; 65762306a36Sopenharmony_ci alt_path = brcm_alt_fw_path(cur->path, 65862306a36Sopenharmony_ci fwctx->req->board_types[i]); 65962306a36Sopenharmony_ci if (!alt_path) 66062306a36Sopenharmony_ci goto fallback; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci ret = firmware_request_nowarn(fw, alt_path, fwctx->dev); 66362306a36Sopenharmony_ci kfree(alt_path); 66462306a36Sopenharmony_ci if (ret == 0) 66562306a36Sopenharmony_ci return ret; 66662306a36Sopenharmony_ci } 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_cifallback: 66962306a36Sopenharmony_ci return request_firmware(fw, cur->path, fwctx->dev); 67062306a36Sopenharmony_ci} 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_cistatic void brcmf_fw_request_done(const struct firmware *fw, void *ctx) 67362306a36Sopenharmony_ci{ 67462306a36Sopenharmony_ci struct brcmf_fw *fwctx = ctx; 67562306a36Sopenharmony_ci int ret; 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci ret = brcmf_fw_complete_request(fw, fwctx); 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) { 68062306a36Sopenharmony_ci brcmf_fw_request_firmware(&fw, fwctx); 68162306a36Sopenharmony_ci ret = brcmf_fw_complete_request(fw, ctx); 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci if (ret) { 68562306a36Sopenharmony_ci brcmf_fw_free_request(fwctx->req); 68662306a36Sopenharmony_ci fwctx->req = NULL; 68762306a36Sopenharmony_ci } 68862306a36Sopenharmony_ci fwctx->done(fwctx->dev, ret, fwctx->req); 68962306a36Sopenharmony_ci kfree(fwctx); 69062306a36Sopenharmony_ci} 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_cistatic void brcmf_fw_request_done_alt_path(const struct firmware *fw, void *ctx) 69362306a36Sopenharmony_ci{ 69462306a36Sopenharmony_ci struct brcmf_fw *fwctx = ctx; 69562306a36Sopenharmony_ci struct brcmf_fw_item *first = &fwctx->req->items[0]; 69662306a36Sopenharmony_ci const char *board_type, *alt_path; 69762306a36Sopenharmony_ci int ret = 0; 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci if (fw) { 70062306a36Sopenharmony_ci brcmf_fw_request_done(fw, ctx); 70162306a36Sopenharmony_ci return; 70262306a36Sopenharmony_ci } 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci /* Try next board firmware */ 70562306a36Sopenharmony_ci if (fwctx->board_index < ARRAY_SIZE(fwctx->req->board_types)) { 70662306a36Sopenharmony_ci board_type = fwctx->req->board_types[fwctx->board_index++]; 70762306a36Sopenharmony_ci if (!board_type) 70862306a36Sopenharmony_ci goto fallback; 70962306a36Sopenharmony_ci alt_path = brcm_alt_fw_path(first->path, board_type); 71062306a36Sopenharmony_ci if (!alt_path) 71162306a36Sopenharmony_ci goto fallback; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci ret = request_firmware_nowait(THIS_MODULE, true, alt_path, 71462306a36Sopenharmony_ci fwctx->dev, GFP_KERNEL, fwctx, 71562306a36Sopenharmony_ci brcmf_fw_request_done_alt_path); 71662306a36Sopenharmony_ci kfree(alt_path); 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci if (ret < 0) 71962306a36Sopenharmony_ci brcmf_fw_request_done(fw, ctx); 72062306a36Sopenharmony_ci return; 72162306a36Sopenharmony_ci } 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_cifallback: 72462306a36Sopenharmony_ci /* Fall back to canonical path if board firmware not found */ 72562306a36Sopenharmony_ci ret = request_firmware_nowait(THIS_MODULE, true, first->path, 72662306a36Sopenharmony_ci fwctx->dev, GFP_KERNEL, fwctx, 72762306a36Sopenharmony_ci brcmf_fw_request_done); 72862306a36Sopenharmony_ci 72962306a36Sopenharmony_ci if (ret < 0) 73062306a36Sopenharmony_ci brcmf_fw_request_done(fw, ctx); 73162306a36Sopenharmony_ci} 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_cistatic bool brcmf_fw_request_is_valid(struct brcmf_fw_request *req) 73462306a36Sopenharmony_ci{ 73562306a36Sopenharmony_ci struct brcmf_fw_item *item; 73662306a36Sopenharmony_ci int i; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci if (!req->n_items) 73962306a36Sopenharmony_ci return false; 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) { 74262306a36Sopenharmony_ci if (!item->path) 74362306a36Sopenharmony_ci return false; 74462306a36Sopenharmony_ci } 74562306a36Sopenharmony_ci return true; 74662306a36Sopenharmony_ci} 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ciint brcmf_fw_get_firmwares(struct device *dev, struct brcmf_fw_request *req, 74962306a36Sopenharmony_ci void (*fw_cb)(struct device *dev, int err, 75062306a36Sopenharmony_ci struct brcmf_fw_request *req)) 75162306a36Sopenharmony_ci{ 75262306a36Sopenharmony_ci struct brcmf_fw_item *first = &req->items[0]; 75362306a36Sopenharmony_ci struct brcmf_fw *fwctx; 75462306a36Sopenharmony_ci char *alt_path = NULL; 75562306a36Sopenharmony_ci int ret; 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev)); 75862306a36Sopenharmony_ci if (!fw_cb) 75962306a36Sopenharmony_ci return -EINVAL; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci if (!brcmf_fw_request_is_valid(req)) 76262306a36Sopenharmony_ci return -EINVAL; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci fwctx = kzalloc(sizeof(*fwctx), GFP_KERNEL); 76562306a36Sopenharmony_ci if (!fwctx) 76662306a36Sopenharmony_ci return -ENOMEM; 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci fwctx->dev = dev; 76962306a36Sopenharmony_ci fwctx->req = req; 77062306a36Sopenharmony_ci fwctx->done = fw_cb; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci /* First try alternative board-specific path if any */ 77362306a36Sopenharmony_ci if (fwctx->req->board_types[0]) 77462306a36Sopenharmony_ci alt_path = brcm_alt_fw_path(first->path, 77562306a36Sopenharmony_ci fwctx->req->board_types[0]); 77662306a36Sopenharmony_ci if (alt_path) { 77762306a36Sopenharmony_ci fwctx->board_index++; 77862306a36Sopenharmony_ci ret = request_firmware_nowait(THIS_MODULE, true, alt_path, 77962306a36Sopenharmony_ci fwctx->dev, GFP_KERNEL, fwctx, 78062306a36Sopenharmony_ci brcmf_fw_request_done_alt_path); 78162306a36Sopenharmony_ci kfree(alt_path); 78262306a36Sopenharmony_ci } else { 78362306a36Sopenharmony_ci ret = request_firmware_nowait(THIS_MODULE, true, first->path, 78462306a36Sopenharmony_ci fwctx->dev, GFP_KERNEL, fwctx, 78562306a36Sopenharmony_ci brcmf_fw_request_done); 78662306a36Sopenharmony_ci } 78762306a36Sopenharmony_ci if (ret < 0) 78862306a36Sopenharmony_ci brcmf_fw_request_done(NULL, fwctx); 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_ci return 0; 79162306a36Sopenharmony_ci} 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_cistruct brcmf_fw_request * 79462306a36Sopenharmony_cibrcmf_fw_alloc_request(u32 chip, u32 chiprev, 79562306a36Sopenharmony_ci const struct brcmf_firmware_mapping mapping_table[], 79662306a36Sopenharmony_ci u32 table_size, struct brcmf_fw_name *fwnames, 79762306a36Sopenharmony_ci u32 n_fwnames) 79862306a36Sopenharmony_ci{ 79962306a36Sopenharmony_ci struct brcmf_fw_request *fwreq; 80062306a36Sopenharmony_ci char chipname[12]; 80162306a36Sopenharmony_ci const char *mp_path; 80262306a36Sopenharmony_ci size_t mp_path_len; 80362306a36Sopenharmony_ci u32 i, j; 80462306a36Sopenharmony_ci char end = '\0'; 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci if (chiprev >= BITS_PER_TYPE(u32)) { 80762306a36Sopenharmony_ci brcmf_err("Invalid chip revision %u\n", chiprev); 80862306a36Sopenharmony_ci return NULL; 80962306a36Sopenharmony_ci } 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci for (i = 0; i < table_size; i++) { 81262306a36Sopenharmony_ci if (mapping_table[i].chipid == chip && 81362306a36Sopenharmony_ci mapping_table[i].revmask & BIT(chiprev)) 81462306a36Sopenharmony_ci break; 81562306a36Sopenharmony_ci } 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_ci brcmf_chip_name(chip, chiprev, chipname, sizeof(chipname)); 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci if (i == table_size) { 82062306a36Sopenharmony_ci brcmf_err("Unknown chip %s\n", chipname); 82162306a36Sopenharmony_ci return NULL; 82262306a36Sopenharmony_ci } 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_ci fwreq = kzalloc(struct_size(fwreq, items, n_fwnames), GFP_KERNEL); 82562306a36Sopenharmony_ci if (!fwreq) 82662306a36Sopenharmony_ci return NULL; 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci brcmf_info("using %s for chip %s\n", 82962306a36Sopenharmony_ci mapping_table[i].fw_base, chipname); 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci mp_path = brcmf_mp_global.firmware_path; 83262306a36Sopenharmony_ci mp_path_len = strnlen(mp_path, BRCMF_FW_ALTPATH_LEN); 83362306a36Sopenharmony_ci if (mp_path_len) 83462306a36Sopenharmony_ci end = mp_path[mp_path_len - 1]; 83562306a36Sopenharmony_ci 83662306a36Sopenharmony_ci fwreq->n_items = n_fwnames; 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci for (j = 0; j < n_fwnames; j++) { 83962306a36Sopenharmony_ci fwreq->items[j].path = fwnames[j].path; 84062306a36Sopenharmony_ci fwnames[j].path[0] = '\0'; 84162306a36Sopenharmony_ci /* check if firmware path is provided by module parameter */ 84262306a36Sopenharmony_ci if (brcmf_mp_global.firmware_path[0] != '\0') { 84362306a36Sopenharmony_ci strscpy(fwnames[j].path, mp_path, 84462306a36Sopenharmony_ci BRCMF_FW_NAME_LEN); 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci if (end != '/') { 84762306a36Sopenharmony_ci strlcat(fwnames[j].path, "/", 84862306a36Sopenharmony_ci BRCMF_FW_NAME_LEN); 84962306a36Sopenharmony_ci } 85062306a36Sopenharmony_ci } 85162306a36Sopenharmony_ci strlcat(fwnames[j].path, mapping_table[i].fw_base, 85262306a36Sopenharmony_ci BRCMF_FW_NAME_LEN); 85362306a36Sopenharmony_ci strlcat(fwnames[j].path, fwnames[j].extension, 85462306a36Sopenharmony_ci BRCMF_FW_NAME_LEN); 85562306a36Sopenharmony_ci fwreq->items[j].path = fwnames[j].path; 85662306a36Sopenharmony_ci } 85762306a36Sopenharmony_ci 85862306a36Sopenharmony_ci return fwreq; 85962306a36Sopenharmony_ci} 860