From bf1dec110d517755e66125d4b8699f50b8e6dfcf Mon Sep 17 00:00:00 2001 From: Manel Caro Date: Thu, 7 Mar 2019 20:13:19 +0100 Subject: Calibrator and uim Initial Commit --- plt.c | 1431 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1431 insertions(+) create mode 100644 plt.c (limited to 'plt.c') diff --git a/plt.c b/plt.c new file mode 100644 index 0000000..1964b9d --- /dev/null +++ b/plt.c @@ -0,0 +1,1431 @@ +/* + * PLT utility for wireless chip supported by TI's driver wl12xx + * + * See README and COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "nl80211.h" + +#include "calibrator.h" +#include "plt.h" +#include "ini.h" +#include "nvs.h" + +#define ZERO_MAC "00:00:00:00:00:00" + +#ifndef SIOCETHTOOL +#define SIOCETHTOOL 0x8946 +#endif + +SECTION(plt); + +#define CMDBUF_SIZE 200 +static int insmod(char *filename) +{ + int ret; + char cmd[CMDBUF_SIZE]; + snprintf(cmd, CMDBUF_SIZE, "%s %s", INSMOD_PATH, filename); + ret = system(cmd); + if (ret) + fprintf(stderr, "Failed to load kernel module using command %s\n", cmd); + return ret; +} + +static int rmmod(char *name) +{ + char cmd[CMDBUF_SIZE]; + char *tmp; + int i, ret; + + /* "basename" */ + tmp = strrchr(name, '/'); + if (!tmp) + tmp = name; + else + tmp++; + + tmp = strdup(tmp); + if (!tmp) + return -ENOMEM; + + /* strip trailing .ko if there */ + i = strlen(tmp); + if (i < 4) { + ret = -EINVAL; + goto out; + } + if (!strcmp(tmp + i - 3, ".ko")) + tmp[i-3] = 0; + + snprintf(cmd, CMDBUF_SIZE, "%s %s", RMMOD_PATH, tmp); + ret = system(cmd); + if (ret) + fprintf(stderr, "Failed to remove kernel module using command %s\n", cmd); +out: + free(tmp); + return ret; +} + +static int fem_detect_valid_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *td[WL1271_TM_ATTR_MAX + 1]; + unsigned char *fem_manuf; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_TESTDATA]) { + fprintf(stderr, "no data!\n"); + return NL_SKIP; + } + + nla_parse(td, WL1271_TM_ATTR_MAX, nla_data(tb[NL80211_ATTR_TESTDATA]), + nla_len(tb[NL80211_ATTR_TESTDATA]), NULL); + + fem_manuf = (unsigned char*) nla_data(td[WL1271_TM_ATTR_DATA]); + + printf("Firmware detect FEM type=%d\n", *fem_manuf); + + return NL_SKIP; +} + +static int plt_power_mode(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + unsigned int pmode; + + if (argc != 1) { + fprintf(stderr, "%s> Missing arguments\n", __func__); + return 2; + } + + if (strcmp(argv[0], "on") == 0) + pmode = PLT_ON; + else if (strcmp(argv[0], "off") == 0) + pmode = PLT_OFF; + else if (strcmp(argv[0], "fem_detect") == 0) + pmode = PLT_FEM_DETECT; + else if (strcmp(argv[0], "chip_awake") == 0) + pmode = PLT_CHIP_AWAKE; + else { + fprintf(stderr, "%s> Invalid parameter\n", __func__); + return 2; + } + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "%s> fail to nla_nest_start()\n", __func__); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_SET_PLT_MODE); + NLA_PUT_U32(msg, WL1271_TM_ATTR_PLT_MODE, pmode); + + nla_nest_end(msg, key); + + if (pmode == PLT_FEM_DETECT) + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, fem_detect_valid_handler, NULL); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, power_mode, "", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_power_mode, + "Set PLT power mode\n"); + +static int plt_tune_channel(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_cal_channel_tune prms; + + if (argc < 1 || argc > 2) + return 1; + + prms.test.id = TEST_CMD_CHANNEL_TUNE; + prms.band = (unsigned char)atoi(argv[0]); + prms.channel = (unsigned char)atoi(argv[1]); + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "fail to nla_nest_start()\n"); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, + sizeof(struct wl1271_cmd_cal_channel_tune), + &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, tune_channel, " ", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_tune_channel, + "Set band and channel for PLT\n"); + +static int plt_ref_point(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_cal_update_ref_point prms; + + if (argc < 1 || argc > 3) + return 1; + + prms.test.id = TEST_CMD_UPDATE_PD_REFERENCE_POINT; + prms.ref_detector = atoi(argv[0]); + prms.ref_power = atoi(argv[1]); + prms.sub_band = atoi(argv[2]); + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "fail to nla_nest_start()\n"); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, ref_point, " ", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_ref_point, + "Set reference point for PLT\n"); + +static int calib_valid_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *td[WL1271_TM_ATTR_MAX + 1]; + struct wl1271_cmd_cal_p2g *prms; +#if 0 + int i; unsigned char *pc; +#endif + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_TESTDATA]) { + fprintf(stderr, "no data!\n"); + return NL_SKIP; + } + + nla_parse(td, WL1271_TM_ATTR_MAX, nla_data(tb[NL80211_ATTR_TESTDATA]), + nla_len(tb[NL80211_ATTR_TESTDATA]), NULL); + + prms = (struct wl1271_cmd_cal_p2g *)nla_data(td[WL1271_TM_ATTR_DATA]); + + if (prms->radio_status) { + fprintf(stderr, "Fail to calibrate with radio status (%d)\n", + (signed short)prms->radio_status); + return 2; + } +#if 0 + printf("%s> id %04x status %04x\ntest id %02x ver %08x len %04x=%d\n", + __func__, + prms->header.id, prms->header.status, prms->test.id, + prms->ver, prms->len, prms->len); + + pc = (unsigned char *)prms->buf; + printf("++++++++++++++++++++++++\n"); + for (i = 0; i < prms->len; i++) { + if (i%0xf == 0) + printf("\n"); + + printf("%02x ", *(unsigned char *)pc); + pc += 1; + } + printf("++++++++++++++++++++++++\n"); +#endif + printf("Writing calibration data to %s\n", (char*) arg); + if (prepare_nvs_file(prms, arg)) { + fprintf(stderr, "Fail to prepare calibrated NVS file\n"); + return 2; + } +#if 0 + printf("\n\tThe NVS file (%s) is ready\n\tCopy it to %s and " + "reboot the system\n\n", + NEW_NVS_NAME, CURRENT_NVS_NAME); +#endif + return NL_SKIP; +} + +static void dump_regs(struct ethtool_drvinfo *info, struct ethtool_regs *regs) +{ + printf("\n\tDriver %s\n\t" + "version %s\n\t" + "FW version %s\n\t" + "Bus info %s\n\t" + "HW version 0x%X\n", + info->driver, info->version, + info->fw_version, info->bus_info, regs->version); +} + + +int do_get_drv_info(char *dev_name, int *hw_ver, + struct ethtool_drvinfo *out_drvinfo) +{ + struct ifreq ifr; + int fd, err; + struct ethtool_drvinfo drvinfo; + struct ethtool_regs *regs; + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, dev_name); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "Cannot get control socket\n"); + return 1; + } + + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (caddr_t)&drvinfo; + err = ioctl(fd, SIOCETHTOOL, &ifr); + if (err < 0) { + fprintf(stderr, "Cannot get driver information\n"); + goto error_out; + } + + regs = calloc(1, sizeof(*regs)+drvinfo.regdump_len); + if (!regs) { + fprintf(stderr, "Cannot allocate memory for register dump\n"); + goto error_out; + } + + regs->cmd = ETHTOOL_GREGS; + regs->len = drvinfo.regdump_len; + ifr.ifr_data = (caddr_t)regs; + err = ioctl(fd, SIOCETHTOOL, &ifr); + if (err < 0) { + fprintf(stderr, "Cannot get register dump\n"); + goto error_out2; + } + + if (hw_ver) + *hw_ver = regs->version; + if (out_drvinfo) + *out_drvinfo=drvinfo; + if (!hw_ver && !out_drvinfo) + dump_regs(&drvinfo, regs); + free(regs); + + return 0; + +error_out2: + free(regs); + +error_out: + close(fd); + + return 1; +} + +int is_fw_ver_valid(char *dev_name, struct fw_version *fw_ver_valid) +{ + char *str, *tmp_str; + struct ethtool_drvinfo drvinfo; + int i=0, ret=0; + struct fw_version fw_ver_crnt; + + ret = do_get_drv_info(dev_name, NULL, &drvinfo); + if(ret) { + printf("\tFailed to get FW version.\n"); + goto error; + } + + str=drvinfo.fw_version; + + /* Looking for the last space in the version.*/ + /* Usually version starting with text and value after spacing.*/ + tmp_str = strrchr (str,' '); + while((tmp_str != NULL) && + (i < (int) (sizeof(fw_ver_valid->ver)/sizeof(int)))) + { + tmp_str++; + fw_ver_crnt.ver[i]=atoi(tmp_str); + if (fw_ver_crnt.ver[i] < fw_ver_valid->ver[i]) { + ret=1; + break; + } else if (fw_ver_crnt.ver[i] > fw_ver_valid->ver[i]) { + ret=0; + break; + } + tmp_str = strchr (tmp_str, '.'); + i++; + } + +error: + return ret; +} + +static int get_chip_arch(char *dev_name, enum wl12xx_arch *arch) +{ + int hw_ver, ret; + + ret = do_get_drv_info(dev_name, &hw_ver, NULL); + if (ret) + return 1; + + *arch = hw_ver >> 16; + + return 0; +} + +static int do_nvs_ver21(struct nl_msg *msg, enum wl12xx_arch arch) +{ + struct nlattr *key; + struct wl1271_cmd_set_nvs_ver prms; + + memset(&prms, 0, sizeof(struct wl1271_cmd_set_nvs_ver)); + + if (arch == WL1271_ARCH) + prms.test.id = TEST_CMD_SET_NVS_VERSION; + else if(arch == WL128X_ARCH) + prms.test.id = TEST_CMD_SET_NVS_VERSION - 1; + else { + fprintf(stderr, "Unkown arch %x\n", arch); + return 1; + } + + prms.nvs_ver = NVS_VERSION_2_1; + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "fail to nla_nest_start()\n"); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + + +static int plt_nvs_ver(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + enum wl12xx_arch arch = UNKNOWN_ARCH; + int ret; + + if (argc < 1) { + fprintf(stderr, "Missing device name\n"); + return 2; + } + + ret = get_chip_arch(argv[0], &arch); + if (ret || (arch == UNKNOWN_ARCH)) { + fprintf(stderr, "Unknown chip arch\n"); + return 2; + } + + return do_nvs_ver21(msg, arch); +} + +COMMAND(plt, nvs_ver, "", + NL80211_CMD_TESTMODE, 0, CIB_PHY, plt_nvs_ver, + "Set NVS version\n"); + + +static int plt_nvs_ver2(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + enum wl12xx_arch arch = UNKNOWN_ARCH; + int ret; + + if (argc < 1) { + fprintf(stderr, "Missing device name\n"); + return 2; + } + + ret = sscanf(argv[0], "%x", &arch); + if(ret != 1) { + fprintf(stderr, "Unknown chip arch\n"); + return 2; + } + + return do_nvs_ver21(msg, arch); +} + +COMMAND(plt, nvs_ver, "", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_nvs_ver2, + "Set NVS version\n"); + +static int plt_tx_bip(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_cal_p2g prms; + int i; + char *nvs_path; + static char void_path[] = {'\0'}; + + if (argc < 8) { + fprintf(stderr, "%s> Missing arguments\n", __func__); + return 2; + } + + if (argc > 8) + nvs_path = argv[8]; + else + nvs_path = void_path; + + memset(&prms, 0, sizeof(struct wl1271_cmd_cal_p2g)); + + prms.test.id = TEST_CMD_P2G_CAL; + for (i = 0; i < 8; i++) + prms.sub_band_mask |= (atoi(argv[i]) & 0x1)< building message failed\n", __func__); + return 2; +} + +COMMAND(plt, tx_bip, + "<0|1> <0|1> <0|1> <0|1> <0|1> <0|1> <0|1> <0|1> []", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_tx_bip, + "Do calibrate\n"); + +static int plt_tx_tone(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_cal_tx_tone prms; + + if (argc < 2) { + fprintf(stderr, "%s> Missing arguments\n", __func__); + return 2; + } + + memset(&prms, 0, sizeof(struct wl1271_cmd_cal_tx_tone)); + + prms.test.id = TEST_CMD_TELEC; + + prms.tone_type = atoi(argv[0]); + if (prms.tone_type < 1 || prms.tone_type > 2) { + fprintf(stderr, "%s> Invalit tone type parameter %d\n", + __func__, prms.tone_type); + return 2; + } + + prms.power = atoi(argv[1]); + if (prms.power > 10000) { + fprintf(stderr, "%s> Invalit power parameter %d\n", + __func__, prms.power); + return 2; + } + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "fail to nla_nest_start()\n"); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, tx_tone, " ", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_tx_tone, + "Do command tx_tone to transmit a tone\n"); + +static int plt_tx_cont(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_pkt_params prms = { + .src_mac = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + + if (argc != 15) + return 1; +#if 0 + printf("%s> delay (%d) rate (%08x) size (%d) amount (%d) power (%d) " + "seed (%d) pkt_mode (%d) DCF (%d) GI (%d) preamble (%d) type " + "(%d) scramble (%d) CLPC (%d), SeqNbrMode (%d) DestMAC (%s)\n", + __func__, + atoi(argv[0]), atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), + atoi(argv[4]), atoi(argv[5]), atoi(argv[6]), atoi(argv[7]), + atoi(argv[8]), atoi(argv[9]), atoi(argv[10]), atoi(argv[11]), + atoi(argv[12]), atoi(argv[13]), argv[14] + ); +#endif + memset((void *)&prms, 0, sizeof(struct wl1271_cmd_pkt_params)); + + prms.test.id = TEST_CMD_FCC; + prms.delay = atoi(argv[0]); + prms.rate = strtol(argv[1], NULL, 0); + prms.size = (unsigned short)atoi(argv[2]); + prms.amount = (unsigned short)atoi(argv[3]); + prms.power = atoi(argv[4]); + prms.seed = (unsigned short)atoi(argv[5]); + prms.pkt_mode = (unsigned char)atoi(argv[6]); + prms.dcf_enable = (unsigned char)atoi(argv[7]); + prms.g_interval = (unsigned char)atoi(argv[8]); + prms.preamble = (unsigned char)atoi(argv[9]); + prms.type = (unsigned char)atoi(argv[10]); + prms.scramble = (unsigned char)atoi(argv[11]); + prms.clpc_enable = (unsigned char)atoi(argv[12]); + prms.seq_nbr_mode = (unsigned char)atoi(argv[13]); + str2mac(prms.dst_mac, argv[14]); + + if (get_mac_addr(0, prms.src_mac)) + fprintf(stderr, "fail to get MAC addr\n"); + + printf("%02X:%02X:%02X:%02X:%02X:%02X\n", + prms.src_mac[0], prms.src_mac[1], prms.src_mac[2], + prms.src_mac[3], prms.src_mac[4], prms.src_mac[5]); + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "fail to nla_nest_start()\n"); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, tx_cont, " \n\t\t " + " \n\t\t " + " ", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_tx_cont, + "Start Tx Cont\n"); + +static int plt_tx_stop(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_pkt_params prms; + + prms.test.id = TEST_CMD_STOP_TX; + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "fail to nla_nest_start()\n"); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, tx_stop, NULL, + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_tx_stop, + "Stop Tx Cont\n"); + +static int plt_start_rx_statcs(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_pkt_params prms; + + prms.test.id = TEST_CMD_RX_STAT_START; + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "%s> fail to nla_nest_start()\n", __func__); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, start_rx_statcs, NULL, + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_start_rx_statcs, + "Start Rx statistics collection\n"); + +static int plt_stop_rx_statcs(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_pkt_params prms; + + prms.test.id = TEST_CMD_RX_STAT_STOP; + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "%s> fail to nla_nest_start()\n", __func__); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, stop_rx_statcs, NULL, + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_stop_rx_statcs, + "Stop Rx statistics collection\n"); + +static int plt_reset_rx_statcs(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_cmd_pkt_params prms; + + prms.test.id = TEST_CMD_RX_STAT_RESET; + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "%s> fail to nla_nest_start()\n", __func__); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + + nla_nest_end(msg, key); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, reset_rx_statcs, NULL, + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_reset_rx_statcs, + "Reset Rx statistics collection\n"); + +static int display_rx_statcs(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *td[WL1271_TM_ATTR_MAX + 1]; + struct wl1271_radio_rx_statcs *prms; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_TESTDATA]) { + fprintf(stderr, "no data!\n"); + return NL_SKIP; + } + + nla_parse(td, WL1271_TM_ATTR_MAX, nla_data(tb[NL80211_ATTR_TESTDATA]), + nla_len(tb[NL80211_ATTR_TESTDATA]), NULL); + + prms = + (struct wl1271_radio_rx_statcs *) + nla_data(td[WL1271_TM_ATTR_DATA]); + + printf("\n\tTotal number of pkts\t- %d\n\tAccepted pkts\t\t- %d\n\t" + "FCS error pkts\t\t- %d\n\tAddress mismatch pkts\t- %d\n\t" + "Average SNR\t\t- % d dBm\n\tAverage RSSI\t\t- % d dBm\n\n", + prms->base_pkt_id, prms->rx_path_statcs.nbr_rx_valid_pkts, + prms->rx_path_statcs.nbr_rx_fcs_err_pkts, + prms->rx_path_statcs.nbr_rx_plcp_err_pkts, + (signed short)prms->rx_path_statcs.ave_snr/8, + (signed short)prms->rx_path_statcs.ave_rssi/8); + + return NL_SKIP; +} + +static int plt_get_rx_statcs(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct nlattr *key; + struct wl1271_radio_rx_statcs prms; + + prms.test.id = TEST_CMD_RX_STAT_GET; + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "%s> fail to nla_nest_start()\n", __func__); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_TEST); + NLA_PUT(msg, WL1271_TM_ATTR_DATA, sizeof(prms), &prms); + NLA_PUT_U8(msg, WL1271_TM_ATTR_ANSWER, 1); + + nla_nest_end(msg, key); + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, display_rx_statcs, NULL); + + /* Important: needed gap between tx_start and tx_get */ + sleep(2); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +COMMAND(plt, get_rx_statcs, NULL, + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_get_rx_statcs, + "Get Rx statistics\n"); + +static int plt_rx_statistics(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + int ret; + + /* power mode on */ + { + char *prms[4] = { "wlan0", "plt", "power_mode", "on" }; + + ret = handle_cmd(state, II_NETDEV, 4, prms); + if (ret < 0) { + fprintf(stderr, "Fail to set PLT power mode on\n"); + return 1; + } + } + + /* start_rx_statcs */ + { + char *prms[3] = { "wlan0", "plt", "start_rx_statcs" }; + + ret = handle_cmd(state, II_NETDEV, 3, prms); + if (ret < 0) { + fprintf(stderr, "Fail to start Rx statistics\n"); + goto fail_out; + } + } + + /* get_rx_statcs */ + { + int err; + char *prms[3] = { "wlan0", "plt", "get_rx_statcs" }; + + err = handle_cmd(state, II_NETDEV, 3, prms); + if (err < 0) { + fprintf(stderr, "Fail to get Rx statistics\n"); + ret = err; + } + } + + + /* stop_rx_statcs */ + { + int err; + char *prms[3] = { "wlan0", "plt", "stop_rx_statcs" }; + + err = handle_cmd(state, II_NETDEV, 3, prms); + if (err < 0) { + fprintf(stderr, "Fail to stop Rx statistics\n"); + ret = err; + } + } + +fail_out: + /* power mode off */ + { + int err; + char *prms[4] = { "wlan0", "plt", "power_mode", "off"}; + + err = handle_cmd(state, II_NETDEV, 4, prms); + if (err < 0) { + fprintf(stderr, "Fail to set PLT power mode on\n"); + return 1; + } + } + + if (ret < 0) + return 1; + + return 0; +} + +COMMAND(plt, rx_statistics, NULL, 0, 0, CIB_NONE, plt_rx_statistics, + "Get Rx statistics\n"); + +static int plt_do_power_on(struct nl80211_state *state, char *devname) +{ + int err; + char *pm_on[4] = { devname, "plt", "power_mode", "on" }; + + err = handle_cmd(state, II_NETDEV, ARRAY_SIZE(pm_on), pm_on); + if (err < 0) + fprintf(stderr, "Fail to set PLT power mode on\n"); + + return err; +} + +static int plt_do_power_off(struct nl80211_state *state, char *devname) +{ + int err; + char *prms[4] = { devname, "plt", "power_mode", "off"}; + + err = handle_cmd(state, II_NETDEV, ARRAY_SIZE(prms), prms); + if (err < 0) + fprintf(stderr, "Failed to set PLT power mode on\n"); + + return err; +} + + +static int plt_do_calibrate(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int single_dual, char *nvs_file, + char *devname, enum wl12xx_arch arch) +{ + int ret = 0, err; + + /* tune channel */ + { + char *tune[5] = { + devname, "plt", "tune_channel", "0", "7" + }; + + err = handle_cmd(state, II_NETDEV, ARRAY_SIZE(tune), tune); + if (err < 0) { + fprintf(stderr, "Fail to tune channel\n"); + ret = err; + goto fail_out; + } + } + + /* Set nvs version 2.1 */ + if (arch == UNKNOWN_ARCH) { + fprintf(stderr, "Unknown arch. Not setting nvs ver 2.1\n"); + } + else { + size_t ret; + char archstr[5] = ""; + char *prms[4] = { + "wlan0", "plt", "nvs_ver", archstr + }; + + ret = snprintf(archstr, sizeof(archstr), "%x", arch); + if (ret > sizeof(archstr)) { + fprintf(stderr, "Bad arch\n"); + goto fail_out; + } + + printf("Using nvs version 2.1\n"); + err = handle_cmd(state, II_NETDEV, 4, prms); + if (err < 0) { + fprintf(stderr, "Fail to set nvs ver 2.1\n"); + ret = err; + } + } + + /* calibrate it */ + { + char *prms[12] = { + devname, "plt", "tx_bip", "1", "0", "0", "0", + "0", "0", "0", "0", nvs_file + }; + printf("Calibrate %s\n", nvs_file); + + /* set flags in case of dual band */ + if (single_dual) { + prms[4] = prms[5] = prms[6] = prms[7] = prms[8] = + prms[9] = prms[10] = "1"; + } + + err = handle_cmd(state, II_NETDEV, ARRAY_SIZE(prms), prms); + if (err < 0) { + fprintf(stderr, "Failed to calibrate\n"); + ret = err; + } + } + +fail_out: + if (ret < 0) + return 1; + + return 0; +} + +static int plt_calibrate(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + int ret, err; + int single_dual = 0; + + if (argc > 2 && (strncmp(argv[2], "dual", 4) == 0)) + single_dual = 1; /* going for dual band calibration */ + else + single_dual = 0; /* going for single band calibration */ + + + err = plt_do_power_on(state, "wlan0"); + if (err < 0) + goto out; + + err = plt_do_calibrate(state, cb, msg, single_dual, NEW_NVS_NAME, + "wlan0", UNKNOWN_ARCH); + + ret = plt_do_power_off(state, "wlan0"); + if (ret < 0) + err = ret; +out: + return err; +} + +COMMAND(plt, calibrate, "[]", 0, 0, CIB_NONE, + plt_calibrate, "Do calibrate for single or dual band chip\n"); + + +static int plt_autocalibrate(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + struct wl12xx_common cmn = { + .auto_fem = 0, + .arch = UNKNOWN_ARCH, + .parse_ops = NULL, + }; + + char *devname, *modpath, *inifile1, *macaddr; + char *set_mac_prms[5]; + int single_dual = 0, res, fems_parsed; + + argc -= 2; + argv += 2; + + if (argc < 4 || argc > 5) { + return 1; + } + + devname = *argv++; + argc--; + + modpath = *argv++; + argc--; + + inifile1 = *argv++; + argc--; + + cmn.nvs_name = get_opt_nvsoutfile(argc--, argv++); + + if (argc) { + macaddr = *argv++; + argc--; + } else { + macaddr = NULL; + } + + if (file_exist(cmn.nvs_name) >= 0) { + fprintf(stderr, "nvs file %s. File already exists. Won't overwrite.\n", cmn.nvs_name); + return 0; + } + + /* Create ref nvs */ + if (read_ini(inifile1, &cmn)) { + fprintf(stderr, "Failed to read ini file %s\n", inifile1); + goto out_removenvs; + } + + fems_parsed = cmn.fem0_bands + cmn.fem1_bands + cmn.fem2_bands + cmn.fem3_bands; + + /* Get nr bands from parsed ini */ + single_dual = ini_get_dual_mode(&cmn); + + if (single_dual == 0) { + if (fems_parsed < 1 || fems_parsed > 4) { + fprintf(stderr, "Incorrect number of FEM sections %d for single mode\n", + fems_parsed); + return 1; + } + } + else if (single_dual == 1) { + if (fems_parsed < 2 && fems_parsed > 8) { + fprintf(stderr, "Incorrect number of FEM sections %d for dual mode\n", + fems_parsed); + return 1; + } + } + else { + fprintf(stderr, "Invalid value for TXBiPFEMAutoDetect %d\n", + single_dual); + return 1; + } + + /* I suppose you can have one FEM with 2.4 only and one in dual band + but it's more likely a mistake. + + In normal situation we have: + ---------------------------- + Single Band Manual - 1 FEM + Dual Band Manual - 2 FEMs + Single Band Auto Fem - 4 FEMs + Dual Band Auto Fem - 8 FEMs */ + if ((single_dual + 1) * (cmn.auto_fem * 3 + 1) != fems_parsed) { + printf("WARNING: %d FEMS for %d bands with autofem %s looks " + "like a strange configuration\n", + fems_parsed, single_dual + 1, + cmn.auto_fem ? "on" : "off"); + } + + cfg_nvs_ops(&cmn); + + if (create_nvs_file(&cmn)) { + fprintf(stderr, "Failed to create reference NVS file\n"); + return 1; + } + + /* Load module */ + res = insmod(modpath); + if (res) { + goto out_removenvs; + } + + res = plt_do_power_on(state, devname); + if (res < 0) + goto out_rmmod; + + res = plt_do_calibrate(state, cb, msg, single_dual, + cmn.nvs_name, devname, cmn.arch); + if (res) { + goto out_power_off; + } + + set_mac_prms[0] = devname; + set_mac_prms[1] = "plt"; + set_mac_prms[2] = "set_mac"; + set_mac_prms[3] = cmn.nvs_name; + set_mac_prms[4] = macaddr; + + res = handle_cmd(state, II_NETDEV, + ARRAY_SIZE(set_mac_prms) - (!macaddr), + set_mac_prms); + if (res) { + goto out_power_off; + } + + /* we can ignore the return value, because we rmmod anyway */ + plt_do_power_off(state, devname); + rmmod(modpath); + + printf("Calibration done. "); + if (cmn.fem0_bands) { + printf("FEM0 has %d bands. ", cmn.fem0_bands); + } + if (cmn.fem1_bands) { + printf("FEM1 has %d bands. ", cmn.fem1_bands); + } + if (cmn.fem2_bands) { + printf("FEM2 has %d bands. ", cmn.fem2_bands); + } + if (cmn.fem3_bands) { + printf("FEM3 has %d bands. ", cmn.fem3_bands); + } + + printf("AutoFEM is %s. ", cmn.auto_fem ? "on" : "off"); + + printf("Resulting nvs is %s\n", + cmn.nvs_name); + return 0; + +out_power_off: + /* we can ignore the return value, because we rmmod anyway */ + plt_do_power_off(state, devname); +out_rmmod: + rmmod(modpath); + +out_removenvs: + fprintf(stderr, "Calibration not complete. Removing half-baked nvs\n"); + unlink(cmn.nvs_name); + return 0; + +} +COMMAND(plt, autocalibrate, " " + "[|from_fuse|default]", 0, 0, CIB_NONE, plt_autocalibrate, + "Do automatic calibration.\n" + "The MAC address value can be:\n" + "from_fuse\ttry to read from the fuse ROM, if not available the command fails\n" + "default\t\twrite 00:00:00:00:00:00 to have the driver read from the fuse ROM,\n" + "\t\t\tfails if not available\n" + "00:00:00:00:00:00\tforce use of a zeroed MAC address (use with caution!)\n"); + +static int plt_get_mac_cb(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *td[WL1271_TM_ATTR_MAX + 1]; + char *addr; + int lower; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_TESTDATA]) { + fprintf(stderr, "no data!\n"); + return NL_SKIP; + } + + nla_parse(td, WL1271_TM_ATTR_MAX, nla_data(tb[NL80211_ATTR_TESTDATA]), + nla_len(tb[NL80211_ATTR_TESTDATA]), NULL); + + addr = (char *) nla_data(td[WL1271_TM_ATTR_DATA]); + + printf("BD_ADDR from fuse:\t0x%0x:0x%0x:0x%0x:0x%0x:0x%0x:0x%0x\n", + addr[0], addr[1], addr[2], + addr[3], addr[4], addr[5]); + + lower = (addr[3] << 16) + (addr[4] << 8) + addr[5]; + + lower++; + printf("First WLAN MAC:\t\t0x%0x:0x%0x:0x%0x:0x%0x:0x%0x:0x%0x\n", + addr[0], addr[1], addr[2], + (lower & 0xff0000) >> 16, + (lower & 0xff00) >> 8, + (lower & 0xff)); + + lower++; + printf("Second WLAN MAC:\t0x%0x:0x%0x:0x%0x:0x%0x:0x%0x:0x%0x\n", + addr[0], addr[1], addr[2], + (lower & 0xff0000) >> 16, + (lower & 0xff00) >> 8, + (lower & 0xff)); + + return NL_SKIP; +} + +static int plt_get_mac_from_fuse(struct nl_msg *msg, struct nl_cb *cb, + nl_recvmsg_msg_cb_t callback, void *arg) +{ + struct nlattr *key; + + key = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!key) { + fprintf(stderr, "%s> fail to nla_nest_start()\n", __func__); + return 1; + } + + NLA_PUT_U32(msg, WL1271_TM_ATTR_CMD_ID, WL1271_TM_CMD_GET_MAC); + + nla_nest_end(msg, key); + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, callback, arg); + + return 0; + +nla_put_failure: + fprintf(stderr, "%s> building message failed\n", __func__); + return 2; +} + +static int plt_get_mac(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + if (argc != 0) + return 1; + + return plt_get_mac_from_fuse(msg, cb, plt_get_mac_cb, NULL); +} +COMMAND(plt, get_mac, "", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_get_mac, + "Read MAC address from the Fuse ROM.\n"); + +static int plt_set_mac_from_fuse_cb(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *td[WL1271_TM_ATTR_MAX + 1]; + char mac[sizeof(ZERO_MAC)]; + char *addr; + char *nvs_file = (char *) arg; + int lower; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_TESTDATA]) { + fprintf(stderr, "no data!\n"); + return NL_SKIP; + } + + nla_parse(td, WL1271_TM_ATTR_MAX, nla_data(tb[NL80211_ATTR_TESTDATA]), + nla_len(tb[NL80211_ATTR_TESTDATA]), NULL); + + addr = (char *) nla_data(td[WL1271_TM_ATTR_DATA]); + + /* + * The first address is the BD_ADDR, the next is the first + * MAC. Increment only the lower part, so we don't overflow + * to the OUI */ + lower = (addr[3] << 16) + (addr[4] << 8) + addr[5] + 1; + + snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], (lower & 0xff0000) >> 16, + (lower & 0xff00) >> 8, (lower & 0xff)); + + /* ignore the return value, since a message was already printed out */ + nvs_set_mac(nvs_file, mac); + + return NL_SKIP; +} + +static int plt_set_mac_default_cb(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + char *nvs_file = (char *) arg; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_TESTDATA]) { + fprintf(stderr, "no data!\n"); + return NL_SKIP; + } + + /* + * No need to parse, we just need to know if the command + * worked (ie. the hardware supports MAC from fuse) so the + * driver can fetch it by itself. + */ + + /* ignore the return value, since a message was already printed out */ + nvs_set_mac(nvs_file, ZERO_MAC); + + return NL_SKIP; +} + +static int plt_set_mac_from_fuse(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + return plt_get_mac_from_fuse(msg, cb, plt_set_mac_from_fuse_cb, argv[0]); +} +HIDDEN(plt, set_mac_from_fuse, "", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_set_mac_from_fuse); + +static int plt_set_mac_default(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + return plt_get_mac_from_fuse(msg, cb, plt_set_mac_default_cb, argv[0]); +} +HIDDEN(plt, set_mac_default, "", + NL80211_CMD_TESTMODE, 0, CIB_NETDEV, plt_set_mac_default); + +static int plt_set_mac(struct nl80211_state *state, struct nl_cb *cb, + struct nl_msg *msg, int argc, char **argv) +{ + char *nvs_file; + + if (argc < 4 || argc > 5) + return 1; + + nvs_file = argv[3]; + + if (argc == 4 || !strcmp(argv[4], "default")) { + char *prms[] = { argv[0], argv[1], "set_mac_default", + nvs_file }; + + return handle_cmd(state, II_NETDEV, ARRAY_SIZE(prms), prms); + } + + if (!strcmp(argv[4], "from_fuse")) { + char *prms[] = { argv[0], argv[1], "set_mac_from_fuse", + nvs_file }; + + return handle_cmd(state, II_NETDEV, ARRAY_SIZE(prms), prms); + } + + if (nvs_set_mac(nvs_file, argv[4]) != 0) + return 1; + + return 0; +} +COMMAND(plt, set_mac, " [|from_fuse|default]", + 0, 0, CIB_NETDEV, plt_set_mac, + "Set a MAC address to the NVS file.\n\n" + "\tspecific address to use (XX:XX:XX:XX:XX:XX)\n" + "from_fuse\ttry to read from the fuse ROM, if not available the command fails\n" + "default\t\twrite 00:00:00:00:00:00 to have the driver read from the fuse ROM,\n" + "\t\t\tfails if not available\n" + "00:00:00:00:00:00\tforce use of a zeroed MAC address (use with caution!)\n"); -- cgit v1.1