352 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			352 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From: Hante Meuleman <meuleman@broadcom.com>
 | |
| Date: Tue, 14 Apr 2015 20:10:33 +0200
 | |
| Subject: [PATCH] brcmfmac: Add support for multiple PCIE devices in
 | |
|  nvram.
 | |
| 
 | |
| With PCIE it is possible to support multiple devices with the
 | |
| same device type. They all load the same nvram file. In order to
 | |
| support this the nvram can specify which part of the nvram is
 | |
| for which pcie device. This patch adds support for these new
 | |
| types of nvram files.
 | |
| 
 | |
| Reviewed-by: Arend Van Spriel <arend@broadcom.com>
 | |
| Reviewed-by: Franky (Zhenhui) Lin <frankyl@broadcom.com>
 | |
| Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com>
 | |
| Reviewed-by: Daniel (Deognyoun) Kim <dekim@broadcom.com>
 | |
| Signed-off-by: Hante Meuleman <meuleman@broadcom.com>
 | |
| Signed-off-by: Arend van Spriel <arend@broadcom.com>
 | |
| ---
 | |
| 
 | |
| --- a/drivers/net/wireless/brcm80211/brcmfmac/firmware.c
 | |
| +++ b/drivers/net/wireless/brcm80211/brcmfmac/firmware.c
 | |
| @@ -23,6 +23,10 @@
 | |
|  #include "debug.h"
 | |
|  #include "firmware.h"
 | |
|  
 | |
| +#define BRCMF_FW_MAX_NVRAM_SIZE			64000
 | |
| +#define BRCMF_FW_NVRAM_DEVPATH_LEN		19	/* devpath0=pcie/1/4/ */
 | |
| +#define BRCMF_FW_NVRAM_PCIEDEV_LEN		9	/* pcie/1/4/ */
 | |
| +
 | |
|  char brcmf_firmware_path[BRCMF_FW_PATH_LEN];
 | |
|  module_param_string(firmware_path, brcmf_firmware_path,
 | |
|  		    BRCMF_FW_PATH_LEN, 0440);
 | |
