mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 14:04:26 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			435 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			435 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From fdd0bd88ceaecf729db103ac8836af5805dd2dc1 Mon Sep 17 00:00:00 2001
 | |
| From: Chung-Hsien Hsu <stanley.hsu@cypress.com>
 | |
| Date: Fri, 10 Nov 2017 17:27:15 +0800
 | |
| Subject: [PATCH] brcmfmac: add CLM download support
 | |
| 
 | |
| The firmware for brcmfmac devices includes information regarding
 | |
| regulatory constraints. For certain devices this information is kept
 | |
| separately in a binary form that needs to be downloaded to the device.
 | |
| This patch adds support to download this so-called CLM blob file. It
 | |
| uses the same naming scheme as the other firmware files with extension
 | |
| of .clm_blob.
 | |
| 
 | |
| The CLM blob file is optional. If the file does not exist, the download
 | |
| process will be bypassed. It will not affect the driver loading.
 | |
| 
 | |
| Reviewed-by: Arend van Spriel <arend.vanspriel@broadcom.com>
 | |
| Signed-off-by: Chung-Hsien Hsu <stanley.hsu@cypress.com>
 | |
| Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
 | |
| ---
 | |
|  .../net/wireless/broadcom/brcm80211/brcmfmac/bus.h |  10 ++
 | |
|  .../wireless/broadcom/brcm80211/brcmfmac/common.c  | 157 +++++++++++++++++++++
 | |
|  .../wireless/broadcom/brcm80211/brcmfmac/core.c    |   2 +
 | |
|  .../wireless/broadcom/brcm80211/brcmfmac/core.h    |   2 +
 | |
|  .../broadcom/brcm80211/brcmfmac/fwil_types.h       |  31 ++++
 | |
|  .../wireless/broadcom/brcm80211/brcmfmac/pcie.c    |  19 +++
 | |
|  .../wireless/broadcom/brcm80211/brcmfmac/sdio.c    |  19 +++
 | |
|  .../net/wireless/broadcom/brcm80211/brcmfmac/usb.c |  18 +++
 | |
|  8 files changed, 258 insertions(+)
 | |
| 
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
 | |
