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