| @@ -46,6 +50,8 @@ enum nvram_parser_state {
 | |
|   * @column: current column in line.
 | |
|   * @pos: byte offset in input buffer.
 | |
|   * @entry: start position of key,value entry.
 | |
| + * @multi_dev_v1: detect pcie multi device v1 (compressed).
 | |
| + * @multi_dev_v2: detect pcie multi device v2.
 | |
|   */
 | |
|  struct nvram_parser {
 | |
|  	enum nvram_parser_state state;
 | |
| @@ -56,6 +62,8 @@ struct nvram_parser {
 | |
|  	u32 column;
 | |
|  	u32 pos;
 | |
|  	u32 entry;
 | |
| +	bool multi_dev_v1;
 | |
| +	bool multi_dev_v2;
 | |
|  };
 | |
|  
 | |
|  static bool is_nvram_char(char c)
 | |
| @@ -108,6 +116,10 @@ static enum nvram_parser_state brcmf_nvr
 | |
|  			st = COMMENT;
 | |
|  		else
 | |
|  			st = VALUE;
 | |
| +		if (strncmp(&nvp->fwnv->data[nvp->entry], "devpath", 7) == 0)
 | |
| +			nvp->multi_dev_v1 = true;
 | |
| +		if (strncmp(&nvp->fwnv->data[nvp->entry], "pcie/", 5) == 0)
 | |
| +			nvp->multi_dev_v2 = true;
 | |
|  	} else if (!is_nvram_char(c)) {
 | |
|  		brcmf_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n",
 | |
|  			  nvp->line, nvp->column);
 | |
| @@ -133,6 +145,8 @@ brcmf_nvram_handle_value(struct nvram_pa
 | |
|  		ekv = (u8 *)&nvp->fwnv->data[nvp->pos];
 | |
|  		skv = (u8 *)&nvp->fwnv->data[nvp->entry];
 | |
|  		cplen = ekv - skv;
 | |
| +		if (nvp->nvram_len + cplen + 1 >= BRCMF_FW_MAX_NVRAM_SIZE)
 | |
| +			return END;
 | |
|  		/* copy to output buffer */
 | |
|  		memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen);
 | |
|  		nvp->nvram_len += cplen;
 | |
| @@ -180,10 +194,18 @@ static enum nvram_parser_state
 | |
|  static int brcmf_init_nvram_parser(struct nvram_parser *nvp,
 | |
|  				   const struct firmware *nv)
 | |
|  {
 | |
| +	size_t size;
 | |
| +
 | |
|  	memset(nvp, 0, sizeof(*nvp));
 | |
|  	nvp->fwnv = nv;
 | |
| +	/* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */
 | |
| +	if (nv->size > BRCMF_FW_MAX_NVRAM_SIZE)
 | |
| +		size = BRCMF_FW_MAX_NVRAM_SIZE;
 | |
| +	else
 | |
| +		size = nv->size;
 | |
|  	/* Alloc for extra 0 byte + roundup by 4 + length field */
 | |
| -	nvp->nvram = kzalloc(nv->size + 1 + 3 + sizeof(u32), GFP_KERNEL);
 | |
| +	size += 1 + 3 + sizeof(u32);
 | |
| +	nvp->nvram = kzalloc(size, GFP_KERNEL);
 | |
|  	if (!nvp->nvram)
 | |
|  		return -ENOMEM;
 | |
|  
 | |
| @@ -192,12 +214,136 @@ static int brcmf_init_nvram_parser(struc
 | |
|  	return 0;
 | |
|  }
 | |
|  
 | |
| +/* brcmf_fw_strip_multi_v1 :Some nvram files contain settings for multiple
 | |
| + * devices. Strip it down for one device, use domain_nr/bus_nr to determine
 | |
| + * which data is to be returned. v1 is the version where nvram is stored
 | |
| + * compressed and "devpath" maps to index for valid entries.
 | |
| + */
 | |
| +static void brcmf_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr,
 | |
| +				    u16 bus_nr)
 | |
| +{
 | |
| +	u32 i, j;
 | |
| +	bool found;
 | |
| +	u8 *nvram;
 | |
| +	u8 id;
 | |
| +
 | |
| +	nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
 | |
| +	if (!nvram)
 | |
| +		goto fail;
 | |
| +
 | |
| +	/* min length: devpath0=pcie/1/4/ + 0:x=y */
 | |
| +	if (nvp->nvram_len < BRCMF_FW_NVRAM_DEVPATH_LEN + 6)
 | |
| +		goto fail;
 | |
| +
 | |
| +	/* First search for the devpathX and see if it is the configuration
 | |
| +	 * for domain_nr/bus_nr. Search complete nvp
 | |
| +	 */
 | |
| +	found = false;
 | |
| +	i = 0;
 | |
| +	while (i < nvp->nvram_len - BRCMF_FW_NVRAM_DEVPATH_LEN) {
 | |
| +		/* Format: devpathX=pcie/Y/Z/
 | |
| +		 * Y = domain_nr, Z = bus_nr, X = virtual ID
 | |
| +		 */
 | |
| +		if ((strncmp(&nvp->nvram[i], "devpath", 7) == 0) &&
 | |
| +		    (strncmp(&nvp->nvram[i + 8], "=pcie/", 6) == 0)) {
 | |
| +			if (((nvp->nvram[i + 14] - '0') == domain_nr) &&
 | |
| +			    ((nvp->nvram[i + 16] - '0') == bus_nr)) {
 | |
| +				id = nvp->nvram[i + 7] - '0';
 | |
| +				found = true;
 | |
| +				break;
 | |
| +			}
 | |
| +		}
 | |
| +		while (nvp->nvram[i] != 0)
 | |
| +			i++;
 | |
| +		i++;
 | |
| +	}
 | |
| +	if (!found)
 | |
| +		goto fail;
 | |
| +
 | |
| +	/* Now copy all valid entries, release old nvram and assign new one */
 | |
| +	i = 0;
 | |
| +	j = 0;
 | |
| +	while (i < nvp->nvram_len) {
 | |
| +		if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) {
 | |
| +			i += 2;
 | |
| +			while (nvp->nvram[i] != 0) {
 | |
| +				nvram[j] = nvp->nvram[i];
 | |
| +				i++;
 | |
| +				j++;
 | |
| +			}
 | |
| +			nvram[j] = 0;
 | |
| +			j++;
 | |
| +		}
 | |
| +		while (nvp->nvram[i] != 0)
 | |
| +			i++;
 | |
| +		i++;
 | |
| +	}
 | |
| +	kfree(nvp->nvram);
 | |
| +	nvp->nvram = nvram;
 | |
| +	nvp->nvram_len = j;
 | |
| +	return;
 | |
| +
 | |
| +fail:
 | |
| +	kfree(nvram);
 | |
| +	nvp->nvram_len = 0;
 | |
| +}
 | |
| +
 | |
| +/* brcmf_fw_strip_multi_v2 :Some nvram files contain settings for multiple
 | |
| + * devices. Strip it down for one device, use domain_nr/bus_nr to determine
 | |
| + * which data is to be returned. v2 is the version where nvram is stored
 | |
| + * uncompressed, all relevant valid entries are identified by
 | |
| + * pcie/domain_nr/bus_nr:
 | |
| + */
 | |
| +static void brcmf_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr,
 | |
| +				    u16 bus_nr)
 | |
| +{
 | |
| +	u32 i, j;
 | |
| +	u8 *nvram;
 | |
| +
 | |
| +	nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
 | |
| +	if (!nvram)
 | |
| +		goto fail;
 | |
| +
 | |
| +	/* Copy all valid entries, release old nvram and assign new one.
 | |
| +	 * Valid entries are of type pcie/X/Y/ where X = domain_nr and
 | |
| +	 * Y = bus_nr.
 | |
| +	 */
 | |
| +	i = 0;
 | |
| +	j = 0;
 | |
| +	while (i < nvp->nvram_len - BRCMF_FW_NVRAM_PCIEDEV_LEN) {
 | |
| +		if ((strncmp(&nvp->nvram[i], "pcie/", 5) == 0) &&
 | |
| +		    (nvp->nvram[i + 6] == '/') && (nvp->nvram[i + 8] == '/') &&
 | |
| +		    ((nvp->nvram[i + 5] - '0') == domain_nr) &&
 | |
| +		    ((nvp->nvram[i + 7] - '0') == bus_nr)) {
 | |
| +			i += BRCMF_FW_NVRAM_PCIEDEV_LEN;
 | |
| +			while (nvp->nvram[i] != 0) {
 | |
| +				nvram[j] = nvp->nvram[i];
 | |
| +				i++;
 | |
| +				j++;
 | |
| +			}
 | |
| +			nvram[j] = 0;
 | |
| +			j++;
 | |
| +		}
 | |
| +		while (nvp->nvram[i] != 0)
 | |
| +			i++;
 | |
| +		i++;
 | |
| +	}
 | |
| +	kfree(nvp->nvram);
 | |
| +	nvp->nvram = nvram;
 | |
| +	nvp->nvram_len = j;
 | |
| +	return;
 | |
| +fail:
 | |
| +	kfree(nvram);
 | |
| +	nvp->nvram_len = 0;
 | |
| +}
 | |
| +
 | |
|  /* brcmf_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil
 | |
|   * and ending in a NUL. Removes carriage returns, empty lines, comment lines,
 | |
|   * and converts newlines to NULs. Shortens buffer as needed and pads with NULs.
 | |
|   * End of buffer is completed with token identifying length of buffer.
 | |
|   */
 | |
