/*
 * Copyright (c) 2002-2013, 2017 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.
 */

/*===========================================================================

                     dfs_bindetects.c

  OVERVIEW:

  Source code borrowed from QCA_MAIN DFS module

  DEPENDENCIES:

  Are listed for each API below.

===========================================================================*/

/*===========================================================================

                      EDIT HISTORY FOR FILE


  This section contains comments describing changes made to the module.
  Notice that changes are listed in reverse chronological order.



  when        who     what, where, why
----------    ---    --------------------------------------------------------

===========================================================================*/


#include "dfs.h"
/*TO DO DFS removing
#include <ieee80211_var.h>
*/
#ifdef ATH_SUPPORT_DFS

int
dfs_bin_fixedpattern_check(struct ath_dfs *dfs, struct dfs_filter *rf, u_int32_t dur, int ext_chan_flag)
{
        struct dfs_pulseline *pl = dfs->pulses;
        int i, n, refpri, primargin, numpulses=0;
        u_int64_t start_ts, end_ts, event_ts, prev_event_ts, next_event_ts, window_start, window_end;
        u_int32_t index, next_index, deltadur;

        /* For fixed pattern types, rf->rf_patterntype=1*/
        primargin = dfs_get_pri_margin(dfs, ext_chan_flag, (rf->rf_patterntype==1));

        refpri = (rf->rf_minpri + rf->rf_maxpri)/2;
        index = pl->pl_lastelem;
        end_ts = pl->pl_elems[index].p_time;
        start_ts = end_ts - (refpri*rf->rf_numpulses);

        DFS_DPRINTK(dfs, ATH_DEBUG_DFS3,
       "lastelem ts=%llu start_ts=%llu, end_ts=%llu\n",
       (unsigned long long) pl->pl_elems[index].p_time,
       (unsigned long long) start_ts,
       (unsigned long long) end_ts);

        /* find the index of first element in our window of interest */
        for(i=0;i<pl->pl_numelems;i++) {
                index = (index-1) & DFS_MAX_PULSE_BUFFER_MASK;
                if(pl->pl_elems[index].p_time >= start_ts )
                        continue;
                else {
                        index = (index) & DFS_MAX_PULSE_BUFFER_MASK;
                        break;
                }
        }
        for (n=0;n<=rf->rf_numpulses; n++) {
                window_start = (start_ts + (refpri*n))-(primargin+n);
                window_end = window_start + 2*(primargin+n);
                DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
                "window_start %u window_end %u \n",
                (u_int32_t)window_start, (u_int32_t)window_end);
                for(i=0;i<pl->pl_numelems;i++) {
                        prev_event_ts = pl->pl_elems[index].p_time;
                        index = (index+1) & DFS_MAX_PULSE_BUFFER_MASK;
                        event_ts = pl->pl_elems[index].p_time;
                        next_index = (index+1) & DFS_MAX_PULSE_BUFFER_MASK;
                        next_event_ts = pl->pl_elems[next_index].p_time;
                     DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
                        "ts %u \n", (u_int32_t)event_ts);
                        if( (event_ts <= window_end) && (event_ts >= window_start)){
                                deltadur = DFS_DIFF(pl->pl_elems[index].p_dur, dur);
                                if( (pl->pl_elems[index].p_dur == 1) ||
                                        ((dur != 1) && (deltadur <= 2))) {
                                        numpulses++;
                                        DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
                                        "numpulses %u \n", numpulses);
                                        break;
                                }
                        }
                        else if( event_ts > window_end) {
                                index = (index-1) & DFS_MAX_PULSE_BUFFER_MASK;
                                break;
                        }
                        else if( event_ts == prev_event_ts) {
                                if( ((next_event_ts - event_ts) > refpri) ||
                                        ((next_event_ts - event_ts) == 0)) {
                                        deltadur = DFS_DIFF(pl->pl_elems[index].p_dur, dur);
                                        if( (pl->pl_elems[index].p_dur == 1) ||
                                                ((pl->pl_elems[index].p_dur != 1) && (deltadur <= 2))) {
                                                numpulses++;
                                                DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
                                                "zero PRI: numpulses %u \n", numpulses);
                                                break;
                                        }
                                }
                        }
                }
        }
        if (numpulses >= dfs_get_filter_threshold(dfs, rf, ext_chan_flag)) {
           DFS_DPRINTK(dfs, ATH_DEBUG_DFS1, "%s FOUND filterID=%u numpulses=%d unadj thresh=%d\n", __func__, rf->rf_pulseid, numpulses, rf->rf_threshold);
           return 1;
        }
        else
                return 0;
}

