blob: 88f027746a800bc7a489e301fa3d94fc792ef58b [file] [log] [blame]
/*
* wl he command module
*
* Broadcom Proprietary and Confidential. Copyright (C) 2017,
* All Rights Reserved.
*
* This is UNPUBLISHED PROPRIETARY SOURCE CODE of Broadcom;
* the contents of this file may not be disclosed to third parties, copied
* or duplicated in any form, in whole or in part, without the prior
* written permission of Broadcom.
*
*
* <<Broadcom-WL-IPTag/Proprietary:>>
*
* $Id: wluc_he.c 627609 2016-03-25 21:00:45Z $
*/
#ifdef WIN32
/* Because IL_BIGENDIAN was removed there are few warnings that need
* to be fixed. Windows was not compiled earlier with IL_BIGENDIAN.
* Hence these warnings were not seen earlier.
* For now ignore the following warnings
*/
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4761)
#include <windows.h>
#endif
#include <wlioctl.h>
#include <bcmutils.h>
#include <bcmendian.h>
#include <proto/802.11ah.h>
#include <miniopt.h>
#include "wlu_common.h"
#include "wlu.h"
#ifndef bzero
#define bzero(mem, len) memset(mem, 0, len)
#endif
#ifdef _WIN32
#if defined(_MSC_VER) && _MSC_VER <= 1200
#define NEED_STRTOULL
#endif
#if defined(_MSC_VER) && _MSC_VER > 1200
#define strtoull _strtoui64
#endif
#endif /* _WIN32 */
#define HE_CMD_HELP_STR \
"HE (802.11ax) protocol control commands\n\n" \
"\tUsage: wl he [command] [cmd options]\n\n" \
"Available commands and command options:\n" \
"\twl he enab [0|1] - query or enable/disable HE feature\n" \
"\twl he features [<features mask>] - query or enable/disable HE sub-features\n" \
"\twl he twt setup [<flow flags>] [<twt type] [<options>] <setup cmd> <flow id> - "\
"setup target wake time (TWT)\n" \
"\t\t<flow flags>:\n" \
"\t\t\t-b - Broadcast TWT\n" \
"\t\t\t-i - Implicit TWT\n" \
"\t\t\t-u - Unannounced\n" \
"\t\t\t-b - Trigger\n" \
"\t\t<twt type>:\n" \
"\t\t\t-y tsf - Absolute TSF\n" \
"\t\t\t-y offset - TSF offset\n" \
"\t\t<options>:\n" \
"\t\t\t-a <peer MAC address>\n" \
"\t\t\t-k <dialog token>\n" \
"\t\t\t-w <target wake time>\n" \
"\t\t\t-d <wake duration>\n" \
"\t\t\t-p <wake interval>\n" \
"\t\t<setup cmd>:\n" \
"\t\t\trequest\n" \
"\t\t\tsuggest\n" \
"\t\t\tdemand\n" \
"\twl he twt teardown [<flow flags>] [<options>] <flow id> - teardown flow\n" \
"\t\t<flow flags>:\n" \
"\t\t\t-b - Broadcast TWT\n" \
"\t\t<options>:\n" \
"\t\t\t-a <peer MAC address>\n" \
"\twl he twt info [<flow flags>] [<options>] <flow id> - request information\n" \
"\t\t<flow flags>:\n" \
"\t\t\t-r - response request\n" \
"\t\t<options>:\n" \
"\t\t\t-a <peer MAC address>\n" \
"\t\t\t-w <target wake time>\n"
static cmd_func_t wl_he_cmd;
/* wl he top level command list */
static cmd_t wl_he_cmds[] = {
{ "he", wl_he_cmd, WLC_GET_VAR, WLC_SET_VAR, HE_CMD_HELP_STR },
{ NULL, NULL, 0, 0, NULL }
};
static char *buf;
/* module initialization */
void
wluc_he_module_init(void)
{
/* get the global buf */
buf = wl_get_buf();
/* register HE commands */
wl_module_cmds_register(wl_he_cmds);
}
/* HE sub cmd */
typedef struct sub_cmd sub_cmd_t;
typedef int (sub_cmd_func_t)(void *wl, const cmd_t *cmd, const sub_cmd_t *sub, char **argv);
struct sub_cmd {
char *name;
uint16 id; /* id for the dongle f/w switch/case */
uint16 type; /* base type of argument IOVT_XXXX */
sub_cmd_func_t *hdlr; /* cmd handler */
};
/* HE ioctl sub cmd handler functions */
static sub_cmd_func_t wl_he_cmd_uint;
static sub_cmd_func_t wl_he_cmd_twt;
static sub_cmd_func_t wl_he_cmd_twt_setup;
static sub_cmd_func_t wl_he_cmd_twt_teardown;
static sub_cmd_func_t wl_he_cmd_twt_info;
/* wl he sub cmd list */
static const sub_cmd_t he_cmd_list[] = {
/* wl he enab [0|1] */
{"enab", WL_HE_CMD_ENAB, IOVT_UINT8, wl_he_cmd_uint},
/* wl he features [<features>] */
{"features", WL_HE_CMD_FEATURES, IOVT_UINT32, wl_he_cmd_uint},
/* wl he twt ... */
{"twt", 0, 0, wl_he_cmd_twt},
{NULL, 0, 0, NULL}
};
/* wl he twt sub cmd list */
static const sub_cmd_t twt_cmd_list[] = {
/* wl he twt setup ... */
{"setup", WL_HE_CMD_TWT_SETUP, IOVT_BUFFER, wl_he_cmd_twt_setup},
/* wl he twt teardown ... */
{"teardown", WL_HE_CMD_TWT_TEARDOWN, IOVT_BUFFER, wl_he_cmd_twt_teardown},
/* wl he twt info ... */
{"info", WL_HE_CMD_TWT_INFO, IOVT_BUFFER, wl_he_cmd_twt_info},
{NULL, 0, 0, NULL}
};
#ifdef NEED_STRTOULL
static unsigned long long int
strtoull(const char *nptr, char **endptr, int base)
{
unsigned long long int result;
unsigned char value;
bool minus;
minus = FALSE;
while (bcm_isspace(*nptr)) {
nptr++;
}
if (nptr[0] == '+') {
nptr++;
}
else if (nptr[0] == '-') {
minus = TRUE;
nptr++;
}
if (base == 0) {
if (nptr[0] == '0') {
if ((nptr[1] == 'x') || (nptr[1] == 'X')) {
base = 16;
nptr = &nptr[2];
} else {
base = 8;
nptr = &nptr[1];
}
} else {
base = 10;
}
} else if (base == 16 &&
(nptr[0] == '0') && ((nptr[1] == 'x') || (nptr[1] == 'X'))) {
nptr = &nptr[2];
}
result = 0;
while (bcm_isxdigit(*nptr) &&
(value = bcm_isdigit(*nptr) ? *nptr - '0' : bcm_toupper(*nptr) - 'A' + 10) <
(unsigned char)base) {
/* TODO: The strtoul() function should only convert the initial part
* of the string in nptr to an unsigned long int value according to
* the given base...so strtoull() should follow the same rule...
*/
result = result * base + value;
nptr++;
}
if (minus) {
result = result * -1;
}
if (endptr) {
*endptr = DISCARD_QUAL(nptr, char);
}
return result;
}
#endif /* NEED_STRTOULL */
/* wl he command */
static int
wl_he_cmd(void *wl, cmd_t *cmd, char **argv)
{
const sub_cmd_t *sub = he_cmd_list;
char *he_query[2] = {"enab", NULL};
char *he_en[3] = {"enab", "1", NULL};
char *he_dis[3] = {"enab", "0", NULL};
int ret = BCME_USAGE_ERROR;
/* skip to cmd name after "he" */
argv++;
if (!*argv) {
/* query he "enab" state */
argv = he_query;
}
else if (*argv[0] == '1') {
argv = he_en;
}
else if (*argv[0] == '0') {
argv = he_dis;
}
else if (!strcmp(*argv, "-h") || !strcmp(*argv, "help")) {
/* help , or -h* */
return BCME_USAGE_ERROR;
}
while (sub->name != NULL) {
if (strcmp(sub->name, *argv) == 0) {
/* dispacth subcmd to the handler */
if (sub->hdlr != NULL) {
ret = sub->hdlr(wl, cmd, sub, ++argv);
}
return ret;
}
sub ++;
}
return BCME_IOCTL_ERROR;
}
typedef struct {
uint16 id;
uint16 len;
uint32 val;
} he_xtlv_v32;
static uint
wl_he_iovt2len(uint iovt)
{
switch (iovt) {
case IOVT_BOOL:
case IOVT_INT8:
case IOVT_UINT8:
return sizeof(uint8);
case IOVT_INT16:
case IOVT_UINT16:
return sizeof(uint16);
case IOVT_INT32:
case IOVT_UINT32:
return sizeof(uint32);
default:
/* ASSERT(0); */
return 0;
}
}
static bool
wl_he_get_uint_cb(void *ctx, uint16 *id, uint16 *len)
{
he_xtlv_v32 *v32 = ctx;
*id = v32->id;
*len = v32->len;
return FALSE;
}
static void
wl_he_pack_uint_cb(void *ctx, uint16 id, uint16 len, uint8 *buf)
{
he_xtlv_v32 *v32 = ctx;
BCM_REFERENCE(id);
BCM_REFERENCE(len);
v32->val = htod32(v32->val);
switch (v32->len) {
case sizeof(uint8):
*buf = (uint8)v32->val;
break;
case sizeof(uint16):
store16_ua(buf, (uint16)v32->val);
break;
case sizeof(uint32):
store16_ua(buf, v32->val);
break;
default:
/* ASSERT(0); */
break;
}
}
/* ******** generic uint8/uint16/uint32 ******** */
static int
wl_he_cmd_uint(void *wl, const cmd_t *cmd, const sub_cmd_t *sub, char **argv)
{
int res = BCME_OK;
/* get */
if (*argv == NULL) {
bcm_xtlv_t mybuf;
uint8 *resp;
mybuf.id = sub->id;
mybuf.len = 0;
/* send getbuf iovar */
res = wlu_var_getbuf_sm(wl, cmd->name, &mybuf, sizeof(mybuf),
(void **)&resp);
/* check the response buff */
if (res == BCME_OK && resp != NULL) {
uint len = wl_he_iovt2len(sub->type);
uint32 v32;
switch (len) {
case sizeof(uint8):
v32 = *resp;
break;
case sizeof(uint16):
v32 = load16_ua(resp);
break;
case sizeof(uint32):
v32 = load32_ua(resp);
break;
default:
v32 = ~0;
break;
}
v32 = dtoh32(v32);
printf("%u\n", v32);
}
}
/* set */
else {
uint8 mybuf[32];
int mybuf_len = sizeof(mybuf);
he_xtlv_v32 v32;
v32.id = sub->id;
v32.len = wl_he_iovt2len(sub->type);
v32.val = atoi(*argv);
res = bcm_pack_xtlv_buf((void *)&v32, mybuf, sizeof(mybuf),
BCM_XTLV_OPTION_ALIGN32, wl_he_get_uint_cb, wl_he_pack_uint_cb,
&mybuf_len);
if (res != BCME_OK) {
goto fail;
}
res = wlu_var_setbuf(wl, cmd->name, mybuf, mybuf_len);
}
return res;
fail:
fprintf(stderr, "error:%d\n", res);
return res;
}
/* wl he twt top level commend */
static int
wl_he_cmd_twt(void *wl, const cmd_t *cmd, const sub_cmd_t *sub, char **argv)
{
const sub_cmd_t *sub2 = twt_cmd_list;
int res = BCME_OK;
BCM_REFERENCE(sub);
if (*argv == NULL) {
return BCME_USAGE_ERROR;
}
while (sub2->name != NULL) {
if (strcmp(sub2->name, *argv) == 0) {
/* dispacth subcmd to the handler */
if (sub2->hdlr != NULL) {
res = sub2->hdlr(wl, cmd, sub2, ++argv);
}
return res;
}
sub2 ++;
}
return BCME_USAGE_ERROR;
}
/* setup command name to value conversion */
static struct {
const char *name;
uint8 val;
} setup_cmd_val[] = {
{"request", TWT_SETUP_CMD_REQUEST_TWT},
{"suggest", TWT_SETUP_CMD_SUGGEST_TWT},
{"demand", TWT_SETUP_CMD_DEMAND_TWT}
};
#define WL_HE_TWT_CMD_INVAL 255
static uint8
wl_he_twt_cmd2val(const char *name)
{
uint i;
for (i = 0; i < ARRAYSIZE(setup_cmd_val); i ++) {
if (strcmp(name, setup_cmd_val[i].name) == 0) {
return setup_cmd_val[i].val;
}
}
return WL_HE_TWT_CMD_INVAL;
}
/* target wake time (twt) type to value conversion */
static struct {
const char *name;
uint8 val;
} twt_type_val[] = {
{"tsf", WL_TWT_TIME_TYPE_BSS},
{"offset", WL_TWT_TIME_TYPE_OFFSET}
};
#define WL_HE_TWT_TYPE_INVAL 255
static uint8
wl_he_twt_type2val(const char *name)
{
uint i;
for (i = 0; i < ARRAYSIZE(twt_type_val); i ++) {
if (strcmp(name, twt_type_val[i].name) == 0) {
return twt_type_val[i].val;
}
}
return WL_HE_TWT_TYPE_INVAL;
}
/* wl he twt setup command */
static int
wl_he_cmd_twt_setup(void *wl, const cmd_t *cmd, const sub_cmd_t *sub, char **argv)
{
int res = BCME_OK;
uint8 mybuf[64];
uint8 *rem = mybuf;
uint16 rem_len = sizeof(mybuf);
wl_twt_setup_t val;
miniopt_t opt;
int opt_err;
int argc;
bool got_mandatory = FALSE;
BCM_REFERENCE(wl);
if (*argv == NULL) {
return BCME_USAGE_ERROR;
}
/* arg count */
for (argc = 0; argv[argc]; argc++)
;
bzero(&val, sizeof(val));
val.version = WL_TWT_SETUP_VER;
val.length = sizeof(val.version) + sizeof(val.length);
miniopt_init(&opt, __FUNCTION__, "biut", FALSE);
while ((opt_err = miniopt(&opt, argv)) != -1) {
if (opt_err == 1) {
res = BCME_USAGE_ERROR;
goto fail;
}
/* flags and options */
if (!opt.positional) {
/* flags */
/* -b (broadcast) */
if (opt.opt == 'b') {
val.desc.flow_flags |= WL_TWT_FLOW_FLAG_BROADCAST;
}
/* -i (implicit) */
else if (opt.opt == 'i') {
val.desc.flow_flags |= WL_TWT_FLOW_FLAG_IMPLICIT;
}
/* -u (unannounced) */
else if (opt.opt == 'u') {
val.desc.flow_flags |= WL_TWT_FLOW_FLAG_UNANNOUNCED;
}
/* -t (trigger) */
else if (opt.opt == 't') {
val.desc.flow_flags |= WL_TWT_FLOW_FLAG_TRIGGER;
}
/* options */
/* -a peer_address */
else if (opt.opt == 'a') {
if (!wl_ether_atoe(opt.valstr, &val.peer)) {
fprintf(stderr, "Malformed TWT peer address '%s'\n",
opt.valstr);
res = BCME_BADARG;
goto fail;
}
}
/* -k dialog_token */
else if (opt.opt == 'k') {
val.dialog = (uint8)strtoul(opt.valstr, NULL, 0);
}
/* -y twt type */
else if (opt.opt == 'y') {
if ((val.desc.wake_type =
wl_he_twt_type2val(opt.valstr)) == WL_HE_TWT_TYPE_INVAL) {
fprintf(stderr, "Unrecognized TWT type '%s'\n",
opt.valstr);
res = BCME_BADARG;
goto fail;
}
}
/* -w target_wake_time (twt) */
else if (opt.opt == 'w') {
uint64 twt = strtoull(opt.valstr, NULL, 0);
val.desc.wake_time_h = htod32((uint32)(twt >> 32));
val.desc.wake_time_l = htod32((uint32)twt);
}
/* -d duration */
else if (opt.opt == 'd') {
val.desc.wake_dur = htod32(opt.uval);
}
/* -p interval */
else if (opt.opt == 'p') {
val.desc.wake_int = htod32(opt.uval);
}
else {
fprintf(stderr, "Unrecognized option '%s'\n", *argv);
res = BCME_BADARG;
goto fail;
}
}
/* positional/mandatory */
else {
if (argc < 2) {
res = BCME_USAGE_ERROR;
goto fail;
}
/* setup_cmd */
if ((val.desc.setup_cmd =
wl_he_twt_cmd2val(*argv)) == WL_HE_TWT_CMD_INVAL) {
fprintf(stderr, "Unrecognized TWT Setup command '%s'\n", *argv);
res = BCME_BADARG;
goto fail;
}
argv++;
/* flow_id */
val.desc.flow_id = (uint8)strtoul(*argv, NULL, 0);
argv++;
got_mandatory = TRUE;
break;
}
argv += opt.consumed;
argc -= opt.consumed;
}
if (!got_mandatory) {
res = BCME_USAGE_ERROR;
goto fail;
}
res = bcm_pack_xtlv_entry(&rem, &rem_len, sub->id,
sizeof(val), (uint8 *)&val, BCM_XTLV_OPTION_ALIGN32);
if (res != BCME_OK) {
goto fail;
}
return wlu_var_setbuf(wl, cmd->name, mybuf, sizeof(mybuf) - rem_len);
fail:
fprintf(stderr, "error:%d\n", res);
return res;
}
/* wl he twt teardown command */
static int
wl_he_cmd_twt_teardown(void *wl, const cmd_t *cmd, const sub_cmd_t *sub, char **argv)
{
int res = BCME_OK;
uint8 mybuf[64];
uint8 *rem = mybuf;
uint16 rem_len = sizeof(mybuf);
wl_twt_teardown_t val;
miniopt_t opt;
int opt_err;
int argc;
bool got_mandatory = FALSE;
BCM_REFERENCE(wl);
if (*argv == NULL) {
return BCME_USAGE_ERROR;
}
/* arg count */
for (argc = 0; argv[argc]; argc++)
;
bzero(&val, sizeof(val));
val.version = WL_TWT_TEARDOWN_VER;
val.length = sizeof(val.version) + sizeof(val.length);
miniopt_init(&opt, __FUNCTION__, "b", FALSE);
while ((opt_err = miniopt(&opt, argv)) != -1) {
if (opt_err == 1) {
res = BCME_USAGE_ERROR;
goto fail;
}
/* flags and options */
if (!opt.positional) {
/* flags */
/* -b (broadcast) */
if (opt.opt == 'b') {
val.flow_flags |= WL_TWT_FLOW_FLAG_BROADCAST;
}
/* options */
/* -a peer_address */
else if (opt.opt == 'a') {
if (!wl_ether_atoe(opt.valstr, &val.peer)) {
res = BCME_BADARG;
goto fail;
}
}
}
/* positionals */
else {
if (argc < 1) {
res = BCME_USAGE_ERROR;
goto fail;
}
/* flow_id */
val.flow_id = (uint8)strtoul(*argv, NULL, 0);
argv++;
got_mandatory = TRUE;
break;
}
argv += opt.consumed;
}
if (!got_mandatory) {
res = BCME_USAGE_ERROR;
goto fail;
}
res = bcm_pack_xtlv_entry(&rem, &rem_len, sub->id,
sizeof(val), (uint8 *)&val, BCM_XTLV_OPTION_ALIGN32);
if (res != BCME_OK) {
goto fail;
}
return wlu_var_setbuf(wl, cmd->name, mybuf, sizeof(mybuf) - rem_len);
fail:
fprintf(stderr, "error:%d\n", res);
return res;
}
/* wl he twt info command */
static int
wl_he_cmd_twt_info(void *wl, const cmd_t *cmd, const sub_cmd_t *sub, char **argv)
{
int res = BCME_OK;
uint8 mybuf[64];
uint8 *rem = mybuf;
uint16 rem_len = sizeof(mybuf);
wl_twt_info_t val;
miniopt_t opt;
int opt_err;
int argc;
bool got_mandatory = FALSE;
BCM_REFERENCE(wl);
if (*argv == NULL) {
return BCME_USAGE_ERROR;
}
/* arg count */
for (argc = 0; argv[argc]; argc++)
;
bzero(&val, sizeof(val));
val.version = WL_TWT_INFO_VER;
val.length = sizeof(val.version) + sizeof(val.length);
miniopt_init(&opt, __FUNCTION__, "r", FALSE);
while ((opt_err = miniopt(&opt, argv)) != -1) {
if (opt_err == 1) {
res = BCME_USAGE_ERROR;
goto fail;
}
/* flags and options */
if (!opt.positional) {
/* flags */
/* -r (response request) */
if (opt.opt == 'r') {
val.desc.flow_flags |= WL_TWT_INFO_FLAG_RESP_REQ;
}
/* options */
/* -a peer_address */
else if (opt.opt == 'a') {
if (!wl_ether_atoe(opt.valstr, &val.peer)) {
res = BCME_BADARG;
goto fail;
}
}
/* -w target_wake_time (twt) */
else if (opt.opt == 'w') {
uint64 twt = strtoull(opt.valstr, NULL, 0);
val.desc.next_twt_h = htod32((uint32)(twt >> 32));
val.desc.next_twt_l = htod32((uint32)twt);
}
}
/* positionals */
else {
if (argc < 1) {
res = BCME_USAGE_ERROR;
goto fail;
}
/* flow_id */
val.desc.flow_id = (uint8)strtoul(*argv, NULL, 0);
argv++;
got_mandatory = TRUE;
break;
}
argv += opt.consumed;
}
if (!got_mandatory) {
res = BCME_USAGE_ERROR;
goto fail;
}
res = bcm_pack_xtlv_entry(&rem, &rem_len, sub->id,
sizeof(val), (uint8 *)&val, BCM_XTLV_OPTION_ALIGN32);
if (res != BCME_OK) {
goto fail;
}
return wlu_var_setbuf(wl, cmd->name, mybuf, sizeof(mybuf) - rem_len);
fail:
fprintf(stderr, "error:%d\n", res);
return res;
}