Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux...
[cascardo/linux.git] / drivers / net / wireless / ath / ath6kl / testmode.c
1 /*
2  * Copyright (c) 2010-2011 Atheros Communications Inc.
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #include "testmode.h"
18
19 #include <net/netlink.h>
20
21 enum ath6kl_tm_attr {
22         __ATH6KL_TM_ATTR_INVALID        = 0,
23         ATH6KL_TM_ATTR_CMD              = 1,
24         ATH6KL_TM_ATTR_DATA             = 2,
25
26         /* keep last */
27         __ATH6KL_TM_ATTR_AFTER_LAST,
28         ATH6KL_TM_ATTR_MAX              = __ATH6KL_TM_ATTR_AFTER_LAST - 1,
29 };
30
31 enum ath6kl_tm_cmd {
32         ATH6KL_TM_CMD_TCMD              = 0,
33         ATH6KL_TM_CMD_RX_REPORT         = 1,
34 };
35
36 #define ATH6KL_TM_DATA_MAX_LEN          5000
37
38 static const struct nla_policy ath6kl_tm_policy[ATH6KL_TM_ATTR_MAX + 1] = {
39         [ATH6KL_TM_ATTR_CMD]            = { .type = NLA_U32 },
40         [ATH6KL_TM_ATTR_DATA]           = { .type = NLA_BINARY,
41                                             .len = ATH6KL_TM_DATA_MAX_LEN },
42 };
43
44 void ath6kl_tm_rx_report_event(struct ath6kl *ar, void *buf, size_t buf_len)
45 {
46         if (down_interruptible(&ar->sem))
47                 return;
48
49         kfree(ar->tm.rx_report);
50
51         ar->tm.rx_report = kmemdup(buf, buf_len, GFP_KERNEL);
52         ar->tm.rx_report_len = buf_len;
53
54         up(&ar->sem);
55
56         wake_up(&ar->event_wq);
57 }
58
59 static int ath6kl_tm_rx_report(struct ath6kl *ar, void *buf, size_t buf_len,
60                                struct sk_buff *skb)
61 {
62         int ret = 0;
63         long left;
64
65         if (down_interruptible(&ar->sem))
66                 return -ERESTARTSYS;
67
68         if (!test_bit(WMI_READY, &ar->flag)) {
69                 ret = -EIO;
70                 goto out;
71         }
72
73         if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) {
74                 ret = -EBUSY;
75                 goto out;
76         }
77
78         if (ath6kl_wmi_test_cmd(ar->wmi, buf, buf_len) < 0) {
79                 up(&ar->sem);
80                 return -EIO;
81         }
82
83         left = wait_event_interruptible_timeout(ar->event_wq,
84                                                 ar->tm.rx_report != NULL,
85                                                 WMI_TIMEOUT);
86
87         if (left == 0) {
88                 ret = -ETIMEDOUT;
89                 goto out;
90         } else if (left < 0) {
91                 ret = left;
92                 goto out;
93         }
94
95         if (ar->tm.rx_report == NULL || ar->tm.rx_report_len == 0) {
96                 ret = -EINVAL;
97                 goto out;
98         }
99
100         NLA_PUT(skb, ATH6KL_TM_ATTR_DATA, ar->tm.rx_report_len,
101                 ar->tm.rx_report);
102
103         kfree(ar->tm.rx_report);
104         ar->tm.rx_report = NULL;
105
106 out:
107         up(&ar->sem);
108
109         return ret;
110
111 nla_put_failure:
112         ret = -ENOBUFS;
113         goto out;
114 }
115
116 int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len)
117 {
118         struct ath6kl *ar = wiphy_priv(wiphy);
119         struct nlattr *tb[ATH6KL_TM_ATTR_MAX + 1];
120         int err, buf_len, reply_len;
121         struct sk_buff *skb;
122         void *buf;
123
124         err = nla_parse(tb, ATH6KL_TM_ATTR_MAX, data, len,
125                         ath6kl_tm_policy);
126         if (err)
127                 return err;
128
129         if (!tb[ATH6KL_TM_ATTR_CMD])
130                 return -EINVAL;
131
132         switch (nla_get_u32(tb[ATH6KL_TM_ATTR_CMD])) {
133         case ATH6KL_TM_CMD_TCMD:
134                 if (!tb[ATH6KL_TM_ATTR_DATA])
135                         return -EINVAL;
136
137                 buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]);
138                 buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]);
139
140                 ath6kl_wmi_test_cmd(ar->wmi, buf, buf_len);
141
142                 return 0;
143
144                 break;
145         case ATH6KL_TM_CMD_RX_REPORT:
146                 if (!tb[ATH6KL_TM_ATTR_DATA])
147                         return -EINVAL;
148
149                 buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]);
150                 buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]);
151
152                 reply_len = nla_total_size(ATH6KL_TM_DATA_MAX_LEN);
153                 skb = cfg80211_testmode_alloc_reply_skb(wiphy, reply_len);
154                 if (!skb)
155                         return -ENOMEM;
156
157                 err = ath6kl_tm_rx_report(ar, buf, buf_len, skb);
158                 if (err < 0) {
159                         kfree_skb(skb);
160                         return err;
161                 }
162
163                 return cfg80211_testmode_reply(skb);
164         default:
165                 return -EOPNOTSUPP;
166         }
167 }