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 287b3115f2SLuciano Coelho #include "wl12xx.h" 297b3115f2SLuciano Coelho #include "debug.h" 307b3115f2SLuciano Coelho #include "acx.h" 317b3115f2SLuciano Coelho #include "reg.h" 327b3115f2SLuciano Coelho #include "ps.h" 337b3115f2SLuciano Coelho #include "io.h" 347b3115f2SLuciano Coelho 357b3115f2SLuciano Coelho #define WL1271_TM_MAX_DATA_LENGTH 1024 367b3115f2SLuciano Coelho 377b3115f2SLuciano Coelho enum wl1271_tm_commands { 387b3115f2SLuciano Coelho WL1271_TM_CMD_UNSPEC, 397b3115f2SLuciano Coelho WL1271_TM_CMD_TEST, 407b3115f2SLuciano Coelho WL1271_TM_CMD_INTERROGATE, 417b3115f2SLuciano Coelho WL1271_TM_CMD_CONFIGURE, 427b3115f2SLuciano Coelho WL1271_TM_CMD_NVS_PUSH, /* Not in use. Keep to not break ABI */ 437b3115f2SLuciano Coelho WL1271_TM_CMD_SET_PLT_MODE, 447b3115f2SLuciano Coelho WL1271_TM_CMD_RECOVER, 457b3115f2SLuciano Coelho WL1271_TM_CMD_GET_MAC, 467b3115f2SLuciano Coelho 477b3115f2SLuciano Coelho __WL1271_TM_CMD_AFTER_LAST 487b3115f2SLuciano Coelho }; 497b3115f2SLuciano Coelho #define WL1271_TM_CMD_MAX (__WL1271_TM_CMD_AFTER_LAST - 1) 507b3115f2SLuciano Coelho 517b3115f2SLuciano Coelho enum wl1271_tm_attrs { 527b3115f2SLuciano Coelho WL1271_TM_ATTR_UNSPEC, 537b3115f2SLuciano Coelho WL1271_TM_ATTR_CMD_ID, 547b3115f2SLuciano Coelho WL1271_TM_ATTR_ANSWER, 557b3115f2SLuciano Coelho WL1271_TM_ATTR_DATA, 567b3115f2SLuciano Coelho WL1271_TM_ATTR_IE_ID, 577b3115f2SLuciano Coelho WL1271_TM_ATTR_PLT_MODE, 587b3115f2SLuciano Coelho 597b3115f2SLuciano Coelho __WL1271_TM_ATTR_AFTER_LAST 607b3115f2SLuciano Coelho }; 617b3115f2SLuciano Coelho #define WL1271_TM_ATTR_MAX (__WL1271_TM_ATTR_AFTER_LAST - 1) 627b3115f2SLuciano Coelho 637b3115f2SLuciano Coelho static struct nla_policy wl1271_tm_policy[WL1271_TM_ATTR_MAX + 1] = { 647b3115f2SLuciano Coelho [WL1271_TM_ATTR_CMD_ID] = { .type = NLA_U32 }, 657b3115f2SLuciano Coelho [WL1271_TM_ATTR_ANSWER] = { .type = NLA_U8 }, 667b3115f2SLuciano Coelho [WL1271_TM_ATTR_DATA] = { .type = NLA_BINARY, 677b3115f2SLuciano Coelho .len = WL1271_TM_MAX_DATA_LENGTH }, 687b3115f2SLuciano Coelho [WL1271_TM_ATTR_IE_ID] = { .type = NLA_U32 }, 697b3115f2SLuciano Coelho [WL1271_TM_ATTR_PLT_MODE] = { .type = NLA_U32 }, 707b3115f2SLuciano Coelho }; 717b3115f2SLuciano Coelho 727b3115f2SLuciano Coelho 737b3115f2SLuciano Coelho static int wl1271_tm_cmd_test(struct wl1271 *wl, struct nlattr *tb[]) 747b3115f2SLuciano Coelho { 757b3115f2SLuciano Coelho int buf_len, ret, len; 767b3115f2SLuciano Coelho struct sk_buff *skb; 777b3115f2SLuciano Coelho void *buf; 787b3115f2SLuciano Coelho u8 answer = 0; 797b3115f2SLuciano Coelho 807b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd test"); 817b3115f2SLuciano Coelho 827b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_DATA]) 837b3115f2SLuciano Coelho return -EINVAL; 847b3115f2SLuciano Coelho 857b3115f2SLuciano Coelho buf = nla_data(tb[WL1271_TM_ATTR_DATA]); 867b3115f2SLuciano Coelho buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); 877b3115f2SLuciano Coelho 887b3115f2SLuciano Coelho if (tb[WL1271_TM_ATTR_ANSWER]) 897b3115f2SLuciano Coelho answer = nla_get_u8(tb[WL1271_TM_ATTR_ANSWER]); 907b3115f2SLuciano Coelho 917b3115f2SLuciano Coelho if (buf_len > sizeof(struct wl1271_command)) 927b3115f2SLuciano Coelho return -EMSGSIZE; 937b3115f2SLuciano Coelho 947b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 957b3115f2SLuciano Coelho 967b3115f2SLuciano Coelho if (wl->state == WL1271_STATE_OFF) { 977b3115f2SLuciano Coelho ret = -EINVAL; 987b3115f2SLuciano Coelho goto out; 997b3115f2SLuciano Coelho } 1007b3115f2SLuciano Coelho 1017b3115f2SLuciano Coelho ret = wl1271_ps_elp_wakeup(wl); 1027b3115f2SLuciano Coelho if (ret < 0) 1037b3115f2SLuciano Coelho goto out; 1047b3115f2SLuciano Coelho 1057b3115f2SLuciano Coelho ret = wl1271_cmd_test(wl, buf, buf_len, answer); 1067b3115f2SLuciano Coelho if (ret < 0) { 1077b3115f2SLuciano Coelho wl1271_warning("testmode cmd test failed: %d", ret); 1087b3115f2SLuciano Coelho goto out_sleep; 1097b3115f2SLuciano Coelho } 1107b3115f2SLuciano Coelho 1117b3115f2SLuciano Coelho if (answer) { 1127b3115f2SLuciano Coelho len = nla_total_size(buf_len); 1137b3115f2SLuciano Coelho skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len); 1147b3115f2SLuciano Coelho if (!skb) { 1157b3115f2SLuciano Coelho ret = -ENOMEM; 1167b3115f2SLuciano Coelho goto out_sleep; 1177b3115f2SLuciano Coelho } 1187b3115f2SLuciano Coelho 1197b3115f2SLuciano Coelho NLA_PUT(skb, WL1271_TM_ATTR_DATA, buf_len, buf); 1207b3115f2SLuciano Coelho ret = cfg80211_testmode_reply(skb); 1217b3115f2SLuciano Coelho if (ret < 0) 1227b3115f2SLuciano Coelho goto out_sleep; 1237b3115f2SLuciano Coelho } 1247b3115f2SLuciano Coelho 1257b3115f2SLuciano Coelho out_sleep: 1267b3115f2SLuciano Coelho wl1271_ps_elp_sleep(wl); 1277b3115f2SLuciano Coelho out: 1287b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 1297b3115f2SLuciano Coelho 1307b3115f2SLuciano Coelho return ret; 1317b3115f2SLuciano Coelho 1327b3115f2SLuciano Coelho nla_put_failure: 1337b3115f2SLuciano Coelho kfree_skb(skb); 1347b3115f2SLuciano Coelho ret = -EMSGSIZE; 1357b3115f2SLuciano Coelho goto out_sleep; 1367b3115f2SLuciano Coelho } 1377b3115f2SLuciano Coelho 1387b3115f2SLuciano Coelho static int wl1271_tm_cmd_interrogate(struct wl1271 *wl, struct nlattr *tb[]) 1397b3115f2SLuciano Coelho { 1407b3115f2SLuciano Coelho int ret; 1417b3115f2SLuciano Coelho struct wl1271_command *cmd; 1427b3115f2SLuciano Coelho struct sk_buff *skb; 1437b3115f2SLuciano Coelho u8 ie_id; 1447b3115f2SLuciano Coelho 1457b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd interrogate"); 1467b3115f2SLuciano Coelho 1477b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_IE_ID]) 1487b3115f2SLuciano Coelho return -EINVAL; 1497b3115f2SLuciano Coelho 1507b3115f2SLuciano Coelho ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); 1517b3115f2SLuciano Coelho 1527b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 1537b3115f2SLuciano Coelho 1547b3115f2SLuciano Coelho if (wl->state == WL1271_STATE_OFF) { 1557b3115f2SLuciano Coelho ret = -EINVAL; 1567b3115f2SLuciano Coelho goto out; 1577b3115f2SLuciano Coelho } 1587b3115f2SLuciano Coelho 1597b3115f2SLuciano Coelho ret = wl1271_ps_elp_wakeup(wl); 1607b3115f2SLuciano Coelho if (ret < 0) 1617b3115f2SLuciano Coelho goto out; 1627b3115f2SLuciano Coelho 1637b3115f2SLuciano Coelho cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); 1647b3115f2SLuciano Coelho if (!cmd) { 1657b3115f2SLuciano Coelho ret = -ENOMEM; 1667b3115f2SLuciano Coelho goto out_sleep; 1677b3115f2SLuciano Coelho } 1687b3115f2SLuciano Coelho 1697b3115f2SLuciano Coelho ret = wl1271_cmd_interrogate(wl, ie_id, cmd, sizeof(*cmd)); 1707b3115f2SLuciano Coelho if (ret < 0) { 1717b3115f2SLuciano Coelho wl1271_warning("testmode cmd interrogate failed: %d", ret); 1727b3115f2SLuciano Coelho goto out_free; 1737b3115f2SLuciano Coelho } 1747b3115f2SLuciano Coelho 1757b3115f2SLuciano Coelho skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, sizeof(*cmd)); 1767b3115f2SLuciano Coelho if (!skb) { 1777b3115f2SLuciano Coelho ret = -ENOMEM; 1787b3115f2SLuciano Coelho goto out_free; 1797b3115f2SLuciano Coelho } 1807b3115f2SLuciano Coelho 1817b3115f2SLuciano Coelho NLA_PUT(skb, WL1271_TM_ATTR_DATA, sizeof(*cmd), cmd); 1827b3115f2SLuciano Coelho ret = cfg80211_testmode_reply(skb); 1837b3115f2SLuciano Coelho if (ret < 0) 1847b3115f2SLuciano Coelho goto out_free; 1857b3115f2SLuciano Coelho 1867b3115f2SLuciano Coelho out_free: 1877b3115f2SLuciano Coelho kfree(cmd); 1887b3115f2SLuciano Coelho out_sleep: 1897b3115f2SLuciano Coelho wl1271_ps_elp_sleep(wl); 1907b3115f2SLuciano Coelho out: 1917b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 1927b3115f2SLuciano Coelho 1937b3115f2SLuciano Coelho return ret; 1947b3115f2SLuciano Coelho 1957b3115f2SLuciano Coelho nla_put_failure: 1967b3115f2SLuciano Coelho kfree_skb(skb); 1977b3115f2SLuciano Coelho ret = -EMSGSIZE; 1987b3115f2SLuciano Coelho goto out_free; 1997b3115f2SLuciano Coelho } 2007b3115f2SLuciano Coelho 2017b3115f2SLuciano Coelho static int wl1271_tm_cmd_configure(struct wl1271 *wl, struct nlattr *tb[]) 2027b3115f2SLuciano Coelho { 2037b3115f2SLuciano Coelho int buf_len, ret; 2047b3115f2SLuciano Coelho void *buf; 2057b3115f2SLuciano Coelho u8 ie_id; 2067b3115f2SLuciano Coelho 2077b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd configure"); 2087b3115f2SLuciano Coelho 2097b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_DATA]) 2107b3115f2SLuciano Coelho return -EINVAL; 2117b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_IE_ID]) 2127b3115f2SLuciano Coelho return -EINVAL; 2137b3115f2SLuciano Coelho 2147b3115f2SLuciano Coelho ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); 2157b3115f2SLuciano Coelho buf = nla_data(tb[WL1271_TM_ATTR_DATA]); 2167b3115f2SLuciano Coelho buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); 2177b3115f2SLuciano Coelho 2187b3115f2SLuciano Coelho if (buf_len > sizeof(struct wl1271_command)) 2197b3115f2SLuciano Coelho return -EMSGSIZE; 2207b3115f2SLuciano Coelho 2217b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 2227b3115f2SLuciano Coelho ret = wl1271_cmd_configure(wl, ie_id, buf, buf_len); 2237b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 2247b3115f2SLuciano Coelho 2257b3115f2SLuciano Coelho if (ret < 0) { 2267b3115f2SLuciano Coelho wl1271_warning("testmode cmd configure failed: %d", ret); 2277b3115f2SLuciano Coelho return ret; 2287b3115f2SLuciano Coelho } 2297b3115f2SLuciano Coelho 2307b3115f2SLuciano Coelho return 0; 2317b3115f2SLuciano Coelho } 2327b3115f2SLuciano Coelho 2337b3115f2SLuciano Coelho static int wl1271_tm_cmd_set_plt_mode(struct wl1271 *wl, struct nlattr *tb[]) 2347b3115f2SLuciano Coelho { 2357b3115f2SLuciano Coelho u32 val; 2367b3115f2SLuciano Coelho int ret; 2377b3115f2SLuciano Coelho 2387b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd set plt mode"); 2397b3115f2SLuciano Coelho 2407b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_PLT_MODE]) 2417b3115f2SLuciano Coelho return -EINVAL; 2427b3115f2SLuciano Coelho 2437b3115f2SLuciano Coelho val = nla_get_u32(tb[WL1271_TM_ATTR_PLT_MODE]); 2447b3115f2SLuciano Coelho 2457b3115f2SLuciano Coelho switch (val) { 2467b3115f2SLuciano Coelho case 0: 2477b3115f2SLuciano Coelho ret = wl1271_plt_stop(wl); 2487b3115f2SLuciano Coelho break; 2497b3115f2SLuciano Coelho case 1: 2507b3115f2SLuciano Coelho ret = wl1271_plt_start(wl); 2517b3115f2SLuciano Coelho break; 2527b3115f2SLuciano Coelho default: 2537b3115f2SLuciano Coelho ret = -EINVAL; 2547b3115f2SLuciano Coelho break; 2557b3115f2SLuciano Coelho } 2567b3115f2SLuciano Coelho 2577b3115f2SLuciano Coelho return ret; 2587b3115f2SLuciano Coelho } 2597b3115f2SLuciano Coelho 2607b3115f2SLuciano Coelho static int wl1271_tm_cmd_recover(struct wl1271 *wl, struct nlattr *tb[]) 2617b3115f2SLuciano Coelho { 2627b3115f2SLuciano Coelho wl1271_debug(DEBUG_TESTMODE, "testmode cmd recover"); 2637b3115f2SLuciano Coelho 2647b3115f2SLuciano Coelho wl12xx_queue_recovery_work(wl); 2657b3115f2SLuciano Coelho 2667b3115f2SLuciano Coelho return 0; 2677b3115f2SLuciano Coelho } 2687b3115f2SLuciano Coelho 2697b3115f2SLuciano Coelho static int wl12xx_tm_cmd_get_mac(struct wl1271 *wl, struct nlattr *tb[]) 2707b3115f2SLuciano Coelho { 2717b3115f2SLuciano Coelho struct sk_buff *skb; 2727b3115f2SLuciano Coelho u8 mac_addr[ETH_ALEN]; 2737b3115f2SLuciano Coelho int ret = 0; 2747b3115f2SLuciano Coelho 2757b3115f2SLuciano Coelho mutex_lock(&wl->mutex); 2767b3115f2SLuciano Coelho 2777b3115f2SLuciano Coelho if (!wl->plt) { 2787b3115f2SLuciano Coelho ret = -EINVAL; 2797b3115f2SLuciano Coelho goto out; 2807b3115f2SLuciano Coelho } 2817b3115f2SLuciano Coelho 2827b3115f2SLuciano Coelho if (wl->fuse_oui_addr == 0 && wl->fuse_nic_addr == 0) { 2837b3115f2SLuciano Coelho ret = -EOPNOTSUPP; 2847b3115f2SLuciano Coelho goto out; 2857b3115f2SLuciano Coelho } 2867b3115f2SLuciano Coelho 2877b3115f2SLuciano Coelho mac_addr[0] = (u8)(wl->fuse_oui_addr >> 16); 2887b3115f2SLuciano Coelho mac_addr[1] = (u8)(wl->fuse_oui_addr >> 8); 2897b3115f2SLuciano Coelho mac_addr[2] = (u8) wl->fuse_oui_addr; 2907b3115f2SLuciano Coelho mac_addr[3] = (u8)(wl->fuse_nic_addr >> 16); 2917b3115f2SLuciano Coelho mac_addr[4] = (u8)(wl->fuse_nic_addr >> 8); 2927b3115f2SLuciano Coelho mac_addr[5] = (u8) wl->fuse_nic_addr; 2937b3115f2SLuciano Coelho 2947b3115f2SLuciano Coelho skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, ETH_ALEN); 2957b3115f2SLuciano Coelho if (!skb) { 2967b3115f2SLuciano Coelho ret = -ENOMEM; 2977b3115f2SLuciano Coelho goto out; 2987b3115f2SLuciano Coelho } 2997b3115f2SLuciano Coelho 3007b3115f2SLuciano Coelho NLA_PUT(skb, WL1271_TM_ATTR_DATA, ETH_ALEN, mac_addr); 3017b3115f2SLuciano Coelho ret = cfg80211_testmode_reply(skb); 3027b3115f2SLuciano Coelho if (ret < 0) 3037b3115f2SLuciano Coelho goto out; 3047b3115f2SLuciano Coelho 3057b3115f2SLuciano Coelho out: 3067b3115f2SLuciano Coelho mutex_unlock(&wl->mutex); 3077b3115f2SLuciano Coelho return ret; 3087b3115f2SLuciano Coelho 3097b3115f2SLuciano Coelho nla_put_failure: 3107b3115f2SLuciano Coelho kfree_skb(skb); 3117b3115f2SLuciano Coelho ret = -EMSGSIZE; 3127b3115f2SLuciano Coelho goto out; 3137b3115f2SLuciano Coelho } 3147b3115f2SLuciano Coelho 3157b3115f2SLuciano Coelho int wl1271_tm_cmd(struct ieee80211_hw *hw, void *data, int len) 3167b3115f2SLuciano Coelho { 3177b3115f2SLuciano Coelho struct wl1271 *wl = hw->priv; 3187b3115f2SLuciano Coelho struct nlattr *tb[WL1271_TM_ATTR_MAX + 1]; 3197b3115f2SLuciano Coelho int err; 3207b3115f2SLuciano Coelho 3217b3115f2SLuciano Coelho err = nla_parse(tb, WL1271_TM_ATTR_MAX, data, len, wl1271_tm_policy); 3227b3115f2SLuciano Coelho if (err) 3237b3115f2SLuciano Coelho return err; 3247b3115f2SLuciano Coelho 3257b3115f2SLuciano Coelho if (!tb[WL1271_TM_ATTR_CMD_ID]) 3267b3115f2SLuciano Coelho return -EINVAL; 3277b3115f2SLuciano Coelho 3287b3115f2SLuciano Coelho switch (nla_get_u32(tb[WL1271_TM_ATTR_CMD_ID])) { 3297b3115f2SLuciano Coelho case WL1271_TM_CMD_TEST: 3307b3115f2SLuciano Coelho return wl1271_tm_cmd_test(wl, tb); 3317b3115f2SLuciano Coelho case WL1271_TM_CMD_INTERROGATE: 3327b3115f2SLuciano Coelho return wl1271_tm_cmd_interrogate(wl, tb); 3337b3115f2SLuciano Coelho case WL1271_TM_CMD_CONFIGURE: 3347b3115f2SLuciano Coelho return wl1271_tm_cmd_configure(wl, tb); 3357b3115f2SLuciano Coelho case WL1271_TM_CMD_SET_PLT_MODE: 3367b3115f2SLuciano Coelho return wl1271_tm_cmd_set_plt_mode(wl, tb); 3377b3115f2SLuciano Coelho case WL1271_TM_CMD_RECOVER: 3387b3115f2SLuciano Coelho return wl1271_tm_cmd_recover(wl, tb); 3397b3115f2SLuciano Coelho case WL1271_TM_CMD_GET_MAC: 3407b3115f2SLuciano Coelho return wl12xx_tm_cmd_get_mac(wl, tb); 3417b3115f2SLuciano Coelho default: 3427b3115f2SLuciano Coelho return -EOPNOTSUPP; 3437b3115f2SLuciano Coelho } 3447b3115f2SLuciano Coelho } 345