carl9170: Update to latest upstream
[linux-libre-firmware.git] / carl9170fw / carlfw / src / wol.c
1 /*
2  * carl9170 firmware - used by the ar9170 wireless device
3  *
4  * WakeUp on WLAN functions
5  *
6  * Copyright 2011       Christian Lamparter <chunkeey@googlemail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #include "carl9170.h"
23 #include "shared/phy.h"
24 #include "timer.h"
25 #include "wl.h"
26 #include "printf.h"
27 #include "rf.h"
28 #include "wol.h"
29 #include "linux/ieee80211.h"
30
31 #ifdef CONFIG_CARL9170FW_WOL
32
33 void wol_cmd(const struct carl9170_wol_cmd *cmd)
34 {
35         memcpy(&fw.wol.cmd, cmd, sizeof(cmd));
36 }
37
38 void wol_prepare(void)
39 {
40         /* set MAC filter */
41         memcpy((void *)AR9170_MAC_REG_MAC_ADDR_L, fw.wol.cmd.mac, 6);
42         memcpy((void *)AR9170_MAC_REG_BSSID_L, fw.wol.cmd.bssid, 6);
43         set(AR9170_MAC_REG_RX_CONTROL, AR9170_MAC_RX_CTRL_DEAGG);
44
45         /* set filter policy to: discard everything */
46         fw.wlan.rx_filter = CARL9170_RX_FILTER_EVERYTHING;
47
48         /* reenable rx dma */
49         wlan_trigger(AR9170_DMA_TRIGGER_RXQ);
50
51         /* initialize the last_beacon timer */
52         fw.wol.last_null = fw.wol.last_beacon = get_clock_counter();
53 }
54
55 #ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS
56 static bool wlan_rx_wol_magic_packet(const struct ieee80211_hdr *hdr, const unsigned int len)
57 {
58         const unsigned char *data, *end, *mac;
59         unsigned int found = 0;
60
61         /*
62          * LIMITATION:
63          * We can only scan the first AR9170_BLOCK_SIZE [=~320] bytes
64          * for MAGIC patterns!
65          */
66
67         mac = (const unsigned char *) AR9170_MAC_REG_MAC_ADDR_L;
68
69         data = (u8 *)((unsigned long)hdr + ieee80211_hdrlen(hdr->frame_control));
70         end = (u8 *)((unsigned long)hdr + len);
71
72         /*
73          * scan for standard WOL Magic frame
74          *
75          * "A physical WakeOnLAN (Magic Packet) will look like this:
76          * ---------------------------------------------------------------
77          * | Synchronization Stream | Target MAC |  Password (optional)  |
78          * |    6 octets            | 96 octets  |   0, 4 or 6           |
79          * ---------------------------------------------------------------
80          *
81          * The Synchronization Stream is defined as 6 bytes of FFh.
82          * The Target MAC block contains 16 duplications of the IEEEaddress
83          * of the target, with no breaks or interruptions.
84          *
85          * The Password field is optional, but if present, contains either
86          * 4 bytes or 6 bytes. The WakeOnLAN dissector was implemented to
87          * dissect the password, if present, according to the command-line
88          * format that ether-wake uses, therefore, if a 4-byte password is
89          * present, it will be dissected as an IPv4 address and if a 6-byte
90          * password is present, it will be dissected as an Ethernet address.
91          *
92          * <http://wiki.wireshark.org/WakeOnLAN>
93          */
94
95         while (data < end) {
96                 if (found >= 6) {
97                         if (*data == mac[found % 6])
98                                 found++;
99                         else
100                                 found = 0;
101                 }
102
103                 /* previous check might reset found counter */
104                 if (found < 6) {
105                         if (*data == 0xff)
106                                 found++;
107                         else
108                                 found = 0;
109                 }
110
111                 if (found == (6 + 16 * 6))
112                         return true;
113
114                 data++;
115         }
116
117         return false;
118 }
119
120 static void wlan_wol_connect_callback(void __unused *dummy, bool success)
121 {
122         if (success)
123                 fw.wol.lost_null = 0;
124         else
125                 fw.wol.lost_null++;
126 }
127
128 static void wlan_wol_connection_monitor(void)
129 {
130         struct carl9170_tx_null_superframe *nullf = &dma_mem.reserved.cmd.null;
131         struct ieee80211_hdr *null = (struct ieee80211_hdr *) &nullf->f.null;
132
133         if (!fw.wlan.fw_desc_available)
134                 return;
135
136         memset(nullf, 0, sizeof(*nullf));
137
138         nullf->s.len = sizeof(struct carl9170_tx_superdesc) +
139                      sizeof(struct ar9170_tx_hwdesc) +
140                      sizeof(struct ieee80211_hdr);
141         nullf->s.ri[0].tries = 3;
142         nullf->s.assign_seq = true;
143         nullf->s.queue = AR9170_TXQ_VO;
144         nullf->f.hdr.length = sizeof(struct ieee80211_hdr) + FCS_LEN;
145
146         nullf->f.hdr.mac.backoff = 1;
147         nullf->f.hdr.mac.hw_duration = 1;
148         nullf->f.hdr.mac.erp_prot = AR9170_TX_MAC_PROT_RTS;
149
150         nullf->f.hdr.phy.modulation = AR9170_TX_PHY_MOD_OFDM;
151         nullf->f.hdr.phy.bandwidth = AR9170_TX_PHY_BW_20MHZ;
152         nullf->f.hdr.phy.chains = AR9170_TX_PHY_TXCHAIN_2;
153         nullf->f.hdr.phy.tx_power = 29; /* 14.5 dBm */
154         nullf->f.hdr.phy.mcs = AR9170_TXRX_PHY_RATE_OFDM_6M;
155
156         /* format outgoing nullfunc */
157         null->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
158                 IEEE80211_STYPE_NULLFUNC | IEEE80211_FCTL_TODS);
159
160         memcpy(null->addr1, fw.wol.cmd.bssid, 6);
161         memcpy(null->addr2, fw.wol.cmd.mac, 6);
162         memcpy(null->addr3, fw.wol.cmd.bssid, 6);
163
164         wlan_tx_fw(&nullf->s, wlan_wol_connect_callback);
165 }
166
167 static bool wlan_rx_wol_disconnect(const unsigned int rx_filter,
168                                    const struct ieee80211_hdr *hdr,
169                                    const unsigned int __unused len)
170 {
171         const unsigned char *bssid;
172         bssid = (const unsigned char *) AR9170_MAC_REG_BSSID_L;
173
174         /* should catch both broadcast and unicast MLMEs */
175         if (!(rx_filter & CARL9170_RX_FILTER_OTHER_RA)) {
176                 if (ieee80211_is_deauth(hdr->frame_control) ||
177                     ieee80211_is_disassoc(hdr->frame_control))
178                         return true;
179         }
180
181         if (ieee80211_is_beacon(hdr->frame_control) &&
182             compare_ether_address(hdr->addr3, bssid)) {
183                 fw.wol.last_beacon = get_clock_counter();
184         }
185
186         return false;
187 }
188
189 #endif /* CARL9170FW_WOL_NL80211_TRIGGERS */
190
191 #ifdef CONFIG_CARL9170FW_WOL_PROBE_REQUEST
192
193 /*
194  * Note: CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID is not a real
195  * string. We have to be careful not to add a \0 at the end.
196  */
197 static const struct {
198         u8 ssid_ie;
199         u8 ssid_len;
200         u8 ssid[sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1];
201 } __packed probe_req = {
202         .ssid_ie = WLAN_EID_SSID,
203         .ssid_len = sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1,
204         .ssid = CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID,
205 };
206
207 static bool wlan_rx_wol_probe_ssid(const struct ieee80211_hdr *hdr, const unsigned int len)
208 {
209         const unsigned char *data, *end, *scan = (void *) &probe_req;
210
211         /*
212          * IEEE 802.11-2007 7.3.2.1 specifies that the SSID is no
213          * longer than 32 octets.
214          */
215         BUILD_BUG_ON((sizeof(CONFIG_CARL9170FW_WOL_PROBE_REQUEST_SSID) - 1) > 32);
216
217         if (ieee80211_is_probe_req(hdr->frame_control)) {
218                 unsigned int i;
219                 end = (u8 *)((unsigned long)hdr + len);
220
221                 /*
222                  * The position of the SSID information element inside
223                  * a probe request frame is more or less "fixed".
224                  */
225                 data = (u8 *)((struct ieee80211_mgmt *)hdr)->u.probe_req.variable;
226                 for (i = 0; i < (unsigned int)(probe_req.ssid_len + 1); i++) {
227                         if (data > end || scan[i] != data[i])
228                                 return false;
229                 }
230
231                 return true;
232         }
233
234         return false;
235 }
236 #endif /* CONFIG_CARL9170FW_WOL_PROBE_REQUEST */
237
238 void wol_rx(const unsigned int rx_filter __unused, const struct ieee80211_hdr *hdr __unused, const unsigned int len __unused)
239 {
240 #ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS
241         /* Disconnect is always enabled */
242         if (fw.wol.cmd.flags & CARL9170_WOL_DISCONNECT &&
243             rx_filter & CARL9170_RX_FILTER_MGMT)
244                 fw.wol.wake_up |= wlan_rx_wol_disconnect(rx_filter, hdr, len);
245
246         if (fw.wol.cmd.flags & CARL9170_WOL_MAGIC_PKT &&
247             rx_filter & CARL9170_RX_FILTER_DATA)
248                 fw.wol.wake_up |= wlan_rx_wol_magic_packet(hdr, len);
249 #endif /* CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS */
250
251 #ifdef CONFIG_CARL9170FW_WOL_PROBE_REQUEST
252         if (rx_filter & CARL9170_RX_FILTER_MGMT)
253                 fw.wol.wake_up |= wlan_rx_wol_probe_ssid(hdr, len);
254 #endif /* CONFIG_CARL9170FW_WOL_PROBE_REQUEST */
255 }
256
257 void wol_janitor(void)
258 {
259         if (unlikely(fw.suspend_mode == CARL9170_HOST_SUSPENDED)) {
260 #ifdef CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS
261                 if (fw.wol.cmd.flags & CARL9170_WOL_DISCONNECT) {
262                         /*
263                          * connection lost after 10sec without receiving
264                          * a beacon
265                           */
266                         if (is_after_msecs(fw.wol.last_beacon, 10000))
267                                 fw.wol.wake_up |= true;
268
269                         if (fw.wol.cmd.null_interval &&
270                             is_after_msecs(fw.wol.last_null, fw.wol.cmd.null_interval))
271                                 wlan_wol_connection_monitor();
272
273                         if (fw.wol.lost_null >= 5)
274                                 fw.wol.wake_up |= true;
275                 }
276 #endif /* CONFIG_CARL9170FW_WOL_NL80211_TRIGGERS */
277
278                 if (fw.wol.wake_up) {
279                         fw.suspend_mode = CARL9170_AWAKE_HOST;
280                         set(AR9170_USB_REG_WAKE_UP, AR9170_USB_WAKE_UP_WAKE);
281                 }
282         }
283 }
284 #else
285
286 #endif /* CONFIG_CARL9170FW_WOL */