void
dfs_add_pulse(struct ath_dfs *dfs, struct dfs_filter *rf, struct dfs_event *re,
      u_int32_t deltaT, u_int64_t this_ts)
{
   u_int32_t index,n, window;
   struct dfs_delayline *dl;

        dl = &rf->rf_dl;
   /* Circular buffer of size 2^n */
   index = (dl->dl_lastelem + 1) & DFS_MAX_DL_MASK;
   //if ((dl->dl_numelems+1) == DFS_MAX_DL_SIZE)
   if ((dl->dl_numelems) == DFS_MAX_DL_SIZE)
      dl->dl_firstelem = (dl->dl_firstelem + 1) & DFS_MAX_DL_MASK;
   else
      dl->dl_numelems++;
   dl->dl_lastelem = index;
   dl->dl_elems[index].de_time = deltaT;
   dl->dl_elems[index].de_ts = this_ts;
        window = deltaT;
   dl->dl_elems[index].de_dur = re->re_dur;
   dl->dl_elems[index].de_rssi = re->re_rssi;
   dl->dl_elems[index].de_seg_id  = re->re_seg_id;
   dl->dl_elems[index].de_sidx    = re->sidx;
   dl->dl_elems[index].de_delta_peak    = re->re_delta_peak;
   dl->dl_elems[index].de_seq_num = dfs->dfs_seq_num;

   DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
       "%s: adding: filter id %d, dur=%d, rssi=%d, ts=%llu\n",
       __func__,
       rf->rf_pulseid,
       re->re_dur,
       re->re_rssi,
       (unsigned long long int) this_ts);

   for (n=0;n<dl->dl_numelems-1; n++) {
      index = (index-1) & DFS_MAX_DL_MASK;
      /*
       * calculate window based on full time stamp instead of deltaT
       * deltaT (de_time) may result in incorrect window value
       */
      window = (u_int32_t) (this_ts - dl->dl_elems[index].de_ts);

      if (window > rf->rf_filterlen) {
         dl->dl_firstelem = (index+1) & DFS_MAX_DL_MASK;
         dl->dl_numelems = n+1;
      }
   }
   DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
      "dl firstElem = %d  lastElem = %d\n",dl->dl_firstelem,
      dl->dl_lastelem);
}


