Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fdd0bd88 authored by Chung-Hsien Hsu's avatar Chung-Hsien Hsu Committed by Kalle Valo
Browse files

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: default avatarArend van Spriel <arend.vanspriel@broadcom.com>
Signed-off-by: default avatarChung-Hsien Hsu <stanley.hsu@cypress.com>
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
parent 4775ae7a
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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_bus *bus, void *data, size_t len)
	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
 */
+157 −0
Original line number Diff line number Diff line
@@ -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_if *ifp)
		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_if *ifp)
	}
	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_if *ifp)
	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) {
+2 −0
Original line number Diff line number Diff line
@@ -988,6 +988,8 @@ static int brcmf_revinfo_read(struct seq_file *s, void *data)
	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;
}

+2 −0
Original line number Diff line number Diff line
@@ -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 */
+31 −0
Original line number Diff line number Diff line
@@ -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,
@@ -826,6 +841,22 @@ struct brcmf_pno_macaddr_le {
	u8 mac[ETH_ALEN];
};

/**
 * 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.
 *
Loading