blob: ff493ca7b892f141c1576ef2075cf2bab62f6cc1 [file] [log] [blame]
/*
* wl lq 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_lq.c 458728 2014-02-27 18:15:25Z $
*/
#ifdef WIN32
#include <windows.h>
#endif
#include <wlioctl.h>
#if defined(DONGLEBUILD)
#include <typedefs.h>
#include <osl.h>
#endif
/* 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
*/
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4761)
#endif
#include <bcmutils.h>
#include <bcmendian.h>
#include "wlu_common.h"
#include "wlu.h"
static cmd_func_t wl_rssi_event, wl_chan_qual_event;
static cmd_func_t wl_chanim_state, wl_chanim_mode;
static cmd_func_t wl_dump_lq;
static cmd_func_t wl_monitor_lq;
static cmd_func_t wl_chanim_acs_record;
static cmd_func_t wl_chanim_stats;
static int _wl_dump_lq(void *wl);
static cmd_t wl_lq_cmds[] = {
{ "rssi_event", wl_rssi_event, WLC_GET_VAR, WLC_SET_VAR,
"Set parameters associated with RSSI event notification\n"
"\tusage: wl rssi_event <rate_limit> <rssi_levels>\n"
"\trate_limit: Number of events posted to application will be limited"
" to 1 per this rate limit. Set to 0 to disable rate limit.\n"
"\trssi_levels: Variable number of RSSI levels (maximum 8) "
" in increasing order (e.g. -85 -70 -60). An event will be posted"
" each time the RSSI of received beacons/packets crosses a level."},
{ "chq_event", wl_chan_qual_event, WLC_GET_VAR, WLC_SET_VAR,
"Set parameters associated with channel quality event notification\n"
"\tusage: wl chq_event <rate_limit> <cca_levels> <nf_levels> <nf_lte_levels>\n"
"\trate_limit: Number of events posted to application will be limited"
" to 1 per this rate limit. Set to 0 to disable rate limit.\n"
"\tcsa/nf/nf_lte levels: Variable number of threshold levels (maximum 8)"
" in pairs of hi-to-low/lo-to-hi, and in increasing order (e.g. -90 -85 -80)."
" A 0 0 pair terminates level array for one metric."
" An event will be posted whenever a threshold is being crossed."},
{"chanim_state", wl_chanim_state, WLC_GET_VAR, -1,
"get channel interference state\n"
"\tUsage: wl chanim_state channel\n"
"\tValid channels: 1 - 14\n"
"\treturns: 0 - Acceptable; 1 - Severe"
},
{"chanim_mode", wl_chanim_mode, WLC_GET_VAR, WLC_SET_VAR,
"get/set channel interference measure (chanim) mode\n"
"\tUsage: wl chanim_mode <value>\n"
"\tvalue: 0 - disabled; 1 - detection only; 2 - detection and avoidance"
},
{"chanim_acs_record", wl_chanim_acs_record, WLC_GET_VAR, -1,
"get the auto channel scan record. \n"
"\t Usage: wl acs_record"
},
{"chanim_stats", wl_chanim_stats, WLC_GET_VAR, -1,
"get chanim stats \n"
"\t Usage: wl chanim_stats"
},
{ "monitor_lq", wl_monitor_lq, WLC_GET_VAR, WLC_SET_VAR,
"Start/Stop monitoring link quality metrics - RSSI and SNR\n"
"\tUsage: wl monitor_lq <0: turn off / 1: turn on"},
{ "monitor_lq_status", wl_dump_lq, WLC_GET_VAR, -1 /* Set not reqd */,
"Returns averaged link quality metrics - RSSI and SNR values"},
{ NULL, NULL, 0, 0, NULL }
};
static char *buf;
/* module initialization */
void
wluc_lq_module_init(void)
{
/* get the global buf */
buf = wl_get_buf();
/* register lq commands */
wl_module_cmds_register(wl_lq_cmds);
}
static int
wl_chan_qual_event(void *wl, cmd_t *cmd, char **argv)
{
int ret;
const char *CHAN_QUAL_NAME[WL_CHAN_QUAL_TOTAL] = {" CCA", " NF", "NF_LTE"};
if (!*++argv) {
/* get */
void *ptr = NULL;
wl_chan_qual_event_t chq;
uint i, j;
if ((ret = wlu_var_getbuf(wl, cmd->name, NULL, 0, &ptr)) < 0)
return ret;
memcpy(&chq, ptr, sizeof(chq));
chq.rate_limit_msec = dtoh32(chq.rate_limit_msec);
printf("rate per %dms\n", chq.rate_limit_msec);
for (i = 0; i < WL_CHAN_QUAL_TOTAL; i++) {
printf("%s[%d]:", CHAN_QUAL_NAME[i], chq.metric[i].id);
for (j = 0; (j < chq.metric[i].num_levels) &&
(j < MAX_CHAN_QUAL_LEVELS); j++) {
printf(" (%d, %d)", chq.metric[i].htol[j], chq.metric[i].ltoh[j]);
}
printf("\n");
}
} else {
/* set */
wl_chan_qual_event_t chq;
uint i;
memset(&chq, 0, sizeof(wl_chan_qual_event_t));
chq.rate_limit_msec = atoi(*argv++);
chq.rate_limit_msec = htod32(chq.rate_limit_msec);
chq.num_metrics = htod16(WL_CHAN_QUAL_TOTAL);
for (i = 0; i < WL_CHAN_QUAL_TOTAL; i++) {
chq.metric[i].id = i;
while (argv[0] && argv[1]) {
int16 htol, ltoh;
htol = htod16(atoi(*argv++));
ltoh = htod16(atoi(*argv++));
/* double zeros terminate one metric */
if ((htol == 0) && (ltoh == 0))
break;
/* make sure that ltoh >= htol */
if (ltoh < htol)
return -1;
/* ignore extra thresholds */
if (chq.metric[i].num_levels >= MAX_CHAN_QUAL_LEVELS)
continue;
chq.metric[i].htol[chq.metric[i].num_levels] = htol;
chq.metric[i].ltoh[chq.metric[i].num_levels] = ltoh;
/* all metric threshold levels must be in increasing order */
if (chq.metric[i].num_levels > 0) {
if ((chq.metric[i].htol[chq.metric[i].num_levels] <=
chq.metric[i].htol[chq.metric[i].num_levels - 1]) ||
(chq.metric[i].ltoh[chq.metric[i].num_levels] <=
chq.metric[i].ltoh[chq.metric[i].num_levels - 1])) {
return -1;
}
}
(chq.metric[i].num_levels)++;
}
}
if (*argv) {
/* too many parameters */
return -1;
}
ret = wlu_var_setbuf(wl, cmd->name, &chq, sizeof(chq));
}
return ret;
}
static int
wl_rssi_event(void *wl, cmd_t *cmd, char **argv)
{
int ret;
if (!*++argv) {
/* get */
void *ptr = NULL;
wl_rssi_event_t rssi;
uint i;
if ((ret = wlu_var_getbuf(wl, cmd->name, NULL, 0, &ptr)) < 0)
return ret;
memcpy(&rssi, ptr, sizeof(rssi));
rssi.rate_limit_msec = dtoh32(rssi.rate_limit_msec);
printf("%d", rssi.rate_limit_msec);
for (i = 0; i < rssi.num_rssi_levels; i++) {
printf(" %d", rssi.rssi_levels[i]);
}
printf("\n");
} else {
/* set */
wl_rssi_event_t rssi;
memset(&rssi, 0, sizeof(wl_rssi_event_t));
rssi.rate_limit_msec = atoi(*argv);
while (*++argv && rssi.num_rssi_levels < MAX_RSSI_LEVELS) {
rssi.rssi_levels[rssi.num_rssi_levels++] = atoi(*argv);
if (rssi.num_rssi_levels > 1) {
if (rssi.rssi_levels[rssi.num_rssi_levels - 1] <=
rssi.rssi_levels[rssi.num_rssi_levels - 2]) {
/* rssi levels must be in increasing order */
return BCME_USAGE_ERROR;
}
}
}
if (*argv) {
/* too many parameters */
return BCME_USAGE_ERROR;
}
rssi.rate_limit_msec = htod32(rssi.rate_limit_msec);
ret = wlu_var_setbuf(wl, cmd->name, &rssi, sizeof(rssi));
}
return ret;
}
static int
wl_chanim_state(void *wl, cmd_t *cmd, char **argv)
{
uint32 chanspec;
int argc = 0;
int ret, val;
argv++;
/* find the arg count */
while (argv[argc])
argc++;
if (argc != 1)
return BCME_USAGE_ERROR;
chanspec = wf_chspec_aton(*argv);
chanspec = wl_chspec32_to_driver(chanspec);
if (chanspec == INVCHANSPEC) {
return BCME_USAGE_ERROR;
}
ret = wlu_iovar_getbuf(wl, cmd->name, &chanspec, sizeof(chanspec),
buf, WLC_IOCTL_SMLEN);
if (ret < 0)
return ret;
val = *(int*)buf;
val = dtoh32(val);
printf("%d\n", val);
return 0;
}
static int
wl_chanim_mode(void *wl, cmd_t *cmd, char **argv)
{
int ret;
int val;
char *endptr;
int mode;
if (!*++argv) {
if (cmd->get < 0)
return -1;
if ((ret = wlu_iovar_getint(wl, cmd->name, &mode)) < 0)
return ret;
switch (mode) {
case CHANIM_DISABLE:
printf("CHANIM mode: disabled.\n");
break;
case CHANIM_DETECT:
printf("CHANIM mode: detect only.\n");
break;
case CHANIM_EXT:
printf("CHANIM mode: external (acsd).\n");
break;
case CHANIM_ACT:
printf("CHANIM mode: detect + act.\n");
break;
}
return 0;
} else {
mode = CHANIM_DETECT;
val = strtol(*argv, &endptr, 0);
if (*endptr != '\0')
return BCME_USAGE_ERROR;
switch (val) {
case 0:
mode = CHANIM_DISABLE;
break;
case 1:
mode = CHANIM_DETECT;
break;
case 2:
mode = CHANIM_EXT;
break;
case 3:
mode = CHANIM_ACT;
break;
default:
return BCME_BADARG;
}
mode = htod32(mode);
return wlu_iovar_setint(wl, cmd->name, mode);
}
}
static int
_wl_dump_lq(void *wl)
{
int ret = BCME_OK, noise = 0;
wl_lq_t *plq = NULL;
void *ptr = NULL;
memset(buf, 0, sizeof(wl_lq_t));
/* Display stats when disabled */
if ((ret = wlu_get(wl, WLC_GET_PHY_NOISE, &noise, sizeof(int))) < 0) {
printf("wlc_get noise failed with retcode:%d\n", ret);
return ret;
}
if ((ret = wlu_var_getbuf_sm (wl, "monitor_lq_status", NULL, 0, &ptr)) < 0) {
printf("wlc_get lq_status failed with retcode:%d\n", ret);
return ret;
}
plq = (wl_lq_t *)ptr;
if (!plq->isvalid) {
printf("Stats collection currently disabled"
"['wl monitor_lq 1' to enable statistics collection]\n");
return ret;
}
noise = dtoh32(noise);
plq->rssi[LQ_IDX_MIN] = dtoh32(plq->rssi[LQ_IDX_MIN]);
plq->rssi[LQ_IDX_MAX] = dtoh32(plq->rssi[LQ_IDX_MAX]);
plq->rssi[LQ_IDX_AVG] = dtoh32(plq->rssi[LQ_IDX_AVG]);
printf("rss: %d, %d, %d\nsnr: %d, %d, %d\n",
plq->rssi[LQ_IDX_MIN],
plq->rssi[LQ_IDX_AVG],
plq->rssi[LQ_IDX_MAX],
plq->rssi[LQ_IDX_MIN]-noise,
plq->rssi[LQ_IDX_AVG]-noise,
plq->rssi[LQ_IDX_MAX]-noise);
return ret;
} /* _wl_dump_lq */
static int
wl_dump_lq(void *wl, cmd_t *cmd, char **argv)
{
int ret = BCME_OK;
UNUSED_PARAMETER(cmd);
if (!*++argv)
ret = _wl_dump_lq(wl);
return ret;
} /* wl_dump_lq */
static int
wl_monitor_lq(void *wl, cmd_t *cmd, char **argv)
{
int ret = BCME_OK;
char *endptr = NULL;
char **startptr = argv;
if (!*++startptr) { /* Get */
ret = wl_varint(wl, cmd, argv);
}
else {
int val = *startptr[0];
val = strtol(*startptr, &endptr, 0);
if (*endptr != '\0') {
return BCME_USAGE_ERROR;
}
val = htod32(val);
if (val == LQ_STOP_MONITOR) {
if ((ret = _wl_dump_lq(wl)))
return ret;
}
ret = wl_varint(wl, cmd, argv); /* Standard set call after getting stats */
}
return ret;
} /* wl_monitor_lq */
static int
wl_chanim_acs_record(void *wl, cmd_t *cmd, char **argv)
{
void *ptr = NULL;
int err = 0, i;
wl_acs_record_t *result;
/* need to add to this str if new acs trigger type is added */
const char *trig_str[] = {"None", "IOCTL", "CHANIM", "TIMER", "BTA"};
UNUSED_PARAMETER(argv);
if ((err = wlu_var_getbuf(wl, cmd->name, NULL, 0, &ptr)) < 0)
return err;
result = (wl_acs_record_t *) ptr;
if (!result->count) {
printf("There is no ACS recorded\n");
return err;
}
printf("current timestamp: %u (ms)\n", result->timestamp);
printf("Timestamp(ms) ACS Trigger Selected Channel Glitch Count CCA Count\n");
for (i = 0; i < result->count; i++) {
uint8 idx = CHANIM_ACS_RECORD - result->count + i;
chanim_acs_record_t * record = &result->acs_record[idx];
record->selected_chspc = wl_chspec_from_driver(record->selected_chspc);
printf("%10u \t%s \t%10d \t%12d \t%8d\n", record->timestamp,
trig_str[record->trigger], wf_chspec_ctlchan(record->selected_chspc),
record->glitch_cnt, record->ccastats);
}
return err;
}
static int
wl_chanim_stats(void *wl, cmd_t *cmd, char **argv)
{
wl_chanim_stats_t *stats;
int stats_size;
int i, err;
void *ptr;
UNUSED_PARAMETER(argv);
/* get fw chanim stats version */
stats_size = WL_CHANIM_STATS_FIXED_LEN +
MAX(sizeof(chanim_stats_t), sizeof(chanim_stats_v2_t));
stats = (wl_chanim_stats_t *)malloc(stats_size);
if (stats == NULL) {
fprintf(stderr, "memory alloc failure\n");
return BCME_NOMEM;
}
memset(stats, 0, stats_size);
stats->buflen = htod32(stats_size);
if ((err = wlu_var_getbuf(wl, cmd->name, stats, stats_size, &ptr)) < 0) {
printf("failed to get chanim results");
free(stats);
return err;
}
memcpy(stats, ptr, stats_size);
stats->version = dtoh32(stats->version);
if (!((stats->version == WL_CHANIM_STATS_VERSION) ||
(stats->version == WL_CHANIM_STATS_V2))) {
printf("Sorry, your driver has wl_chanim_stats version %d "
"but this program supports only version %d and %d.\n",
stats->version, WL_CHANIM_STATS_V2, WL_CHANIM_STATS_VERSION);
free(stats);
return 0;
}
/* get fw chanim stats */
stats->buflen = htod32(stats_size);
stats->count = htod32(WL_CHANIM_COUNT_ONE);
if ((err = wlu_var_getbuf(wl, cmd->name, stats, stats_size, &ptr)) < 0) {
printf("failed to get chanim results");
free(stats);
return err;
}
memcpy(stats, ptr, stats_size);
stats->version = dtoh32(stats->version);
stats->buflen = dtoh32(stats->buflen);
stats->count = dtoh32(stats->count);
printf("version: %d \n", stats->version);
if (stats->version == WL_CHANIM_STATS_VERSION) {
chanim_stats_t *stats_v3 = (chanim_stats_t *)stats->stats;
stats_v3->glitchcnt = dtoh32(stats_v3->glitchcnt);
stats_v3->badplcp = dtoh32(stats_v3->badplcp);
stats_v3->chanspec = dtoh16(stats_v3->chanspec);
stats_v3->timestamp = dtoh32(stats_v3->timestamp);
printf("chanspec tx inbss obss nocat nopkt doze txop "
"goodtx badtx glitch badplcp knoise idle timestamp\n");
printf("0x%4x\t", stats_v3->chanspec);
for (i = 0; i < CCASTATS_MAX; i++) {
printf("%d\t", stats_v3->ccastats[i]);
}
printf("%d\t%d\t%d\t%d\t%d", stats_v3->glitchcnt, stats_v3->badplcp,
stats_v3->bgnoise, stats_v3->chan_idle, stats_v3->timestamp);
printf("\n");
} else if (stats->version == WL_CHANIM_STATS_V2) {
chanim_stats_v2_t *stats_v2 = (chanim_stats_v2_t *)stats->stats;
stats_v2->glitchcnt = dtoh32(stats_v2->glitchcnt);
stats_v2->badplcp = dtoh32(stats_v2->badplcp);
stats_v2->chanspec = dtoh16(stats_v2->chanspec);
stats_v2->timestamp = dtoh32(stats_v2->timestamp);
printf("chanspec tx inbss obss nocat nopkt doze txop "
"goodtx badtx glitch badplcp knoise idle timestamp\n");
printf("0x%4x\t", stats_v2->chanspec);
for (i = 0; i < CCASTATS_V2_MAX; i++) {
printf("%d\t", stats_v2->ccastats[i]);
}
printf("%d\t%d\t%d\t%d\t%d", stats_v2->glitchcnt, stats_v2->badplcp,
stats_v2->bgnoise, stats_v2->chan_idle, stats_v2->timestamp);
printf("\n");
}
free(stats);
return (err);
}