Skip to content

Commit

Permalink
brcmfmac: add CLM download support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Chung-Hsien Hsu authored and Kalle Valo committed Nov 11, 2017
1 parent 4775ae7 commit fdd0bd8
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 0 deletions.
10 changes: 10 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
};


Expand Down Expand Up @@ -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
*/
Expand Down
157 changes: 157 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.");
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
31 changes: 31 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
*
Expand Down
19 changes: 19 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,24 @@ static int brcmf_pcie_get_memdump(struct device *dev, void *data, size_t len)
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,
Expand All @@ -1359,6 +1377,7 @@ static const struct brcmf_bus_ops brcmf_pcie_bus_ops = {
.wowl_config = brcmf_pcie_wowl_config,
.get_ramsize = brcmf_pcie_get_ramsize,
.get_memdump = brcmf_pcie_get_memdump,
.get_fwname = brcmf_pcie_get_fwname,
};


Expand Down
19 changes: 19 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -3995,6 +4013,7 @@ static const struct brcmf_bus_ops brcmf_sdio_bus_ops = {
.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,
Expand Down
18 changes: 18 additions & 0 deletions drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -1128,12 +1128,30 @@ static void brcmf_usb_wowl_config(struct device *dev, bool enabled)
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)
Expand Down

0 comments on commit fdd0bd8

Please sign in to comment.