Commit | Line | Data |
---|---|---|
29942bc1 ZK |
1 | /* |
2 | * Copyright (c) 2008-2011 Atheros Communications Inc. | |
3 | * Copyright (c) 2011 Neratec Solutions AG | |
4 | * | |
5 | * Permission to use, copy, modify, and/or distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include "hw.h" | |
19 | #include "hw-ops.h" | |
20 | #include "ath9k.h" | |
21 | #include "dfs.h" | |
22 | #include "dfs_debug.h" | |
23 | ||
29942bc1 ZK |
24 | /* internal struct to pass radar data */ |
25 | struct ath_radar_data { | |
26 | u8 pulse_bw_info; | |
27 | u8 rssi; | |
28 | u8 ext_rssi; | |
29 | u8 pulse_length_ext; | |
30 | u8 pulse_length_pri; | |
31 | }; | |
32 | ||
8fc2b61a ZK |
33 | /**** begin: CHIRP ************************************************************/ |
34 | ||
35 | /* min and max gradients for defined FCC chirping pulses, given by | |
36 | * - 20MHz chirp width over a pulse width of 50us | |
37 | * - 5MHz chirp width over a pulse width of 100us | |
38 | */ | |
39 | static const int BIN_DELTA_MIN = 1; | |
40 | static const int BIN_DELTA_MAX = 10; | |
41 | ||
42 | /* we need at least 3 deltas / 4 samples for a reliable chirp detection */ | |
43 | #define NUM_DIFFS 3 | |
44 | static const int FFT_NUM_SAMPLES = (NUM_DIFFS + 1); | |
45 | ||
46 | /* Threshold for difference of delta peaks */ | |
47 | static const int MAX_DIFF = 2; | |
48 | ||
49 | /* width range to be checked for chirping */ | |
50 | static const int MIN_CHIRP_PULSE_WIDTH = 20; | |
51 | static const int MAX_CHIRP_PULSE_WIDTH = 110; | |
52 | ||
53 | struct ath9k_dfs_fft_20 { | |
54 | u8 bin[28]; | |
55 | u8 lower_bins[3]; | |
56 | } __packed; | |
57 | struct ath9k_dfs_fft_40 { | |
58 | u8 bin[64]; | |
59 | u8 lower_bins[3]; | |
60 | u8 upper_bins[3]; | |
61 | } __packed; | |
62 | ||
63 | static inline int fft_max_index(u8 *bins) | |
64 | { | |
65 | return (bins[2] & 0xfc) >> 2; | |
66 | } | |
67 | static inline int fft_max_magnitude(u8 *bins) | |
68 | { | |
69 | return (bins[0] & 0xc0) >> 6 | bins[1] << 2 | (bins[2] & 0x03) << 10; | |
70 | } | |
71 | static inline u8 fft_bitmap_weight(u8 *bins) | |
72 | { | |
73 | return bins[0] & 0x3f; | |
74 | } | |
75 | ||
76 | static int ath9k_get_max_index_ht40(struct ath9k_dfs_fft_40 *fft, | |
77 | bool is_ctl, bool is_ext) | |
78 | { | |
79 | const int DFS_UPPER_BIN_OFFSET = 64; | |
80 | /* if detected radar on both channels, select the significant one */ | |
81 | if (is_ctl && is_ext) { | |
82 | /* first check wether channels have 'strong' bins */ | |
83 | is_ctl = fft_bitmap_weight(fft->lower_bins) != 0; | |
84 | is_ext = fft_bitmap_weight(fft->upper_bins) != 0; | |
85 | ||
86 | /* if still unclear, take higher magnitude */ | |
87 | if (is_ctl && is_ext) { | |
88 | int mag_lower = fft_max_magnitude(fft->lower_bins); | |
89 | int mag_upper = fft_max_magnitude(fft->upper_bins); | |
90 | if (mag_upper > mag_lower) | |
91 | is_ctl = false; | |
92 | else | |
93 | is_ext = false; | |
94 | } | |
95 | } | |
96 | if (is_ctl) | |
97 | return fft_max_index(fft->lower_bins); | |
98 | return fft_max_index(fft->upper_bins) + DFS_UPPER_BIN_OFFSET; | |
99 | } | |
100 | static bool ath9k_check_chirping(struct ath_softc *sc, u8 *data, | |
101 | int datalen, bool is_ctl, bool is_ext) | |
102 | { | |
103 | int i; | |
104 | int max_bin[FFT_NUM_SAMPLES]; | |
105 | struct ath_hw *ah = sc->sc_ah; | |
106 | struct ath_common *common = ath9k_hw_common(ah); | |
107 | int prev_delta; | |
108 | ||
109 | if (IS_CHAN_HT40(ah->curchan)) { | |
110 | struct ath9k_dfs_fft_40 *fft = (struct ath9k_dfs_fft_40 *) data; | |
111 | int num_fft_packets = datalen / sizeof(*fft); | |
112 | if (num_fft_packets == 0) | |
113 | return false; | |
114 | ||
115 | ath_dbg(common, DFS, "HT40: datalen=%d, num_fft_packets=%d\n", | |
116 | datalen, num_fft_packets); | |
117 | if (num_fft_packets < (FFT_NUM_SAMPLES)) { | |
118 | ath_dbg(common, DFS, "not enough packets for chirp\n"); | |
119 | return false; | |
120 | } | |
121 | /* HW sometimes adds 2 garbage bytes in front of FFT samples */ | |
122 | if ((datalen % sizeof(*fft)) == 2) { | |
123 | fft = (struct ath9k_dfs_fft_40 *) (data + 2); | |
124 | ath_dbg(common, DFS, "fixing datalen by 2\n"); | |
125 | } | |
126 | if (IS_CHAN_HT40MINUS(ah->curchan)) { | |
127 | int temp = is_ctl; | |
128 | is_ctl = is_ext; | |
129 | is_ext = temp; | |
130 | } | |
131 | for (i = 0; i < FFT_NUM_SAMPLES; i++) | |
132 | max_bin[i] = ath9k_get_max_index_ht40(fft + i, is_ctl, | |
133 | is_ext); | |
134 | } else { | |
135 | struct ath9k_dfs_fft_20 *fft = (struct ath9k_dfs_fft_20 *) data; | |
136 | int num_fft_packets = datalen / sizeof(*fft); | |
137 | if (num_fft_packets == 0) | |
138 | return false; | |
139 | ath_dbg(common, DFS, "HT20: datalen=%d, num_fft_packets=%d\n", | |
140 | datalen, num_fft_packets); | |
141 | if (num_fft_packets < (FFT_NUM_SAMPLES)) { | |
142 | ath_dbg(common, DFS, "not enough packets for chirp\n"); | |
143 | return false; | |
144 | } | |
145 | /* in ht20, this is a 6-bit signed number => shift it to 0 */ | |
146 | for (i = 0; i < FFT_NUM_SAMPLES; i++) | |
147 | max_bin[i] = fft_max_index(fft[i].lower_bins) ^ 0x20; | |
148 | } | |
149 | ath_dbg(common, DFS, "bin_max = [%d, %d, %d, %d]\n", | |
150 | max_bin[0], max_bin[1], max_bin[2], max_bin[3]); | |
151 | ||
152 | /* Check for chirp attributes within specs | |
153 | * a) delta of adjacent max_bins is within range | |
154 | * b) delta of adjacent deltas are within tolerance | |
155 | */ | |
156 | prev_delta = 0; | |
157 | for (i = 0; i < NUM_DIFFS; i++) { | |
158 | int ddelta = -1; | |
159 | int delta = max_bin[i + 1] - max_bin[i]; | |
160 | ||
161 | /* ensure gradient is within valid range */ | |
162 | if (abs(delta) < BIN_DELTA_MIN || abs(delta) > BIN_DELTA_MAX) { | |
163 | ath_dbg(common, DFS, "CHIRP: invalid delta %d " | |
164 | "in sample %d\n", delta, i); | |
165 | return false; | |
166 | } | |
167 | if (i == 0) | |
168 | goto done; | |
169 | ddelta = delta - prev_delta; | |
170 | if (abs(ddelta) > MAX_DIFF) { | |
171 | ath_dbg(common, DFS, "CHIRP: ddelta %d too high\n", | |
172 | ddelta); | |
173 | return false; | |
174 | } | |
175 | done: | |
176 | ath_dbg(common, DFS, "CHIRP - %d: delta=%d, ddelta=%d\n", | |
177 | i, delta, ddelta); | |
178 | prev_delta = delta; | |
179 | } | |
180 | return true; | |
181 | } | |
182 | /**** end: CHIRP **************************************************************/ | |
183 | ||
29942bc1 ZK |
184 | /* convert pulse duration to usecs, considering clock mode */ |
185 | static u32 dur_to_usecs(struct ath_hw *ah, u32 dur) | |
186 | { | |
187 | const u32 AR93X_NSECS_PER_DUR = 800; | |
188 | const u32 AR93X_NSECS_PER_DUR_FAST = (8000 / 11); | |
189 | u32 nsecs; | |
190 | ||
191 | if (IS_CHAN_A_FAST_CLOCK(ah, ah->curchan)) | |
192 | nsecs = dur * AR93X_NSECS_PER_DUR_FAST; | |
193 | else | |
194 | nsecs = dur * AR93X_NSECS_PER_DUR; | |
195 | ||
196 | return (nsecs + 500) / 1000; | |
197 | } | |
198 | ||
199 | #define PRI_CH_RADAR_FOUND 0x01 | |
200 | #define EXT_CH_RADAR_FOUND 0x02 | |
201 | static bool | |
202 | ath9k_postprocess_radar_event(struct ath_softc *sc, | |
56dc389f ZK |
203 | struct ath_radar_data *ard, |
204 | struct pulse_event *pe) | |
29942bc1 ZK |
205 | { |
206 | u8 rssi; | |
207 | u16 dur; | |
208 | ||
29942bc1 ZK |
209 | /* |
210 | * Only the last 2 bits of the BW info are relevant, they indicate | |
211 | * which channel the radar was detected in. | |
212 | */ | |
56dc389f | 213 | ard->pulse_bw_info &= 0x03; |
29942bc1 | 214 | |
56dc389f | 215 | switch (ard->pulse_bw_info) { |
29942bc1 ZK |
216 | case PRI_CH_RADAR_FOUND: |
217 | /* radar in ctrl channel */ | |
56dc389f | 218 | dur = ard->pulse_length_pri; |
29942bc1 ZK |
219 | DFS_STAT_INC(sc, pri_phy_errors); |
220 | /* | |
221 | * cannot use ctrl channel RSSI | |
222 | * if extension channel is stronger | |
223 | */ | |
56dc389f | 224 | rssi = (ard->ext_rssi >= (ard->rssi + 3)) ? 0 : ard->rssi; |
29942bc1 ZK |
225 | break; |
226 | case EXT_CH_RADAR_FOUND: | |
227 | /* radar in extension channel */ | |
56dc389f | 228 | dur = ard->pulse_length_ext; |
29942bc1 ZK |
229 | DFS_STAT_INC(sc, ext_phy_errors); |
230 | /* | |
231 | * cannot use extension channel RSSI | |
232 | * if control channel is stronger | |
233 | */ | |
56dc389f | 234 | rssi = (ard->rssi >= (ard->ext_rssi + 12)) ? 0 : ard->ext_rssi; |
29942bc1 ZK |
235 | break; |
236 | case (PRI_CH_RADAR_FOUND | EXT_CH_RADAR_FOUND): | |
237 | /* | |
238 | * Conducted testing, when pulse is on DC, both pri and ext | |
239 | * durations are reported to be same | |
240 | * | |
241 | * Radiated testing, when pulse is on DC, different pri and | |
242 | * ext durations are reported, so take the larger of the two | |
243 | */ | |
56dc389f ZK |
244 | if (ard->pulse_length_ext >= ard->pulse_length_pri) |
245 | dur = ard->pulse_length_ext; | |
29942bc1 | 246 | else |
56dc389f | 247 | dur = ard->pulse_length_pri; |
29942bc1 ZK |
248 | DFS_STAT_INC(sc, dc_phy_errors); |
249 | ||
250 | /* when both are present use stronger one */ | |
56dc389f | 251 | rssi = (ard->rssi < ard->ext_rssi) ? ard->ext_rssi : ard->rssi; |
29942bc1 ZK |
252 | break; |
253 | default: | |
254 | /* | |
255 | * Bogus bandwidth info was received in descriptor, | |
256 | * so ignore this PHY error | |
257 | */ | |
258 | DFS_STAT_INC(sc, bwinfo_discards); | |
259 | return false; | |
260 | } | |
261 | ||
262 | if (rssi == 0) { | |
263 | DFS_STAT_INC(sc, rssi_discards); | |
264 | return false; | |
265 | } | |
266 | ||
29942bc1 | 267 | /* convert duration to usecs */ |
56dc389f ZK |
268 | pe->width = dur_to_usecs(sc->sc_ah, dur); |
269 | pe->rssi = rssi; | |
29942bc1 ZK |
270 | |
271 | DFS_STAT_INC(sc, pulses_detected); | |
272 | return true; | |
273 | } | |
58766977 ZK |
274 | |
275 | static void | |
276 | ath9k_dfs_process_radar_pulse(struct ath_softc *sc, struct pulse_event *pe) | |
277 | { | |
278 | struct dfs_pattern_detector *pd = sc->dfs_detector; | |
279 | DFS_STAT_INC(sc, pulses_processed); | |
280 | if (pd == NULL) | |
281 | return; | |
282 | if (!pd->add_pulse(pd, pe)) | |
283 | return; | |
284 | DFS_STAT_INC(sc, radar_detected); | |
285 | ieee80211_radar_detected(sc->hw); | |
286 | } | |
29942bc1 ZK |
287 | |
288 | /* | |
289 | * DFS: check PHY-error for radar pulse and feed the detector | |
290 | */ | |
291 | void ath9k_dfs_process_phyerr(struct ath_softc *sc, void *data, | |
292 | struct ath_rx_status *rs, u64 mactime) | |
293 | { | |
294 | struct ath_radar_data ard; | |
295 | u16 datalen; | |
296 | char *vdata_end; | |
56dc389f | 297 | struct pulse_event pe; |
29942bc1 ZK |
298 | struct ath_hw *ah = sc->sc_ah; |
299 | struct ath_common *common = ath9k_hw_common(ah); | |
300 | ||
b96f20b3 | 301 | DFS_STAT_INC(sc, pulses_total); |
56dc389f ZK |
302 | if ((rs->rs_phyerr != ATH9K_PHYERR_RADAR) && |
303 | (rs->rs_phyerr != ATH9K_PHYERR_FALSE_RADAR_EXT)) { | |
ec9a5705 | 304 | ath_dbg(common, DFS, |
29942bc1 ZK |
305 | "Error: rs_phyer=0x%x not a radar error\n", |
306 | rs->rs_phyerr); | |
b96f20b3 | 307 | DFS_STAT_INC(sc, pulses_no_dfs); |
29942bc1 ZK |
308 | return; |
309 | } | |
310 | ||
311 | datalen = rs->rs_datalen; | |
312 | if (datalen == 0) { | |
313 | DFS_STAT_INC(sc, datalen_discards); | |
314 | return; | |
315 | } | |
316 | ||
e45e91d8 FF |
317 | ard.rssi = rs->rs_rssi_ctl[0]; |
318 | ard.ext_rssi = rs->rs_rssi_ext[0]; | |
29942bc1 ZK |
319 | |
320 | /* | |
321 | * hardware stores this as 8 bit signed value. | |
322 | * we will cap it at 0 if it is a negative number | |
323 | */ | |
324 | if (ard.rssi & 0x80) | |
325 | ard.rssi = 0; | |
326 | if (ard.ext_rssi & 0x80) | |
327 | ard.ext_rssi = 0; | |
328 | ||
329 | vdata_end = (char *)data + datalen; | |
330 | ard.pulse_bw_info = vdata_end[-1]; | |
331 | ard.pulse_length_ext = vdata_end[-2]; | |
332 | ard.pulse_length_pri = vdata_end[-3]; | |
56dc389f ZK |
333 | pe.freq = ah->curchan->channel; |
334 | pe.ts = mactime; | |
58766977 ZK |
335 | if (!ath9k_postprocess_radar_event(sc, &ard, &pe)) |
336 | return; | |
337 | ||
8fc2b61a ZK |
338 | if (pe.width > MIN_CHIRP_PULSE_WIDTH && |
339 | pe.width < MAX_CHIRP_PULSE_WIDTH) { | |
340 | bool is_ctl = !!(ard.pulse_bw_info & PRI_CH_RADAR_FOUND); | |
341 | bool is_ext = !!(ard.pulse_bw_info & EXT_CH_RADAR_FOUND); | |
342 | int clen = datalen - 3; | |
343 | pe.chirp = ath9k_check_chirping(sc, data, clen, is_ctl, is_ext); | |
344 | } else { | |
345 | pe.chirp = false; | |
346 | } | |
347 | ||
58766977 ZK |
348 | ath_dbg(common, DFS, |
349 | "ath9k_dfs_process_phyerr: type=%d, freq=%d, ts=%llu, " | |
350 | "width=%d, rssi=%d, delta_ts=%llu\n", | |
351 | ard.pulse_bw_info, pe.freq, pe.ts, pe.width, pe.rssi, | |
352 | pe.ts - sc->dfs_prev_pulse_ts); | |
353 | sc->dfs_prev_pulse_ts = pe.ts; | |
354 | if (ard.pulse_bw_info & PRI_CH_RADAR_FOUND) | |
355 | ath9k_dfs_process_radar_pulse(sc, &pe); | |
8f010d9c ZK |
356 | if (IS_CHAN_HT40(ah->curchan) && |
357 | ard.pulse_bw_info & EXT_CH_RADAR_FOUND) { | |
58766977 ZK |
358 | pe.freq += IS_CHAN_HT40PLUS(ah->curchan) ? 20 : -20; |
359 | ath9k_dfs_process_radar_pulse(sc, &pe); | |
29942bc1 ZK |
360 | } |
361 | } | |
58766977 ZK |
362 | #undef PRI_CH_RADAR_FOUND |
363 | #undef EXT_CH_RADAR_FOUND |