forked from Minki/linux
a1cee865c3
Each bus driver is maintaing an exported bus state indicating if upper layers can or cannot send data. SDIO is using this state also for more private states. This makes handling the states and state changes complex. This patch minimises the exposed states and makes SDIO keep track of an internal state where necessary. 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> Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
421 lines
9.5 KiB
C
421 lines
9.5 KiB
C
/*
|
|
* Copyright (c) 2012 Broadcom Corporation
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/* FWIL is the Firmware Interface Layer. In this module the support functions
|
|
* are located to set and get variables to and from the firmware.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/netdevice.h>
|
|
#include <brcmu_utils.h>
|
|
#include <brcmu_wifi.h>
|
|
#include "core.h"
|
|
#include "bus.h"
|
|
#include "debug.h"
|
|
#include "tracepoint.h"
|
|
#include "fwil.h"
|
|
#include "proto.h"
|
|
|
|
|
|
#define MAX_HEX_DUMP_LEN 64
|
|
|
|
#ifdef DEBUG
|
|
static const char * const brcmf_fil_errstr[] = {
|
|
"BCME_OK",
|
|
"BCME_ERROR",
|
|
"BCME_BADARG",
|
|
"BCME_BADOPTION",
|
|
"BCME_NOTUP",
|
|
"BCME_NOTDOWN",
|
|
"BCME_NOTAP",
|
|
"BCME_NOTSTA",
|
|
"BCME_BADKEYIDX",
|
|
"BCME_RADIOOFF",
|
|
"BCME_NOTBANDLOCKED",
|
|
"BCME_NOCLK",
|
|
"BCME_BADRATESET",
|
|
"BCME_BADBAND",
|
|
"BCME_BUFTOOSHORT",
|
|
"BCME_BUFTOOLONG",
|
|
"BCME_BUSY",
|
|
"BCME_NOTASSOCIATED",
|
|
"BCME_BADSSIDLEN",
|
|
"BCME_OUTOFRANGECHAN",
|
|
"BCME_BADCHAN",
|
|
"BCME_BADADDR",
|
|
"BCME_NORESOURCE",
|
|
"BCME_UNSUPPORTED",
|
|
"BCME_BADLEN",
|
|
"BCME_NOTREADY",
|
|
"BCME_EPERM",
|
|
"BCME_NOMEM",
|
|
"BCME_ASSOCIATED",
|
|
"BCME_RANGE",
|
|
"BCME_NOTFOUND",
|
|
"BCME_WME_NOT_ENABLED",
|
|
"BCME_TSPEC_NOTFOUND",
|
|
"BCME_ACM_NOTSUPPORTED",
|
|
"BCME_NOT_WME_ASSOCIATION",
|
|
"BCME_SDIO_ERROR",
|
|
"BCME_DONGLE_DOWN",
|
|
"BCME_VERSION",
|
|
"BCME_TXFAIL",
|
|
"BCME_RXFAIL",
|
|
"BCME_NODEVICE",
|
|
"BCME_NMODE_DISABLED",
|
|
"BCME_NONRESIDENT",
|
|
"BCME_SCANREJECT",
|
|
"BCME_USAGE_ERROR",
|
|
"BCME_IOCTL_ERROR",
|
|
"BCME_SERIAL_PORT_ERR",
|
|
"BCME_DISABLED",
|
|
"BCME_DECERR",
|
|
"BCME_ENCERR",
|
|
"BCME_MICERR",
|
|
"BCME_REPLAY",
|
|
"BCME_IE_NOTFOUND",
|
|
};
|
|
|
|
static const char *brcmf_fil_get_errstr(u32 err)
|
|
{
|
|
if (err >= ARRAY_SIZE(brcmf_fil_errstr))
|
|
return "(unknown)";
|
|
|
|
return brcmf_fil_errstr[err];
|
|
}
|
|
#else
|
|
static const char *brcmf_fil_get_errstr(u32 err)
|
|
{
|
|
return "";
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
static s32
|
|
brcmf_fil_cmd_data(struct brcmf_if *ifp, u32 cmd, void *data, u32 len, bool set)
|
|
{
|
|
struct brcmf_pub *drvr = ifp->drvr;
|
|
s32 err;
|
|
|
|
if (drvr->bus_if->state != BRCMF_BUS_UP) {
|
|
brcmf_err("bus is down. we have nothing to do.\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (data != NULL)
|
|
len = min_t(uint, len, BRCMF_DCMD_MAXLEN);
|
|
if (set)
|
|
err = brcmf_proto_set_dcmd(drvr, ifp->ifidx, cmd, data, len);
|
|
else
|
|
err = brcmf_proto_query_dcmd(drvr, ifp->ifidx, cmd, data, len);
|
|
|
|
if (err >= 0)
|
|
return 0;
|
|
|
|
brcmf_dbg(FIL, "Failed: %s (%d)\n",
|
|
brcmf_fil_get_errstr((u32)(-err)), err);
|
|
return -EBADE;
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_cmd_data_set(struct brcmf_if *ifp, u32 cmd, void *data, u32 len)
|
|
{
|
|
s32 err;
|
|
|
|
mutex_lock(&ifp->drvr->proto_block);
|
|
|
|
brcmf_dbg(FIL, "ifidx=%d, cmd=%d, len=%d\n", ifp->ifidx, cmd, len);
|
|
brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data,
|
|
min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
|
|
|
|
err = brcmf_fil_cmd_data(ifp, cmd, data, len, true);
|
|
mutex_unlock(&ifp->drvr->proto_block);
|
|
|
|
return err;
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_cmd_data_get(struct brcmf_if *ifp, u32 cmd, void *data, u32 len)
|
|
{
|
|
s32 err;
|
|
|
|
mutex_lock(&ifp->drvr->proto_block);
|
|
err = brcmf_fil_cmd_data(ifp, cmd, data, len, false);
|
|
|
|
brcmf_dbg(FIL, "ifidx=%d, cmd=%d, len=%d\n", ifp->ifidx, cmd, len);
|
|
brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data,
|
|
min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
|
|
|
|
mutex_unlock(&ifp->drvr->proto_block);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
s32
|
|
brcmf_fil_cmd_int_set(struct brcmf_if *ifp, u32 cmd, u32 data)
|
|
{
|
|
s32 err;
|
|
__le32 data_le = cpu_to_le32(data);
|
|
|
|
mutex_lock(&ifp->drvr->proto_block);
|
|
brcmf_dbg(FIL, "ifidx=%d, cmd=%d, value=%d\n", ifp->ifidx, cmd, data);
|
|
err = brcmf_fil_cmd_data(ifp, cmd, &data_le, sizeof(data_le), true);
|
|
mutex_unlock(&ifp->drvr->proto_block);
|
|
|
|
return err;
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_cmd_int_get(struct brcmf_if *ifp, u32 cmd, u32 *data)
|
|
{
|
|
s32 err;
|
|
__le32 data_le = cpu_to_le32(*data);
|
|
|
|
mutex_lock(&ifp->drvr->proto_block);
|
|
err = brcmf_fil_cmd_data(ifp, cmd, &data_le, sizeof(data_le), false);
|
|
mutex_unlock(&ifp->drvr->proto_block);
|
|
*data = le32_to_cpu(data_le);
|
|
brcmf_dbg(FIL, "ifidx=%d, cmd=%d, value=%d\n", ifp->ifidx, cmd, *data);
|
|
|
|
return err;
|
|
}
|
|
|
|
static u32
|
|
brcmf_create_iovar(char *name, const char *data, u32 datalen,
|
|
char *buf, u32 buflen)
|
|
{
|
|
u32 len;
|
|
|
|
len = strlen(name) + 1;
|
|
|
|
if ((len + datalen) > buflen)
|
|
return 0;
|
|
|
|
memcpy(buf, name, len);
|
|
|
|
/* append data onto the end of the name string */
|
|
if (data && datalen)
|
|
memcpy(&buf[len], data, datalen);
|
|
|
|
return len + datalen;
|
|
}
|
|
|
|
|
|
s32
|
|
brcmf_fil_iovar_data_set(struct brcmf_if *ifp, char *name, const void *data,
|
|
u32 len)
|
|
{
|
|
struct brcmf_pub *drvr = ifp->drvr;
|
|
s32 err;
|
|
u32 buflen;
|
|
|
|
mutex_lock(&drvr->proto_block);
|
|
|
|
brcmf_dbg(FIL, "ifidx=%d, name=%s, len=%d\n", ifp->ifidx, name, len);
|
|
brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data,
|
|
min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
|
|
|
|
buflen = brcmf_create_iovar(name, data, len, drvr->proto_buf,
|
|
sizeof(drvr->proto_buf));
|
|
if (buflen) {
|
|
err = brcmf_fil_cmd_data(ifp, BRCMF_C_SET_VAR, drvr->proto_buf,
|
|
buflen, true);
|
|
} else {
|
|
err = -EPERM;
|
|
brcmf_err("Creating iovar failed\n");
|
|
}
|
|
|
|
mutex_unlock(&drvr->proto_block);
|
|
return err;
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_iovar_data_get(struct brcmf_if *ifp, char *name, void *data,
|
|
u32 len)
|
|
{
|
|
struct brcmf_pub *drvr = ifp->drvr;
|
|
s32 err;
|
|
u32 buflen;
|
|
|
|
mutex_lock(&drvr->proto_block);
|
|
|
|
buflen = brcmf_create_iovar(name, data, len, drvr->proto_buf,
|
|
sizeof(drvr->proto_buf));
|
|
if (buflen) {
|
|
err = brcmf_fil_cmd_data(ifp, BRCMF_C_GET_VAR, drvr->proto_buf,
|
|
buflen, false);
|
|
if (err == 0)
|
|
memcpy(data, drvr->proto_buf, len);
|
|
} else {
|
|
err = -EPERM;
|
|
brcmf_err("Creating iovar failed\n");
|
|
}
|
|
|
|
brcmf_dbg(FIL, "ifidx=%d, name=%s, len=%d\n", ifp->ifidx, name, len);
|
|
brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data,
|
|
min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
|
|
|
|
mutex_unlock(&drvr->proto_block);
|
|
return err;
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_iovar_int_set(struct brcmf_if *ifp, char *name, u32 data)
|
|
{
|
|
__le32 data_le = cpu_to_le32(data);
|
|
|
|
return brcmf_fil_iovar_data_set(ifp, name, &data_le, sizeof(data_le));
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_iovar_int_get(struct brcmf_if *ifp, char *name, u32 *data)
|
|
{
|
|
__le32 data_le = cpu_to_le32(*data);
|
|
s32 err;
|
|
|
|
err = brcmf_fil_iovar_data_get(ifp, name, &data_le, sizeof(data_le));
|
|
if (err == 0)
|
|
*data = le32_to_cpu(data_le);
|
|
return err;
|
|
}
|
|
|
|
static u32
|
|
brcmf_create_bsscfg(s32 bssidx, char *name, char *data, u32 datalen, char *buf,
|
|
u32 buflen)
|
|
{
|
|
const s8 *prefix = "bsscfg:";
|
|
s8 *p;
|
|
u32 prefixlen;
|
|
u32 namelen;
|
|
u32 iolen;
|
|
__le32 bssidx_le;
|
|
|
|
if (bssidx == 0)
|
|
return brcmf_create_iovar(name, data, datalen, buf, buflen);
|
|
|
|
prefixlen = strlen(prefix);
|
|
namelen = strlen(name) + 1; /* lengh of iovar name + null */
|
|
iolen = prefixlen + namelen + sizeof(bssidx_le) + datalen;
|
|
|
|
if (buflen < iolen) {
|
|
brcmf_err("buffer is too short\n");
|
|
return 0;
|
|
}
|
|
|
|
p = buf;
|
|
|
|
/* copy prefix, no null */
|
|
memcpy(p, prefix, prefixlen);
|
|
p += prefixlen;
|
|
|
|
/* copy iovar name including null */
|
|
memcpy(p, name, namelen);
|
|
p += namelen;
|
|
|
|
/* bss config index as first data */
|
|
bssidx_le = cpu_to_le32(bssidx);
|
|
memcpy(p, &bssidx_le, sizeof(bssidx_le));
|
|
p += sizeof(bssidx_le);
|
|
|
|
/* parameter buffer follows */
|
|
if (datalen)
|
|
memcpy(p, data, datalen);
|
|
|
|
return iolen;
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_bsscfg_data_set(struct brcmf_if *ifp, char *name,
|
|
void *data, u32 len)
|
|
{
|
|
struct brcmf_pub *drvr = ifp->drvr;
|
|
s32 err;
|
|
u32 buflen;
|
|
|
|
mutex_lock(&drvr->proto_block);
|
|
|
|
brcmf_dbg(FIL, "ifidx=%d, bssidx=%d, name=%s, len=%d\n", ifp->ifidx,
|
|
ifp->bssidx, name, len);
|
|
brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data,
|
|
min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
|
|
|
|
buflen = brcmf_create_bsscfg(ifp->bssidx, name, data, len,
|
|
drvr->proto_buf, sizeof(drvr->proto_buf));
|
|
if (buflen) {
|
|
err = brcmf_fil_cmd_data(ifp, BRCMF_C_SET_VAR, drvr->proto_buf,
|
|
buflen, true);
|
|
} else {
|
|
err = -EPERM;
|
|
brcmf_err("Creating bsscfg failed\n");
|
|
}
|
|
|
|
mutex_unlock(&drvr->proto_block);
|
|
return err;
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_bsscfg_data_get(struct brcmf_if *ifp, char *name,
|
|
void *data, u32 len)
|
|
{
|
|
struct brcmf_pub *drvr = ifp->drvr;
|
|
s32 err;
|
|
u32 buflen;
|
|
|
|
mutex_lock(&drvr->proto_block);
|
|
|
|
buflen = brcmf_create_bsscfg(ifp->bssidx, name, data, len,
|
|
drvr->proto_buf, sizeof(drvr->proto_buf));
|
|
if (buflen) {
|
|
err = brcmf_fil_cmd_data(ifp, BRCMF_C_GET_VAR, drvr->proto_buf,
|
|
buflen, false);
|
|
if (err == 0)
|
|
memcpy(data, drvr->proto_buf, len);
|
|
} else {
|
|
err = -EPERM;
|
|
brcmf_err("Creating bsscfg failed\n");
|
|
}
|
|
brcmf_dbg(FIL, "ifidx=%d, bssidx=%d, name=%s, len=%d\n", ifp->ifidx,
|
|
ifp->bssidx, name, len);
|
|
brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data,
|
|
min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
|
|
|
|
mutex_unlock(&drvr->proto_block);
|
|
return err;
|
|
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_bsscfg_int_set(struct brcmf_if *ifp, char *name, u32 data)
|
|
{
|
|
__le32 data_le = cpu_to_le32(data);
|
|
|
|
return brcmf_fil_bsscfg_data_set(ifp, name, &data_le,
|
|
sizeof(data_le));
|
|
}
|
|
|
|
s32
|
|
brcmf_fil_bsscfg_int_get(struct brcmf_if *ifp, char *name, u32 *data)
|
|
{
|
|
__le32 data_le = cpu_to_le32(*data);
|
|
s32 err;
|
|
|
|
err = brcmf_fil_bsscfg_data_get(ifp, name, &data_le,
|
|
sizeof(data_le));
|
|
if (err == 0)
|
|
*data = le32_to_cpu(data_le);
|
|
return err;
|
|
}
|