| /* |
| * 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); |
| } |