/* * PLT utility for wireless chip supported by TI's driver wl12xx * * See README and COPYING for more details. */ #include <sys/ioctl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdbool.h> #include <netlink/genl/genl.h> #include <netlink/genl/family.h> #include <netlink/genl/ctrl.h> #include <netlink/msg.h> #include <netlink/attr.h> #include <linux/wireless.h> #include "nl80211.h" #include "calibrator.h" #include "plt.h" #include "ini.h" #include "nvs.h" SECTION(plt); static void str2mac(unsigned char *pmac, char *pch) { int i; for (i = 0; i < MAC_ADDR_LEN; i++) { pmac[i] = (unsigned char)strtoul(pch, &pch, 16); pch++; } } 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 = 1; } else if (strcmp(argv[0], "off") == 0) { pmode = 0; } 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); return 0; nla_put_failure: fprintf(stderr, "%s> building message failed\n", __func__); return 2; } COMMAND(plt, power_mode, "<on|off>", 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, "<band> <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, "<voltage> <power> <subband>", 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 ith 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 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 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[PATH_MAX]; if (argc < 8) { fprintf(stderr, "%s> Missing arguments\n", __func__); return 2; } if (argc > 8) { strncpy(nvs_path, argv[8], strlen(argv[8])); } else { nvs_path[0] = '\0'; } 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)<<i; } 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_PUT_U8(msg, WL1271_TM_ATTR_ANSWER, 1); nla_nest_end(msg, key); nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, calib_valid_handler, nvs_path); return 0; nla_put_failure: fprintf(stderr, "%s> 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> [<nvs file>]", 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.power = atoi(argv[0]); prms.tone_type = 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(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, "<power> <tone type>", 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 = atoi(argv[1]); 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, "<delay> <rate> <size> <amount> <power>\n\t\t<seed> " "<pkt mode> <DC on/off> <gi> <preamble>\n\t\t<type> <scramble> " "<clpc> <seq nbr mode> <dest mac>", 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_calibrate(struct nl80211_state *state, struct nl_cb *cb, struct nl_msg *msg, int argc, char **argv) { int ret = 0, 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 */ } /* power mode on */ { char *pm_on[4] = { "wlan0", "plt", "power_mode", "on" }; err = handle_cmd(state, II_NETDEV, 4, pm_on); if (err < 0) { fprintf(stderr, "Fail to set PLT power mode on\n"); ret = err; goto fail_out_final; } } /* tune channel */ { char *pm_on[5] = { "wlan0", "plt", "tune_channel", "0", "7" }; err = handle_cmd(state, II_NETDEV, 5, pm_on); if (err < 0) { fprintf(stderr, "Fail to tune channel\n"); ret = err; goto fail_out; } } /* calibrate it */ { char *prms[11] = { "wlan0", "plt", "tx_bip", "1", "0", "0", "0", "0", "0", "0", "0" }; /* 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, 11, prms); if (err < 0) { fprintf(stderr, "Fail to calibrate\n"); ret = err; } } fail_out: /* power mode off */ { 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"); ret = err; } } fail_out_final: if (ret < 0) { return 1; } return 0; } COMMAND(plt, calibrate, "[<single|dual>]", 0, 0, CIB_NONE, plt_calibrate, "Do calibrate for single or dual band chip\n");