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 }
168