18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: ISC
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2013 Broadcom Corporation
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/efi.h>
78c2ecf20Sopenharmony_ci#include <linux/kernel.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci#include <linux/device.h>
108c2ecf20Sopenharmony_ci#include <linux/firmware.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/bcm47xx_nvram.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include "debug.h"
158c2ecf20Sopenharmony_ci#include "firmware.h"
168c2ecf20Sopenharmony_ci#include "core.h"
178c2ecf20Sopenharmony_ci#include "common.h"
188c2ecf20Sopenharmony_ci#include "chip.h"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define BRCMF_FW_MAX_NVRAM_SIZE			64000
218c2ecf20Sopenharmony_ci#define BRCMF_FW_NVRAM_DEVPATH_LEN		19	/* devpath0=pcie/1/4/ */
228c2ecf20Sopenharmony_ci#define BRCMF_FW_NVRAM_PCIEDEV_LEN		10	/* pcie/1/4/ + \0 */
238c2ecf20Sopenharmony_ci#define BRCMF_FW_DEFAULT_BOARDREV		"boardrev=0xff"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cienum nvram_parser_state {
268c2ecf20Sopenharmony_ci	IDLE,
278c2ecf20Sopenharmony_ci	KEY,
288c2ecf20Sopenharmony_ci	VALUE,
298c2ecf20Sopenharmony_ci	COMMENT,
308c2ecf20Sopenharmony_ci	END
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci/**
348c2ecf20Sopenharmony_ci * struct nvram_parser - internal info for parser.
358c2ecf20Sopenharmony_ci *
368c2ecf20Sopenharmony_ci * @state: current parser state.
378c2ecf20Sopenharmony_ci * @data: input buffer being parsed.
388c2ecf20Sopenharmony_ci * @nvram: output buffer with parse result.
398c2ecf20Sopenharmony_ci * @nvram_len: length of parse result.
408c2ecf20Sopenharmony_ci * @line: current line.
418c2ecf20Sopenharmony_ci * @column: current column in line.
428c2ecf20Sopenharmony_ci * @pos: byte offset in input buffer.
438c2ecf20Sopenharmony_ci * @entry: start position of key,value entry.
448c2ecf20Sopenharmony_ci * @multi_dev_v1: detect pcie multi device v1 (compressed).
458c2ecf20Sopenharmony_ci * @multi_dev_v2: detect pcie multi device v2.
468c2ecf20Sopenharmony_ci * @boardrev_found: nvram contains boardrev information.
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_cistruct nvram_parser {
498c2ecf20Sopenharmony_ci	enum nvram_parser_state state;
508c2ecf20Sopenharmony_ci	const u8 *data;
518c2ecf20Sopenharmony_ci	u8 *nvram;
528c2ecf20Sopenharmony_ci	u32 nvram_len;
538c2ecf20Sopenharmony_ci	u32 line;
548c2ecf20Sopenharmony_ci	u32 column;
558c2ecf20Sopenharmony_ci	u32 pos;
568c2ecf20Sopenharmony_ci	u32 entry;
578c2ecf20Sopenharmony_ci	bool multi_dev_v1;
588c2ecf20Sopenharmony_ci	bool multi_dev_v2;
598c2ecf20Sopenharmony_ci	bool boardrev_found;
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci/*
638c2ecf20Sopenharmony_ci * is_nvram_char() - check if char is a valid one for NVRAM entry
648c2ecf20Sopenharmony_ci *
658c2ecf20Sopenharmony_ci * It accepts all printable ASCII chars except for '#' which opens a comment.
668c2ecf20Sopenharmony_ci * Please note that ' ' (space) while accepted is not a valid key name char.
678c2ecf20Sopenharmony_ci */
688c2ecf20Sopenharmony_cistatic bool is_nvram_char(char c)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	/* comment marker excluded */
718c2ecf20Sopenharmony_ci	if (c == '#')
728c2ecf20Sopenharmony_ci		return false;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	/* key and value may have any other readable character */
758c2ecf20Sopenharmony_ci	return (c >= 0x20 && c < 0x7f);
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic bool is_whitespace(char c)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	return (c == ' ' || c == '\r' || c == '\n' || c == '\t');
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic enum nvram_parser_state brcmf_nvram_handle_idle(struct nvram_parser *nvp)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	char c;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	c = nvp->data[nvp->pos];
888c2ecf20Sopenharmony_ci	if (c == '\n')
898c2ecf20Sopenharmony_ci		return COMMENT;
908c2ecf20Sopenharmony_ci	if (is_whitespace(c) || c == '\0')
918c2ecf20Sopenharmony_ci		goto proceed;
928c2ecf20Sopenharmony_ci	if (c == '#')
938c2ecf20Sopenharmony_ci		return COMMENT;
948c2ecf20Sopenharmony_ci	if (is_nvram_char(c)) {
958c2ecf20Sopenharmony_ci		nvp->entry = nvp->pos;
968c2ecf20Sopenharmony_ci		return KEY;
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci	brcmf_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n",
998c2ecf20Sopenharmony_ci		  nvp->line, nvp->column);
1008c2ecf20Sopenharmony_ciproceed:
1018c2ecf20Sopenharmony_ci	nvp->column++;
1028c2ecf20Sopenharmony_ci	nvp->pos++;
1038c2ecf20Sopenharmony_ci	return IDLE;
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic enum nvram_parser_state brcmf_nvram_handle_key(struct nvram_parser *nvp)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	enum nvram_parser_state st = nvp->state;
1098c2ecf20Sopenharmony_ci	char c;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	c = nvp->data[nvp->pos];
1128c2ecf20Sopenharmony_ci	if (c == '=') {
1138c2ecf20Sopenharmony_ci		/* ignore RAW1 by treating as comment */
1148c2ecf20Sopenharmony_ci		if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0)
1158c2ecf20Sopenharmony_ci			st = COMMENT;
1168c2ecf20Sopenharmony_ci		else
1178c2ecf20Sopenharmony_ci			st = VALUE;
1188c2ecf20Sopenharmony_ci		if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0)
1198c2ecf20Sopenharmony_ci			nvp->multi_dev_v1 = true;
1208c2ecf20Sopenharmony_ci		if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0)
1218c2ecf20Sopenharmony_ci			nvp->multi_dev_v2 = true;
1228c2ecf20Sopenharmony_ci		if (strncmp(&nvp->data[nvp->entry], "boardrev", 8) == 0)
1238c2ecf20Sopenharmony_ci			nvp->boardrev_found = true;
1248c2ecf20Sopenharmony_ci	} else if (!is_nvram_char(c) || c == ' ') {
1258c2ecf20Sopenharmony_ci		brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n",
1268c2ecf20Sopenharmony_ci			  nvp->line, nvp->column);
1278c2ecf20Sopenharmony_ci		return COMMENT;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	nvp->column++;
1318c2ecf20Sopenharmony_ci	nvp->pos++;
1328c2ecf20Sopenharmony_ci	return st;
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic enum nvram_parser_state
1368c2ecf20Sopenharmony_cibrcmf_nvram_handle_value(struct nvram_parser *nvp)
1378c2ecf20Sopenharmony_ci{
1388c2ecf20Sopenharmony_ci	char c;
1398c2ecf20Sopenharmony_ci	char *skv;
1408c2ecf20Sopenharmony_ci	char *ekv;
1418c2ecf20Sopenharmony_ci	u32 cplen;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	c = nvp->data[nvp->pos];
1448c2ecf20Sopenharmony_ci	if (!is_nvram_char(c)) {
1458c2ecf20Sopenharmony_ci		/* key,value pair complete */
1468c2ecf20Sopenharmony_ci		ekv = (u8 *)&nvp->data[nvp->pos];
1478c2ecf20Sopenharmony_ci		skv = (u8 *)&nvp->data[nvp->entry];
1488c2ecf20Sopenharmony_ci		cplen = ekv - skv;
1498c2ecf20Sopenharmony_ci		if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE)
1508c2ecf20Sopenharmony_ci			return END;
1518c2ecf20Sopenharmony_ci		/* copy to output buffer */
1528c2ecf20Sopenharmony_ci		memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen);
1538c2ecf20Sopenharmony_ci		nvp->nvram_len += cplen;
1548c2ecf20Sopenharmony_ci		nvp->nvram[nvp->nvram_len] = '\0';
1558c2ecf20Sopenharmony_ci		nvp->nvram_len++;
1568c2ecf20Sopenharmony_ci		return IDLE;
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci	nvp->pos++;
1598c2ecf20Sopenharmony_ci	nvp->column++;
1608c2ecf20Sopenharmony_ci	return VALUE;
1618c2ecf20Sopenharmony_ci}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cistatic enum nvram_parser_state
1648c2ecf20Sopenharmony_cibrcmf_nvram_handle_comment(struct nvram_parser *nvp)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	char *eoc, *sol;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	sol = (char *)&nvp->data[nvp->pos];
1698c2ecf20Sopenharmony_ci	eoc = strchr(sol, '\n');
1708c2ecf20Sopenharmony_ci	if (!eoc) {
1718c2ecf20Sopenharmony_ci		eoc = strchr(sol, '\0');
1728c2ecf20Sopenharmony_ci		if (!eoc)
1738c2ecf20Sopenharmony_ci			return END;
1748c2ecf20Sopenharmony_ci	}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	/* eat all moving to next line */
1778c2ecf20Sopenharmony_ci	nvp->line++;
1788c2ecf20Sopenharmony_ci	nvp->column = 1;
1798c2ecf20Sopenharmony_ci	nvp->pos += (eoc - sol) + 1;
1808c2ecf20Sopenharmony_ci	return IDLE;
1818c2ecf20Sopenharmony_ci}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_cistatic enum nvram_parser_state brcmf_nvram_handle_end(struct nvram_parser *nvp)
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	/* final state */
1868c2ecf20Sopenharmony_ci	return END;
1878c2ecf20Sopenharmony_ci}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cistatic enum nvram_parser_state
1908c2ecf20Sopenharmony_ci(*nv_parser_states[])(struct nvram_parser *nvp) = {
1918c2ecf20Sopenharmony_ci	brcmf_nvram_handle_idle,
1928c2ecf20Sopenharmony_ci	brcmf_nvram_handle_key,
1938c2ecf20Sopenharmony_ci	brcmf_nvram_handle_value,
1948c2ecf20Sopenharmony_ci	brcmf_nvram_handle_comment,
1958c2ecf20Sopenharmony_ci	brcmf_nvram_handle_end
1968c2ecf20Sopenharmony_ci};
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic int brcmf_init_nvram_parser(struct nvram_parser *nvp,
1998c2ecf20Sopenharmony_ci				   const u8 *data, size_t data_len)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	size_t size;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	memset(nvp, 0, sizeof(*nvp));
2048c2ecf20Sopenharmony_ci	nvp->data = data;
2058c2ecf20Sopenharmony_ci	/* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */
2068c2ecf20Sopenharmony_ci	if (data_len > BRCMF_FW_MAX_NVRAM_SIZE)
2078c2ecf20Sopenharmony_ci		size = BRCMF_FW_MAX_NVRAM_SIZE;
2088c2ecf20Sopenharmony_ci	else
2098c2ecf20Sopenharmony_ci		size = data_len;
2108c2ecf20Sopenharmony_ci	/* Add space for properties we may add */
2118c2ecf20Sopenharmony_ci	size += strlen(BRCMF_FW_DEFAULT_BOARDREV) + 1;
2128c2ecf20Sopenharmony_ci	/* Alloc for extra 0 byte + roundup by 4 + length field */
2138c2ecf20Sopenharmony_ci	size += 1 + 3 + sizeof(u32);
2148c2ecf20Sopenharmony_ci	nvp->nvram = kzalloc(size, GFP_KERNEL);
2158c2ecf20Sopenharmony_ci	if (!nvp->nvram)
2168c2ecf20Sopenharmony_ci		return -ENOMEM;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	nvp->line = 1;
2198c2ecf20Sopenharmony_ci	nvp->column = 1;
2208c2ecf20Sopenharmony_ci	return 0;
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci/* brcmf_fw_strip_multi_v1 :Some nvram files contain settings for multiple
2248c2ecf20Sopenharmony_ci * devices. Strip it down for one device, use domain_nr/bus_nr to determine
2258c2ecf20Sopenharmony_ci * which data is to be returned. v1 is the version where nvram is stored
2268c2ecf20Sopenharmony_ci * compressed and "devpath" maps to index for valid entries.
2278c2ecf20Sopenharmony_ci */
2288c2ecf20Sopenharmony_cistatic void brcmf_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr,
2298c2ecf20Sopenharmony_ci				    u16 bus_nr)
2308c2ecf20Sopenharmony_ci{
2318c2ecf20Sopenharmony_ci	/* Device path with a leading '=' key-value separator */
2328c2ecf20Sopenharmony_ci	char pci_path[] = "=pci/?/?";
2338c2ecf20Sopenharmony_ci	size_t pci_len;
2348c2ecf20Sopenharmony_ci	char pcie_path[] = "=pcie/?/?";
2358c2ecf20Sopenharmony_ci	size_t pcie_len;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	u32 i, j;
2388c2ecf20Sopenharmony_ci	bool found;
2398c2ecf20Sopenharmony_ci	u8 *nvram;
2408c2ecf20Sopenharmony_ci	u8 id;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
2438c2ecf20Sopenharmony_ci	if (!nvram)
2448c2ecf20Sopenharmony_ci		goto fail;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	/* min length: devpath0=pcie/1/4/ + 0:x=y */
2478c2ecf20Sopenharmony_ci	if (nvp->nvram_len < BRCMF_FW_NVRAM_DEVPATH_LEN + 6)
2488c2ecf20Sopenharmony_ci		goto fail;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	/* First search for the devpathX and see if it is the configuration
2518c2ecf20Sopenharmony_ci	 * for domain_nr/bus_nr. Search complete nvp
2528c2ecf20Sopenharmony_ci	 */
2538c2ecf20Sopenharmony_ci	snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr,
2548c2ecf20Sopenharmony_ci		 bus_nr);
2558c2ecf20Sopenharmony_ci	pci_len = strlen(pci_path);
2568c2ecf20Sopenharmony_ci	snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr,
2578c2ecf20Sopenharmony_ci		 bus_nr);
2588c2ecf20Sopenharmony_ci	pcie_len = strlen(pcie_path);
2598c2ecf20Sopenharmony_ci	found = false;
2608c2ecf20Sopenharmony_ci	i = 0;
2618c2ecf20Sopenharmony_ci	while (i < nvp->nvram_len - BRCMF_FW_NVRAM_DEVPATH_LEN) {
2628c2ecf20Sopenharmony_ci		/* Format: devpathX=pcie/Y/Z/
2638c2ecf20Sopenharmony_ci		 * Y = domain_nr, Z = bus_nr, X = virtual ID
2648c2ecf20Sopenharmony_ci		 */
2658c2ecf20Sopenharmony_ci		if (strncmp(&nvp->nvram[i], "devpath", 7) == 0 &&
2668c2ecf20Sopenharmony_ci		    (!strncmp(&nvp->nvram[i + 8], pci_path, pci_len) ||
2678c2ecf20Sopenharmony_ci		     !strncmp(&nvp->nvram[i + 8], pcie_path, pcie_len))) {
2688c2ecf20Sopenharmony_ci			id = nvp->nvram[i + 7] - '0';
2698c2ecf20Sopenharmony_ci			found = true;
2708c2ecf20Sopenharmony_ci			break;
2718c2ecf20Sopenharmony_ci		}
2728c2ecf20Sopenharmony_ci		while (nvp->nvram[i] != 0)
2738c2ecf20Sopenharmony_ci			i++;
2748c2ecf20Sopenharmony_ci		i++;
2758c2ecf20Sopenharmony_ci	}
2768c2ecf20Sopenharmony_ci	if (!found)
2778c2ecf20Sopenharmony_ci		goto fail;
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	/* Now copy all valid entries, release old nvram and assign new one */
2808c2ecf20Sopenharmony_ci	i = 0;
2818c2ecf20Sopenharmony_ci	j = 0;
2828c2ecf20Sopenharmony_ci	while (i < nvp->nvram_len) {
2838c2ecf20Sopenharmony_ci		if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) {
2848c2ecf20Sopenharmony_ci			i += 2;
2858c2ecf20Sopenharmony_ci			if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)
2868c2ecf20Sopenharmony_ci				nvp->boardrev_found = true;
2878c2ecf20Sopenharmony_ci			while (nvp->nvram[i] != 0) {
2888c2ecf20Sopenharmony_ci				nvram[j] = nvp->nvram[i];
2898c2ecf20Sopenharmony_ci				i++;
2908c2ecf20Sopenharmony_ci				j++;
2918c2ecf20Sopenharmony_ci			}
2928c2ecf20Sopenharmony_ci			nvram[j] = 0;
2938c2ecf20Sopenharmony_ci			j++;
2948c2ecf20Sopenharmony_ci		}
2958c2ecf20Sopenharmony_ci		while (nvp->nvram[i] != 0)
2968c2ecf20Sopenharmony_ci			i++;
2978c2ecf20Sopenharmony_ci		i++;
2988c2ecf20Sopenharmony_ci	}
2998c2ecf20Sopenharmony_ci	kfree(nvp->nvram);
3008c2ecf20Sopenharmony_ci	nvp->nvram = nvram;
3018c2ecf20Sopenharmony_ci	nvp->nvram_len = j;
3028c2ecf20Sopenharmony_ci	return;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_cifail:
3058c2ecf20Sopenharmony_ci	kfree(nvram);
3068c2ecf20Sopenharmony_ci	nvp->nvram_len = 0;
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci/* brcmf_fw_strip_multi_v2 :Some nvram files contain settings for multiple
3108c2ecf20Sopenharmony_ci * devices. Strip it down for one device, use domain_nr/bus_nr to determine
3118c2ecf20Sopenharmony_ci * which data is to be returned. v2 is the version where nvram is stored
3128c2ecf20Sopenharmony_ci * uncompressed, all relevant valid entries are identified by
3138c2ecf20Sopenharmony_ci * pcie/domain_nr/bus_nr:
3148c2ecf20Sopenharmony_ci */
3158c2ecf20Sopenharmony_cistatic void brcmf_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr,
3168c2ecf20Sopenharmony_ci				    u16 bus_nr)
3178c2ecf20Sopenharmony_ci{
3188c2ecf20Sopenharmony_ci	char prefix[BRCMF_FW_NVRAM_PCIEDEV_LEN];
3198c2ecf20Sopenharmony_ci	size_t len;
3208c2ecf20Sopenharmony_ci	u32 i, j;
3218c2ecf20Sopenharmony_ci	u8 *nvram;
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
3248c2ecf20Sopenharmony_ci	if (!nvram)
3258c2ecf20Sopenharmony_ci		goto fail;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	/* Copy all valid entries, release old nvram and assign new one.
3288c2ecf20Sopenharmony_ci	 * Valid entries are of type pcie/X/Y/ where X = domain_nr and
3298c2ecf20Sopenharmony_ci	 * Y = bus_nr.
3308c2ecf20Sopenharmony_ci	 */
3318c2ecf20Sopenharmony_ci	snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr);
3328c2ecf20Sopenharmony_ci	len = strlen(prefix);
3338c2ecf20Sopenharmony_ci	i = 0;
3348c2ecf20Sopenharmony_ci	j = 0;
3358c2ecf20Sopenharmony_ci	while (i < nvp->nvram_len - len) {
3368c2ecf20Sopenharmony_ci		if (strncmp(&nvp->nvram[i], prefix, len) == 0) {
3378c2ecf20Sopenharmony_ci			i += len;
3388c2ecf20Sopenharmony_ci			if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)
3398c2ecf20Sopenharmony_ci				nvp->boardrev_found = true;
3408c2ecf20Sopenharmony_ci			while (nvp->nvram[i] != 0) {
3418c2ecf20Sopenharmony_ci				nvram[j] = nvp->nvram[i];
3428c2ecf20Sopenharmony_ci				i++;
3438c2ecf20Sopenharmony_ci				j++;
3448c2ecf20Sopenharmony_ci			}
3458c2ecf20Sopenharmony_ci			nvram[j] = 0;
3468c2ecf20Sopenharmony_ci			j++;
3478c2ecf20Sopenharmony_ci		}
3488c2ecf20Sopenharmony_ci		while (nvp->nvram[i] != 0)
3498c2ecf20Sopenharmony_ci			i++;
3508c2ecf20Sopenharmony_ci		i++;
3518c2ecf20Sopenharmony_ci	}
3528c2ecf20Sopenharmony_ci	kfree(nvp->nvram);
3538c2ecf20Sopenharmony_ci	nvp->nvram = nvram;
3548c2ecf20Sopenharmony_ci	nvp->nvram_len = j;
3558c2ecf20Sopenharmony_ci	return;
3568c2ecf20Sopenharmony_cifail:
3578c2ecf20Sopenharmony_ci	kfree(nvram);
3588c2ecf20Sopenharmony_ci	nvp->nvram_len = 0;
3598c2ecf20Sopenharmony_ci}
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_cistatic void brcmf_fw_add_defaults(struct nvram_parser *nvp)
3628c2ecf20Sopenharmony_ci{
3638c2ecf20Sopenharmony_ci	if (nvp->boardrev_found)
3648c2ecf20Sopenharmony_ci		return;
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	memcpy(&nvp->nvram[nvp->nvram_len], &BRCMF_FW_DEFAULT_BOARDREV,
3678c2ecf20Sopenharmony_ci	       strlen(BRCMF_FW_DEFAULT_BOARDREV));
3688c2ecf20Sopenharmony_ci	nvp->nvram_len += strlen(BRCMF_FW_DEFAULT_BOARDREV);
3698c2ecf20Sopenharmony_ci	nvp->nvram[nvp->nvram_len] = '\0';
3708c2ecf20Sopenharmony_ci	nvp->nvram_len++;
3718c2ecf20Sopenharmony_ci}
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci/* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil
3748c2ecf20Sopenharmony_ci * and ending in a NUL. Removes carriage returns, empty lines, comment lines,
3758c2ecf20Sopenharmony_ci * and converts newlines to NULs. Shortens buffer as needed and pads with NULs.
3768c2ecf20Sopenharmony_ci * End of buffer is completed with token identifying length of buffer.
3778c2ecf20Sopenharmony_ci */
3788c2ecf20Sopenharmony_cistatic void *brcmf_fw_nvram_strip(const u8 *data, size_t data_len,
3798c2ecf20Sopenharmony_ci				  u32 *new_length, u16 domain_nr, u16 bus_nr)
3808c2ecf20Sopenharmony_ci{
3818c2ecf20Sopenharmony_ci	struct nvram_parser nvp;
3828c2ecf20Sopenharmony_ci	u32 pad;
3838c2ecf20Sopenharmony_ci	u32 token;
3848c2ecf20Sopenharmony_ci	__le32 token_le;
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci	if (brcmf_init_nvram_parser(&nvp, data, data_len) < 0)
3878c2ecf20Sopenharmony_ci		return NULL;
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	while (nvp.pos < data_len) {
3908c2ecf20Sopenharmony_ci		nvp.state = nv_parser_states[nvp.state](&nvp);
3918c2ecf20Sopenharmony_ci		if (nvp.state == END)
3928c2ecf20Sopenharmony_ci			break;
3938c2ecf20Sopenharmony_ci	}
3948c2ecf20Sopenharmony_ci	if (nvp.multi_dev_v1) {
3958c2ecf20Sopenharmony_ci		nvp.boardrev_found = false;
3968c2ecf20Sopenharmony_ci		brcmf_fw_strip_multi_v1(&nvp, domain_nr, bus_nr);
3978c2ecf20Sopenharmony_ci	} else if (nvp.multi_dev_v2) {
3988c2ecf20Sopenharmony_ci		nvp.boardrev_found = false;
3998c2ecf20Sopenharmony_ci		brcmf_fw_strip_multi_v2(&nvp, domain_nr, bus_nr);
4008c2ecf20Sopenharmony_ci	}
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	if (nvp.nvram_len == 0) {
4038c2ecf20Sopenharmony_ci		kfree(nvp.nvram);
4048c2ecf20Sopenharmony_ci		return NULL;
4058c2ecf20Sopenharmony_ci	}
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	brcmf_fw_add_defaults(&nvp);
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	pad = nvp.nvram_len;
4108c2ecf20Sopenharmony_ci	*new_length = roundup(nvp.nvram_len + 1, 4);
4118c2ecf20Sopenharmony_ci	while (pad != *new_length) {
4128c2ecf20Sopenharmony_ci		nvp.nvram[pad] = 0;
4138c2ecf20Sopenharmony_ci		pad++;
4148c2ecf20Sopenharmony_ci	}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_ci	token = *new_length / 4;
4178c2ecf20Sopenharmony_ci	token = (~token << 16) | (token & 0x0000FFFF);
4188c2ecf20Sopenharmony_ci	token_le = cpu_to_le32(token);
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le));
4218c2ecf20Sopenharmony_ci	*new_length += sizeof(token_le);
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	return nvp.nvram;
4248c2ecf20Sopenharmony_ci}
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_civoid brcmf_fw_nvram_free(void *nvram)
4278c2ecf20Sopenharmony_ci{
4288c2ecf20Sopenharmony_ci	kfree(nvram);
4298c2ecf20Sopenharmony_ci}
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_cistruct brcmf_fw {
4328c2ecf20Sopenharmony_ci	struct device *dev;
4338c2ecf20Sopenharmony_ci	struct brcmf_fw_request *req;
4348c2ecf20Sopenharmony_ci	u32 curpos;
4358c2ecf20Sopenharmony_ci	void (*done)(struct device *dev, int err, struct brcmf_fw_request *req);
4368c2ecf20Sopenharmony_ci};
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_cistatic void brcmf_fw_request_done(const struct firmware *fw, void *ctx);
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci#ifdef CONFIG_EFI
4418c2ecf20Sopenharmony_ci/* In some cases the EFI-var stored nvram contains "ccode=ALL" or "ccode=XV"
4428c2ecf20Sopenharmony_ci * to specify "worldwide" compatible settings, but these 2 ccode-s do not work
4438c2ecf20Sopenharmony_ci * properly. "ccode=ALL" causes channels 12 and 13 to not be available,
4448c2ecf20Sopenharmony_ci * "ccode=XV" causes all 5GHz channels to not be available. So we replace both
4458c2ecf20Sopenharmony_ci * with "ccode=X2" which allows channels 12+13 and 5Ghz channels in
4468c2ecf20Sopenharmony_ci * no-Initiate-Radiation mode. This means that we will never send on these
4478c2ecf20Sopenharmony_ci * channels without first having received valid wifi traffic on the channel.
4488c2ecf20Sopenharmony_ci */
4498c2ecf20Sopenharmony_cistatic void brcmf_fw_fix_efi_nvram_ccode(char *data, unsigned long data_len)
4508c2ecf20Sopenharmony_ci{
4518c2ecf20Sopenharmony_ci	char *ccode;
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	ccode = strnstr((char *)data, "ccode=ALL", data_len);
4548c2ecf20Sopenharmony_ci	if (!ccode)
4558c2ecf20Sopenharmony_ci		ccode = strnstr((char *)data, "ccode=XV\r", data_len);
4568c2ecf20Sopenharmony_ci	if (!ccode)
4578c2ecf20Sopenharmony_ci		return;
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	ccode[6] = 'X';
4608c2ecf20Sopenharmony_ci	ccode[7] = '2';
4618c2ecf20Sopenharmony_ci	ccode[8] = '\r';
4628c2ecf20Sopenharmony_ci}
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_cistatic u8 *brcmf_fw_nvram_from_efi(size_t *data_len_ret)
4658c2ecf20Sopenharmony_ci{
4668c2ecf20Sopenharmony_ci	const u16 name[] = { 'n', 'v', 'r', 'a', 'm', 0 };
4678c2ecf20Sopenharmony_ci	struct efivar_entry *nvram_efivar;
4688c2ecf20Sopenharmony_ci	unsigned long data_len = 0;
4698c2ecf20Sopenharmony_ci	u8 *data = NULL;
4708c2ecf20Sopenharmony_ci	int err;
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	nvram_efivar = kzalloc(sizeof(*nvram_efivar), GFP_KERNEL);
4738c2ecf20Sopenharmony_ci	if (!nvram_efivar)
4748c2ecf20Sopenharmony_ci		return NULL;
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	memcpy(&nvram_efivar->var.VariableName, name, sizeof(name));
4778c2ecf20Sopenharmony_ci	nvram_efivar->var.VendorGuid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61,
4788c2ecf20Sopenharmony_ci						0xb5, 0x1f, 0x43, 0x26,
4798c2ecf20Sopenharmony_ci						0x81, 0x23, 0xd1, 0x13);
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	err = efivar_entry_size(nvram_efivar, &data_len);
4828c2ecf20Sopenharmony_ci	if (err)
4838c2ecf20Sopenharmony_ci		goto fail;
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	data = kmalloc(data_len, GFP_KERNEL);
4868c2ecf20Sopenharmony_ci	if (!data)
4878c2ecf20Sopenharmony_ci		goto fail;
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	err = efivar_entry_get(nvram_efivar, NULL, &data_len, data);
4908c2ecf20Sopenharmony_ci	if (err)
4918c2ecf20Sopenharmony_ci		goto fail;
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci	brcmf_fw_fix_efi_nvram_ccode(data, data_len);
4948c2ecf20Sopenharmony_ci	brcmf_info("Using nvram EFI variable\n");
4958c2ecf20Sopenharmony_ci
4968c2ecf20Sopenharmony_ci	kfree(nvram_efivar);
4978c2ecf20Sopenharmony_ci	*data_len_ret = data_len;
4988c2ecf20Sopenharmony_ci	return data;
4998c2ecf20Sopenharmony_ci
5008c2ecf20Sopenharmony_cifail:
5018c2ecf20Sopenharmony_ci	kfree(data);
5028c2ecf20Sopenharmony_ci	kfree(nvram_efivar);
5038c2ecf20Sopenharmony_ci	return NULL;
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci#else
5068c2ecf20Sopenharmony_cistatic inline u8 *brcmf_fw_nvram_from_efi(size_t *data_len) { return NULL; }
5078c2ecf20Sopenharmony_ci#endif
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_cistatic void brcmf_fw_free_request(struct brcmf_fw_request *req)
5108c2ecf20Sopenharmony_ci{
5118c2ecf20Sopenharmony_ci	struct brcmf_fw_item *item;
5128c2ecf20Sopenharmony_ci	int i;
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci	for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {
5158c2ecf20Sopenharmony_ci		if (item->type == BRCMF_FW_TYPE_BINARY)
5168c2ecf20Sopenharmony_ci			release_firmware(item->binary);
5178c2ecf20Sopenharmony_ci		else if (item->type == BRCMF_FW_TYPE_NVRAM)
5188c2ecf20Sopenharmony_ci			brcmf_fw_nvram_free(item->nv_data.data);
5198c2ecf20Sopenharmony_ci	}
5208c2ecf20Sopenharmony_ci	kfree(req);
5218c2ecf20Sopenharmony_ci}
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_cistatic int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx)
5248c2ecf20Sopenharmony_ci{
5258c2ecf20Sopenharmony_ci	struct brcmf_fw *fwctx = ctx;
5268c2ecf20Sopenharmony_ci	struct brcmf_fw_item *cur;
5278c2ecf20Sopenharmony_ci	bool free_bcm47xx_nvram = false;
5288c2ecf20Sopenharmony_ci	bool kfree_nvram = false;
5298c2ecf20Sopenharmony_ci	u32 nvram_length = 0;
5308c2ecf20Sopenharmony_ci	void *nvram = NULL;
5318c2ecf20Sopenharmony_ci	u8 *data = NULL;
5328c2ecf20Sopenharmony_ci	size_t data_len;
5338c2ecf20Sopenharmony_ci
5348c2ecf20Sopenharmony_ci	brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(fwctx->dev));
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci	cur = &fwctx->req->items[fwctx->curpos];
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci	if (fw && fw->data) {
5398c2ecf20Sopenharmony_ci		data = (u8 *)fw->data;
5408c2ecf20Sopenharmony_ci		data_len = fw->size;
5418c2ecf20Sopenharmony_ci	} else {
5428c2ecf20Sopenharmony_ci		if ((data = bcm47xx_nvram_get_contents(&data_len)))
5438c2ecf20Sopenharmony_ci			free_bcm47xx_nvram = true;
5448c2ecf20Sopenharmony_ci		else if ((data = brcmf_fw_nvram_from_efi(&data_len)))
5458c2ecf20Sopenharmony_ci			kfree_nvram = true;
5468c2ecf20Sopenharmony_ci		else if (!(cur->flags & BRCMF_FW_REQF_OPTIONAL))
5478c2ecf20Sopenharmony_ci			goto fail;
5488c2ecf20Sopenharmony_ci	}
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	if (data)
5518c2ecf20Sopenharmony_ci		nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length,
5528c2ecf20Sopenharmony_ci					     fwctx->req->domain_nr,
5538c2ecf20Sopenharmony_ci					     fwctx->req->bus_nr);
5548c2ecf20Sopenharmony_ci
5558c2ecf20Sopenharmony_ci	if (free_bcm47xx_nvram)
5568c2ecf20Sopenharmony_ci		bcm47xx_nvram_release_contents(data);
5578c2ecf20Sopenharmony_ci	if (kfree_nvram)
5588c2ecf20Sopenharmony_ci		kfree(data);
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	release_firmware(fw);
5618c2ecf20Sopenharmony_ci	if (!nvram && !(cur->flags & BRCMF_FW_REQF_OPTIONAL))
5628c2ecf20Sopenharmony_ci		goto fail;
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci	brcmf_dbg(TRACE, "nvram %p len %d\n", nvram, nvram_length);
5658c2ecf20Sopenharmony_ci	cur->nv_data.data = nvram;
5668c2ecf20Sopenharmony_ci	cur->nv_data.len = nvram_length;
5678c2ecf20Sopenharmony_ci	return 0;
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_cifail:
5708c2ecf20Sopenharmony_ci	return -ENOENT;
5718c2ecf20Sopenharmony_ci}
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_cistatic int brcmf_fw_complete_request(const struct firmware *fw,
5748c2ecf20Sopenharmony_ci				     struct brcmf_fw *fwctx)
5758c2ecf20Sopenharmony_ci{
5768c2ecf20Sopenharmony_ci	struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos];
5778c2ecf20Sopenharmony_ci	int ret = 0;
5788c2ecf20Sopenharmony_ci
5798c2ecf20Sopenharmony_ci	brcmf_dbg(TRACE, "firmware %s %sfound\n", cur->path, fw ? "" : "not ");
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	switch (cur->type) {
5828c2ecf20Sopenharmony_ci	case BRCMF_FW_TYPE_NVRAM:
5838c2ecf20Sopenharmony_ci		ret = brcmf_fw_request_nvram_done(fw, fwctx);
5848c2ecf20Sopenharmony_ci		break;
5858c2ecf20Sopenharmony_ci	case BRCMF_FW_TYPE_BINARY:
5868c2ecf20Sopenharmony_ci		if (fw)
5878c2ecf20Sopenharmony_ci			cur->binary = fw;
5888c2ecf20Sopenharmony_ci		else
5898c2ecf20Sopenharmony_ci			ret = -ENOENT;
5908c2ecf20Sopenharmony_ci		break;
5918c2ecf20Sopenharmony_ci	default:
5928c2ecf20Sopenharmony_ci		/* something fishy here so bail out early */
5938c2ecf20Sopenharmony_ci		brcmf_err("unknown fw type: %d\n", cur->type);
5948c2ecf20Sopenharmony_ci		release_firmware(fw);
5958c2ecf20Sopenharmony_ci		ret = -EINVAL;
5968c2ecf20Sopenharmony_ci	}
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ci	return (cur->flags & BRCMF_FW_REQF_OPTIONAL) ? 0 : ret;
5998c2ecf20Sopenharmony_ci}
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_cistatic int brcmf_fw_request_firmware(const struct firmware **fw,
6028c2ecf20Sopenharmony_ci				     struct brcmf_fw *fwctx)
6038c2ecf20Sopenharmony_ci{
6048c2ecf20Sopenharmony_ci	struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos];
6058c2ecf20Sopenharmony_ci	int ret;
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	/* nvram files are board-specific, first try a board-specific path */
6088c2ecf20Sopenharmony_ci	if (cur->type == BRCMF_FW_TYPE_NVRAM && fwctx->req->board_type) {
6098c2ecf20Sopenharmony_ci		char alt_path[BRCMF_FW_NAME_LEN];
6108c2ecf20Sopenharmony_ci
6118c2ecf20Sopenharmony_ci		strlcpy(alt_path, cur->path, BRCMF_FW_NAME_LEN);
6128c2ecf20Sopenharmony_ci		/* strip .txt at the end */
6138c2ecf20Sopenharmony_ci		alt_path[strlen(alt_path) - 4] = 0;
6148c2ecf20Sopenharmony_ci		strlcat(alt_path, ".", BRCMF_FW_NAME_LEN);
6158c2ecf20Sopenharmony_ci		strlcat(alt_path, fwctx->req->board_type, BRCMF_FW_NAME_LEN);
6168c2ecf20Sopenharmony_ci		strlcat(alt_path, ".txt", BRCMF_FW_NAME_LEN);
6178c2ecf20Sopenharmony_ci
6188c2ecf20Sopenharmony_ci		ret = request_firmware(fw, alt_path, fwctx->dev);
6198c2ecf20Sopenharmony_ci		if (ret == 0)
6208c2ecf20Sopenharmony_ci			return ret;
6218c2ecf20Sopenharmony_ci	}
6228c2ecf20Sopenharmony_ci
6238c2ecf20Sopenharmony_ci	return request_firmware(fw, cur->path, fwctx->dev);
6248c2ecf20Sopenharmony_ci}
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_cistatic void brcmf_fw_request_done(const struct firmware *fw, void *ctx)
6278c2ecf20Sopenharmony_ci{
6288c2ecf20Sopenharmony_ci	struct brcmf_fw *fwctx = ctx;
6298c2ecf20Sopenharmony_ci	int ret;
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_ci	ret = brcmf_fw_complete_request(fw, fwctx);
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) {
6348c2ecf20Sopenharmony_ci		brcmf_fw_request_firmware(&fw, fwctx);
6358c2ecf20Sopenharmony_ci		ret = brcmf_fw_complete_request(fw, ctx);
6368c2ecf20Sopenharmony_ci	}
6378c2ecf20Sopenharmony_ci
6388c2ecf20Sopenharmony_ci	if (ret) {
6398c2ecf20Sopenharmony_ci		brcmf_fw_free_request(fwctx->req);
6408c2ecf20Sopenharmony_ci		fwctx->req = NULL;
6418c2ecf20Sopenharmony_ci	}
6428c2ecf20Sopenharmony_ci	fwctx->done(fwctx->dev, ret, fwctx->req);
6438c2ecf20Sopenharmony_ci	kfree(fwctx);
6448c2ecf20Sopenharmony_ci}
6458c2ecf20Sopenharmony_ci
6468c2ecf20Sopenharmony_cistatic bool brcmf_fw_request_is_valid(struct brcmf_fw_request *req)
6478c2ecf20Sopenharmony_ci{
6488c2ecf20Sopenharmony_ci	struct brcmf_fw_item *item;
6498c2ecf20Sopenharmony_ci	int i;
6508c2ecf20Sopenharmony_ci
6518c2ecf20Sopenharmony_ci	if (!req->n_items)
6528c2ecf20Sopenharmony_ci		return false;
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_ci	for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {
6558c2ecf20Sopenharmony_ci		if (!item->path)
6568c2ecf20Sopenharmony_ci			return false;
6578c2ecf20Sopenharmony_ci	}
6588c2ecf20Sopenharmony_ci	return true;
6598c2ecf20Sopenharmony_ci}
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ciint brcmf_fw_get_firmwares(struct device *dev, struct brcmf_fw_request *req,
6628c2ecf20Sopenharmony_ci			   void (*fw_cb)(struct device *dev, int err,
6638c2ecf20Sopenharmony_ci					 struct brcmf_fw_request *req))
6648c2ecf20Sopenharmony_ci{
6658c2ecf20Sopenharmony_ci	struct brcmf_fw_item *first = &req->items[0];
6668c2ecf20Sopenharmony_ci	struct brcmf_fw *fwctx;
6678c2ecf20Sopenharmony_ci	int ret;
6688c2ecf20Sopenharmony_ci
6698c2ecf20Sopenharmony_ci	brcmf_dbg(TRACE, "enter: dev=%s\n", dev_name(dev));
6708c2ecf20Sopenharmony_ci	if (!fw_cb)
6718c2ecf20Sopenharmony_ci		return -EINVAL;
6728c2ecf20Sopenharmony_ci
6738c2ecf20Sopenharmony_ci	if (!brcmf_fw_request_is_valid(req))
6748c2ecf20Sopenharmony_ci		return -EINVAL;
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci	fwctx = kzalloc(sizeof(*fwctx), GFP_KERNEL);
6778c2ecf20Sopenharmony_ci	if (!fwctx)
6788c2ecf20Sopenharmony_ci		return -ENOMEM;
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci	fwctx->dev = dev;
6818c2ecf20Sopenharmony_ci	fwctx->req = req;
6828c2ecf20Sopenharmony_ci	fwctx->done = fw_cb;
6838c2ecf20Sopenharmony_ci
6848c2ecf20Sopenharmony_ci	ret = request_firmware_nowait(THIS_MODULE, true, first->path,
6858c2ecf20Sopenharmony_ci				      fwctx->dev, GFP_KERNEL, fwctx,
6868c2ecf20Sopenharmony_ci				      brcmf_fw_request_done);
6878c2ecf20Sopenharmony_ci	if (ret < 0)
6888c2ecf20Sopenharmony_ci		brcmf_fw_request_done(NULL, fwctx);
6898c2ecf20Sopenharmony_ci
6908c2ecf20Sopenharmony_ci	return 0;
6918c2ecf20Sopenharmony_ci}
6928c2ecf20Sopenharmony_ci
6938c2ecf20Sopenharmony_cistruct brcmf_fw_request *
6948c2ecf20Sopenharmony_cibrcmf_fw_alloc_request(u32 chip, u32 chiprev,
6958c2ecf20Sopenharmony_ci		       const struct brcmf_firmware_mapping mapping_table[],
6968c2ecf20Sopenharmony_ci		       u32 table_size, struct brcmf_fw_name *fwnames,
6978c2ecf20Sopenharmony_ci		       u32 n_fwnames)
6988c2ecf20Sopenharmony_ci{
6998c2ecf20Sopenharmony_ci	struct brcmf_fw_request *fwreq;
7008c2ecf20Sopenharmony_ci	char chipname[12];
7018c2ecf20Sopenharmony_ci	const char *mp_path;
7028c2ecf20Sopenharmony_ci	size_t mp_path_len;
7038c2ecf20Sopenharmony_ci	u32 i, j;
7048c2ecf20Sopenharmony_ci	char end = '\0';
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci	if (chiprev >= BITS_PER_TYPE(u32)) {
7078c2ecf20Sopenharmony_ci		brcmf_err("Invalid chip revision %u\n", chiprev);
7088c2ecf20Sopenharmony_ci		return NULL;
7098c2ecf20Sopenharmony_ci	}
7108c2ecf20Sopenharmony_ci
7118c2ecf20Sopenharmony_ci	for (i = 0; i < table_size; i++) {
7128c2ecf20Sopenharmony_ci		if (mapping_table[i].chipid == chip &&
7138c2ecf20Sopenharmony_ci		    mapping_table[i].revmask & BIT(chiprev))
7148c2ecf20Sopenharmony_ci			break;
7158c2ecf20Sopenharmony_ci	}
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci	brcmf_chip_name(chip, chiprev, chipname, sizeof(chipname));
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_ci	if (i == table_size) {
7208c2ecf20Sopenharmony_ci		brcmf_err("Unknown chip %s\n", chipname);
7218c2ecf20Sopenharmony_ci		return NULL;
7228c2ecf20Sopenharmony_ci	}
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci	fwreq = kzalloc(struct_size(fwreq, items, n_fwnames), GFP_KERNEL);
7258c2ecf20Sopenharmony_ci	if (!fwreq)
7268c2ecf20Sopenharmony_ci		return NULL;
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_ci	brcmf_info("using %s for chip %s\n",
7298c2ecf20Sopenharmony_ci		   mapping_table[i].fw_base, chipname);
7308c2ecf20Sopenharmony_ci
7318c2ecf20Sopenharmony_ci	mp_path = brcmf_mp_global.firmware_path;
7328c2ecf20Sopenharmony_ci	mp_path_len = strnlen(mp_path, BRCMF_FW_ALTPATH_LEN);
7338c2ecf20Sopenharmony_ci	if (mp_path_len)
7348c2ecf20Sopenharmony_ci		end = mp_path[mp_path_len - 1];
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci	fwreq->n_items = n_fwnames;
7378c2ecf20Sopenharmony_ci
7388c2ecf20Sopenharmony_ci	for (j = 0; j < n_fwnames; j++) {
7398c2ecf20Sopenharmony_ci		fwreq->items[j].path = fwnames[j].path;
7408c2ecf20Sopenharmony_ci		fwnames[j].path[0] = '\0';
7418c2ecf20Sopenharmony_ci		/* check if firmware path is provided by module parameter */
7428c2ecf20Sopenharmony_ci		if (brcmf_mp_global.firmware_path[0] != '\0') {
7438c2ecf20Sopenharmony_ci			strlcpy(fwnames[j].path, mp_path,
7448c2ecf20Sopenharmony_ci				BRCMF_FW_NAME_LEN);
7458c2ecf20Sopenharmony_ci
7468c2ecf20Sopenharmony_ci			if (end != '/') {
7478c2ecf20Sopenharmony_ci				strlcat(fwnames[j].path, "/",
7488c2ecf20Sopenharmony_ci					BRCMF_FW_NAME_LEN);
7498c2ecf20Sopenharmony_ci			}
7508c2ecf20Sopenharmony_ci		}
7518c2ecf20Sopenharmony_ci		strlcat(fwnames[j].path, mapping_table[i].fw_base,
7528c2ecf20Sopenharmony_ci			BRCMF_FW_NAME_LEN);
7538c2ecf20Sopenharmony_ci		strlcat(fwnames[j].path, fwnames[j].extension,
7548c2ecf20Sopenharmony_ci			BRCMF_FW_NAME_LEN);
7558c2ecf20Sopenharmony_ci		fwreq->items[j].path = fwnames[j].path;
7568c2ecf20Sopenharmony_ci	}
7578c2ecf20Sopenharmony_ci
7588c2ecf20Sopenharmony_ci	return fwreq;
7598c2ecf20Sopenharmony_ci}
760