int
dfs_bin_check(struct ath_dfs *dfs, struct dfs_filter *rf, u_int32_t deltaT,
    u_int32_t width, int ext_chan_flag)
{
        u_int32_t refpri, refdur, searchpri, deltapri,deltapri_2,deltapri_3, averagerefpri;
        u_int32_t n, i, primargin, durmargin, highscore, highscoreindex;
        int score[DFS_MAX_DL_SIZE], delayindex, dindex, found=0;
        struct dfs_delayline *dl;
        u_int32_t scoreindex, lowpriindex= 0, lowpri = 0xffff;
        int numpulses=0;
        int lowprichk=3, pri_match=0;

        dl = &rf->rf_dl;
        if( dl->dl_numelems < (rf->rf_threshold-1)) {
                return 0;
        }
        if( deltaT > rf->rf_filterlen)
                return 0;

        primargin = dfs_get_pri_margin(dfs, ext_chan_flag, (rf->rf_patterntype==1));


        if(rf->rf_maxdur < 10) {
                durmargin = 4;
        }
        else {
                durmargin = 6;
        }

        if( rf->rf_patterntype == 1 ){
                found = dfs_bin_fixedpattern_check(dfs, rf, width, ext_chan_flag);
                if(found) {
                        dl->dl_numelems = 0;
                }
                return found;
        }

        OS_MEMZERO(score, sizeof(int)*DFS_MAX_DL_SIZE);
        /* find out the lowest pri */
        for (n=0;n<dl->dl_numelems; n++) {
                delayindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
                refpri = dl->dl_elems[delayindex].de_time;
                if( refpri == 0)
                        continue;
                else if(refpri < lowpri) {
                        lowpri = dl->dl_elems[delayindex].de_time;
                        lowpriindex = n;
                }
 }
        /* find out the each delay element's pri score */
        for (n=0;n<dl->dl_numelems; n++) {
                delayindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
                refpri = dl->dl_elems[delayindex].de_time;
                if( refpri == 0)
                        continue;
                if (refpri < rf->rf_maxpri) { // use only valid PRI range for high score
                    for (i=0;i<dl->dl_numelems; i++) {
                        dindex = (dl->dl_firstelem + i) & DFS_MAX_DL_MASK;
                        searchpri = dl->dl_elems[dindex].de_time;
                        deltapri = DFS_DIFF(searchpri, refpri);
                        deltapri_2 = DFS_DIFF(searchpri, 2*refpri);
                        deltapri_3 = DFS_DIFF(searchpri, 3*refpri);
                        if (rf->rf_ignore_pri_window==2) {
                            pri_match = ((deltapri < primargin) || (deltapri_2 < primargin) || (deltapri_3 < primargin));
                        } else {
                            pri_match = (deltapri < primargin);
                        }

                        if (pri_match)
                            score[n]++;
                    }
                } else {
                    score[n] = 0;
                }
                if( score[n] > rf->rf_threshold) {
                        /* we got the most possible candidate,
                         * no need to continue further */
                        break;
                }
        }
        /* find out the high scorer */
        highscore = 0;
        highscoreindex = 0;
        for (n=0;n<dl->dl_numelems; n++) {
                if( score[n] > highscore) {
                        highscore = score[n];
                        highscoreindex = n;
                }
                else if( score[n] == highscore ) {
                        /*more than one pri has highscore take the least pri */
                        delayindex = (dl->dl_firstelem + highscoreindex) & DFS_MAX_DL_MASK;
                        dindex = (dl->dl_firstelem + n) & DFS_MAX_DL_MASK;
                        if( dl->dl_elems[dindex].de_time <=
                                dl->dl_elems[delayindex].de_time ) {
                                highscoreindex = n;
                        }
                }
        }
        /* find the average pri of pulses around the pri of highscore or
         * the pulses around the lowest pri */
        if (rf->rf_ignore_pri_window > 0) {
            lowprichk = (rf->rf_threshold >> 1)+1;
        } else {
            lowprichk = 3;
        }

        if( highscore < lowprichk) {
                scoreindex = lowpriindex;
        }
        else {
                scoreindex = highscoreindex;
        }
        /* We got the possible pri, save its parameters as reference */
        delayindex = (dl->dl_firstelem + scoreindex) & DFS_MAX_DL_MASK;
        refdur = dl->dl_elems[delayindex].de_dur;
        refpri = dl->dl_elems[delayindex].de_time;
        averagerefpri = 0;

        if (rf->rf_fixed_pri_radar_pulse) {
            refpri = (rf->rf_minpri + rf->rf_maxpri)/2;
        }

        numpulses = dfs_bin_pri_check(dfs, rf, dl, score[scoreindex], refpri,
          refdur, ext_chan_flag, refpri);
        if (numpulses >= dfs_get_filter_threshold(dfs, rf, ext_chan_flag)) {
            found = 1;
            DFS_DPRINTK(dfs, ATH_DEBUG_DFS1, "ext_flag=%d MATCH filter=%u numpulses=%u thresh=%u refdur=%d refpri=%d primargin=%d\n", ext_chan_flag, rf->rf_pulseid, numpulses,rf->rf_threshold, refdur, refpri, primargin);
            dfs_print_delayline(dfs, &rf->rf_dl);
            dfs_print_filter(dfs, rf);
        }
        return found;
}

