| /* |
| * Bad AP Manager for ADPS |
| * |
| * Copyright (C) 2021, Broadcom. |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2 (the "GPL"), |
| * available at http://www.broadcom.com/licenses/GPLv2.php, with the |
| * following added to such license: |
| * |
| * As a special exception, the copyright holders of this software give you |
| * permission to link this software with independent modules, and to copy and |
| * distribute the resulting executable under terms of your choice, provided that |
| * you also meet, for each linked independent module, the terms and conditions of |
| * the license of that module. An independent module is a module which is not |
| * derived from this software. The special exception does not apply to any |
| * modifications of the software. |
| * |
| * |
| * <<Broadcom-WL-IPTag/Dual:>> |
| */ |
| #include <linuxver.h> |
| #include <bcmiov.h> |
| #include <linux/list_sort.h> |
| #include <wl_cfg80211.h> |
| #include <wlioctl.h> |
| #include <wldev_common.h> |
| #include <wl_bam.h> |
| |
| static int |
| wl_bad_ap_mngr_add_entry(wl_bad_ap_mngr_t *bad_ap_mngr, wl_bad_ap_info_t *bad_ap_info) |
| { |
| unsigned long flags; |
| wl_bad_ap_info_entry_t *entry; |
| |
| entry = MALLOCZ(bad_ap_mngr->osh, sizeof(*entry)); |
| if (entry == NULL) { |
| WL_ERR(("%s: allocation for list failed\n", __FUNCTION__)); |
| return BCME_NOMEM; |
| } |
| |
| memcpy(&entry->bad_ap, bad_ap_info, sizeof(entry->bad_ap)); |
| INIT_LIST_HEAD(&entry->list); |
| spin_lock_irqsave(&bad_ap_mngr->lock, flags); |
| list_add_tail(&entry->list, &bad_ap_mngr->list); |
| spin_unlock_irqrestore(&bad_ap_mngr->lock, flags); |
| |
| bad_ap_mngr->num++; |
| |
| return BCME_OK; |
| } |
| |
| #if !defined(DHD_ADPS_BAM_EXPORT) |
| #define WL_BAD_AP_INFO_FILE_PATH PLATFORM_PATH".bad_ap_list.info" |
| #define WL_BAD_AP_MAX_BUF_SIZE 1024u |
| |
| /* Bad AP information data format |
| * |
| * Status and Reason: come from event |
| * Connection count: Increase connecting Bad AP |
| * |
| * BSSID,year-month-day hour:min:sec,Status,Reason,Connection count |
| * ex) XX:XX:XX:XX:XX:XX,1970-01-01 00:00:00,1,2,1 |
| * |
| */ |
| #define WL_BAD_AP_INFO_FMT \ |
| "%02x:%02x:%02x:%02x:%02x:%02x,%04ld-%02d-%02d %02d:%02d:%02d,%u,%u,%u\n" |
| #define WL_BAD_AP_INFO_FMT_ITEM_CNT 15u |
| |
| static inline void |
| wl_bad_ap_mngr_tm2ts(struct timespec *ts, const struct tm tm) |
| { |
| ts->tv_sec = mktime(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); |
| ts->tv_nsec = 0; |
| } |
| |
| /* Ignore compiler warnings due to -Werror=cast-qual */ |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif |
| static int |
| wl_bad_ap_mngr_timecmp(void *priv, struct list_head *a, struct list_head *b) |
| { |
| int ret; |
| |
| struct timespec ts1; |
| struct timespec ts2; |
| |
| wl_bad_ap_info_entry_t *e1 = CONTAINEROF(a, wl_bad_ap_info_entry_t, list); |
| wl_bad_ap_info_entry_t *e2 = CONTAINEROF(b, wl_bad_ap_info_entry_t, list); |
| |
| wl_bad_ap_mngr_tm2ts(&ts1, e1->bad_ap.tm); |
| wl_bad_ap_mngr_tm2ts(&ts2, e2->bad_ap.tm); |
| |
| ret = timespec_compare((const struct timespec *)&ts1, (const struct timespec *)&ts2); |
| |
| return ret; |
| } |
| |
| static void |
| wl_bad_ap_mngr_update(struct bcm_cfg80211 *cfg, wl_bad_ap_info_t *bad_ap_info) |
| { |
| wl_bad_ap_info_entry_t *entry; |
| unsigned long flags; |
| |
| if (list_empty(&cfg->bad_ap_mngr.list)) { |
| return; |
| } |
| |
| WL_CFG_BAM_LOCK(&cfg->bad_ap_mngr.lock, flags); |
| /* sort by timestamp */ |
| list_sort(NULL, &cfg->bad_ap_mngr.list, wl_bad_ap_mngr_timecmp); |
| |
| /* update entry with the latest bad ap information */ |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif |
| entry = list_first_entry(&cfg->bad_ap_mngr.list, wl_bad_ap_info_entry_t, list); |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| if (entry != NULL) { |
| memcpy(&entry->bad_ap, bad_ap_info, sizeof(entry->bad_ap)); |
| } |
| WL_CFG_BAM_UNLOCK(&cfg->bad_ap_mngr.lock, flags); |
| } |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| |
| static inline int |
| wl_bad_ap_mngr_fread_bad_ap_info(char *buf, int buf_len, wl_bad_ap_info_t *bad_ap) |
| { |
| return snprintf(buf, buf_len, WL_BAD_AP_INFO_FMT, |
| bad_ap->bssid.octet[0], bad_ap->bssid.octet[1], |
| bad_ap->bssid.octet[2], bad_ap->bssid.octet[3], |
| bad_ap->bssid.octet[4], bad_ap->bssid.octet[5], |
| bad_ap->tm.tm_year + 1900, bad_ap->tm.tm_mon + 1, bad_ap->tm.tm_mday, |
| bad_ap->tm.tm_hour, bad_ap->tm.tm_min, bad_ap->tm.tm_sec, |
| bad_ap->status, bad_ap->reason, bad_ap->connect_count); |
| } |
| |
| static int |
| wl_bad_ap_mngr_fparse(struct bcm_cfg80211 *cfg, struct file *fp) |
| { |
| int len; |
| int pos = 0; |
| char tmp[128]; |
| int ret = BCME_ERROR; |
| |
| wl_bad_ap_info_t bad_ap; |
| char *buf = NULL; |
| |
| buf = MALLOCZ(cfg->osh, WL_BAD_AP_MAX_BUF_SIZE); |
| if (buf == NULL) { |
| WL_ERR(("%s: allocation for buf failed\n", __FUNCTION__)); |
| return BCME_NOMEM; |
| } |
| |
| ret = dhd_vfs_read(fp, buf, WL_BAD_AP_MAX_BUF_SIZE, &fp->f_pos); |
| if (ret < 0) { |
| WL_ERR(("%s: file read failed (%d)\n", __FUNCTION__, ret)); |
| goto fail; |
| } |
| |
| len = ret; |
| do { |
| ret = sscanf(&buf[pos], WL_BAD_AP_INFO_FMT, |
| (uint32 *)&bad_ap.bssid.octet[0], (uint32 *)&bad_ap.bssid.octet[1], |
| (uint32 *)&bad_ap.bssid.octet[2], (uint32 *)&bad_ap.bssid.octet[3], |
| (uint32 *)&bad_ap.bssid.octet[4], (uint32 *)&bad_ap.bssid.octet[5], |
| (long int *)&bad_ap.tm.tm_year, (uint32 *)&bad_ap.tm.tm_mon, |
| (uint32 *)&bad_ap.tm.tm_mday, (uint32 *)&bad_ap.tm.tm_hour, |
| (uint32 *)&bad_ap.tm.tm_min, (uint32 *)&bad_ap.tm.tm_sec, |
| (uint32 *)&bad_ap.status, (uint32 *)&bad_ap.reason, |
| (uint32 *)&bad_ap.connect_count); |
| if (ret != WL_BAD_AP_INFO_FMT_ITEM_CNT) { |
| WL_ERR(("%s: file parse failed(expected: %d actual: %d)\n", |
| __FUNCTION__, WL_BAD_AP_INFO_FMT_ITEM_CNT, ret)); |
| ret = BCME_ERROR; |
| goto fail; |
| } |
| |
| /* convert struct tm format */ |
| bad_ap.tm.tm_year -= 1900; |
| bad_ap.tm.tm_mon -= 1; |
| |
| ret = wl_bad_ap_mngr_add(&cfg->bad_ap_mngr, &bad_ap); |
| if (ret < 0) { |
| WL_ERR(("%s: bad ap add failed\n", __FUNCTION__)); |
| goto fail; |
| } |
| |
| ret = wl_bad_ap_mngr_fread_bad_ap_info(tmp, sizeof(tmp), &bad_ap); |
| if (ret < 0) { |
| WL_ERR(("%s: wl_bad_ap_mngr_fread_bad_ap_info failed (%d)\n", |
| __FUNCTION__, ret)); |
| goto fail; |
| } |
| |
| if (cfg->bad_ap_mngr.num >= WL_BAD_AP_MAX_ENTRY_NUM) { |
| break; |
| } |
| |
| len -= ret; |
| pos += ret; |
| } while (len > 0); |
| |
| ret = BCME_OK; |
| |
| fail: |
| if (buf) { |
| MFREE(cfg->osh, buf, WL_BAD_AP_MAX_BUF_SIZE); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| wl_bad_ap_mngr_fread(struct bcm_cfg80211 *cfg, const char *fname) |
| { |
| int ret = BCME_ERROR; |
| |
| mm_segment_t fs; |
| struct file *fp = NULL; |
| |
| if (fname == NULL) { |
| WL_ERR(("%s: fname is NULL\n", __FUNCTION__)); |
| return ret; |
| } |
| mutex_lock(&cfg->bad_ap_mngr.fs_lock); |
| |
| fs = get_fs(); |
| set_fs(KERNEL_DS); |
| |
| fp = dhd_filp_open(fname, O_RDONLY, 0); |
| if (IS_ERR(fp) || (fp == NULL)) { |
| fp = NULL; |
| WL_ERR(("%s: file open failed(%d)\n", __FUNCTION__, ret)); |
| goto fail; |
| } |
| |
| if ((ret = wl_bad_ap_mngr_fparse(cfg, fp)) < 0) { |
| goto fail; |
| } |
| fail: |
| if (fp) { |
| dhd_filp_close(fp, NULL); |
| } |
| set_fs(fs); |
| |
| mutex_unlock(&cfg->bad_ap_mngr.fs_lock); |
| |
| return ret; |
| } |
| |
| static int |
| wl_bad_ap_mngr_fwrite(struct bcm_cfg80211 *cfg, const char *fname) |
| { |
| int ret = BCME_ERROR; |
| |
| mm_segment_t fs; |
| struct file *fp = NULL; |
| |
| int len = 0; |
| char tmp[WL_BAD_AP_MAX_BUF_SIZE]; |
| wl_bad_ap_info_t *bad_ap; |
| wl_bad_ap_info_entry_t *entry; |
| |
| if (list_empty(&cfg->bad_ap_mngr.list)) { |
| return BCME_ERROR; |
| } |
| |
| if (fname == NULL) { |
| return BCME_NOTFOUND; |
| } |
| |
| mutex_lock(&cfg->bad_ap_mngr.fs_lock); |
| |
| fs = get_fs(); |
| set_fs(KERNEL_DS); |
| |
| fp = dhd_filp_open(fname, O_CREAT | O_RDWR | O_TRUNC, 0666); |
| if (IS_ERR(fp) || (fp == NULL)) { |
| ret = PTR_ERR(fp); |
| WL_ERR(("%s: file open failed(%d)\n", __FUNCTION__, ret)); |
| fp = NULL; |
| goto fail; |
| } |
| |
| memset(tmp, 0, sizeof(tmp)); |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif |
| list_for_each_entry(entry, &cfg->bad_ap_mngr.list, list) { |
| bad_ap = &entry->bad_ap; |
| ret = wl_bad_ap_mngr_fread_bad_ap_info(&tmp[len], sizeof(tmp) - len, bad_ap); |
| if (ret < 0) { |
| WL_ERR(("%s: snprintf failed(%d)\n", __FUNCTION__, ret)); |
| goto fail; |
| } |
| |
| len += ret; |
| } |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| |
| ret = dhd_vfs_write(fp, tmp, len, &fp->f_pos); |
| if (ret < 0) { |
| WL_ERR(("%s: file write failed(%d)\n", __FUNCTION__, ret)); |
| goto fail; |
| } |
| /* Sync file from filesystem to physical media */ |
| ret = dhd_vfs_fsync(fp, 0); |
| if (ret < 0) { |
| WL_ERR(("%s: sync file failed(%d)\n", __FUNCTION__, ret)); |
| goto fail; |
| } |
| ret = BCME_OK; |
| fail: |
| if (fp) { |
| dhd_filp_close(fp, NULL); |
| } |
| set_fs(fs); |
| mutex_unlock(&cfg->bad_ap_mngr.fs_lock); |
| |
| return ret; |
| } |
| #else |
| extern wl_bad_ap_mngr_t *g_bad_ap_mngr; |
| #endif /* DHD_ADPS_BAM_EXPORT */ |
| |
| wl_bad_ap_info_entry_t* |
| wl_bad_ap_mngr_find(wl_bad_ap_mngr_t *bad_ap_mngr, const struct ether_addr *bssid) |
| { |
| wl_bad_ap_info_entry_t *entry; |
| unsigned long flags; |
| |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif |
| spin_lock_irqsave(&bad_ap_mngr->lock, flags); |
| list_for_each_entry(entry, &bad_ap_mngr->list, list) { |
| if (!memcmp(&entry->bad_ap.bssid.octet, bssid->octet, ETHER_ADDR_LEN)) { |
| spin_unlock_irqrestore(&bad_ap_mngr->lock, flags); |
| return entry; |
| } |
| } |
| spin_unlock_irqrestore(&bad_ap_mngr->lock, flags); |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| return NULL; |
| } |
| |
| int |
| wl_bad_ap_mngr_add(wl_bad_ap_mngr_t *bad_ap_mngr, wl_bad_ap_info_t *bad_ap_info) |
| { |
| int ret; |
| wl_bad_ap_info_entry_t *entry; |
| unsigned long flags; |
| |
| BCM_REFERENCE(entry); |
| BCM_REFERENCE(flags); |
| |
| #if !defined(DHD_ADPS_BAM_EXPORT) |
| ret = wl_bad_ap_mngr_add_entry(bad_ap_mngr, bad_ap_info); |
| #else |
| if (bad_ap_mngr->num == WL_BAD_AP_MAX_ENTRY_NUM) { |
| /* Remove the oldest entry if entry list is full */ |
| spin_lock_irqsave(&bad_ap_mngr->lock, flags); |
| list_del(bad_ap_mngr->list.next); |
| bad_ap_mngr->num--; |
| spin_unlock_irqrestore(&bad_ap_mngr->lock, flags); |
| } |
| |
| /* delete duplicated entry to update it at tail to keep the odrer */ |
| entry = wl_bad_ap_mngr_find(bad_ap_mngr, &bad_ap_info->bssid); |
| if (entry != NULL) { |
| spin_lock_irqsave(&bad_ap_mngr->lock, flags); |
| list_del(&entry->list); |
| bad_ap_mngr->num--; |
| spin_unlock_irqrestore(&bad_ap_mngr->lock, flags); |
| } |
| |
| ret = wl_bad_ap_mngr_add_entry(bad_ap_mngr, bad_ap_info); |
| if (ret < 0) { |
| WL_ERR(("%s - fail to add bad ap data(%d)\n", __FUNCTION__, ret)); |
| return ret; |
| } |
| #endif /* DHD_ADPS_BAM_EXPORT */ |
| return ret; |
| } |
| |
| void |
| wl_bad_ap_mngr_deinit(struct bcm_cfg80211 *cfg) |
| { |
| wl_bad_ap_info_entry_t *entry; |
| unsigned long flags; |
| |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif |
| WL_CFG_BAM_LOCK(&cfg->bad_ap_mngr.lock, flags); |
| while (!list_empty(&cfg->bad_ap_mngr.list)) { |
| entry = list_entry(cfg->bad_ap_mngr.list.next, wl_bad_ap_info_entry_t, list); |
| if (entry) { |
| list_del(&cfg->bad_ap_mngr.list); |
| MFREE(cfg->osh, entry, sizeof(*entry)); |
| } |
| } |
| WL_CFG_BAM_UNLOCK(&cfg->bad_ap_mngr.lock, flags); |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| #if !defined(DHD_ADPS_BAM_EXPORT) |
| mutex_destroy(&cfg->bad_ap_mngr.fs_lock); |
| #endif /* !DHD_ADPS_BAM_EXPORT */ |
| } |
| |
| void |
| wl_bad_ap_mngr_init(struct bcm_cfg80211 *cfg) |
| { |
| cfg->bad_ap_mngr.osh = cfg->osh; |
| cfg->bad_ap_mngr.num = 0; |
| |
| spin_lock_init(&cfg->bad_ap_mngr.lock); |
| INIT_LIST_HEAD(&cfg->bad_ap_mngr.list); |
| |
| #if !defined(DHD_ADPS_BAM_EXPORT) |
| mutex_init(&cfg->bad_ap_mngr.fs_lock); |
| #else |
| g_bad_ap_mngr = &cfg->bad_ap_mngr; |
| #endif /* !DHD_ADPS_BAM_EXPORT */ |
| } |
| |
| static int |
| wl_event_adps_bad_ap_mngr(struct bcm_cfg80211 *cfg, void *data) |
| { |
| int ret = BCME_OK; |
| |
| wl_event_adps_t *event_data = (wl_event_adps_t *)data; |
| wl_event_adps_bad_ap_t *bad_ap_data; |
| |
| wl_bad_ap_info_entry_t *entry; |
| wl_bad_ap_info_t temp; |
| #if !defined(DHD_ADPS_BAM_EXPORT) |
| struct timespec ts; |
| #endif /* !DHD_ADPS_BAM_EXPORT */ |
| |
| if (event_data->version != WL_EVENT_ADPS_VER_1) { |
| return BCME_VERSION; |
| } |
| |
| if (event_data->length != (OFFSETOF(wl_event_adps_t, data) + sizeof(*bad_ap_data))) { |
| return BCME_ERROR; |
| } |
| |
| BCM_REFERENCE(ret); |
| BCM_REFERENCE(entry); |
| bad_ap_data = (wl_event_adps_bad_ap_t *)event_data->data; |
| |
| #if !defined(DHD_ADPS_BAM_EXPORT) |
| /* Update Bad AP list */ |
| if (list_empty(&cfg->bad_ap_mngr.list)) { |
| wl_bad_ap_mngr_fread(cfg, WL_BAD_AP_INFO_FILE_PATH); |
| } |
| |
| getnstimeofday(&ts); |
| entry = wl_bad_ap_mngr_find(&cfg->bad_ap_mngr, &bad_ap_data->ea); |
| if (entry != NULL) { |
| time_to_tm((ts.tv_sec - (sys_tz.tz_minuteswest * 60)), 0, &entry->bad_ap.tm); |
| entry->bad_ap.status = bad_ap_data->status; |
| entry->bad_ap.reason = bad_ap_data->reason; |
| entry->bad_ap.connect_count++; |
| } |
| else { |
| time_to_tm((ts.tv_sec - (sys_tz.tz_minuteswest * 60)), 0, &temp.tm); |
| temp.status = bad_ap_data->status; |
| temp.reason = bad_ap_data->reason; |
| temp.connect_count = 1; |
| memcpy(temp.bssid.octet, &bad_ap_data->ea.octet, ETHER_ADDR_LEN); |
| |
| if (cfg->bad_ap_mngr.num < WL_BAD_AP_MAX_ENTRY_NUM) { |
| wl_bad_ap_mngr_add(&cfg->bad_ap_mngr, &temp); |
| } |
| else { |
| wl_bad_ap_mngr_update(cfg, &temp); |
| } |
| } |
| |
| wl_bad_ap_mngr_fwrite(cfg, WL_BAD_AP_INFO_FILE_PATH); |
| #else |
| memcpy(temp.bssid.octet, &bad_ap_data->ea.octet, ETHER_ADDR_LEN); |
| ret = wl_bad_ap_mngr_add(&cfg->bad_ap_mngr, &temp); |
| #endif /* !DHD_ADPS_BAM_EXPORT */ |
| |
| return ret; |
| } |
| |
| static int |
| wl_adps_get_mode(struct net_device *ndev, uint8 band) |
| { |
| int len; |
| int ret; |
| |
| uint8 *pdata; |
| char buf[WLC_IOCTL_SMLEN]; |
| |
| bcm_iov_buf_t iov_buf; |
| bcm_iov_buf_t *resp; |
| wl_adps_params_v1_t *data = NULL; |
| |
| memset(&iov_buf, 0, sizeof(iov_buf)); |
| len = OFFSETOF(bcm_iov_buf_t, data) + sizeof(band); |
| |
| iov_buf.version = WL_ADPS_IOV_VER; |
| iov_buf.len = sizeof(band); |
| iov_buf.id = WL_ADPS_IOV_MODE; |
| pdata = (uint8 *)iov_buf.data; |
| *pdata = band; |
| |
| ret = wldev_iovar_getbuf(ndev, "adps", &iov_buf, len, buf, WLC_IOCTL_SMLEN, NULL); |
| if (ret < 0) { |
| return ret; |
| } |
| resp = (bcm_iov_buf_t *)buf; |
| data = (wl_adps_params_v1_t *)resp->data; |
| |
| return data->mode; |
| } |
| |
| /* |
| * Return value: |
| * Disabled: 0 |
| * Enabled: bitmap of WLC_BAND_2G or WLC_BAND_5G when ADPS is enabled at each BAND |
| * |
| */ |
| int |
| wl_adps_enabled(struct bcm_cfg80211 *cfg, struct net_device *ndev) |
| { |
| uint8 i; |
| int mode; |
| int ret = 0; |
| |
| for (i = 1; i <= MAX_BANDS; i++) { |
| mode = wl_adps_get_mode(ndev, i); |
| if (mode > 0) { |
| ret |= (1 << i); |
| } |
| } |
| |
| return ret; |
| } |
| |
| int |
| wl_adps_set_suspend(struct bcm_cfg80211 *cfg, struct net_device *ndev, uint8 suspend) |
| { |
| int ret = BCME_OK; |
| |
| int buf_len; |
| bcm_iov_buf_t *iov_buf = NULL; |
| wl_adps_suspend_v1_t *data = NULL; |
| |
| buf_len = OFFSETOF(bcm_iov_buf_t, data) + sizeof(*data); |
| iov_buf = MALLOCZ(cfg->osh, buf_len); |
| if (iov_buf == NULL) { |
| WL_ERR(("%s - failed to alloc %d bytes for iov_buf\n", |
| __FUNCTION__, buf_len)); |
| ret = BCME_NOMEM; |
| goto exit; |
| } |
| |
| iov_buf->version = WL_ADPS_IOV_VER; |
| iov_buf->len = sizeof(*data); |
| iov_buf->id = WL_ADPS_IOV_SUSPEND; |
| |
| data = (wl_adps_suspend_v1_t *)iov_buf->data; |
| data->version = ADPS_SUB_IOV_VERSION_1; |
| data->length = sizeof(*data); |
| data->suspend = suspend; |
| |
| ret = wldev_iovar_setbuf(ndev, "adps", (char *)iov_buf, buf_len, |
| cfg->ioctl_buf, WLC_IOCTL_SMLEN, NULL); |
| if (ret < 0) { |
| if (ret == BCME_UNSUPPORTED) { |
| WL_ERR(("%s - adps suspend is not supported\n", __FUNCTION__)); |
| ret = BCME_OK; |
| } |
| else { |
| WL_ERR(("%s - fail to set adps suspend %d (%d)\n", |
| __FUNCTION__, suspend, ret)); |
| } |
| goto exit; |
| } |
| WL_INFORM_MEM(("[%s] Detect BAD AP and Suspend ADPS\n", ndev->name)); |
| exit: |
| if (iov_buf) { |
| MFREE(cfg->osh, iov_buf, buf_len); |
| } |
| return ret; |
| } |
| |
| bool |
| wl_adps_bad_ap_check(struct bcm_cfg80211 *cfg, const struct ether_addr *bssid) |
| { |
| #if !defined(DHD_ADPS_BAM_EXPORT) |
| /* Update Bad AP list */ |
| if (list_empty(&cfg->bad_ap_mngr.list)) { |
| wl_bad_ap_mngr_fread(cfg, WL_BAD_AP_INFO_FILE_PATH); |
| } |
| #endif /* DHD_ADPS_BAM_EXPORT */ |
| |
| if (wl_bad_ap_mngr_find(&cfg->bad_ap_mngr, bssid) != NULL) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| s32 |
| wl_adps_event_handler(struct bcm_cfg80211 *cfg, bcm_struct_cfgdev *cfgdev, |
| const wl_event_msg_t *e, void *data) |
| { |
| int ret = BCME_OK; |
| wl_event_adps_t *event_data = (wl_event_adps_t *)data; |
| |
| switch (event_data->type) { |
| case WL_E_TYPE_ADPS_BAD_AP: |
| ret = wl_event_adps_bad_ap_mngr(cfg, data); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |