Path: blob/main/sys/contrib/dev/broadcom/brcm80211/brcmfmac/firmware.c
178665 views
// SPDX-License-Identifier: ISC1/*2* Copyright (c) 2013 Broadcom Corporation3*/45#include <linux/efi.h>6#include <linux/kernel.h>7#include <linux/slab.h>8#include <linux/device.h>9#include <linux/firmware.h>10#include <linux/module.h>11#include <linux/bcm47xx_nvram.h>1213#include "debug.h"14#include "firmware.h"15#include "core.h"16#include "common.h"17#include "chip.h"1819#define BRCMF_FW_MAX_NVRAM_SIZE 6400020#define BRCMF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */21#define BRCMF_FW_NVRAM_PCIEDEV_LEN 20 /* pcie/1/4/ + \0 */22#define BRCMF_FW_DEFAULT_BOARDREV "boardrev=0xff"23#define BRCMF_FW_MACADDR_FMT "macaddr=%pM"24#define BRCMF_FW_MACADDR_LEN (7 + ETH_ALEN * 3)2526enum nvram_parser_state {27IDLE,28KEY,29VALUE,30COMMENT,31END32};3334/**35* struct nvram_parser - internal info for parser.36*37* @state: current parser state.38* @data: input buffer being parsed.39* @nvram: output buffer with parse result.40* @nvram_len: length of parse result.41* @line: current line.42* @column: current column in line.43* @pos: byte offset in input buffer.44* @entry: start position of key,value entry.45* @multi_dev_v1: detect pcie multi device v1 (compressed).46* @multi_dev_v2: detect pcie multi device v2.47* @boardrev_found: nvram contains boardrev information.48* @strip_mac: strip the MAC address.49*/50struct nvram_parser {51enum nvram_parser_state state;52const u8 *data;53u8 *nvram;54u32 nvram_len;55u32 line;56u32 column;57u32 pos;58u32 entry;59bool multi_dev_v1;60bool multi_dev_v2;61bool boardrev_found;62bool strip_mac;63};6465/*66* is_nvram_char() - check if char is a valid one for NVRAM entry67*68* It accepts all printable ASCII chars except for '#' which opens a comment.69* Please note that ' ' (space) while accepted is not a valid key name char.70*/71static bool is_nvram_char(char c)72{73/* comment marker excluded */74if (c == '#')75return false;7677/* key and value may have any other readable character */78return (c >= 0x20 && c < 0x7f);79}8081static bool is_whitespace(char c)82{83return (c == ' ' || c == '\r' || c == '\n' || c == '\t');84}8586static enum nvram_parser_state brcmf_nvram_handle_idle(struct nvram_parser *nvp)87{88char c;8990c = nvp->data[nvp->pos];91if (c == '\n')92return COMMENT;93if (is_whitespace(c) || c == '\0')94goto proceed;95if (c == '#')96return COMMENT;97if (is_nvram_char(c)) {98nvp->entry = nvp->pos;99return KEY;100}101brcmf_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n",102nvp->line, nvp->column);103proceed:104nvp->column++;105nvp->pos++;106return IDLE;107}108109static enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp)110{111enum nvram_parser_state st = nvp->state;112char c;113114c = nvp->data[nvp->pos];115if (c == '=') {116/* ignore RAW1 by treating as comment */117if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0)118st = COMMENT;119else120st = VALUE;121if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0)122nvp->multi_dev_v1 = true;123if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0)124nvp->multi_dev_v2 = true;125if (strncmp(&nvp->data[nvp->entry], "boardrev", 8) == 0)126nvp->boardrev_found = true;127/* strip macaddr if platform MAC overrides */128if (nvp->strip_mac &&129strncmp(&nvp->data[nvp->entry], "macaddr", 7) == 0)130st = COMMENT;131} else if (!is_nvram_char(c) || c == ' ') {132brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n",133nvp->line, nvp->column);134return COMMENT;135}136137nvp->column++;138nvp->pos++;139return st;140}141142static enum nvram_parser_state143brcmf_nvram_handle_value(struct nvram_parser *nvp)144{145char c;146#if defined(__linux__)147char *skv;148char *ekv;149#elif defined(__FreeBSD__)150const char *skv;151const char *ekv;152#endif153u32 cplen;154155c = nvp->data[nvp->pos];156if (!is_nvram_char(c)) {157/* key,value pair complete */158#if defined(__linux__)159ekv = (u8 *)&nvp->data[nvp->pos];160skv = (u8 *)&nvp->data[nvp->entry];161#elif defined(__FreeBSD__)162ekv = &nvp->data[nvp->pos];163skv = &nvp->data[nvp->entry];164#endif165cplen = ekv - skv;166if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE)167return END;168/* copy to output buffer */169memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen);170nvp->nvram_len += cplen;171nvp->nvram[nvp->nvram_len] = '\0';172nvp->nvram_len++;173return IDLE;174}175nvp->pos++;176nvp->column++;177return VALUE;178}179180static enum nvram_parser_state181brcmf_nvram_handle_comment(struct nvram_parser *nvp)182{183#if defined(__linux__)184char *eoc, *sol;185186sol = (char *)&nvp->data[nvp->pos];187#elif defined(__FreeBSD__)188const char *eoc, *sol;189190sol = &nvp->data[nvp->pos];191#endif192eoc = strchr(sol, '\n');193if (!eoc) {194eoc = strchr(sol, '\0');195if (!eoc)196return END;197}198199/* eat all moving to next line */200nvp->line++;201nvp->column = 1;202nvp->pos += (eoc - sol) + 1;203return IDLE;204}205206static enum nvram_parser_state brcmf_nvram_handle_end(struct nvram_parser *nvp)207{208/* final state */209return END;210}211212static enum nvram_parser_state213(*nv_parser_states[])(struct nvram_parser *nvp) = {214brcmf_nvram_handle_idle,215brcmf_nvram_handle_key,216brcmf_nvram_handle_value,217brcmf_nvram_handle_comment,218brcmf_nvram_handle_end219};220221static int brcmf_init_nvram_parser(struct nvram_parser *nvp,222const u8 *data, size_t data_len)223{224size_t size;225226memset(nvp, 0, sizeof(*nvp));227nvp->data = data;228/* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */229if (data_len > BRCMF_FW_MAX_NVRAM_SIZE)230size = BRCMF_FW_MAX_NVRAM_SIZE;231else232size = data_len;233/* Add space for properties we may add */234size += strlen(BRCMF_FW_DEFAULT_BOARDREV) + 1;235size += BRCMF_FW_MACADDR_LEN + 1;236/* Alloc for extra 0 byte + roundup by 4 + length field */237size += 1 + 3 + sizeof(u32);238nvp->nvram = kzalloc(size, GFP_KERNEL);239if (!nvp->nvram)240return -ENOMEM;241242nvp->line = 1;243nvp->column = 1;244return 0;245}246247/* brcmf_fw_strip_multi_v1 :Some nvram files contain settings for multiple248* devices. Strip it down for one device, use domain_nr/bus_nr to determine249* which data is to be returned. v1 is the version where nvram is stored250* compressed and "devpath" maps to index for valid entries.251*/252static void brcmf_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr,253u16 bus_nr)254{255/* Device path with a leading '=' key-value separator */256char pci_path[20];257size_t pci_len;258char pcie_path[20];259size_t pcie_len;260261u32 i, j;262bool found;263u8 *nvram;264u8 id;265266nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);267if (!nvram)268goto fail;269270/* min length: devpath0=pcie/1/4/ + 0:x=y */271if (nvp->nvram_len < BRCMF_FW_NVRAM_DEVPATH_LEN + 6)272goto fail;273274/* First search for the devpathX and see if it is the configuration275* for domain_nr/bus_nr. Search complete nvp276*/277snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr,278bus_nr);279pci_len = strlen(pci_path);280snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr,281bus_nr);282pcie_len = strlen(pcie_path);283found = false;284i = 0;285while (i < nvp->nvram_len - BRCMF_FW_NVRAM_DEVPATH_LEN) {286/* Format: devpathX=pcie/Y/Z/287* Y = domain_nr, Z = bus_nr, X = virtual ID288*/289if (strncmp(&nvp->nvram[i], "devpath", 7) == 0 &&290(!strncmp(&nvp->nvram[i + 8], pci_path, pci_len) ||291!strncmp(&nvp->nvram[i + 8], pcie_path, pcie_len))) {292id = nvp->nvram[i + 7] - '0';293found = true;294break;295}296while (nvp->nvram[i] != 0)297i++;298i++;299}300if (!found)301goto fail;302303/* Now copy all valid entries, release old nvram and assign new one */304i = 0;305j = 0;306while (i < nvp->nvram_len) {307if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) {308i += 2;309if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)310nvp->boardrev_found = true;311while (nvp->nvram[i] != 0) {312nvram[j] = nvp->nvram[i];313i++;314j++;315}316nvram[j] = 0;317j++;318}319while (nvp->nvram[i] != 0)320i++;321i++;322}323kfree(nvp->nvram);324nvp->nvram = nvram;325nvp->nvram_len = j;326return;327328fail:329kfree(nvram);330nvp->nvram_len = 0;331}332333/* brcmf_fw_strip_multi_v2 :Some nvram files contain settings for multiple334* devices. Strip it down for one device, use domain_nr/bus_nr to determine335* which data is to be returned. v2 is the version where nvram is stored336* uncompressed, all relevant valid entries are identified by337* pcie/domain_nr/bus_nr:338*/339static void brcmf_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr,340u16 bus_nr)341{342char prefix[BRCMF_FW_NVRAM_PCIEDEV_LEN];343size_t len;344u32 i, j;345u8 *nvram;346347nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);348if (!nvram) {349nvp->nvram_len = 0;350return;351}352353/* Copy all valid entries, release old nvram and assign new one.354* Valid entries are of type pcie/X/Y/ where X = domain_nr and355* Y = bus_nr.356*/357snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr);358len = strlen(prefix);359i = 0;360j = 0;361while (i < nvp->nvram_len - len) {362if (strncmp(&nvp->nvram[i], prefix, len) == 0) {363i += len;364if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)365nvp->boardrev_found = true;366while (nvp->nvram[i] != 0) {367nvram[j] = nvp->nvram[i];368i++;369j++;370}371nvram[j] = 0;372j++;373}374while (nvp->nvram[i] != 0)375i++;376i++;377}378kfree(nvp->nvram);379nvp->nvram = nvram;380nvp->nvram_len = j;381}382383static void brcmf_fw_add_defaults(struct nvram_parser *nvp)384{385if (nvp->boardrev_found)386return;387388memcpy(&nvp->nvram[nvp->nvram_len], &BRCMF_FW_DEFAULT_BOARDREV,389strlen(BRCMF_FW_DEFAULT_BOARDREV));390nvp->nvram_len += strlen(BRCMF_FW_DEFAULT_BOARDREV);391nvp->nvram[nvp->nvram_len] = '\0';392nvp->nvram_len++;393}394395static void brcmf_fw_add_macaddr(struct nvram_parser *nvp, u8 *mac)396{397int len;398399len = scnprintf(&nvp->nvram[nvp->nvram_len], BRCMF_FW_MACADDR_LEN + 1,400BRCMF_FW_MACADDR_FMT, mac);401WARN_ON(len != BRCMF_FW_MACADDR_LEN);402nvp->nvram_len += len + 1;403}404405/* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil406* and ending in a NUL. Removes carriage returns, empty lines, comment lines,407* and converts newlines to NULs. Shortens buffer as needed and pads with NULs.408* End of buffer is completed with token identifying length of buffer.409*/410static void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len,411u32 *new_length, u16 domain_nr, u16 bus_nr,412struct device *dev)413{414struct nvram_parser nvp;415u32 pad;416u32 token;417__le32 token_le;418u8 mac[ETH_ALEN];419420if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0)421return NULL;422423if (eth_platform_get_mac_address(dev, mac) == 0)424nvp.strip_mac = true;425426while (nvp.pos < data_len) {427nvp.state = nv_parser_states[nvp.state](&nvp);428if (nvp.state == END)429break;430}431if (nvp.multi_dev_v1) {432nvp.boardrev_found = false;433brcmf_fw_strip_multi_v1(&nvp, domain_nr, bus_nr);434} else if (nvp.multi_dev_v2) {435nvp.boardrev_found = false;436brcmf_fw_strip_multi_v2(&nvp, domain_nr, bus_nr);437}438439if (nvp.nvram_len == 0) {440kfree(nvp.nvram);441return NULL;442}443444brcmf_fw_add_defaults(&nvp);445446if (nvp.strip_mac)447brcmf_fw_add_macaddr(&nvp, mac);448449pad = nvp.nvram_len;450*new_length = roundup(nvp.nvram_len + 1, 4);451while (pad != *new_length) {452nvp.nvram[pad] = 0;453pad++;454}455456token = *new_length / 4;457token = (~token << 16) | (token & 0x0000FFFF);458token_le = cpu_to_le32(token);459460memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le));461*new_length += sizeof(token_le);462463return nvp.nvram;464}465466void brcmf_fw_nvram_free(void *nvram)467{468kfree(nvram);469}470471struct brcmf_fw {472struct device *dev;473struct brcmf_fw_request *req;474u32 curpos;475unsigned int board_index;476void (*done)(struct device *dev, int err, struct brcmf_fw_request *req);477};478479#ifdef CONFIG_EFI480/* In some cases the EFI-var stored nvram contains "ccode=ALL" or "ccode=XV"481* to specify "worldwide" compatible settings, but these 2 ccode-s do not work482* properly. "ccode=ALL" causes channels 12 and 13 to not be available,483* "ccode=XV" causes all 5GHz channels to not be available. So we replace both484* with "ccode=X2" which allows channels 12+13 and 5Ghz channels in485* no-Initiate-Radiation mode. This means that we will never send on these486* channels without first having received valid wifi traffic on the channel.487*/488static void brcmf_fw_fix_efi_nvram_ccode(char *data, unsigned long data_len)489{490char *ccode;491492ccode = strnstr((char *)data, "ccode=ALL", data_len);493if (!ccode)494ccode = strnstr((char *)data, "ccode=XV\r", data_len);495if (!ccode)496return;497498ccode[6] = 'X';499ccode[7] = '2';500ccode[8] = '\r';501}502503static u8 *brcmf_fw_nvram_from_efi(size_t *data_len_ret)504{505efi_guid_t guid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61, 0xb5, 0x1f,5060x43, 0x26, 0x81, 0x23, 0xd1, 0x13);507unsigned long data_len = 0;508efi_status_t status;509u8 *data = NULL;510511if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))512return NULL;513514status = efi.get_variable(L"nvram", &guid, NULL, &data_len, NULL);515if (status != EFI_BUFFER_TOO_SMALL)516goto fail;517518data = kmalloc(data_len, GFP_KERNEL);519if (!data)520goto fail;521522status = efi.get_variable(L"nvram", &guid, NULL, &data_len, data);523if (status != EFI_SUCCESS)524goto fail;525526brcmf_fw_fix_efi_nvram_ccode(data, data_len);527brcmf_info("Using nvram EFI variable\n");528529*data_len_ret = data_len;530return data;531fail:532kfree(data);533return NULL;534}535#else536static inline u8 *brcmf_fw_nvram_from_efi(size_t *data_len) { return NULL; }537#endif538539static void brcmf_fw_free_request(struct brcmf_fw_request *req)540{541struct brcmf_fw_item *item;542int i;543544for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {545if (item->type == BRCMF_FW_TYPE_BINARY)546release_firmware(item->binary);547else if (item->type == BRCMF_FW_TYPE_NVRAM)548brcmf_fw_nvram_free(item->nv_data.data);549}550kfree(req);551}552553static int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx)554{555struct brcmf_fw *fwctx = ctx;556struct brcmf_fw_item *cur;557bool free_bcm47xx_nvram = false;558bool kfree_nvram = false;559u32 nvram_length = 0;560void *nvram = NULL;561#if defined(__linux__)562u8 *data = NULL;563#elif defined(__FreeBSD__)564const u8 *data = NULL;565#endif566size_t data_len;567568brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev));569570cur = &fwctx->req->items[fwctx->curpos];571572if (fw && fw->data) {573#if defined(__linux__)574data = (u8 *)fw->data;575#elif defined(__FreeBSD__)576data = fw->data;577#endif578data_len = fw->size;579} else {580data = bcm47xx_nvram_get_contents(&data_len);581if (data) {582free_bcm47xx_nvram = true;583} else {584data = brcmf_fw_nvram_from_efi(&data_len);585if (data)586kfree_nvram = true;587else if (!(cur->flags & BRCMF_FW_REQF_OPTIONAL))588goto fail;589}590}591592if (data)593nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length,594fwctx->req->domain_nr,595fwctx->req->bus_nr,596fwctx->dev);597598if (free_bcm47xx_nvram)599bcm47xx_nvram_release_contents(data);600if (kfree_nvram)601kfree(data);602603release_firmware(fw);604if (!nvram && !(cur->flags & BRCMF_FW_REQF_OPTIONAL))605goto fail;606607brcmf_dbg(TRACE, "nvram %p len %d\n", nvram, nvram_length);608cur->nv_data.data = nvram;609cur->nv_data.len = nvram_length;610return 0;611612fail:613return -ENOENT;614}615616static int brcmf_fw_complete_request(const struct firmware *fw,617struct brcmf_fw *fwctx)618{619struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos];620int ret = 0;621622brcmf_dbg(TRACE, "firmware %s %sfound\n", cur->path, fw ? "" : "not ");623624switch (cur->type) {625case BRCMF_FW_TYPE_NVRAM:626ret = brcmf_fw_request_nvram_done(fw, fwctx);627break;628case BRCMF_FW_TYPE_BINARY:629if (fw)630cur->binary = fw;631else632ret = -ENOENT;633break;634default:635/* something fishy here so bail out early */636brcmf_err("unknown fw type: %d\n", cur->type);637release_firmware(fw);638ret = -EINVAL;639}640641return (cur->flags & BRCMF_FW_REQF_OPTIONAL) ? 0 : ret;642}643644static char *brcm_alt_fw_path(const char *path, const char *board_type)645{646char base[BRCMF_FW_NAME_LEN];647const char *suffix;648char *ret;649650if (!board_type)651return NULL;652653suffix = strrchr(path, '.');654if (!suffix || suffix == path)655return NULL;656657/* strip extension at the end */658strscpy(base, path, BRCMF_FW_NAME_LEN);659base[suffix - path] = 0;660661ret = kasprintf(GFP_KERNEL, "%s.%s%s", base, board_type, suffix);662if (!ret)663brcmf_err("out of memory allocating firmware path for '%s'\n",664path);665666brcmf_dbg(TRACE, "FW alt path: %s\n", ret);667668return ret;669}670671static int brcmf_fw_request_firmware(const struct firmware **fw,672struct brcmf_fw *fwctx)673{674struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos];675unsigned int i;676int ret;677678/* Files can be board-specific, first try board-specific paths */679for (i = 0; i < ARRAY_SIZE(fwctx->req->board_types); i++) {680char *alt_path;681682if (!fwctx->req->board_types[i])683goto fallback;684alt_path = brcm_alt_fw_path(cur->path,685fwctx->req->board_types[i]);686if (!alt_path)687goto fallback;688689ret = firmware_request_nowarn(fw, alt_path, fwctx->dev);690kfree(alt_path);691if (ret == 0)692return ret;693}694695fallback:696return request_firmware(fw, cur->path, fwctx->dev);697}698699static void brcmf_fw_request_done(const struct firmware *fw, void *ctx)700{701struct brcmf_fw *fwctx = ctx;702int ret;703704ret = brcmf_fw_complete_request(fw, fwctx);705706while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) {707brcmf_fw_request_firmware(&fw, fwctx);708ret = brcmf_fw_complete_request(fw, ctx);709}710711if (ret) {712brcmf_fw_free_request(fwctx->req);713fwctx->req = NULL;714}715fwctx->done(fwctx->dev, ret, fwctx->req);716kfree(fwctx);717}718719static void brcmf_fw_request_done_alt_path(const struct firmware *fw, void *ctx)720{721struct brcmf_fw *fwctx = ctx;722struct brcmf_fw_item *first = &fwctx->req->items[0];723const char *board_type, *alt_path;724int ret = 0;725726if (fw) {727brcmf_fw_request_done(fw, ctx);728return;729}730731/* Try next board firmware */732if (fwctx->board_index < ARRAY_SIZE(fwctx->req->board_types)) {733board_type = fwctx->req->board_types[fwctx->board_index++];734if (!board_type)735goto fallback;736alt_path = brcm_alt_fw_path(first->path, board_type);737if (!alt_path)738goto fallback;739740ret = request_firmware_nowait(THIS_MODULE, true, alt_path,741fwctx->dev, GFP_KERNEL, fwctx,742brcmf_fw_request_done_alt_path);743kfree(alt_path);744745if (ret < 0)746brcmf_fw_request_done(fw, ctx);747return;748}749750fallback:751/* Fall back to canonical path if board firmware not found */752ret = request_firmware_nowait(THIS_MODULE, true, first->path,753fwctx->dev, GFP_KERNEL, fwctx,754brcmf_fw_request_done);755756if (ret < 0)757brcmf_fw_request_done(fw, ctx);758}759760static bool brcmf_fw_request_is_valid(struct brcmf_fw_request *req)761{762struct brcmf_fw_item *item;763int i;764765if (!req->n_items)766return false;767768for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {769if (!item->path)770return false;771}772return true;773}774775int brcmf_fw_get_firmwares(struct device *dev, struct brcmf_fw_request *req,776void (*fw_cb)(struct device *dev, int err,777struct brcmf_fw_request *req))778{779struct brcmf_fw_item *first = &req->items[0];780struct brcmf_fw *fwctx;781char *alt_path = NULL;782int ret;783784brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev));785if (!fw_cb)786return -EINVAL;787788if (!brcmf_fw_request_is_valid(req))789return -EINVAL;790791fwctx = kzalloc(sizeof(*fwctx), GFP_KERNEL);792if (!fwctx)793return -ENOMEM;794795fwctx->dev = dev;796fwctx->req = req;797fwctx->done = fw_cb;798799/* First try alternative board-specific path if any */800if (fwctx->req->board_types[0])801alt_path = brcm_alt_fw_path(first->path,802fwctx->req->board_types[0]);803if (alt_path) {804fwctx->board_index++;805ret = request_firmware_nowait(THIS_MODULE, true, alt_path,806fwctx->dev, GFP_KERNEL, fwctx,807brcmf_fw_request_done_alt_path);808kfree(alt_path);809} else {810ret = request_firmware_nowait(THIS_MODULE, true, first->path,811fwctx->dev, GFP_KERNEL, fwctx,812brcmf_fw_request_done);813}814if (ret < 0)815brcmf_fw_request_done(NULL, fwctx);816817return 0;818}819820struct brcmf_fw_request *821brcmf_fw_alloc_request(u32 chip, u32 chiprev,822const struct brcmf_firmware_mapping mapping_table[],823u32 table_size, struct brcmf_fw_name *fwnames,824u32 n_fwnames)825{826struct brcmf_fw_request *fwreq;827char chipname[12];828const char *mp_path;829size_t mp_path_len;830u32 i, j;831char end = '\0';832833if (chiprev >= BITS_PER_TYPE(u32)) {834brcmf_err("Invalid chip revision %u\n", chiprev);835return NULL;836}837838for (i = 0; i < table_size; i++) {839if (mapping_table[i].chipid == chip &&840mapping_table[i].revmask & BIT(chiprev))841break;842}843844brcmf_chip_name(chip, chiprev, chipname, sizeof(chipname));845846if (i == table_size) {847brcmf_err("Unknown chip %s\n", chipname);848return NULL;849}850851fwreq = kzalloc(struct_size(fwreq, items, n_fwnames), GFP_KERNEL);852if (!fwreq)853return NULL;854855brcmf_info("using %s for chip %s\n",856mapping_table[i].fw_base, chipname);857858mp_path = brcmf_mp_global.firmware_path;859mp_path_len = strnlen(mp_path, BRCMF_FW_ALTPATH_LEN);860if (mp_path_len)861end = mp_path[mp_path_len - 1];862863fwreq->n_items = n_fwnames;864865for (j = 0; j < n_fwnames; j++) {866fwreq->items[j].path = fwnames[j].path;867fwnames[j].path[0] = '\0';868/* check if firmware path is provided by module parameter */869if (brcmf_mp_global.firmware_path[0] != '\0') {870strscpy(fwnames[j].path, mp_path,871BRCMF_FW_NAME_LEN);872873if (end != '/') {874strlcat(fwnames[j].path, "/",875BRCMF_FW_NAME_LEN);876}877}878strlcat(fwnames[j].path, mapping_table[i].fw_base,879BRCMF_FW_NAME_LEN);880strlcat(fwnames[j].path, fwnames[j].extension,881BRCMF_FW_NAME_LEN);882fwreq->items[j].path = fwnames[j].path;883}884885return fwreq;886}887888889