int
dfs_bin_pri_check(struct ath_dfs *dfs, struct dfs_filter *rf,
    struct dfs_delayline *dl, u_int32_t score, u_int32_t refpri,
    u_int32_t refdur, int ext_chan_flag, int fundamentalpri)
{
        u_int32_t searchpri, searchdur, searchrssi, deltapri = 0,deltapri1 = 0, deltapri2 = 0, deltadur, averagerefpri=0,MatchCount = 0;
        u_int32_t  delta_ts_variance, delta_time_stamps, prev_good_timestamp=0;
        int delayindex, dindex;
        u_int32_t i, j=0, primargin, durmargin, highscore=score, highscoreindex=0;
        int numpulses=1;  //first pulse in the burst is most likely being filtered out based on maxfilterlen
        int priscorechk=1,numpulsetochk=2,primatch=0;
        int32_t sidx_min = DFS_BIG_SIDX;
        int32_t sidx_max = -DFS_BIG_SIDX;
        u_int8_t delta_peak_match_count = 1;

        //Use the adjusted PRI margin to reduce false alarms
        /* For non fixed pattern types, rf->rf_patterntype=0*/
        primargin = dfs_get_pri_margin(dfs, ext_chan_flag, (rf->rf_patterntype==1));

        if ( (refpri > rf->rf_maxpri) || (refpri < rf->rf_minpri) ) {
         numpulses = 0;
         return numpulses;
      }


      if(rf->rf_maxdur < 10) {
         durmargin = 4;
        } else {
         durmargin = 6;
        }

        if ((!rf->rf_fixed_pri_radar_pulse)) {
            if (rf->rf_ignore_pri_window==1) {
               priscorechk = (rf->rf_threshold >> 1);
            } else {
               priscorechk = 1;
            }

            MatchCount = 0;
            if( score > priscorechk) {
                for (i=0;i<dl->dl_numelems; i++) {
                    dindex = (dl->dl_firstelem + i) & DFS_MAX_DL_MASK;
                    searchpri = dl->dl_elems[dindex].de_time;
                    deltapri = DFS_DIFF(searchpri, refpri);
                    if( deltapri < primargin) {
                            averagerefpri += searchpri;
                            MatchCount++;
                    }
                }
                if (rf->rf_patterntype != 2) {
                    if (MatchCount > 0)
                      refpri = (averagerefpri/MatchCount);  //average
                } else {
                    refpri = (averagerefpri/score);
                }
            }
        }
        /* Note: Following primultiple calculation should be done once per filter
         * during initialization stage (dfs_attach) and stored in its array
         * atleast for fixed frequency types like FCC Bin1 to save some CPU cycles.
         * multiplication, devide operators in the following code are left as it is
         * for readability hoping the complier will use left/right shifts wherever possible
         */
        DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
                "refpri = %d high score = %d index = %d numpulses = %d\n",
                refpri, highscore, highscoreindex, numpulses);
   /* Count the other delay elements that have  pri and dur with in the
         * acceptable range from the reference one */
        for (i=0; i<dl->dl_numelems; i++) {
                delayindex = (dl->dl_firstelem + i) & DFS_MAX_DL_MASK;
                searchpri = dl->dl_elems[delayindex].de_time;
                if( searchpri == 0) {
                        /* This events PRI is zero, take it as a
                         * valid pulse but decrement next event's PRI by refpri
                         */
                        dindex = (delayindex+1)& DFS_MAX_DL_MASK;
                        dl->dl_elems[dindex].de_time -=  refpri;
                        searchpri = refpri;
                }
                searchdur = dl->dl_elems[delayindex].de_dur;
                searchrssi = dl->dl_elems[delayindex].de_rssi;
                deltadur = DFS_DIFF(searchdur, refdur);
                deltapri = DFS_DIFF(searchpri, refpri);

                //deltapri3 = DFS_DIFF(searchpri, 3 * refpri);
                primatch=0;

                if ((rf->rf_ignore_pri_window>0) && (rf->rf_patterntype!=2)) {
                     for (j=0;j<rf->rf_numpulses;j++){
                         deltapri1 = DFS_DIFF(searchpri, (j+1)*refpri);
                         if (deltapri1 < (2*primargin)) {
                             primatch = 1;
                             break;
                         }
                     }
                 } else {
                     if (( deltapri1 < primargin) || ( deltapri2 < primargin)) {
                         primatch = 1;
                     }
                 }

                if ( primatch && ( deltadur < durmargin) ) {
                    if ( (numpulses == 1)  ) {
                        dl->dl_seq_num_second =
                                    dl->dl_elems[delayindex].de_seq_num;
                        /* update sidx min/max for false detection check later*/
                        numpulses++;
                        if (sidx_min > dl->dl_elems[delayindex].de_sidx)
                            sidx_min = dl->dl_elems[delayindex].de_sidx;
                        if (sidx_max < dl->dl_elems[delayindex].de_sidx)
                            sidx_max = dl->dl_elems[delayindex].de_sidx;
                        if ((rf->rf_check_delta_peak) &&
                                (dl->dl_elems[delayindex].de_delta_peak != 0))
                            delta_peak_match_count++;
                    } else {
                        delta_time_stamps = dl->dl_elems[delayindex].de_ts -
                            prev_good_timestamp;
                        if ((rf->rf_ignore_pri_window>0)) {
                            numpulsetochk = rf->rf_numpulses;

                            if ((rf->rf_patterntype==2) && (fundamentalpri<refpri+100)) {
                                numpulsetochk = 4;
                            }
                        } else {
                            numpulsetochk = 4;
                        }
                        for (j = 0; j < numpulsetochk; j++){
                            delta_ts_variance = DFS_DIFF(delta_time_stamps, ((j+1)*fundamentalpri));
                            if ( delta_ts_variance < (2*(j+1)*primargin) ) {
                                dl->dl_seq_num_stop =
                                        dl->dl_elems[delayindex].de_seq_num;
                                numpulses++;
                                /**
                                 * update sidx min/max for false detection check
                                 * later
                                 */
                                if (sidx_min > dl->dl_elems[delayindex].de_sidx)
                                    sidx_min = dl->dl_elems[delayindex].de_sidx;
                                if (sidx_max < dl->dl_elems[delayindex].de_sidx)
                                    sidx_max = dl->dl_elems[delayindex].de_sidx;
                                if ((rf->rf_check_delta_peak) &&
                                        (dl->dl_elems[delayindex].de_delta_peak
                                        != 0))
                                    delta_peak_match_count++;
                                if (rf->rf_ignore_pri_window>0) {
                                    break;
                                }
                            }
                        }
         }
         prev_good_timestamp = dl->dl_elems[delayindex].de_ts;
         dl->dl_search_pri = searchpri;
         dl->dl_min_sidx = sidx_min;
         dl->dl_max_sidx = sidx_max;
         dl->dl_delta_peak_match_count = delta_peak_match_count;
                        DFS_DPRINTK(dfs, ATH_DEBUG_DFS2,
                        "rf->minpri=%d rf->maxpri=%d searchpri = %d index = %d numpulses = %d deltapri=%d j=%d\n",
                        rf->rf_minpri, rf->rf_maxpri, searchpri, i, numpulses, deltapri, j);
      }

        }
        return numpulses;
}
#endif /* ATH_SUPPORT_DFS */
