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