| @@ -71,6 +71,7 @@ struct brcmf_bus_dcmd {
 | |
|   * @wowl_config: specify if dongle is configured for wowl when going to suspend
 | |
|   * @get_ramsize: obtain size of device memory.
 | |
|   * @get_memdump: obtain device memory dump in provided buffer.
 | |
| + * @get_fwname: obtain firmware name.
 | |
|   *
 | |
|   * This structure provides an abstract interface towards the
 | |
|   * bus specific driver. For control messages to common driver
 | |
| @@ -87,6 +88,8 @@ struct brcmf_bus_ops {
 | |
|  	void (*wowl_config)(struct device *dev, bool enabled);
 | |
|  	size_t (*get_ramsize)(struct device *dev);
 | |
|  	int (*get_memdump)(struct device *dev, void *data, size_t len);
 | |
| +	int (*get_fwname)(struct device *dev, uint chip, uint chiprev,
 | |
| +			  unsigned char *fw_name);
 | |
|  };
 | |
|  
 | |
|  
 | |
| @@ -224,6 +227,13 @@ int brcmf_bus_get_memdump(struct brcmf_b
 | |
|  	return bus->ops->get_memdump(bus->dev, data, len);
 | |
|  }
 | |
|  
 | |
| +static inline
 | |
| +int brcmf_bus_get_fwname(struct brcmf_bus *bus, uint chip, uint chiprev,
 | |
| +			 unsigned char *fw_name)
 | |
| +{
 | |
| +	return bus->ops->get_fwname(bus->dev, chip, chiprev, fw_name);
 | |
| +}
 | |
| +
 | |
|  /*
 | |
|   * interface functions from common layer
 | |
|   */
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
 | |
| @@ -18,6 +18,7 @@
 | |
|  #include <linux/string.h>
 | |
|  #include <linux/netdevice.h>
 | |
|  #include <linux/module.h>
 | |
| +#include <linux/firmware.h>
 | |
|  #include <brcmu_wifi.h>
 | |
|  #include <brcmu_utils.h>
 | |
|  #include "core.h"
 | |
| @@ -28,6 +29,7 @@
 | |
|  #include "tracepoint.h"
 | |
|  #include "common.h"
 | |
|  #include "of.h"
 | |
| +#include "firmware.h"
 | |
|  
 | |
|  MODULE_AUTHOR("Broadcom Corporation");
 | |
|  MODULE_DESCRIPTION("Broadcom 802.11 wireless LAN fullmac driver.");
 | |
| @@ -104,12 +106,140 @@ void brcmf_c_set_joinpref_default(struct
 | |
|  		brcmf_err("Set join_pref error (%d)\n", err);
 | |
|  }
 | |
|  
 | |
| +static int brcmf_c_download(struct brcmf_if *ifp, u16 flag,
 | |
| +			    struct brcmf_dload_data_le *dload_buf,
 | |
| +			    u32 len)
 | |
| +{
 | |
| +	s32 err;
 | |
| +
 | |
| +	flag |= (DLOAD_HANDLER_VER << DLOAD_FLAG_VER_SHIFT);
 | |
| +	dload_buf->flag = cpu_to_le16(flag);
 | |
| +	dload_buf->dload_type = cpu_to_le16(DL_TYPE_CLM);
 | |
| +	dload_buf->len = cpu_to_le32(len);
 | |
| +	dload_buf->crc = cpu_to_le32(0);
 | |
| +	len = sizeof(*dload_buf) + len - 1;
 | |
| +
 | |
| +	err = brcmf_fil_iovar_data_set(ifp, "clmload", dload_buf, len);
 | |
| +
 | |
| +	return err;
 | |
| +}
 | |
| +
 | |
| +static int brcmf_c_get_clm_name(struct brcmf_if *ifp, u8 *clm_name)
 | |
| +{
 | |
| +	struct brcmf_bus *bus = ifp->drvr->bus_if;
 | |
| +	struct brcmf_rev_info *ri = &ifp->drvr->revinfo;
 | |
| +	u8 fw_name[BRCMF_FW_NAME_LEN];
 | |
| +	u8 *ptr;
 | |
| +	size_t len;
 | |
| +	s32 err;
 | |
| +
 | |
| +	memset(fw_name, 0, BRCMF_FW_NAME_LEN);
 | |
| +	err = brcmf_bus_get_fwname(bus, ri->chipnum, ri->chiprev, fw_name);
 | |
| +	if (err) {
 | |
| +		brcmf_err("get firmware name failed (%d)\n", err);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	/* generate CLM blob file name */
 | |
| +	ptr = strrchr(fw_name, '.');
 | |
| +	if (!ptr) {
 | |
| +		err = -ENOENT;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	len = ptr - fw_name + 1;
 | |
| +	if (len + strlen(".clm_blob") > BRCMF_FW_NAME_LEN) {
 | |
| +		err = -E2BIG;
 | |
| +	} else {
 | |
| +		strlcpy(clm_name, fw_name, len);
 | |
| +		strlcat(clm_name, ".clm_blob", BRCMF_FW_NAME_LEN);
 | |
| +	}
 | |
| +done:
 | |
| +	return err;
 | |
| +}
 | |
| +
 | |
| +static int brcmf_c_process_clm_blob(struct brcmf_if *ifp)
 | |
| +{
 | |
| +	struct device *dev = ifp->drvr->bus_if->dev;
 | |
| +	struct brcmf_dload_data_le *chunk_buf;
 | |
| +	const struct firmware *clm = NULL;
 | |
| +	u8 clm_name[BRCMF_FW_NAME_LEN];
 | |
| +	u32 chunk_len;
 | |
| +	u32 datalen;
 | |
| +	u32 cumulative_len;
 | |
| +	u16 dl_flag = DL_BEGIN;
 | |
| +	u32 status;
 | |
| +	s32 err;
 | |
| +
 | |
| +	brcmf_dbg(TRACE, "Enter\n");
 | |
| +
 | |
| +	memset(clm_name, 0, BRCMF_FW_NAME_LEN);
 | |
| +	err = brcmf_c_get_clm_name(ifp, clm_name);
 | |
| +	if (err) {
 | |
| +		brcmf_err("get CLM blob file name failed (%d)\n", err);
 | |
| +		return err;
 | |
| +	}
 | |
| +
 | |
| +	err = request_firmware(&clm, clm_name, dev);
 | |
| +	if (err) {
 | |
| +		if (err == -ENOENT) {
 | |
| +			brcmf_dbg(INFO, "continue with CLM data currently present in firmware\n");
 | |
| +			return 0;
 | |
| +		}
 | |
| +		brcmf_err("request CLM blob file failed (%d)\n", err);
 | |
| +		return err;
 | |
| +	}
 | |
| +
 | |
| +	chunk_buf = kzalloc(sizeof(*chunk_buf) + MAX_CHUNK_LEN - 1, GFP_KERNEL);
 | |
| +	if (!chunk_buf) {
 | |
| +		err = -ENOMEM;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	datalen = clm->size;
 | |
| +	cumulative_len = 0;
 | |
| +	do {
 | |
| +		if (datalen > MAX_CHUNK_LEN) {
 | |
| +			chunk_len = MAX_CHUNK_LEN;
 | |
| +		} else {
 | |
| +			chunk_len = datalen;
 | |
| +			dl_flag |= DL_END;
 | |
| +		}
 | |
| +		memcpy(chunk_buf->data, clm->data + cumulative_len, chunk_len);
 | |
| +
 | |
| +		err = brcmf_c_download(ifp, dl_flag, chunk_buf, chunk_len);
 | |
| +
 | |
| +		dl_flag &= ~DL_BEGIN;
 | |
| +
 | |
| +		cumulative_len += chunk_len;
 | |
| +		datalen -= chunk_len;
 | |
| +	} while ((datalen > 0) && (err == 0));
 | |
| +
 | |
| +	if (err) {
 | |
| +		brcmf_err("clmload (%zu byte file) failed (%d); ",
 | |
| +			  clm->size, err);
 | |
| +		/* Retrieve clmload_status and print */
 | |
| +		err = brcmf_fil_iovar_int_get(ifp, "clmload_status", &status);
 | |
| +		if (err)
 | |
| +			brcmf_err("get clmload_status failed (%d)\n", err);
 | |
| +		else
 | |
| +			brcmf_dbg(INFO, "clmload_status=%d\n", status);
 | |
| +		err = -EIO;
 | |
| +	}
 | |
| +
 | |
| +	kfree(chunk_buf);
 | |
| +done:
 | |
| +	release_firmware(clm);
 | |
| +	return err;
 | |
| +}
 | |
| +
 | |
|  int brcmf_c_preinit_dcmds(struct brcmf_if *ifp)
 | |
|  {
 | |
|  	s8 eventmask[BRCMF_EVENTING_MASK_LEN];
 | |
|  	u8 buf[BRCMF_DCMD_SMLEN];
 | |
|  	struct brcmf_rev_info_le revinfo;
 | |
|  	struct brcmf_rev_info *ri;
 | |
| +	char *clmver;
 | |
|  	char *ptr;
 | |
|  	s32 err;
 | |
|  
 | |
| @@ -148,6 +278,13 @@ int brcmf_c_preinit_dcmds(struct brcmf_i
 | |
|  	}
 | |
|  	ri->result = err;
 | |
|  
 | |
| +	/* Do any CLM downloading */
 | |
| +	err = brcmf_c_process_clm_blob(ifp);
 | |
| +	if (err < 0) {
 | |
| +		brcmf_err("download CLM blob file failed, %d\n", err);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
|  	/* query for 'ver' to get version info from firmware */
 | |
|  	memset(buf, 0, sizeof(buf));
 | |
|  	strcpy(buf, "ver");
 | |
| @@ -167,6 +304,26 @@ int brcmf_c_preinit_dcmds(struct brcmf_i
 | |
|  	ptr = strrchr(buf, ' ') + 1;
 | |
|  	strlcpy(ifp->drvr->fwver, ptr, sizeof(ifp->drvr->fwver));
 | |
|  
 | |
| +	/* Query for 'clmver' to get CLM version info from firmware */
 | |
| +	memset(buf, 0, sizeof(buf));
 | |
| +	err = brcmf_fil_iovar_data_get(ifp, "clmver", buf, sizeof(buf));
 | |
| +	if (err) {
 | |
| +		brcmf_dbg(TRACE, "retrieving clmver failed, %d\n", err);
 | |
| +	} else {
 | |
| +		clmver = (char *)buf;
 | |
| +		/* store CLM version for adding it to revinfo debugfs file */
 | |
| +		memcpy(ifp->drvr->clmver, clmver, sizeof(ifp->drvr->clmver));
 | |
| +
 | |
| +		/* Replace all newline/linefeed characters with space
 | |
| +		 * character
 | |
| +		 */
 | |
| +		ptr = clmver;
 | |
| +		while ((ptr = strnchr(ptr, '\n', sizeof(buf))) != NULL)
 | |
| +			*ptr = ' ';
 | |
| +
 | |
| +		brcmf_dbg(INFO, "CLM version = %s\n", clmver);
 | |
| +	}
 | |
| +
 | |
|  	/* set mpc */
 | |
|  	err = brcmf_fil_iovar_int_set(ifp, "mpc", 1);
 | |
|  	if (err) {
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
 | |
| @@ -1009,6 +1009,8 @@ static int brcmf_revinfo_read(struct seq
 | |
|  	seq_printf(s, "anarev: %u\n", ri->anarev);
 | |
|  	seq_printf(s, "nvramrev: %08x\n", ri->nvramrev);
 | |
|  
 | |
| +	seq_printf(s, "clmver: %s\n", bus_if->drvr->clmver);
 | |
| +
 | |
|  	return 0;
 | |
|  }
 | |
|  
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
 | |
| @@ -141,6 +141,8 @@ struct brcmf_pub {
 | |
|  	struct notifier_block inetaddr_notifier;
 | |
|  	struct notifier_block inet6addr_notifier;
 | |
|  	struct brcmf_mp_device *settings;
 | |
| +
 | |
| +	u8 clmver[BRCMF_DCMD_SMLEN];
 | |
|  };
 | |
|  
 | |
|  /* forward declarations */
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
 | |
| @@ -155,6 +155,21 @@
 | |
|  #define BRCMF_MFP_CAPABLE		1
 | |
|  #define BRCMF_MFP_REQUIRED		2
 | |
|  
 | |
| +/* MAX_CHUNK_LEN is the maximum length for data passing to firmware in each
 | |
| + * ioctl. It is relatively small because firmware has small maximum size input
 | |
| + * playload restriction for ioctls.
 | |
| + */
 | |
| +#define MAX_CHUNK_LEN			1400
 | |
| +
 | |
| +#define DLOAD_HANDLER_VER		1	/* Downloader version */
 | |
| +#define DLOAD_FLAG_VER_MASK		0xf000	/* Downloader version mask */
 | |
| +#define DLOAD_FLAG_VER_SHIFT		12	/* Downloader version shift */
 | |
| +
 | |
| +#define DL_BEGIN			0x0002
 | |
| +#define DL_END				0x0004
 | |
| +
 | |
| +#define DL_TYPE_CLM			2
 | |
| +
 | |
|  /* join preference types for join_pref iovar */
 | |
|  enum brcmf_join_pref_types {
 | |
|  	BRCMF_JOIN_PREF_RSSI = 1,
 | |
| @@ -827,6 +842,22 @@ struct brcmf_pno_macaddr_le {
 | |
|  };
 | |
|  
 | |
|  /**
 | |
| + * struct brcmf_dload_data_le - data passing to firmware for downloading
 | |
| + * @flag: flags related to download data.
 | |
| + * @dload_type: type of download data.
 | |
| + * @len: length in bytes of download data.
 | |
| + * @crc: crc of download data.
 | |
| + * @data: download data.
 | |
| + */
 | |
| +struct brcmf_dload_data_le {
 | |
| +	__le16 flag;
 | |
| +	__le16 dload_type;
 | |
| +	__le32 len;
 | |
| +	__le32 crc;
 | |
| +	u8 data[1];
 | |
| +};
 | |
| +
 | |
| +/**
 | |
|   * struct brcmf_pno_bssid_le - bssid configuration for PNO scan.
 | |
|   *
 | |
|   * @bssid: BSS network identifier.
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
 | |
| @@ -1350,6 +1350,24 @@ static int brcmf_pcie_get_memdump(struct
 | |
|  	return 0;
 | |
|  }
 | |
|  
 | |
| +static int brcmf_pcie_get_fwname(struct device *dev, u32 chip, u32 chiprev,
 | |
| +				 u8 *fw_name)
 | |
| +{
 | |
| +	struct brcmf_bus *bus_if = dev_get_drvdata(dev);
 | |
| +	struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie;
 | |
| +	struct brcmf_pciedev_info *devinfo = buspub->devinfo;
 | |
| +	int ret = 0;
 | |
| +
 | |
| +	if (devinfo->fw_name[0] != '\0')
 | |
| +		strlcpy(fw_name, devinfo->fw_name, BRCMF_FW_NAME_LEN);
 | |
| +	else
 | |
| +		ret = brcmf_fw_map_chip_to_name(chip, chiprev,
 | |
| +						brcmf_pcie_fwnames,
 | |
| +						ARRAY_SIZE(brcmf_pcie_fwnames),
 | |
| +						fw_name, NULL);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
|  
 | |
|  static const struct brcmf_bus_ops brcmf_pcie_bus_ops = {
 | |
|  	.txdata = brcmf_pcie_tx,
 | |
| @@ -1359,6 +1377,7 @@ static const struct brcmf_bus_ops brcmf_
 | |
|  	.wowl_config = brcmf_pcie_wowl_config,
 | |
|  	.get_ramsize = brcmf_pcie_get_ramsize,
 | |
|  	.get_memdump = brcmf_pcie_get_memdump,
 | |
| +	.get_fwname = brcmf_pcie_get_fwname,
 | |
|  };
 | |
|  
 | |
|  
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
 | |
| @@ -3985,6 +3985,24 @@ brcmf_sdio_watchdog(unsigned long data)
 | |
|  	}
 | |
|  }
 | |
|  
 | |
| +static int brcmf_sdio_get_fwname(struct device *dev, u32 chip, u32 chiprev,
 | |
| +				 u8 *fw_name)
 | |
| +{
 | |
| +	struct brcmf_bus *bus_if = dev_get_drvdata(dev);
 | |
| +	struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
 | |
| +	int ret = 0;
 | |
| +
 | |
| +	if (sdiodev->fw_name[0] != '\0')
 | |
| +		strlcpy(fw_name, sdiodev->fw_name, BRCMF_FW_NAME_LEN);
 | |
| +	else
 | |
| +		ret = brcmf_fw_map_chip_to_name(chip, chiprev,
 | |
| +						brcmf_sdio_fwnames,
 | |
| +						ARRAY_SIZE(brcmf_sdio_fwnames),
 | |
| +						fw_name, NULL);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
|  static const struct brcmf_bus_ops brcmf_sdio_bus_ops = {
 | |
|  	.stop = brcmf_sdio_bus_stop,
 | |
|  	.preinit = brcmf_sdio_bus_preinit,
 | |
| @@ -3995,6 +4013,7 @@ static const struct brcmf_bus_ops brcmf_
 | |
|  	.wowl_config = brcmf_sdio_wowl_config,
 | |
|  	.get_ramsize = brcmf_sdio_bus_get_ramsize,
 | |
|  	.get_memdump = brcmf_sdio_bus_get_memdump,
 | |
| +	.get_fwname = brcmf_sdio_get_fwname,
 | |
|  };
 | |
|  
 | |
|  static void brcmf_sdio_firmware_callback(struct device *dev, int err,
 | |
| --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
 | |
| +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
 | |
| @@ -1128,12 +1128,30 @@ static void brcmf_usb_wowl_config(struct
 | |
|  		device_set_wakeup_enable(devinfo->dev, false);
 | |
|  }
 | |
|  
 | |
| +static int brcmf_usb_get_fwname(struct device *dev, u32 chip, u32 chiprev,
 | |
| +				u8 *fw_name)
 | |
| +{
 | |
| +	struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(dev);
 | |
| +	int ret = 0;
 | |
| +
 | |
| +	if (devinfo->fw_name[0] != '\0')
 | |
| +		strlcpy(fw_name, devinfo->fw_name, BRCMF_FW_NAME_LEN);
 | |
| +	else
 | |
| +		ret = brcmf_fw_map_chip_to_name(chip, chiprev,
 | |
| +						brcmf_usb_fwnames,
 | |
| +						ARRAY_SIZE(brcmf_usb_fwnames),
 | |
| +						fw_name, NULL);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
|  static const struct brcmf_bus_ops brcmf_usb_bus_ops = {
 | |
|  	.txdata = brcmf_usb_tx,
 | |
|  	.stop = brcmf_usb_down,
 | |
|  	.txctl = brcmf_usb_tx_ctlpkt,
 | |
|  	.rxctl = brcmf_usb_rx_ctlpkt,
 | |
|  	.wowl_config = brcmf_usb_wowl_config,
 | |
| +	.get_fwname = brcmf_usb_get_fwname,
 | |
|  };
 | |
|  
 | |
|  static int brcmf_usb_bus_setup(struct brcmf_usbdev_info *devinfo)
 |