17b3115f2SLuciano Coelho /* 27b3115f2SLuciano Coelho * This file is part of wl1271 37b3115f2SLuciano Coelho * 47b3115f2SLuciano Coelho * Copyright (C) 2010 Nokia Corporation 57b3115f2SLuciano Coelho * 67b3115f2SLuciano Coelho * Contact: Luciano Coelho <luciano.coelho@nokia.com> 77b3115f2SLuciano Coelho * 87b3115f2SLuciano Coelho * This program is free software; you can redistribute it and/or 97b3115f2SLuciano Coelho * modify it under the terms of the GNU General Public License 107b3115f2SLuciano Coelho * version 2 as published by the Free Software Foundation. 117b3115f2SLuciano Coelho * 127b3115f2SLuciano Coelho * This program is distributed in the hope that it will be useful, but 137b3115f2SLuciano Coelho * WITHOUT ANY WARRANTY; without even the implied warranty of 147b3115f2SLuciano Coelho * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 157b3115f2SLuciano Coelho * General Public License for more details. 167b3115f2SLuciano Coelho * 177b3115f2SLuciano Coelho * You should have received a copy of the GNU General Public License 187b3115f2SLuciano Coelho * along with this program; if not, write to the Free Software 197b3115f2SLuciano Coelho * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 207b3115f2SLuciano Coelho * 02110-1301 USA 217b3115f2SLuciano Coelho * 227b3115f2SLuciano Coelho */ 237b3115f2SLuciano Coelho #include "testmode.h" 247b3115f2SLuciano Coelho 257b3115f2SLuciano Coelho #include <linux/slab.h> 267b3115f2SLuciano Coelho #include <net/genetlink.h> 277b3115f2SLuciano Coelho 28c31be25aSLuciano Coelho #include "wlcore.h" 297b3115f2SLuciano Coelho #include "debug.h" 307b3115f2SLuciano Coelho #include "acx.h" 317b3115f2SLuciano Coelho #include "ps.h" 327b3115f2SLuciano Coelho #include "io.h" 337b3115f2SLuciano Coelho 347b3115f2SLuciano Coelho #define WL1271_TM_MAX_DATA_LENGTH 1024 357b3115f2SLuciano Coelho 367b3115f2SLuciano Coelho enum wl1271_tm_commands { 377b3115f2SLuciano Coelho WL1271_TM_CMD_UNSPEC, 387b3115f2SLuciano Coelho WL1271_TM_CMD_TEST, 397b3115f2SLuciano Coelho WL1271_TM_CMD_INTERROGATE, 407b3115f2SLuciano Coelho WL1271_TM_CMD_CONFIGURE, 417b3115f2SLuciano Coelho WL1271_TM_CMD_NVS_PUSH, /* Not in use. Keep to not break ABI */ 427b3115f2SLuciano Coelho WL1271_TM_CMD_SET_PLT_MODE, 43fd92dc5dSArik Nemtsov WL1271_TM_CMD_RECOVER, /* Not in use. Keep to not break ABI */ 447b3115f2SLuciano Coelho WL1271_TM_CMD_GET_MAC, 457b3115f2SLuciano Coelho 467b3115f2SLuciano Coelho __WL1271_TM_CMD_AFTER_LAST 477b3115f2SLuciano Coelho }; 487b3115f2SLuciano Coelho #define WL1271_TM_CMD_MAX (__WL1271_TM_CMD_AFTER_LAST - 1) 497b3115f2SLuciano Coelho 507b3115f2SLuciano Coelho enum wl1271_tm_attrs { 517b3115f2SLuciano Coelho WL1271_TM_ATTR_UNSPEC, 527b3115f2SLuciano Coelho WL1271_TM_ATTR_CMD_ID, 537b3115f2SLuciano Coelho WL1271_TM_ATTR_ANSWER, 547b3115f2SLuciano Coelho WL1271_TM_ATTR_DATA, 557b3115f2SLuciano Coelho WL1271_TM_ATTR_IE_ID, 567b3115f2SLuciano Coelho WL1271_TM_ATTR_PLT_MODE, 577b3115f2SLuciano Coelho 587b3115f2SLuciano Coelho __WL1271_TM_ATTR_AFTER_LAST 597b3115f2SLuciano Coelho }; 607b3115f2SLuciano Coelho #define WL1271_TM_ATTR_MAX (__WL1271_TM_ATTR_AFTER_LAST - 1) 617b3115f2SLuciano Coelho 627b3115f2SLuciano Coelho static struct nla_policy wl1271_tm_policy[WL1271_TM_ATTR_MAX + 1] = { 637b3115f2SLuciano Coelho [WL1271_TM_ATTR_CMD_ID] = { .type = NLA_U32 }, 647b3115f2SLuciano Coelho [WL1271_TM_ATTR_ANSWER] = { .type = NLA_U8 }, 657b3115f2SLuciano Coelho [WL1271_TM_ATTR_DATA] = { .type = NLA_BINARY, 667b3115f2SLuciano Coelho .len = WL1271_TM_MAX_DATA_LENGTH }, 677b3115f2SLuciano Coelho [WL1271_TM_ATTR_IE_ID] = { .type = NLA_U32 }, 687b3115f2SLuciano Coelho [WL1271_TM_ATTR_PLT_MODE] = { .type = NLA_U32 }, 697b3115f2SLuciano Coelho }; 707b3115f2SLuciano Coelho 717b3115f2SLuciano Coelho 727b3115f2SLuciano Coelho static int wl1271_tm_cmd_test(struct wl1271 *wl, struct nlattr *tb[]) 737b3115f2SLuciano Coelho { 747b3115f2SLuciano Coelho int buf_len, ret, len; 757b3115f2SLuciano Coelho struct sk_buff *skb; 767b3115f2SLuciano Coelho void *buf; 777b3115f2SLuciano Coelho u8 answer = 0; 787b3115f2SLuciano Coelho 797b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd test"); 807b3115f2SLuciano Coelho 817b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_DATA]) 827b3115f2SLuciano Coelho return -EINVAL; 837b3115f2SLuciano Coelho 847b3115f2SLuciano Coelho buf = nla_data(tb[WL1271_TM_ATTR_DATA]); 857b3115f2SLuciano Coelho buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); 867b3115f2SLuciano Coelho 877b3115f2SLuciano Coelho if (tb[WL1271_TM_ATTR_ANSWER]) 887b3115f2SLuciano Coelho answer = nla_get_u8(tb[WL1271_TM_ATTR_ANSWER]); 897b3115f2SLuciano Coelho 907b3115f2SLuciano Coelho if (buf_len > sizeof(struct wl1271_command)) 917b3115f2SLuciano Coelho return -EMSGSIZE; 927b3115f2SLuciano Coelho 937b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 947b3115f2SLuciano Coelho 957b3115f2SLuciano Coelho if (wl->state == WL1271_STATE_OFF) { 967b3115f2SLuciano Coelho ret = -EINVAL; 977b3115f2SLuciano Coelho goto out; 987b3115f2SLuciano Coelho } 997b3115f2SLuciano Coelho 1007b3115f2SLuciano Coelho ret = wl1271_ps_elp_wakeup(wl); 1017b3115f2SLuciano Coelho if (ret < 0) 1027b3115f2SLuciano Coelho goto out; 1037b3115f2SLuciano Coelho 1047b3115f2SLuciano Coelho ret = wl1271_cmd_test(wl, buf, buf_len, answer); 1057b3115f2SLuciano Coelho if (ret < 0) { 1067b3115f2SLuciano Coelho wl1271_warning("testmode cmd test failed: %d", ret); 1077b3115f2SLuciano Coelho goto out_sleep; 1087b3115f2SLuciano Coelho } 1097b3115f2SLuciano Coelho 1107b3115f2SLuciano Coelho if (answer) { 111b0b09e31SYair Shapira /* If we got bip calibration answer print radio status */ 112b0b09e31SYair Shapira struct wl1271_cmd_cal_p2g *params = 113b0b09e31SYair Shapira (struct wl1271_cmd_cal_p2g *) buf; 114b0b09e31SYair Shapira 115b0b09e31SYair Shapira s16 radio_status = (s16) le16_to_cpu(params->radio_status); 116b0b09e31SYair Shapira 117b0b09e31SYair Shapira if (params->test.id == TEST_CMD_P2G_CAL && 118b0b09e31SYair Shapira radio_status < 0) 119b0b09e31SYair Shapira wl1271_warning("testmode cmd: radio status=%d", 120b0b09e31SYair Shapira radio_status); 121b0b09e31SYair Shapira else 122b0b09e31SYair Shapira wl1271_info("testmode cmd: radio status=%d", 123b0b09e31SYair Shapira radio_status); 124b0b09e31SYair Shapira 1257b3115f2SLuciano Coelho len = nla_total_size(buf_len); 1267b3115f2SLuciano Coelho skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len); 1277b3115f2SLuciano Coelho if (!skb) { 1287b3115f2SLuciano Coelho ret = -ENOMEM; 1297b3115f2SLuciano Coelho goto out_sleep; 1307b3115f2SLuciano Coelho } 1317b3115f2SLuciano Coelho 13259ef43e6SJohn W. Linville if (nla_put(skb, WL1271_TM_ATTR_DATA, buf_len, buf)) 13359ef43e6SJohn W. Linville goto nla_put_failure; 1347b3115f2SLuciano Coelho ret = cfg80211_testmode_reply(skb); 1357b3115f2SLuciano Coelho if (ret < 0) 1367b3115f2SLuciano Coelho goto out_sleep; 1377b3115f2SLuciano Coelho } 1387b3115f2SLuciano Coelho 1397b3115f2SLuciano Coelho out_sleep: 1407b3115f2SLuciano Coelho wl1271_ps_elp_sleep(wl); 1417b3115f2SLuciano Coelho out: 1427b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 1437b3115f2SLuciano Coelho 1447b3115f2SLuciano Coelho return ret; 1457b3115f2SLuciano Coelho 1467b3115f2SLuciano Coelho nla_put_failure: 1477b3115f2SLuciano Coelho kfree_skb(skb); 1487b3115f2SLuciano Coelho ret = -EMSGSIZE; 1497b3115f2SLuciano Coelho goto out_sleep; 1507b3115f2SLuciano Coelho } 1517b3115f2SLuciano Coelho 1527b3115f2SLuciano Coelho static int wl1271_tm_cmd_interrogate(struct wl1271 *wl, struct nlattr *tb[]) 1537b3115f2SLuciano Coelho { 1547b3115f2SLuciano Coelho int ret; 1557b3115f2SLuciano Coelho struct wl1271_command *cmd; 1567b3115f2SLuciano Coelho struct sk_buff *skb; 1577b3115f2SLuciano Coelho u8 ie_id; 1587b3115f2SLuciano Coelho 1597b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd interrogate"); 1607b3115f2SLuciano Coelho 1617b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_IE_ID]) 1627b3115f2SLuciano Coelho return -EINVAL; 1637b3115f2SLuciano Coelho 1647b3115f2SLuciano Coelho ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); 1657b3115f2SLuciano Coelho 1667b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 1677b3115f2SLuciano Coelho 1687b3115f2SLuciano Coelho if (wl->state == WL1271_STATE_OFF) { 1697b3115f2SLuciano Coelho ret = -EINVAL; 1707b3115f2SLuciano Coelho goto out; 1717b3115f2SLuciano Coelho } 1727b3115f2SLuciano Coelho 1737b3115f2SLuciano Coelho ret = wl1271_ps_elp_wakeup(wl); 1747b3115f2SLuciano Coelho if (ret < 0) 1757b3115f2SLuciano Coelho goto out; 1767b3115f2SLuciano Coelho 1777b3115f2SLuciano Coelho cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); 1787b3115f2SLuciano Coelho if (!cmd) { 1797b3115f2SLuciano Coelho ret = -ENOMEM; 1807b3115f2SLuciano Coelho goto out_sleep; 1817b3115f2SLuciano Coelho } 1827b3115f2SLuciano Coelho 1837b3115f2SLuciano Coelho ret = wl1271_cmd_interrogate(wl, ie_id, cmd, sizeof(*cmd)); 1847b3115f2SLuciano Coelho if (ret < 0) { 1857b3115f2SLuciano Coelho wl1271_warning("testmode cmd interrogate failed: %d", ret); 1867b3115f2SLuciano Coelho goto out_free; 1877b3115f2SLuciano Coelho } 1887b3115f2SLuciano Coelho 1897b3115f2SLuciano Coelho skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, sizeof(*cmd)); 1907b3115f2SLuciano Coelho if (!skb) { 1917b3115f2SLuciano Coelho ret = -ENOMEM; 1927b3115f2SLuciano Coelho goto out_free; 1937b3115f2SLuciano Coelho } 1947b3115f2SLuciano Coelho 19559ef43e6SJohn W. Linville if (nla_put(skb, WL1271_TM_ATTR_DATA, sizeof(*cmd), cmd)) 19659ef43e6SJohn W. Linville goto nla_put_failure; 1977b3115f2SLuciano Coelho ret = cfg80211_testmode_reply(skb); 1987b3115f2SLuciano Coelho if (ret < 0) 1997b3115f2SLuciano Coelho goto out_free; 2007b3115f2SLuciano Coelho 2017b3115f2SLuciano Coelho out_free: 2027b3115f2SLuciano Coelho kfree(cmd); 2037b3115f2SLuciano Coelho out_sleep: 2047b3115f2SLuciano Coelho wl1271_ps_elp_sleep(wl); 2057b3115f2SLuciano Coelho out: 2067b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 2077b3115f2SLuciano Coelho 2087b3115f2SLuciano Coelho return ret; 2097b3115f2SLuciano Coelho 2107b3115f2SLuciano Coelho nla_put_failure: 2117b3115f2SLuciano Coelho kfree_skb(skb); 2127b3115f2SLuciano Coelho ret = -EMSGSIZE; 2137b3115f2SLuciano Coelho goto out_free; 2147b3115f2SLuciano Coelho } 2157b3115f2SLuciano Coelho 2167b3115f2SLuciano Coelho static int wl1271_tm_cmd_configure(struct wl1271 *wl, struct nlattr *tb[]) 2177b3115f2SLuciano Coelho { 2187b3115f2SLuciano Coelho int buf_len, ret; 2197b3115f2SLuciano Coelho void *buf; 2207b3115f2SLuciano Coelho u8 ie_id; 2217b3115f2SLuciano Coelho 2227b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd configure"); 2237b3115f2SLuciano Coelho 2247b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_DATA]) 2257b3115f2SLuciano Coelho return -EINVAL; 2267b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_IE_ID]) 2277b3115f2SLuciano Coelho return -EINVAL; 2287b3115f2SLuciano Coelho 2297b3115f2SLuciano Coelho ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); 2307b3115f2SLuciano Coelho buf = nla_data(tb[WL1271_TM_ATTR_DATA]); 2317b3115f2SLuciano Coelho buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); 2327b3115f2SLuciano Coelho 2337b3115f2SLuciano Coelho if (buf_len > sizeof(struct wl1271_command)) 2347b3115f2SLuciano Coelho return -EMSGSIZE; 2357b3115f2SLuciano Coelho 2367b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 2377b3115f2SLuciano Coelho ret = wl1271_cmd_configure(wl, ie_id, buf, buf_len); 2387b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 2397b3115f2SLuciano Coelho 2407b3115f2SLuciano Coelho if (ret < 0) { 2417b3115f2SLuciano Coelho wl1271_warning("testmode cmd configure failed: %d", ret); 2427b3115f2SLuciano Coelho return ret; 2437b3115f2SLuciano Coelho } 2447b3115f2SLuciano Coelho 2457b3115f2SLuciano Coelho return 0; 2467b3115f2SLuciano Coelho } 2477b3115f2SLuciano Coelho 2487b3115f2SLuciano Coelho static int wl1271_tm_cmd_set_plt_mode(struct wl1271 *wl, struct nlattr *tb[]) 2497b3115f2SLuciano Coelho { 2507b3115f2SLuciano Coelho u32 val; 2517b3115f2SLuciano Coelho int ret; 2527b3115f2SLuciano Coelho 2537b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd set plt mode"); 2547b3115f2SLuciano Coelho 2557b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_PLT_MODE]) 2567b3115f2SLuciano Coelho return -EINVAL; 2577b3115f2SLuciano Coelho 2587b3115f2SLuciano Coelho val = nla_get_u32(tb[WL1271_TM_ATTR_PLT_MODE]); 2597b3115f2SLuciano Coelho 2607b3115f2SLuciano Coelho switch (val) { 2617b3115f2SLuciano Coelho case 0: 2627b3115f2SLuciano Coelho ret = wl1271_plt_stop(wl); 2637b3115f2SLuciano Coelho break; 2647b3115f2SLuciano Coelho case 1: 2657b3115f2SLuciano Coelho ret = wl1271_plt_start(wl); 2667b3115f2SLuciano Coelho break; 2677b3115f2SLuciano Coelho default: 2687b3115f2SLuciano Coelho ret = -EINVAL; 2697b3115f2SLuciano Coelho break; 2707b3115f2SLuciano Coelho } 2717b3115f2SLuciano Coelho 2727b3115f2SLuciano Coelho return ret; 2737b3115f2SLuciano Coelho } 2747b3115f2SLuciano Coelho 2757b3115f2SLuciano Coelho static int wl12xx_tm_cmd_get_mac(struct wl1271 *wl, struct nlattr *tb[]) 2767b3115f2SLuciano Coelho { 2777b3115f2SLuciano Coelho struct sk_buff *skb; 2787b3115f2SLuciano Coelho u8 mac_addr[ETH_ALEN]; 2797b3115f2SLuciano Coelho int ret = 0; 2807b3115f2SLuciano Coelho 2817b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 2827b3115f2SLuciano Coelho 2837b3115f2SLuciano Coelho if (!wl->plt) { 2847b3115f2SLuciano Coelho ret = -EINVAL; 2857b3115f2SLuciano Coelho goto out; 2867b3115f2SLuciano Coelho } 2877b3115f2SLuciano Coelho 2887b3115f2SLuciano Coelho if (wl->fuse_oui_addr == 0 && wl->fuse_nic_addr == 0) { 2897b3115f2SLuciano Coelho ret = -EOPNOTSUPP; 2907b3115f2SLuciano Coelho goto out; 2917b3115f2SLuciano Coelho } 2927b3115f2SLuciano Coelho 2937b3115f2SLuciano Coelho mac_addr[0] = (u8)(wl->fuse_oui_addr >> 16); 2947b3115f2SLuciano Coelho mac_addr[1] = (u8)(wl->fuse_oui_addr >> 8); 2957b3115f2SLuciano Coelho mac_addr[2] = (u8) wl->fuse_oui_addr; 2967b3115f2SLuciano Coelho mac_addr[3] = (u8)(wl->fuse_nic_addr >> 16); 2977b3115f2SLuciano Coelho mac_addr[4] = (u8)(wl->fuse_nic_addr >> 8); 2987b3115f2SLuciano Coelho mac_addr[5] = (u8) wl->fuse_nic_addr; 2997b3115f2SLuciano Coelho 3007b3115f2SLuciano Coelho skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, ETH_ALEN); 3017b3115f2SLuciano Coelho if (!skb) { 3027b3115f2SLuciano Coelho ret = -ENOMEM; 3037b3115f2SLuciano Coelho goto out; 3047b3115f2SLuciano Coelho } 3057b3115f2SLuciano Coelho 30659ef43e6SJohn W. Linville if (nla_put(skb, WL1271_TM_ATTR_DATA, ETH_ALEN, mac_addr)) 30759ef43e6SJohn W. Linville goto nla_put_failure; 3087b3115f2SLuciano Coelho ret = cfg80211_testmode_reply(skb); 3097b3115f2SLuciano Coelho if (ret < 0) 3107b3115f2SLuciano Coelho goto out; 3117b3115f2SLuciano Coelho 3127b3115f2SLuciano Coelho out: 3137b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 3147b3115f2SLuciano Coelho return ret; 3157b3115f2SLuciano Coelho 3167b3115f2SLuciano Coelho nla_put_failure: 3177b3115f2SLuciano Coelho kfree_skb(skb); 3187b3115f2SLuciano Coelho ret = -EMSGSIZE; 3197b3115f2SLuciano Coelho goto out; 3207b3115f2SLuciano Coelho } 3217b3115f2SLuciano Coelho 3227b3115f2SLuciano Coelho int wl1271_tm_cmd(struct ieee80211_hw *hw, void *data, int len) 3237b3115f2SLuciano Coelho { 3247b3115f2SLuciano Coelho struct wl1271 *wl = hw->priv; 3257b3115f2SLuciano Coelho struct nlattr *tb[WL1271_TM_ATTR_MAX + 1]; 3267b3115f2SLuciano Coelho int err; 3277b3115f2SLuciano Coelho 3287b3115f2SLuciano Coelho err = nla_parse(tb, WL1271_TM_ATTR_MAX, data, len, wl1271_tm_policy); 3297b3115f2SLuciano Coelho if (err) 3307b3115f2SLuciano Coelho return err; 3317b3115f2SLuciano Coelho 3327b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_CMD_ID]) 3337b3115f2SLuciano Coelho return -EINVAL; 3347b3115f2SLuciano Coelho 3357b3115f2SLuciano Coelho switch (nla_get_u32(tb[WL1271_TM_ATTR_CMD_ID])) { 3367b3115f2SLuciano Coelho case WL1271_TM_CMD_TEST: 3377b3115f2SLuciano Coelho return wl1271_tm_cmd_test(wl, tb); 3387b3115f2SLuciano Coelho case WL1271_TM_CMD_INTERROGATE: 3397b3115f2SLuciano Coelho return wl1271_tm_cmd_interrogate(wl, tb); 3407b3115f2SLuciano Coelho case WL1271_TM_CMD_CONFIGURE: 3417b3115f2SLuciano Coelho return wl1271_tm_cmd_configure(wl, tb); 3427b3115f2SLuciano Coelho case WL1271_TM_CMD_SET_PLT_MODE: 3437b3115f2SLuciano Coelho return wl1271_tm_cmd_set_plt_mode(wl, tb); 3447b3115f2SLuciano Coelho case WL1271_TM_CMD_GET_MAC: 3457b3115f2SLuciano Coelho return wl12xx_tm_cmd_get_mac(wl, tb); 3467b3115f2SLuciano Coelho default: 3477b3115f2SLuciano Coelho return -EOPNOTSUPP; 3487b3115f2SLuciano Coelho } 3497b3115f2SLuciano Coelho } 350