| -static void *brcmf_fw_nvram_strip(const struct firmware *nv, u32 *new_length)
 | |
| +static void *brcmf_fw_nvram_strip(const struct firmware *nv, u32 *new_length,
 | |
| +				  u16 domain_nr, u16 bus_nr)
 | |
|  {
 | |
|  	struct nvram_parser nvp;
 | |
|  	u32 pad;
 | |
| @@ -212,6 +358,16 @@ static void *brcmf_fw_nvram_strip(const
 | |
|  		if (nvp.state == END)
 | |
|  			break;
 | |
|  	}
 | |
| +	if (nvp.multi_dev_v1)
 | |
| +		brcmf_fw_strip_multi_v1(&nvp, domain_nr, bus_nr);
 | |
| +	else if (nvp.multi_dev_v2)
 | |
| +		brcmf_fw_strip_multi_v2(&nvp, domain_nr, bus_nr);
 | |
| +
 | |
| +	if (nvp.nvram_len == 0) {
 | |
| +		kfree(nvp.nvram);
 | |
| +		return NULL;
 | |
| +	}
 | |
| +
 | |
|  	pad = nvp.nvram_len;
 | |
|  	*new_length = roundup(nvp.nvram_len + 1, 4);
 | |
|  	while (pad != *new_length) {
 | |
| @@ -239,6 +395,8 @@ struct brcmf_fw {
 | |
|  	u16 flags;
 | |
|  	const struct firmware *code;
 | |
|  	const char *nvram_name;
 | |
| +	u16 domain_nr;
 | |
| +	u16 bus_nr;
 | |
|  	void (*done)(struct device *dev, const struct firmware *fw,
 | |
|  		     void *nvram_image, u32 nvram_len);
 | |
|  };
 | |
| @@ -254,7 +412,8 @@ static void brcmf_fw_request_nvram_done(
 | |
|  		goto fail;
 | |
|  
 | |
|  	if (fw) {
 | |
| -		nvram = brcmf_fw_nvram_strip(fw, &nvram_length);
 | |
| +		nvram = brcmf_fw_nvram_strip(fw, &nvram_length,
 | |
| +					     fwctx->domain_nr, fwctx->bus_nr);
 | |
|  		release_firmware(fw);
 | |
|  		if (!nvram && !(fwctx->flags & BRCMF_FW_REQ_NV_OPTIONAL))
 | |
|  			goto fail;
 | |
| @@ -309,11 +468,12 @@ fail:
 | |
|  	kfree(fwctx);
 | |
|  }
 | |
|  
 | |
| -int brcmf_fw_get_firmwares(struct device *dev, u16 flags,
 | |
| -			   const char *code, const char *nvram,
 | |
| -			   void (*fw_cb)(struct device *dev,
 | |
| -					 const struct firmware *fw,
 | |
| -					 void *nvram_image, u32 nvram_len))
 | |
| +int brcmf_fw_get_firmwares_pcie(struct device *dev, u16 flags,
 | |
| +				const char *code, const char *nvram,
 | |
| +				void (*fw_cb)(struct device *dev,
 | |
| +					      const struct firmware *fw,
 | |
| +					      void *nvram_image, u32 nvram_len),
 | |
| +				u16 domain_nr, u16 bus_nr)
 | |
|  {
 | |
|  	struct brcmf_fw *fwctx;
 | |
|  
 | |
| @@ -333,8 +493,21 @@ int brcmf_fw_get_firmwares(struct device
 | |
|  	fwctx->done = fw_cb;
 | |
|  	if (flags & BRCMF_FW_REQUEST_NVRAM)
 | |
|  		fwctx->nvram_name = nvram;
 | |
| +	fwctx->domain_nr = domain_nr;
 | |
| +	fwctx->bus_nr = bus_nr;
 | |
|  
 | |
|  	return request_firmware_nowait(THIS_MODULE, true, code, dev,
 | |
|  				       GFP_KERNEL, fwctx,
 | |
|  				       brcmf_fw_request_code_done);
 | |
|  }
 | |
| +
 | |
| +int brcmf_fw_get_firmwares(struct device *dev, u16 flags,
 | |
| +			   const char *code, const char *nvram,
 | |
| +			   void (*fw_cb)(struct device *dev,
 | |
| +					 const struct firmware *fw,
 | |
| +					 void *nvram_image, u32 nvram_len))
 | |
| +{
 | |
| +	return brcmf_fw_get_firmwares_pcie(dev, flags, code, nvram, fw_cb, 0,
 | |
| +					   0);
 | |
| +}
 | |
| +
 | |
| --- a/drivers/net/wireless/brcm80211/brcmfmac/firmware.h
 | |
| +++ b/drivers/net/wireless/brcm80211/brcmfmac/firmware.h
 | |
| @@ -32,6 +32,12 @@ void brcmf_fw_nvram_free(void *nvram);
 | |
|   * fails it will not use the callback, but call device_release_driver()
 | |
|   * instead which will call the driver .remove() callback.
 | |
|   */
 | |
| +int brcmf_fw_get_firmwares_pcie(struct device *dev, u16 flags,
 | |
| +				const char *code, const char *nvram,
 | |
| +				void (*fw_cb)(struct device *dev,
 | |
| +					      const struct firmware *fw,
 | |
| +					      void *nvram_image, u32 nvram_len),
 | |
| +				u16 domain_nr, u16 bus_nr);
 | |
|  int brcmf_fw_get_firmwares(struct device *dev, u16 flags,
 | |
|  			   const char *code, const char *nvram,
 | |
|  			   void (*fw_cb)(struct device *dev,
 | |
| --- a/drivers/net/wireless/brcm80211/brcmfmac/pcie.c
 | |
| +++ b/drivers/net/wireless/brcm80211/brcmfmac/pcie.c
 | |
| @@ -1649,8 +1649,13 @@ brcmf_pcie_probe(struct pci_dev *pdev, c
 | |
|  	struct brcmf_pciedev_info *devinfo;
 | |
|  	struct brcmf_pciedev *pcie_bus_dev;
 | |
|  	struct brcmf_bus *bus;
 | |
| +	u16 domain_nr;
 | |
| +	u16 bus_nr;
 | |
|  
 | |
| -	brcmf_dbg(PCIE, "Enter %x:%x\n", pdev->vendor, pdev->device);
 | |
| +	domain_nr = pci_domain_nr(pdev->bus) + 1;
 | |
| +	bus_nr = pdev->bus->number;
 | |
| +	brcmf_dbg(PCIE, "Enter %x:%x (%d/%d)\n", pdev->vendor, pdev->device,
 | |
| +		  domain_nr, bus_nr);
 | |
|  
 | |
|  	ret = -ENOMEM;
 | |
|  	devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
 | |
| @@ -1699,10 +1704,10 @@ brcmf_pcie_probe(struct pci_dev *pdev, c
 | |
|  	if (ret)
 | |
|  		goto fail_bus;
 | |
|  
 | |
| -	ret = brcmf_fw_get_firmwares(bus->dev, BRCMF_FW_REQUEST_NVRAM |
 | |
| -					       BRCMF_FW_REQ_NV_OPTIONAL,
 | |
| -				     devinfo->fw_name, devinfo->nvram_name,
 | |
| -				     brcmf_pcie_setup);
 | |
| +	ret = brcmf_fw_get_firmwares_pcie(bus->dev, BRCMF_FW_REQUEST_NVRAM |
 | |
| +						    BRCMF_FW_REQ_NV_OPTIONAL,
 | |
| +					  devinfo->fw_name, devinfo->nvram_name,
 | |
| +					  brcmf_pcie_setup, domain_nr, bus_nr);
 | |
|  	if (ret == 0)
 | |
|  		return 0;
 | |
|  fail_bus:
 | 
