| /* |
| * Copyright (c) 2002-2016 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| #include "dfs.h" |
| #include "dfs_phyerr.h" /* For chip-specific phyerr func declarations */ |
| /* TO DO DFS |
| #include <ieee80211_var.h> |
| */ |
| #ifndef UNINET |
| /* TO DO DFS |
| #include <ieee80211_channel.h> |
| */ |
| #endif |
| #ifdef ATH_SUPPORT_DFS |
| |
| /* |
| * Return the frequency width for the current operating channel. |
| * |
| * This isn't the channel width - it's how wide the reported event |
| * may be. For HT20 this is 20MHz. For HT40 on Howl and later it'll |
| * still be 20MHz - the hardware returns either pri or ext channel. |
| */ |
| static inline int dfs_get_event_freqwidth(struct ath_dfs *dfs) |
| { |
| |
| /* Handle edge cases during startup/transition, shouldn't happen! */ |
| if (dfs == NULL) |
| return 0; |
| if (dfs->ic == NULL || dfs->ic->ic_curchan == NULL) |
| return 0; |
| |
| /* |
| * XXX For now, assume 20MHz wide - but this is incorrect when |
| * operating in half/quarter mode! |
| */ |
| return 20; |
| } |
| |
| /* |
| * Return the centre frequency for the current operating channel and |
| * event. |
| * |
| * This is for post-Owl 11n chips which report pri/extension channel |
| * events. |
| */ |
| static inline uint16_t |
| dfs_get_event_freqcentre(struct ath_dfs *dfs, int is_pri, int is_ext, int is_dc) |
| { |
| struct ieee80211com *ic; |
| int chan_offset = 0, chan_width; |
| uint16_t freq; |
| |
| /* Handle edge cases during startup/transition, shouldn't happen! */ |
| if (dfs == NULL) |
| return 0; |
| if (dfs->ic == NULL || dfs->ic->ic_curchan == NULL) |
| return 0; |
| |
| ic = dfs->ic; |
| |
| /* |
| * |
| * For wide channels, DC and ext frequencies need a bit of hand-holding |
| * based on whether it's an upper or lower channel. |
| */ |
| chan_width = dfs_get_event_freqwidth(dfs); |
| qdf_spin_lock_bh(&ic->chan_lock); |
| if (IEEE80211_IS_CHAN_11N_HT40PLUS(ic->ic_curchan)) |
| chan_offset = chan_width; |
| else if (IEEE80211_IS_CHAN_11N_HT40MINUS(ic->ic_curchan)) |
| chan_offset = -chan_width; |
| else |
| chan_offset = 0; |
| |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| |
| /* |
| * Check for DC events first - the sowl code may just set all |
| * the bits together.. |
| */ |
| if (is_dc) { |
| /* |
| * XXX TODO: Should DC events be considered 40MHz wide here? |
| */ |
| qdf_spin_lock_bh(&ic->chan_lock); |
| freq = ieee80211_chan2freq(ic, ic->ic_curchan) + |
| (chan_offset / 2); |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| return freq; |
| } |
| |
| /* |
| * For non-wide channels, the centre frequency is just ic_freq. |
| * The centre frequency for pri events is still ic_freq. |
| */ |
| if (is_pri) { |
| qdf_spin_lock_bh(&ic->chan_lock); |
| freq = ieee80211_chan2freq(ic, ic->ic_curchan); |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| return freq; |
| } |
| |
| if (is_ext) { |
| qdf_spin_lock_bh(&ic->chan_lock); |
| freq = ieee80211_chan2freq(ic, ic->ic_curchan) + chan_width; |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| return freq; |
| } |
| |
| /* XXX shouldn't get here */ |
| qdf_spin_lock_bh(&ic->chan_lock); |
| freq = ieee80211_chan2freq(ic, ic->ic_curchan); |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| return freq; |
| } |
| |
| /* |
| * Process an Owl-style phy error. |
| * |
| * Return 1 on success or 0 on failure. |
| */ |
| int |
| dfs_process_phyerr_owl(struct ath_dfs *dfs, void *buf, uint16_t datalen, |
| uint8_t rssi, uint8_t ext_rssi, uint32_t rs_tstamp, |
| uint64_t fulltsf, struct dfs_phy_err *e) |
| { |
| const char *cbuf = (const char *)buf; |
| uint8_t dur; |
| int event_width; |
| |
| /* XXX this shouldn't be kept count here */ |
| dfs->ath_dfs_stats.owl_phy_errors++; |
| |
| /* |
| * HW cannot detect extension channel radar so it only passes us |
| * primary channel radar data |
| */ |
| if (datalen == 0) |
| dur = 0; |
| else |
| dur = ((uint8_t *) cbuf)[0]; |
| |
| /* |
| * This is a spurious event; toss. |
| */ |
| if (rssi == 0 && dur == 0) |
| dfs->ath_dfs_stats.datalen_discards++; |
| return 0; |
| |
| /* |
| * Fill out dfs_phy_err with the information we have |
| * at hand. |
| */ |
| OS_MEMSET(e, 0, sizeof(*e)); |
| e->rssi = rssi; |
| e->dur = dur; |
| e->is_pri = 1; |
| e->is_ext = 0; |
| e->is_dc = 0; |
| e->is_early = 1; |
| e->fulltsf = fulltsf; |
| e->rs_tstamp = rs_tstamp; |
| |
| /* |
| * Owl only ever reports events on the primary channel; |
| * it doesn't even see events on the secondary channel. |
| */ |
| event_width = dfs_get_event_freqwidth(dfs); |
| e->freq = dfs_get_event_freqcentre(dfs, 1, 0, 0) * 1000; |
| e->freq_lo = e->freq - (event_width / 2) * 1000; |
| e->freq_hi = e->freq + (event_width / 2) * 1000; |
| |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR_SUM, |
| "%s: rssi=%u dur=%u,freq=%dMHz, freq_lo=%dMHz, freq_hi=%dMHz\n", |
| __func__, rssi, dur, e->freq / 1000, e->freq_lo / 1000, |
| e->freq_hi / 1000); |
| |
| return 1; |
| } |
| |
| /* |
| * Process a Sowl/Howl style phy error. |
| */ |
| int |
| dfs_process_phyerr_sowl(struct ath_dfs *dfs, void *buf, uint16_t datalen, |
| uint8_t rssi, uint8_t ext_rssi, uint32_t rs_tstamp, |
| uint64_t fulltsf, struct dfs_phy_err *e) |
| { |
| #define EXT_CH_RADAR_FOUND 0x02 |
| #define PRI_CH_RADAR_FOUND 0x01 |
| #define EXT_CH_RADAR_EARLY_FOUND 0x04 |
| const char *cbuf = (const char *)buf; |
| uint8_t dur = 0; |
| uint8_t pulse_bw_info, pulse_length_ext, pulse_length_pri; |
| int pri_found = 0, ext_found = 0; |
| int early_ext = 0; |
| int event_width; |
| |
| /* |
| * If radar can be detected on the extension channel, datalen zero |
| * pulses are bogus, discard them. |
| */ |
| if (!datalen) { |
| dfs->ath_dfs_stats.datalen_discards++; |
| return 0; |
| } |
| |
| /* Ensure that we have at least three bytes of payload */ |
| if (datalen < 3) { |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS, |
| "%s: short error frame (%d bytes)\n", |
| __func__, datalen); |
| dfs->ath_dfs_stats.datalen_discards++; |
| return 0; |
| } |
| |
| /* |
| * Fetch the payload directly - the compiler will happily generate |
| * byte-read instructions with a const char * cbuf pointer. |
| */ |
| pulse_length_pri = cbuf[datalen - 3]; |
| pulse_length_ext = cbuf[datalen - 2]; |
| pulse_bw_info = cbuf[datalen - 1]; |
| |
| /* |
| * Only the last 3 bits of the BW info are relevant, they indicate |
| * which channel the radar was detected in. |
| */ |
| pulse_bw_info &= 0x07; |
| |
| /* |
| * If pulse on DC, both primary and extension flags will be set |
| */ |
| if (((pulse_bw_info & EXT_CH_RADAR_FOUND) && |
| (pulse_bw_info & PRI_CH_RADAR_FOUND))) { |
| /* |
| * Conducted testing, when pulse is on DC, both |
| * pri and ext durations are reported to be same. |
| * |
| * Radiated testing, when pulse is on DC, different |
| * pri and ext durations are reported, so take the |
| * larger of the two |
| */ |
| if (pulse_length_ext >= pulse_length_pri) { |
| dur = pulse_length_ext; |
| ext_found = 1; |
| } else { |
| dur = pulse_length_pri; |
| pri_found = 1; |
| } |
| dfs->ath_dfs_stats.dc_phy_errors++; |
| } else { |
| if (pulse_bw_info & EXT_CH_RADAR_FOUND) { |
| dur = pulse_length_ext; |
| pri_found = 0; |
| ext_found = 1; |
| dfs->ath_dfs_stats.ext_phy_errors++; |
| } |
| if (pulse_bw_info & PRI_CH_RADAR_FOUND) { |
| dur = pulse_length_pri; |
| pri_found = 1; |
| ext_found = 0; |
| dfs->ath_dfs_stats.pri_phy_errors++; |
| } |
| if (pulse_bw_info & EXT_CH_RADAR_EARLY_FOUND) { |
| dur = pulse_length_ext; |
| pri_found = 0; |
| ext_found = 1; |
| early_ext = 1; |
| dfs->ath_dfs_stats.early_ext_phy_errors++; |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "EARLY ext channel dur=%u rssi=%u datalen=%d\n", |
| dur, rssi, datalen); |
| } |
| if (!pulse_bw_info) { |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "dur=%u rssi=%u bw_info=0x%x datalen = %d\n", |
| dur, rssi, pulse_bw_info, (datalen & 0x3)); |
| /* |
| * Bogus bandwidth info received in descriptor, |
| * so ignore this PHY error |
| */ |
| dfs->ath_dfs_stats.bwinfo_errors++; |
| return 0; |
| } |
| } |
| |
| /* |
| * Always use combined RSSI reported, unless RSSI reported on |
| * extension is stronger |
| */ |
| if ((ext_rssi > rssi) && (ext_rssi < 128)) |
| rssi = ext_rssi; |
| |
| /* |
| * Fill out the rssi/duration fields from above. |
| */ |
| OS_MEMSET(e, 0, sizeof(*e)); |
| e->rssi = rssi; |
| e->dur = dur; |
| e->is_pri = pri_found; |
| e->is_ext = ext_found; |
| e->is_dc = !!(((pulse_bw_info & EXT_CH_RADAR_FOUND) && |
| (pulse_bw_info & PRI_CH_RADAR_FOUND))); |
| e->is_early = early_ext; |
| e->fulltsf = fulltsf; |
| e->rs_tstamp = rs_tstamp; |
| |
| /* |
| * Sowl and later can report pri/ext events. |
| */ |
| event_width = dfs_get_event_freqwidth(dfs); |
| e->freq = dfs_get_event_freqcentre(dfs, e->is_pri, e->is_ext, e->is_dc) |
| * 1000; |
| e->freq_lo = e->freq - (event_width / 2) * 1000; |
| e->freq_hi = e->freq + (event_width / 2) * 1000; |
| |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR_SUM, |
| "%s: pulse_bw_info=0x%x pulse_length_ext=%u pulse_length_pri=%u " |
| "rssi=%u ext_rssi=%u, freq=%d MHz, freq_lo=%d MHz, " |
| "freq_hi=%d MHz\n", |
| __func__, |
| pulse_bw_info, |
| pulse_length_ext, |
| pulse_length_pri, |
| rssi, |
| ext_rssi, |
| e->freq / 1000, e->freq_lo / 1000, e->freq_hi / 1000); |
| |
| return 1; |
| } |
| |
| /* |
| * Process a Merlin/Osprey style phy error. |
| */ |
| int |
| dfs_process_phyerr_merlin(struct ath_dfs *dfs, void *buf, |
| uint16_t datalen, uint8_t rssi, uint8_t ext_rssi, |
| uint32_t rs_tstamp, uint64_t fulltsf, |
| struct dfs_phy_err *e) |
| { |
| const char *cbuf = (const char *)buf; |
| uint8_t pulse_bw_info = 0; |
| |
| /* |
| * Process using the sowl code |
| */ |
| if (!dfs_process_phyerr_sowl(dfs, buf, datalen, rssi, ext_rssi, |
| rs_tstamp, fulltsf, e)) { |
| return 0; |
| } |
| |
| /* |
| * For osprey (and Merlin) bw_info has implication for selecting |
| * RSSI value. So re-fetch the bw_info field so the RSSI values |
| * can be appropriately overridden. |
| */ |
| pulse_bw_info = cbuf[datalen - 1]; |
| |
| switch (pulse_bw_info & 0x03) { |
| case 0x00: |
| /* No radar in ctrl or ext channel */ |
| rssi = 0; |
| break; |
| case 0x01: |
| /* radar in ctrl channel */ |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "RAW RSSI: rssi=%u ext_rssi=%u\n", rssi, ext_rssi); |
| if (ext_rssi >= (rssi + 3)) { |
| /* |
| * cannot use ctrl channel RSSI if |
| * extension channel is stronger |
| */ |
| rssi = 0; |
| } |
| break; |
| case 0x02: |
| /* radar in extension channel */ |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "RAW RSSI: rssi=%u ext_rssi=%u\n", rssi, ext_rssi); |
| if (rssi >= (ext_rssi + 12)) { |
| /* |
| * cannot use extension channel RSSI if control channel |
| * is stronger |
| */ |
| rssi = 0; |
| } else { |
| rssi = ext_rssi; |
| } |
| break; |
| case 0x03: |
| /* when both are present use stronger one */ |
| if (rssi < ext_rssi) |
| rssi = ext_rssi; |
| break; |
| } |
| |
| /* |
| * Override the rssi decision made by the sowl code. |
| * The rest of the fields (duration, timestamp, etc) |
| * are left untouched. |
| */ |
| e->rssi = rssi; |
| |
| return 1; |
| } |
| |
| static void dump_phyerr_contents(const char *d, int len) |
| { |
| #ifdef CONFIG_ENABLE_DUMP_PHYERR_CONTENTS |
| int i, n, bufsize = 64; |
| |
| /* |
| * This is statically sized for a 4-digit address + 16 * 2 digit |
| * data string. |
| * |
| * It's done so the printk() passed to the kernel is an entire |
| * line, so the kernel logging code will atomically print it. |
| * Otherwise we'll end up with interleaved lines with output |
| * from other kernel threads. |
| */ |
| char buf[64]; |
| |
| /* Initial conditions */ |
| buf[0] = '\n'; |
| n = 0; |
| |
| for (i = 0; i < len; i++) { |
| if (i % 16 == 0) |
| n += snprintf(buf + n, bufsize - n, "%04x: ", i); |
| n += snprintf(buf + n, bufsize - n, "%02x ", d[i] & 0xff); |
| if (i % 16 == 15) { |
| n = 0; |
| buf[0] = '\0'; |
| } |
| } |
| |
| /* |
| * Print the final line if we didn't print it above. |
| */ |
| if (n != 0) |
| QDF_TRACE(QDF_MODULE_ID_SAP, QDF_TRACE_LEVEL_INFO, "%s: %s\n", |
| __func__, buf); |
| #endif /* def CONFIG_ENABLE_DUMP_PHYERR_CONTENTS */ |
| } |
| |
| void |
| dfs_process_phyerr(struct ieee80211com *ic, void *buf, uint16_t datalen, |
| uint8_t r_rssi, uint8_t r_ext_rssi, uint32_t r_rs_tstamp, |
| uint64_t r_fulltsf, bool enable_log) |
| { |
| struct ath_dfs *dfs = (struct ath_dfs *)ic->ic_dfs; |
| struct dfs_ieee80211_channel *chan = ic->ic_curchan; |
| struct dfs_event *event; |
| struct dfs_phy_err e; |
| int empty; |
| |
| if (dfs == NULL) { |
| QDF_TRACE(QDF_MODULE_ID_SAP, QDF_TRACE_LEVEL_ERROR, |
| "%s: sc_dfs is NULL\n", __func__); |
| return; |
| } |
| |
| dfs->dfs_phyerr_count++; |
| dump_phyerr_contents(buf, datalen); |
| /* |
| * XXX The combined_rssi_ok support has been removed. |
| * This was only clear for Owl. |
| * |
| * XXX TODO: re-add this; it requires passing in the ctl/ext |
| * RSSI set from the RX status descriptor. |
| * |
| * XXX TODO TODO: this may be done for us from the legacy |
| * phy error path in ath_dev; please review that code. |
| */ |
| |
| /* |
| * At this time we have a radar pulse that we need to examine and |
| * queue. But if dfs_process_radarevent already detected radar and set |
| * CHANNEL_INTERFERENCE flag then do not queue any more radar data. |
| * When we are in a new channel this flag will be clear and we will |
| * start queueing data for new channel. (EV74162) |
| */ |
| if (dfs->dfs_debug_mask & ATH_DEBUG_DFS_PHYERR_PKT) |
| dump_phyerr_contents(buf, datalen); |
| |
| if (chan == NULL) { |
| QDF_TRACE(QDF_MODULE_ID_SAP, QDF_TRACE_LEVEL_ERROR, |
| "%s: chan is NULL\n", __func__); |
| return; |
| } |
| |
| qdf_spin_lock_bh(&ic->chan_lock); |
| if (IEEE80211_IS_CHAN_RADAR(chan)) { |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS1, |
| "%s: Radar already found in the channel, " |
| " do not queue radar data\n", __func__); |
| return; |
| } |
| |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| dfs->ath_dfs_stats.total_phy_errors++; |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS2, |
| "%s[%d] phyerr %d len %d\n", |
| __func__, __LINE__, |
| dfs->ath_dfs_stats.total_phy_errors, datalen); |
| |
| /* |
| * hardware stores this as 8 bit signed value. |
| * we will cap it at 0 if it is a negative number |
| */ |
| if (r_rssi & 0x80) |
| r_rssi = 0; |
| |
| if (r_ext_rssi & 0x80) |
| r_ext_rssi = 0; |
| |
| OS_MEMSET(&e, 0, sizeof(e)); |
| |
| /* |
| * This is a bit evil - instead of just passing in |
| * the chip version, the existing code uses a set |
| * of HAL capability bits to determine what is |
| * possible. |
| * |
| * The way I'm decoding it is thus: |
| * |
| * + DFS enhancement? Merlin or later |
| * + DFS extension channel? Sowl or later. (Howl?) |
| * + otherwise, Owl (and legacy.) |
| */ |
| if (dfs->dfs_caps.ath_chip_is_bb_tlv) { |
| if (dfs_process_phyerr_bb_tlv(dfs, buf, datalen, r_rssi, |
| r_ext_rssi, r_rs_tstamp, |
| r_fulltsf, &e, |
| enable_log) == 0) { |
| dfs->dfs_phyerr_reject_count++; |
| QDF_TRACE(QDF_MODULE_ID_SAP, QDF_TRACE_LEVEL_INFO, |
| "%s:Rejected phyerr count after parsing=%d\n", |
| __func__, dfs->dfs_phyerr_reject_count); |
| return; |
| } else { |
| if (dfs->dfs_phyerr_freq_min > e.freq) |
| dfs->dfs_phyerr_freq_min = e.freq; |
| |
| if (dfs->dfs_phyerr_freq_max < e.freq) |
| dfs->dfs_phyerr_freq_max = e.freq; |
| } |
| } else if (dfs->dfs_caps.ath_dfs_use_enhancement) { |
| if (dfs_process_phyerr_merlin(dfs, buf, datalen, r_rssi, |
| r_ext_rssi, r_rs_tstamp, |
| r_fulltsf, &e) == 0) { |
| return; |
| } |
| } else if (dfs->dfs_caps.ath_dfs_ext_chan_ok) { |
| if (dfs_process_phyerr_sowl(dfs, buf, datalen, r_rssi, |
| r_ext_rssi, r_rs_tstamp, r_fulltsf, |
| &e) == 0) { |
| return; |
| } |
| } else { |
| if (dfs_process_phyerr_owl(dfs, buf, datalen, r_rssi, |
| r_ext_rssi, r_rs_tstamp, r_fulltsf, |
| &e) == 0) { |
| return; |
| } |
| } |
| |
| QDF_TRACE(QDF_MODULE_ID_SAP, QDF_TRACE_LEVEL_INFO, |
| "\n %s: Frequency at which the phyerror was injected = %d", |
| __func__, e.freq); |
| /* |
| * If the hardware supports radar reporting on the extension channel |
| * it will supply FFT data for longer radar pulses. |
| * |
| * TLV chips don't go through this software check - the hardware |
| * check should be enough. If we want to do software checking |
| * later on then someone will have to craft an FFT parser |
| * suitable for the TLV FFT data format. |
| */ |
| if ((!dfs->dfs_caps.ath_chip_is_bb_tlv) && |
| dfs->dfs_caps.ath_dfs_ext_chan_ok) { |
| /* |
| * HW has a known issue with chirping pulses injected at or |
| * around DC in 40MHz mode. Such pulses are reported with |
| * much lower durations and SW then discards them because |
| * they do not fit the minimum bin5 pulse duration. |
| * |
| * To work around this issue, if a pulse is within a 10us |
| * range of the bin5 min duration, check if the pulse is |
| * chirping. If the pulse is chirping, bump up the duration |
| * to the minimum bin5 duration. |
| * |
| * This makes sure that a valid chirping pulse will not be |
| * discarded because of incorrect low duration. |
| * |
| * TBD - Is it possible to calculate the 'real' duration of |
| * the pulse using the slope of the FFT data? |
| * |
| * TBD - Use FFT data to differentiate between radar pulses |
| * and false PHY errors. |
| * This will let us reduce the number of false alarms seen. |
| * |
| * BIN 5 chirping pulses are only for FCC or Japan MMK4 domain |
| */ |
| if (((dfs->dfsdomain == DFS_FCC_REGION) || |
| (dfs->dfsdomain == DFS_MKK_REGION)) && |
| (e.dur >= MAYBE_BIN5_DUR) && (e.dur < MAX_BIN5_DUR)) { |
| int add_dur; |
| int slope = 0, dc_found = 0; |
| |
| /* |
| * Set the event chirping flags; as we're doing |
| * an actual chirp check. |
| */ |
| e.do_check_chirp = 1; |
| e.is_hw_chirp = 0; |
| e.is_sw_chirp = 0; |
| |
| /* |
| * dfs_check_chirping() expects is_pri and is_ext |
| * to be '1' for true and '0' for false for now, |
| * as the function itself uses these values in |
| * constructing things rather than testing them |
| * for 'true' or 'false'. |
| */ |
| add_dur = dfs_check_chirping(dfs, buf, datalen, |
| (e.is_pri ? 1 : 0), |
| (e.is_ext ? 1 : 0), |
| &slope, &dc_found); |
| if (add_dur) { |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "old dur %d slope =%d\n", e.dur, |
| slope); |
| e.is_sw_chirp = 1; |
| /* bump up to a random bin5 pulse duration */ |
| if (e.dur < MIN_BIN5_DUR) { |
| e.dur = dfs_get_random_bin5_dur(dfs, |
| e.fulltsf); |
| } |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "new dur %d\n", e.dur); |
| } else { |
| /* set the duration so that it is rejected */ |
| e.is_sw_chirp = 0; |
| e.dur = MAX_BIN5_DUR + 100; |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "is_chirping = %d dur=%d\n", |
| add_dur, e.dur); |
| } |
| } else { |
| /* |
| * We have a pulse that is either bigger than |
| * MAX_BIN5_DUR or * less than MAYBE_BIN5_DUR |
| */ |
| if ((dfs->dfsdomain == DFS_FCC_REGION) || |
| (dfs->dfsdomain == DFS_MKK_REGION)) { |
| /* |
| * XXX Would this result in very large pulses |
| * wrapping around to become short pulses? |
| */ |
| if (e.dur >= MAX_BIN5_DUR) { |
| /* |
| * set the duration so that it is |
| * rejected |
| */ |
| e.dur = MAX_BIN5_DUR + 50; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Add the parsed, checked and filtered entry to the radar pulse |
| * event list. This is then checked by dfs_radar_processevent(). |
| * |
| * XXX TODO: some filtering is still done below this point - fix |
| * XXX this! |
| */ |
| ATH_DFSEVENTQ_LOCK(dfs); |
| empty = STAILQ_EMPTY(&(dfs->dfs_eventq)); |
| ATH_DFSEVENTQ_UNLOCK(dfs); |
| if (empty) { |
| return; |
| } |
| |
| /* |
| * If the channel is a turbo G channel, then the event is |
| * for the adaptive radio (AR) pattern matching rather than |
| * radar detection. |
| */ |
| qdf_spin_lock_bh(&ic->chan_lock); |
| if ((chan->ic_flags & CHANNEL_108G) == CHANNEL_108G) { |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| if (!(dfs->dfs_proc_phyerr & DFS_AR_EN)) { |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS2, |
| "%s: DFS_AR_EN not enabled\n", __func__); |
| return; |
| } |
| ATH_DFSEVENTQ_LOCK(dfs); |
| event = STAILQ_FIRST(&(dfs->dfs_eventq)); |
| if (event == NULL) { |
| ATH_DFSEVENTQ_UNLOCK(dfs); |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS, |
| "%s: no more events space left\n", |
| __func__); |
| return; |
| } |
| STAILQ_REMOVE_HEAD(&(dfs->dfs_eventq), re_list); |
| ATH_DFSEVENTQ_UNLOCK(dfs); |
| event->re_rssi = e.rssi; |
| event->re_dur = e.dur; |
| event->re_full_ts = e.fulltsf; |
| event->re_ts = (e.rs_tstamp) & DFS_TSMASK; |
| event->re_chanindex = dfs->dfs_curchan_radindex; |
| event->re_flags = 0; |
| event->sidx = e.sidx; |
| if (e.rsu_version == DFS_RADAR_SUMMARY_REPORT_VERSION_3) { |
| event->delta_peak = e.delta_peak; |
| event->delta_diff = e.delta_diff; |
| event->agc_total_gain = e.agc_total_gain; |
| event->agc_mb_gain = e.agc_mb_gain; |
| event->radar_subchan_mask = e.radar_subchan_mask; |
| event->pulse_height = e.pulse_height; |
| event->triggering_agc_event = e.triggering_agc_event; |
| event->pulse_rssi = e.pulse_rssi; |
| event->radar_fft_pri80_inband_power = |
| e.radar_fft_pri80_inband_power; |
| event->radar_fft_ext80_inband_power = |
| e.radar_fft_ext80_inband_power; |
| event->rsu_version = e.rsu_version; |
| event->peak_mag = e.peak_mag; |
| } |
| /* |
| * Copy the segment ID of the phyerror |
| * from the radar summary report only |
| * if SAP is operating in 80p80 mode |
| * and both primary and extension segments |
| * are DFS. |
| */ |
| if (chan->ic_80p80_both_dfs) |
| event->radar_80p80_segid = e.radar_80p80_segid; |
| |
| /* |
| * Handle chirp flags. |
| */ |
| if (e.do_check_chirp) { |
| event->re_flags |= DFS_EVENT_CHECKCHIRP; |
| if (e.is_hw_chirp) |
| event->re_flags |= DFS_EVENT_HW_CHIRP; |
| if (e.is_sw_chirp) |
| event->re_flags |= DFS_EVENT_SW_CHIRP; |
| } |
| |
| ATH_ARQ_LOCK(dfs); |
| STAILQ_INSERT_TAIL(&(dfs->dfs_arq), event, re_list); |
| ATH_ARQ_UNLOCK(dfs); |
| } else { |
| if (IEEE80211_IS_CHAN_DFS(chan)) { |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| if (!(dfs->dfs_proc_phyerr & DFS_RADAR_EN)) { |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS3, |
| "%s: DFS_RADAR_EN not enabled\n", |
| __func__); |
| return; |
| } |
| /* |
| * rssi is not accurate for short pulses, so do |
| * not filter based on that for short duration pulses |
| * |
| * XXX do this filtering above? |
| */ |
| if (dfs->dfs_caps.ath_dfs_ext_chan_ok) { |
| if ((e.rssi < dfs->dfs_rinfo.rn_minrssithresh && |
| (e.dur > 4)) || |
| e.dur > (dfs->dfs_rinfo.rn_maxpulsedur)) { |
| dfs->ath_dfs_stats.rssi_discards++; |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS1, |
| "Extension channel pulse is " |
| "discarded: dur=%d, " |
| "maxpulsedur=%d, rssi=%d, " |
| "minrssi=%d\n", |
| e.dur, |
| dfs->dfs_rinfo. |
| rn_maxpulsedur, e.rssi, |
| dfs->dfs_rinfo. |
| rn_minrssithresh); |
| return; |
| } |
| } else { |
| if (e.rssi < dfs->dfs_rinfo.rn_minrssithresh || |
| e.dur > dfs->dfs_rinfo.rn_maxpulsedur) { |
| /* XXX TODO add a debug statement? */ |
| dfs->ath_dfs_stats.rssi_discards++; |
| return; |
| } |
| } |
| |
| /* |
| * Add the event to the list, if there's space. |
| */ |
| ATH_DFSEVENTQ_LOCK(dfs); |
| event = STAILQ_FIRST(&(dfs->dfs_eventq)); |
| if (event == NULL) { |
| ATH_DFSEVENTQ_UNLOCK(dfs); |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS, |
| "%s: no more events space left\n", |
| __func__); |
| return; |
| } |
| STAILQ_REMOVE_HEAD(&(dfs->dfs_eventq), re_list); |
| ATH_DFSEVENTQ_UNLOCK(dfs); |
| |
| dfs->dfs_phyerr_queued_count++; |
| dfs->dfs_phyerr_w53_counter++; |
| |
| event->re_dur = e.dur; |
| event->re_full_ts = e.fulltsf; |
| event->re_ts = (e.rs_tstamp) & DFS_TSMASK; |
| event->re_rssi = e.rssi; |
| event->sidx = e.sidx; |
| if (e.rsu_version == |
| DFS_RADAR_SUMMARY_REPORT_VERSION_3) { |
| event->delta_peak = e.delta_peak; |
| event->delta_diff = e.delta_diff; |
| event->agc_total_gain = e.agc_total_gain; |
| event->agc_mb_gain = e.agc_mb_gain; |
| event->radar_subchan_mask = e.radar_subchan_mask; |
| event->pulse_height = e.pulse_height; |
| event->triggering_agc_event = |
| e.triggering_agc_event; |
| event->pulse_rssi = e.pulse_rssi; |
| event->radar_fft_pri80_inband_power = |
| e.radar_fft_pri80_inband_power; |
| event->radar_fft_ext80_inband_power = |
| e.radar_fft_ext80_inband_power; |
| event->rsu_version = e.rsu_version; |
| event->dfs_phyerr_eventq_serial_num = |
| dfs->dfs_phyerr_queued_count; |
| event->peak_mag = e.peak_mag; |
| } |
| /* |
| * Copy the segment ID of the phyerror |
| * from the radar summary report only |
| * if SAP is operating in 80p80 mode |
| * and both primary and extension segments |
| * are DFS. |
| */ |
| if (chan->ic_80p80_both_dfs) |
| event->radar_80p80_segid = e.radar_80p80_segid; |
| |
| /* |
| * Handle chirp flags. |
| */ |
| if (e.do_check_chirp) { |
| event->re_flags |= DFS_EVENT_CHECKCHIRP; |
| if (e.is_hw_chirp) |
| event->re_flags |= DFS_EVENT_HW_CHIRP; |
| if (e.is_sw_chirp) |
| event->re_flags |= DFS_EVENT_SW_CHIRP; |
| } |
| |
| /* |
| * Correctly set which channel is being reported on |
| */ |
| if (e.is_pri) { |
| event->re_chanindex = dfs->dfs_curchan_radindex; |
| } else { |
| if (dfs->dfs_extchan_radindex == -1) { |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "%s - phyerr on ext channel\n", |
| __func__); |
| } |
| event->re_chanindex = dfs->dfs_extchan_radindex; |
| DFS_DPRINTK(dfs, ATH_DEBUG_DFS_PHYERR, |
| "%s New extension channel event is added " |
| "to queue\n", __func__); |
| } |
| ATH_DFSQ_LOCK(dfs); |
| STAILQ_INSERT_TAIL(&(dfs->dfs_radarq), event, re_list); |
| ATH_DFSQ_UNLOCK(dfs); |
| } else { |
| qdf_spin_unlock_bh(&ic->chan_lock); |
| } |
| } |
| |
| /* |
| * Schedule the radar/AR task as appropriate. |
| * |
| * XXX isn't a lock needed for ath_radar_tasksched? |
| */ |
| |
| /* |
| * Commenting out the dfs_process_ar_event() since the function is never |
| * called at run time as dfs_arq will be empty and the function |
| * dfs_process_ar_event is obsolete and function definition is removed |
| * as part of dfs_ar.c file |
| * |
| * if (!STAILQ_EMPTY(&dfs->dfs_arq)) |
| * // XXX shouldn't this be a task/timer too? |
| * dfs_process_ar_event(dfs, ic->ic_curchan); |
| */ |
| |
| if (!STAILQ_EMPTY(&dfs->dfs_radarq) && !dfs->ath_radar_tasksched) { |
| dfs->ath_radar_tasksched = 1; |
| OS_SET_TIMER(&dfs->ath_dfs_task_timer, 0); |
| } |
| #undef EXT_CH_RADAR_FOUND |
| #undef PRI_CH_RADAR_FOUND |
| #undef EXT_CH_RADAR_EARLY_FOUND |
| } |
| #endif /* ATH_SUPPORT_DFS */ |