diff --git a/umac/dfs/core/src/misc/dfs_nol.c b/umac/dfs/core/src/misc/dfs_nol.c new file mode 100644 index 000000000000..025375839f08 --- /dev/null +++ b/umac/dfs/core/src/misc/dfs_nol.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2016-2017 The Linux Foundation. All rights reserved. + * Copyright (c) 2002-2010, Atheros Communications 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. + */ + +/** + * DOC: This file contains NOL related functionality, NOL being the non + * occupancy list. After radar has been detected in a particular channel, + * the channel cannot be used for a period of 30 minutes which is called + * the non occupancy. The NOL is basically a list of all the channels that + * radar has been detected on. Each channel has a 30 minute timer associated + * with it. This file contains the functionality to add a channel to the NOL, + * the NOL timer function and the functionality to remove a channel from the + * NOL when its time is up. + */ + +#include "../dfs.h" +#include "../dfs_channel.h" +#include "../dfs_ioctl.h" +#include "../dfs_ioctl_private.h" +#include "qdf_time.h" +#include "wlan_dfs_mlme_api.h" +#include "../dfs_internal.h" + +/* for loop to traverse the channel list. */ + +/** + * dfs_nol_delete() - Delete the given frequency/chwidth from the NOL. + * @dfs: Pointer to wlan_dfs structure. + * @delfreq: Freq to delete. + * @delchwidth: Channel width to delete. + */ +static void dfs_nol_delete(struct wlan_dfs *dfs, + uint16_t delfreq, + uint16_t delchwidth) +{ + struct dfs_nolelem *nol, **prev_next; + + if (dfs == NULL) { + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS, + "%s: sc_dfs is NULL\n", __func__); + return; + } + + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: remove channel=%d/%d MHz from NOL\n", + __func__, delfreq, delchwidth); + prev_next = &(dfs->dfs_nol); + nol = dfs->dfs_nol; + while (nol != NULL) { + if (nol->nol_freq == delfreq && + nol->nol_chwidth == delchwidth) { + *prev_next = nol->nol_next; + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s removing channel %d/%dMHz from NOL tstamp=%d\n", + __func__, nol->nol_freq, + nol->nol_chwidth, + (qdf_system_ticks_to_msecs + (qdf_system_ticks()) / 1000)); + qdf_timer_stop(&nol->nol_timer); + qdf_mem_free(nol); + nol = NULL; + nol = *prev_next; + + /* Update the NOL counter. */ + dfs->dfs_nol_count--; + + /* Be paranoid! */ + if (dfs->dfs_nol_count < 0) { + DFS_PRINTK( + "%s: dfs_nol_count < 0; eek!\n", + __func__); + dfs->dfs_nol_count = 0; + } + + } else { + prev_next = &(nol->nol_next); + nol = nol->nol_next; + } + } +} + +/** + * dfs_remove_from_nol() - Remove the freq from NOL list. + * + * When NOL times out, this function removes the channel from NOL list. + */ +static os_timer_func(dfs_remove_from_nol) +{ + struct dfs_nol_timer_arg *nol_arg; + struct wlan_dfs *dfs; + uint16_t delfreq; + uint16_t delchwidth; + + OS_GET_TIMER_ARG(nol_arg, struct dfs_nol_timer_arg *); + + dfs = nol_arg->dfs; + delfreq = nol_arg->delfreq; + delchwidth = nol_arg->delchwidth; + + /* Delete the given NOL entry. */ + dfs_nol_delete(dfs, delfreq, delchwidth); + + /* Update the wireless stack with the new NOL. */ + dfs_nol_update(dfs); + + dfs_mlme_nol_timeout_notification(dfs->dfs_pdev_obj); + + qdf_mem_free(nol_arg); +} + +void dfs_print_nol(struct wlan_dfs *dfs) +{ + struct dfs_nolelem *nol; + int i = 0; + uint32_t diff_ms, remaining_sec; + + if (dfs == NULL) { + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: sc_dfs is NULL\n", __func__); + return; + } + + nol = dfs->dfs_nol; + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, "%s: NOL\n", __func__); + while (nol != NULL) { + diff_ms = qdf_system_ticks_to_msecs(qdf_system_ticks() - + nol->nol_start_ticks); + diff_ms = (nol->nol_timeout_ms - diff_ms); + remaining_sec = diff_ms / 1000; /* Convert to seconds */ + DFS_PRINTK( + "nol:%d channel=%d MHz width=%d MHz time left=%u seconds nol starttick=%llu\n", + i++, nol->nol_freq, + nol->nol_chwidth, + remaining_sec, + (uint64_t)nol->nol_start_ticks); + nol = nol->nol_next; + } +} + +void dfs_print_nolhistory(struct wlan_dfs *dfs) +{ + struct dfs_ieee80211_channel *c, lc; + int i, j = 0; + int nchans = 0; + + if (dfs == NULL) { + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: sc_dfs is NULL\n", __func__); + return; + } + + c = &lc; + + dfs_mlme_get_ic_nchans(dfs->dfs_pdev_obj, &nchans); + for (i = 0; i < nchans; i++) { + dfs_mlme_get_ic_channels(dfs->dfs_pdev_obj, + &(c->ic_freq), + &(c->ic_flags), + &(c->ic_flagext), + &(c->ic_ieee), + &(c->ic_vhtop_ch_freq_seg1), + &(c->ic_vhtop_ch_freq_seg2), + i); + if (IEEE80211_IS_CHAN_HISTORY_RADAR(c)) { + DFS_PRINTK( + "nolhistory:%d channel=%d MHz Flags=%X\n", + j, c->ic_freq, c->ic_flags); + j++; + } + } +} + +void dfs_get_nol(struct wlan_dfs *dfs, + struct dfsreq_nolelem *dfs_nol, + int *nchan) +{ + struct dfs_nolelem *nol; + + *nchan = 0; + + if (dfs == NULL) { + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: sc_dfs is NULL\n", __func__); + return; + } + + nol = dfs->dfs_nol; + while (nol != NULL) { + dfs_nol[*nchan].nol_freq = nol->nol_freq; + dfs_nol[*nchan].nol_chwidth = nol->nol_chwidth; + dfs_nol[*nchan].nol_start_ticks = nol->nol_start_ticks; + dfs_nol[*nchan].nol_timeout_ms = nol->nol_timeout_ms; + ++(*nchan); + nol = nol->nol_next; + } +} + +void dfs_set_nol(struct wlan_dfs *dfs, + struct dfsreq_nolelem *dfs_nol, + int nchan) +{ +#define TIME_IN_MS 1000 + uint32_t nol_time_left_ms; + struct dfs_ieee80211_channel chan; + int i; + + if (dfs == NULL) { + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: sc_dfs is NULL\n", __func__); + return; + } + + for (i = 0; i < nchan; i++) { + nol_time_left_ms = + qdf_system_ticks_to_msecs(qdf_system_ticks() - + dfs_nol[i].nol_start_ticks); + if (nol_time_left_ms < dfs_nol[i].nol_timeout_ms) { + chan.ic_freq = dfs_nol[i].nol_freq; + chan.ic_flags = 0; + chan.ic_flagext = 0; + nol_time_left_ms = + (dfs_nol[i].nol_timeout_ms - nol_time_left_ms); + dfs_nol_addchan(dfs, chan.ic_freq, + (nol_time_left_ms / TIME_IN_MS)); + } + } +#undef TIME_IN_MS + dfs_nol_update(dfs); +} + +void dfs_nol_addchan(struct wlan_dfs *dfs, + uint16_t freq, + uint32_t dfs_nol_timeout) +{ +#define TIME_IN_MS 1000 +#define TIME_IN_US (TIME_IN_MS * 1000) + struct dfs_nolelem *nol, *elem, *prev; + struct dfs_nol_timer_arg *dfs_nol_arg; + /* For now, assume all events are 20MHz wide. */ + int ch_width = 20; + + if (dfs == NULL) { + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: dfs is NULL\n", __func__); + return; + } + nol = dfs->dfs_nol; + prev = dfs->dfs_nol; + elem = NULL; + while (nol != NULL) { + if ((nol->nol_freq == freq) && + (nol->nol_chwidth == ch_width)) { + nol->nol_start_ticks = qdf_system_ticks(); + nol->nol_timeout_ms = dfs_nol_timeout * TIME_IN_MS; + + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: Update OS Ticks for NOL %d MHz / %d MHz\n", + __func__, nol->nol_freq, nol->nol_chwidth); + + qdf_timer_stop(&nol->nol_timer); + OS_SET_TIMER(&nol->nol_timer, + dfs_nol_timeout * TIME_IN_MS); + return; + } + prev = nol; + nol = nol->nol_next; + } + + /* Add a new element to the NOL. */ + elem = (struct dfs_nolelem *)qdf_mem_malloc(sizeof(struct dfs_nolelem)); + if (elem == NULL) + goto bad; + + dfs_nol_arg = (struct dfs_nol_timer_arg *)qdf_mem_malloc( + sizeof(struct dfs_nol_timer_arg)); + if (dfs_nol_arg == NULL) { + qdf_mem_free(elem); + goto bad; + } + elem->nol_freq = freq; + elem->nol_chwidth = ch_width; + elem->nol_start_ticks = qdf_system_ticks(); + elem->nol_timeout_ms = dfs_nol_timeout*TIME_IN_MS; + elem->nol_next = NULL; + if (prev) { + prev->nol_next = elem; + } else { + /* This is the first element in the NOL. */ + dfs->dfs_nol = elem; + } + dfs_nol_arg->dfs = dfs; + dfs_nol_arg->delfreq = elem->nol_freq; + dfs_nol_arg->delchwidth = elem->nol_chwidth; + + qdf_timer_init(NULL, + &elem->nol_timer, dfs_remove_from_nol, + dfs_nol_arg, QDF_TIMER_TYPE_WAKE_APPS); + OS_SET_TIMER(&elem->nol_timer, dfs_nol_timeout * TIME_IN_MS); + + /* Update the NOL counter. */ + dfs->dfs_nol_count++; + + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL, + "%s: new NOL channel %d MHz / %d MHz\n", + __func__, elem->nol_freq, elem->nol_chwidth); + return; + +bad: + DFS_DPRINTK(dfs, WLAN_DEBUG_DFS_NOL | WLAN_DEBUG_DFS, + "%s: failed to allocate memory for nol entry\n", + __func__); + +#undef TIME_IN_MS +#undef TIME_IN_US +} + +void dfs_get_nol_chfreq_and_chwidth(struct dfs_nol_chan_entry *nollist, + uint32_t *nol_chfreq, + uint32_t *nol_chwidth, + int index) +{ + if (nollist == NULL) + return; + + *nol_chfreq = nollist[index].nol_chfreq; + *nol_chwidth = nollist[index].nol_chwidth; +} + +void dfs_nol_update(struct wlan_dfs *dfs) +{ + struct dfs_nol_chan_entry *dfs_nol; + struct dfs_nolelem *nol; + int nlen; + + /* + * Allocate enough entries to store the NOL. At least on Linux + * (don't ask why), if you allocate a 0 entry array, the + * returned pointer is 0x10. Make sure you're aware of this + * when you start debugging. + */ + dfs_nol = (struct dfs_nol_chan_entry *)qdf_mem_malloc( + sizeof(struct dfs_nol_chan_entry) * dfs->dfs_nol_count); + + if (dfs_nol == NULL) { + /* + * XXX TODO: if this fails, just schedule a task to retry + * updating the NOL at a later stage. That way the NOL + * update _DOES_ happen - hopefully the failure was just + * temporary. + */ + DFS_PRINTK( + "%s: failed to allocate NOL update memory!\n", + __func__); + return; + } + + /* Populate the nol array. */ + nlen = 0; + + nol = dfs->dfs_nol; + while (nol != NULL && nlen < dfs->dfs_nol_count) { + dfs_nol[nlen].nol_chfreq = nol->nol_freq; + dfs_nol[nlen].nol_chwidth = nol->nol_chwidth; + dfs_nol[nlen].nol_start_ticks = nol->nol_start_ticks; + dfs_nol[nlen].nol_timeout_ms = nol->nol_timeout_ms; + nlen++; + nol = nol->nol_next; + } + + /* Be suitably paranoid for now. */ + if (nlen != dfs->dfs_nol_count) + DFS_PRINTK("%s: nlen (%d) != dfs->dfs_nol_count (%d)!\n", + __func__, nlen, dfs->dfs_nol_count); + + /* + * Call the driver layer to have it recalculate the NOL flags + * for each driver/umac channel. If the list is empty, pass + * NULL instead of dfs_nol. The operating system may have some + * special representation for "malloc a 0 byte memory region" + * - for example, Linux 2.6.38-13 (ubuntu) returns 0x10 rather + * than a valid allocation (and is likely not NULL so the + * pointer doesn't match NULL checks in any later code. + */ + dfs_mlme_clist_update(dfs->dfs_pdev_obj, + (nlen > 0) ? dfs_nol : NULL, + nlen); + + qdf_mem_free(dfs_nol); +} + +void dfs_nol_timer_cleanup(struct wlan_dfs *dfs) +{ + struct dfs_nolelem *nol = dfs->dfs_nol, *prev; + + while (nol) { + qdf_timer_stop(&nol->nol_timer); + nol = nol->nol_next; + } + + /* Free NOL elem, don't mix this while loop with above loop. */ + nol = dfs->dfs_nol; + while (nol) { + prev = nol; + nol = nol->nol_next; + qdf_mem_free(prev); + /* Update the NOL counter. */ + dfs->dfs_nol_count--; + + if (dfs->dfs_nol_count < 0) { + DFS_PRINTK("%s: dfs_nol_count < 0\n", __func__); + ASSERT(0); + } + } + dfs->dfs_nol = NULL; + dfs_nol_update(dfs); + dfs_mlme_set_no_chans_available(dfs->dfs_pdev_obj, 0); +} + +int dfs_get_rn_use_nol(struct wlan_dfs *dfs) +{ + return dfs->dfs_rinfo.rn_use_nol; +} + +int dfs_get_nol_timeout(struct wlan_dfs *dfs) +{ + return dfs->wlan_dfs_nol_timeout; +}