mirror of
https://github.com/RinCat/RTL88x2BU-Linux-Driver.git
synced 2024-12-27 02:21:35 +00:00
787 lines
17 KiB
C
787 lines
17 KiB
C
|
/******************************************************************************
|
||
|
*
|
||
|
* Copyright(c) 2007 - 2017 Realtek Corporation.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify it
|
||
|
* under the terms of version 2 of the GNU General Public License as
|
||
|
* published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
|
* more details.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
#define _RTW_WDS_C_
|
||
|
|
||
|
#include <drv_types.h>
|
||
|
|
||
|
#if defined(CONFIG_RTW_WDS)
|
||
|
#include <linux/jhash.h>
|
||
|
|
||
|
#if defined(CONFIG_AP_MODE)
|
||
|
|
||
|
#ifdef PLATFORM_LINUX
|
||
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0))
|
||
|
static void rtw_wpath_free_rcu(struct rtw_wds_path *wpath)
|
||
|
{
|
||
|
kfree_rcu(wpath, rcu);
|
||
|
rtw_mstat_update(MSTAT_TYPE_PHY, MSTAT_FREE, sizeof(struct rtw_wds_path));
|
||
|
}
|
||
|
#else
|
||
|
static void rtw_wpath_free_rcu_callback(rtw_rcu_head *head)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath;
|
||
|
|
||
|
wpath = container_of(head, struct rtw_wds_path, rcu);
|
||
|
rtw_mfree(wpath, sizeof(struct rtw_wds_path));
|
||
|
}
|
||
|
|
||
|
static void rtw_wpath_free_rcu(struct rtw_wds_path *wpath)
|
||
|
{
|
||
|
call_rcu(&wpath->rcu, rtw_wpath_free_rcu_callback);
|
||
|
}
|
||
|
#endif
|
||
|
#endif /* PLATFORM_LINUX */
|
||
|
|
||
|
static void rtw_wds_path_free_rcu(struct rtw_wds_table *tbl, struct rtw_wds_path *wpath);
|
||
|
|
||
|
static u32 rtw_wds_table_hash(const void *addr, u32 len, u32 seed)
|
||
|
{
|
||
|
/* Use last four bytes of hw addr as hash index */
|
||
|
return jhash_1word(*(u32 *)(addr+2), seed);
|
||
|
}
|
||
|
|
||
|
static const rtw_rhashtable_params rtw_wds_rht_params = {
|
||
|
.nelem_hint = 2,
|
||
|
.automatic_shrinking = true,
|
||
|
.key_len = ETH_ALEN,
|
||
|
.key_offset = offsetof(struct rtw_wds_path, dst),
|
||
|
.head_offset = offsetof(struct rtw_wds_path, rhash),
|
||
|
.hashfn = rtw_wds_table_hash,
|
||
|
};
|
||
|
|
||
|
static void rtw_wds_path_rht_free(void *ptr, void *tblptr)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath = ptr;
|
||
|
struct rtw_wds_table *tbl = tblptr;
|
||
|
|
||
|
rtw_wds_path_free_rcu(tbl, wpath);
|
||
|
}
|
||
|
|
||
|
static struct rtw_wds_table *rtw_wds_table_alloc(void)
|
||
|
{
|
||
|
struct rtw_wds_table *newtbl;
|
||
|
|
||
|
newtbl = rtw_malloc(sizeof(struct rtw_wds_table));
|
||
|
if (!newtbl)
|
||
|
return NULL;
|
||
|
|
||
|
return newtbl;
|
||
|
}
|
||
|
|
||
|
static void rtw_wds_table_free(struct rtw_wds_table *tbl)
|
||
|
{
|
||
|
rtw_rhashtable_free_and_destroy(&tbl->rhead,
|
||
|
rtw_wds_path_rht_free, tbl);
|
||
|
rtw_mfree(tbl, sizeof(struct rtw_wds_table));
|
||
|
}
|
||
|
|
||
|
void rtw_wds_path_assign_nexthop(struct rtw_wds_path *wpath, struct sta_info *sta)
|
||
|
{
|
||
|
rtw_rcu_assign_pointer(wpath->next_hop, sta);
|
||
|
}
|
||
|
|
||
|
static struct rtw_wds_path *rtw_wpath_lookup(struct rtw_wds_table *tbl, const u8 *dst)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath;
|
||
|
|
||
|
if (!tbl)
|
||
|
return NULL;
|
||
|
|
||
|
wpath = rtw_rhashtable_lookup_fast(&tbl->rhead, dst, rtw_wds_rht_params);
|
||
|
|
||
|
return wpath;
|
||
|
}
|
||
|
|
||
|
struct rtw_wds_path *rtw_wds_path_lookup(_adapter *adapter, const u8 *dst)
|
||
|
{
|
||
|
return rtw_wpath_lookup(adapter->wds_paths, dst);
|
||
|
}
|
||
|
|
||
|
static struct rtw_wds_path *
|
||
|
__rtw_wds_path_lookup_by_idx(struct rtw_wds_table *tbl, int idx)
|
||
|
{
|
||
|
int i = 0, ret;
|
||
|
struct rtw_wds_path *wpath = NULL;
|
||
|
rtw_rhashtable_iter iter;
|
||
|
|
||
|
if (!tbl)
|
||
|
return NULL;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_enter(&tbl->rhead, &iter);
|
||
|
if (ret)
|
||
|
return NULL;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_start(&iter);
|
||
|
if (ret && ret != -EAGAIN)
|
||
|
goto err;
|
||
|
|
||
|
while ((wpath = rtw_rhashtable_walk_next(&iter))) {
|
||
|
if (IS_ERR(wpath) && PTR_ERR(wpath) == -EAGAIN)
|
||
|
continue;
|
||
|
if (IS_ERR(wpath))
|
||
|
break;
|
||
|
if (i++ == idx)
|
||
|
break;
|
||
|
}
|
||
|
err:
|
||
|
rtw_rhashtable_walk_stop(&iter);
|
||
|
rtw_rhashtable_walk_exit(&iter);
|
||
|
|
||
|
if (IS_ERR(wpath) || !wpath)
|
||
|
return NULL;
|
||
|
|
||
|
return wpath;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Locking: must be called within a read rcu section.
|
||
|
*/
|
||
|
struct rtw_wds_path *
|
||
|
rtw_wds_path_lookup_by_idx(_adapter *adapter, int idx)
|
||
|
{
|
||
|
return __rtw_wds_path_lookup_by_idx(adapter->wds_paths, idx);
|
||
|
}
|
||
|
|
||
|
void dump_wpath(void *sel, _adapter *adapter)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath;
|
||
|
int idx = 0;
|
||
|
char dst[ETH_ALEN];
|
||
|
char next_hop[ETH_ALEN];
|
||
|
u32 age_ms;
|
||
|
|
||
|
RTW_PRINT_SEL(sel, "num:%d\n", ATOMIC_READ(&adapter->wds_path_num));
|
||
|
RTW_PRINT_SEL(sel, "%-17s %-17s %-6s\n"
|
||
|
, "dst", "next_hop", "age"
|
||
|
);
|
||
|
|
||
|
do {
|
||
|
rtw_rcu_read_lock();
|
||
|
|
||
|
wpath = rtw_wds_path_lookup_by_idx(adapter, idx);
|
||
|
if (wpath) {
|
||
|
_rtw_memcpy(dst, wpath->dst, ETH_ALEN);
|
||
|
_rtw_memcpy(next_hop, wpath->next_hop->cmn.mac_addr, ETH_ALEN);
|
||
|
age_ms = rtw_get_passing_time_ms(wpath->last_update);
|
||
|
}
|
||
|
|
||
|
rtw_rcu_read_unlock();
|
||
|
|
||
|
if (wpath) {
|
||
|
RTW_PRINT_SEL(sel, MAC_FMT" "MAC_FMT" %6u\n"
|
||
|
, MAC_ARG(dst), MAC_ARG(next_hop)
|
||
|
, age_ms < 999999 ? age_ms : 999999
|
||
|
);
|
||
|
}
|
||
|
|
||
|
idx++;
|
||
|
} while (wpath);
|
||
|
}
|
||
|
|
||
|
static
|
||
|
struct rtw_wds_path *rtw_wds_path_new(_adapter *adapter,
|
||
|
const u8 *dst)
|
||
|
{
|
||
|
struct rtw_wds_path *new_wpath;
|
||
|
|
||
|
new_wpath = rtw_zmalloc(sizeof(struct rtw_wds_path));
|
||
|
if (!new_wpath)
|
||
|
return NULL;
|
||
|
|
||
|
new_wpath->adapter = adapter;
|
||
|
_rtw_memcpy(new_wpath->dst, dst, ETH_ALEN);
|
||
|
new_wpath->last_update = rtw_get_current_time();
|
||
|
|
||
|
return new_wpath;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns: 0 on success
|
||
|
*
|
||
|
* State: the initial state of the new path is set to 0
|
||
|
*/
|
||
|
struct rtw_wds_path *rtw_wds_path_add(_adapter *adapter,
|
||
|
const u8 *dst, struct sta_info *next_hop)
|
||
|
{
|
||
|
struct rtw_wds_table *tbl = adapter->wds_paths;
|
||
|
struct rtw_wds_path *wpath, *new_wpath;
|
||
|
int ret;
|
||
|
|
||
|
if (!tbl)
|
||
|
return ERR_PTR(-ENOTSUPP);
|
||
|
|
||
|
if (_rtw_memcmp(dst, adapter_mac_addr(adapter), ETH_ALEN) == _TRUE)
|
||
|
/* never add ourselves as neighbours */
|
||
|
return ERR_PTR(-ENOTSUPP);
|
||
|
|
||
|
if (IS_MCAST(dst))
|
||
|
return ERR_PTR(-ENOTSUPP);
|
||
|
|
||
|
if (ATOMIC_INC_UNLESS(&adapter->wds_path_num, RTW_WDS_MAX_PATHS) == 0)
|
||
|
return ERR_PTR(-ENOSPC);
|
||
|
|
||
|
new_wpath = rtw_wds_path_new(adapter, dst);
|
||
|
if (!new_wpath)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
do {
|
||
|
ret = rtw_rhashtable_lookup_insert_fast(&tbl->rhead,
|
||
|
&new_wpath->rhash,
|
||
|
rtw_wds_rht_params);
|
||
|
|
||
|
if (ret == -EEXIST)
|
||
|
wpath = rtw_rhashtable_lookup_fast(&tbl->rhead,
|
||
|
dst,
|
||
|
rtw_wds_rht_params);
|
||
|
|
||
|
} while (unlikely(ret == -EEXIST && !wpath));
|
||
|
|
||
|
if (ret && ret != -EEXIST)
|
||
|
return ERR_PTR(ret);
|
||
|
|
||
|
/* At this point either new_wpath was added, or we found a
|
||
|
* matching entry already in the table; in the latter case
|
||
|
* free the unnecessary new entry.
|
||
|
*/
|
||
|
if (ret == -EEXIST) {
|
||
|
rtw_mfree(new_wpath, sizeof(struct rtw_wds_path));
|
||
|
new_wpath = wpath;
|
||
|
}
|
||
|
rtw_wds_path_assign_nexthop(new_wpath, next_hop);
|
||
|
|
||
|
return new_wpath;
|
||
|
}
|
||
|
|
||
|
static void rtw_wds_path_free_rcu(struct rtw_wds_table *tbl,
|
||
|
struct rtw_wds_path *wpath)
|
||
|
{
|
||
|
_adapter *adapter = wpath->adapter;
|
||
|
|
||
|
ATOMIC_DEC(&adapter->wds_path_num);
|
||
|
|
||
|
rtw_wpath_free_rcu(wpath);
|
||
|
}
|
||
|
|
||
|
static void __rtw_wds_path_del(struct rtw_wds_table *tbl, struct rtw_wds_path *wpath)
|
||
|
{
|
||
|
rtw_rhashtable_remove_fast(&tbl->rhead, &wpath->rhash, rtw_wds_rht_params);
|
||
|
rtw_wds_path_free_rcu(tbl, wpath);
|
||
|
}
|
||
|
|
||
|
void rtw_wds_path_flush_by_nexthop(struct sta_info *sta)
|
||
|
{
|
||
|
_adapter *adapter = sta->padapter;
|
||
|
struct rtw_wds_table *tbl = adapter->wds_paths;
|
||
|
struct rtw_wds_path *wpath;
|
||
|
rtw_rhashtable_iter iter;
|
||
|
int ret;
|
||
|
|
||
|
if (!tbl)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_enter(&tbl->rhead, &iter);
|
||
|
if (ret)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_start(&iter);
|
||
|
if (ret && ret != -EAGAIN)
|
||
|
goto out;
|
||
|
|
||
|
while ((wpath = rtw_rhashtable_walk_next(&iter))) {
|
||
|
if (IS_ERR(wpath) && PTR_ERR(wpath) == -EAGAIN)
|
||
|
continue;
|
||
|
if (IS_ERR(wpath))
|
||
|
break;
|
||
|
|
||
|
if (rtw_rcu_access_pointer(wpath->next_hop) == sta)
|
||
|
__rtw_wds_path_del(tbl, wpath);
|
||
|
}
|
||
|
out:
|
||
|
rtw_rhashtable_walk_stop(&iter);
|
||
|
rtw_rhashtable_walk_exit(&iter);
|
||
|
}
|
||
|
|
||
|
static void rtw_wds_table_flush_by_iface(struct rtw_wds_table *tbl)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath;
|
||
|
rtw_rhashtable_iter iter;
|
||
|
int ret;
|
||
|
|
||
|
if (!tbl)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_enter(&tbl->rhead, &iter);
|
||
|
if (ret)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_start(&iter);
|
||
|
if (ret && ret != -EAGAIN)
|
||
|
goto out;
|
||
|
|
||
|
while ((wpath = rtw_rhashtable_walk_next(&iter))) {
|
||
|
if (IS_ERR(wpath) && PTR_ERR(wpath) == -EAGAIN)
|
||
|
continue;
|
||
|
if (IS_ERR(wpath))
|
||
|
break;
|
||
|
__rtw_wds_path_del(tbl, wpath);
|
||
|
}
|
||
|
out:
|
||
|
rtw_rhashtable_walk_stop(&iter);
|
||
|
rtw_rhashtable_walk_exit(&iter);
|
||
|
}
|
||
|
|
||
|
void rtw_wds_path_flush_by_iface(_adapter *adapter)
|
||
|
{
|
||
|
rtw_wds_table_flush_by_iface(adapter->wds_paths);
|
||
|
}
|
||
|
|
||
|
static int rtw_wds_table_path_del(struct rtw_wds_table *tbl,
|
||
|
const u8 *addr)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath;
|
||
|
|
||
|
if (!tbl)
|
||
|
return -ENXIO;
|
||
|
|
||
|
rtw_rcu_read_lock();
|
||
|
wpath = rtw_rhashtable_lookup_fast(&tbl->rhead, addr, rtw_wds_rht_params);
|
||
|
if (!wpath) {
|
||
|
rtw_rcu_read_unlock();
|
||
|
return -ENXIO;
|
||
|
}
|
||
|
|
||
|
__rtw_wds_path_del(tbl, wpath);
|
||
|
rtw_rcu_read_unlock();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int rtw_wds_path_del(_adapter *adapter, const u8 *addr)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = rtw_wds_table_path_del(adapter->wds_paths, addr);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
int rtw_wds_pathtbl_init(_adapter *adapter)
|
||
|
{
|
||
|
struct rtw_wds_table *tbl_path;
|
||
|
int ret;
|
||
|
|
||
|
tbl_path = rtw_wds_table_alloc();
|
||
|
if (!tbl_path)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
rtw_rhashtable_init(&tbl_path->rhead, &rtw_wds_rht_params);
|
||
|
|
||
|
ATOMIC_SET(&adapter->wds_path_num, 0);
|
||
|
adapter->wds_paths = tbl_path;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static
|
||
|
void rtw_wds_path_tbl_expire(_adapter *adapter,
|
||
|
struct rtw_wds_table *tbl)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath;
|
||
|
rtw_rhashtable_iter iter;
|
||
|
int ret;
|
||
|
|
||
|
if (!tbl)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_enter(&tbl->rhead, &iter);
|
||
|
if (ret)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_start(&iter);
|
||
|
if (ret && ret != -EAGAIN)
|
||
|
goto out;
|
||
|
|
||
|
while ((wpath = rtw_rhashtable_walk_next(&iter))) {
|
||
|
if (IS_ERR(wpath) && PTR_ERR(wpath) == -EAGAIN)
|
||
|
continue;
|
||
|
if (IS_ERR(wpath))
|
||
|
break;
|
||
|
if (rtw_time_after(rtw_get_current_time(), wpath->last_update + RTW_WDS_PATH_EXPIRE))
|
||
|
__rtw_wds_path_del(tbl, wpath);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
rtw_rhashtable_walk_stop(&iter);
|
||
|
rtw_rhashtable_walk_exit(&iter);
|
||
|
}
|
||
|
|
||
|
void rtw_wds_path_expire(_adapter *adapter)
|
||
|
{
|
||
|
rtw_wds_path_tbl_expire(adapter, adapter->wds_paths);
|
||
|
}
|
||
|
|
||
|
void rtw_wds_pathtbl_unregister(_adapter *adapter)
|
||
|
{
|
||
|
if (adapter->wds_paths) {
|
||
|
rtw_wds_table_free(adapter->wds_paths);
|
||
|
adapter->wds_paths = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int rtw_wds_nexthop_lookup(_adapter *adapter, const u8 *da, u8 *ra)
|
||
|
{
|
||
|
struct rtw_wds_path *wpath;
|
||
|
struct sta_info *next_hop;
|
||
|
int err = -ENOENT;
|
||
|
|
||
|
rtw_rcu_read_lock();
|
||
|
wpath = rtw_wds_path_lookup(adapter, da);
|
||
|
|
||
|
if (!wpath)
|
||
|
goto endlookup;
|
||
|
|
||
|
next_hop = rtw_rcu_dereference(wpath->next_hop);
|
||
|
if (next_hop) {
|
||
|
_rtw_memcpy(ra, next_hop->cmn.mac_addr, ETH_ALEN);
|
||
|
err = 0;
|
||
|
}
|
||
|
|
||
|
endlookup:
|
||
|
rtw_rcu_read_unlock();
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
#endif /* defined(CONFIG_AP_MODE) */
|
||
|
|
||
|
/* WDS group adddressed proxy TX record */
|
||
|
struct rtw_wds_gptr {
|
||
|
u8 src[ETH_ALEN];
|
||
|
systime last_update;
|
||
|
rtw_rhash_head rhash;
|
||
|
_adapter *adapter;
|
||
|
rtw_rcu_head rcu;
|
||
|
};
|
||
|
|
||
|
#define RTW_WDS_GPTR_EXPIRE (2 * HZ)
|
||
|
|
||
|
/* Maximum number of gptrs per interface */
|
||
|
#define RTW_WDS_MAX_GPTRS 1024
|
||
|
|
||
|
#ifdef PLATFORM_LINUX
|
||
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0))
|
||
|
static void rtw_wgptr_free_rcu(struct rtw_wds_gptr *wgptr)
|
||
|
{
|
||
|
kfree_rcu(wgptr, rcu);
|
||
|
rtw_mstat_update(MSTAT_TYPE_PHY, MSTAT_FREE, sizeof(struct rtw_wds_gptr));
|
||
|
}
|
||
|
#else
|
||
|
static void rtw_wgptr_free_rcu_callback(rtw_rcu_head *head)
|
||
|
{
|
||
|
struct rtw_wds_gptr *wgptr;
|
||
|
|
||
|
wgptr = container_of(head, struct rtw_wds_gptr, rcu);
|
||
|
rtw_mfree(wgptr, sizeof(struct rtw_wds_gptr));
|
||
|
}
|
||
|
|
||
|
static void rtw_wgptr_free_rcu(struct rtw_wds_gptr *wgptr)
|
||
|
{
|
||
|
call_rcu(&wgptr->rcu, rtw_wgptr_free_rcu_callback);
|
||
|
}
|
||
|
#endif
|
||
|
#endif /* PLATFORM_LINUX */
|
||
|
|
||
|
static void rtw_wds_gptr_free_rcu(struct rtw_wds_gptr_table *tbl, struct rtw_wds_gptr *wgptr)
|
||
|
{
|
||
|
_adapter *adapter = wgptr->adapter;
|
||
|
|
||
|
ATOMIC_DEC(&adapter->wds_gpt_record_num);
|
||
|
|
||
|
rtw_wgptr_free_rcu(wgptr);
|
||
|
}
|
||
|
|
||
|
static u32 rtw_wds_gptr_table_hash(const void *addr, u32 len, u32 seed)
|
||
|
{
|
||
|
/* Use last four bytes of hw addr as hash index */
|
||
|
return jhash_1word(*(u32 *)(addr+2), seed);
|
||
|
}
|
||
|
|
||
|
static const rtw_rhashtable_params rtw_wds_gptr_rht_params = {
|
||
|
.nelem_hint = 2,
|
||
|
.automatic_shrinking = true,
|
||
|
.key_len = ETH_ALEN,
|
||
|
.key_offset = offsetof(struct rtw_wds_gptr, src),
|
||
|
.head_offset = offsetof(struct rtw_wds_gptr, rhash),
|
||
|
.hashfn = rtw_wds_gptr_table_hash,
|
||
|
};
|
||
|
|
||
|
static void rtw_wds_gptr_rht_free(void *ptr, void *tblptr)
|
||
|
{
|
||
|
struct rtw_wds_gptr *wgptr = ptr;
|
||
|
struct rtw_wds_gptr_table *tbl = tblptr;
|
||
|
|
||
|
rtw_wds_gptr_free_rcu(tbl, wgptr);
|
||
|
}
|
||
|
|
||
|
static struct rtw_wds_gptr_table *rtw_wds_gptr_table_alloc(void)
|
||
|
{
|
||
|
struct rtw_wds_gptr_table *newtbl;
|
||
|
|
||
|
newtbl = rtw_malloc(sizeof(struct rtw_wds_gptr_table));
|
||
|
if (!newtbl)
|
||
|
return NULL;
|
||
|
|
||
|
return newtbl;
|
||
|
}
|
||
|
|
||
|
static void rtw_wds_gptr_table_free(struct rtw_wds_gptr_table *tbl)
|
||
|
{
|
||
|
rtw_rhashtable_free_and_destroy(&tbl->rhead,
|
||
|
rtw_wds_gptr_rht_free, tbl);
|
||
|
rtw_mfree(tbl, sizeof(struct rtw_wds_gptr_table));
|
||
|
}
|
||
|
|
||
|
static struct rtw_wds_gptr *rtw_wds_gptr_lookup(_adapter *adapter, const u8 *src)
|
||
|
{
|
||
|
struct rtw_wds_gptr_table *tbl = adapter->wds_gpt_records;
|
||
|
|
||
|
if (!tbl)
|
||
|
return NULL;
|
||
|
|
||
|
return rtw_rhashtable_lookup_fast(&tbl->rhead, src, rtw_wds_gptr_rht_params);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Locking: must be called within a read rcu section.
|
||
|
*/
|
||
|
static struct rtw_wds_gptr *rtw_wds_gptr_lookup_by_idx(_adapter *adapter, int idx)
|
||
|
{
|
||
|
int i = 0, ret;
|
||
|
struct rtw_wds_gptr_table *tbl = adapter->wds_gpt_records;
|
||
|
struct rtw_wds_gptr *wgptr = NULL;
|
||
|
rtw_rhashtable_iter iter;
|
||
|
|
||
|
if (!tbl)
|
||
|
return NULL;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_enter(&tbl->rhead, &iter);
|
||
|
if (ret)
|
||
|
return NULL;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_start(&iter);
|
||
|
if (ret && ret != -EAGAIN)
|
||
|
goto err;
|
||
|
|
||
|
while ((wgptr = rtw_rhashtable_walk_next(&iter))) {
|
||
|
if (IS_ERR(wgptr) && PTR_ERR(wgptr) == -EAGAIN)
|
||
|
continue;
|
||
|
if (IS_ERR(wgptr))
|
||
|
break;
|
||
|
if (i++ == idx)
|
||
|
break;
|
||
|
}
|
||
|
err:
|
||
|
rtw_rhashtable_walk_stop(&iter);
|
||
|
rtw_rhashtable_walk_exit(&iter);
|
||
|
|
||
|
if (IS_ERR(wgptr) || !wgptr)
|
||
|
return NULL;
|
||
|
|
||
|
return wgptr;
|
||
|
}
|
||
|
|
||
|
void dump_wgptr(void *sel, _adapter *adapter)
|
||
|
{
|
||
|
struct rtw_wds_gptr *wgptr;
|
||
|
int idx = 0;
|
||
|
char src[ETH_ALEN];
|
||
|
u32 age_ms;
|
||
|
|
||
|
RTW_PRINT_SEL(sel, "num:%d\n", ATOMIC_READ(&adapter->wds_gpt_record_num));
|
||
|
RTW_PRINT_SEL(sel, "%-17s %-6s\n"
|
||
|
, "src", "age"
|
||
|
);
|
||
|
|
||
|
do {
|
||
|
rtw_rcu_read_lock();
|
||
|
|
||
|
wgptr = rtw_wds_gptr_lookup_by_idx(adapter, idx);
|
||
|
if (wgptr) {
|
||
|
_rtw_memcpy(src, wgptr->src, ETH_ALEN);
|
||
|
age_ms = rtw_get_passing_time_ms(wgptr->last_update);
|
||
|
}
|
||
|
|
||
|
rtw_rcu_read_unlock();
|
||
|
|
||
|
if (wgptr) {
|
||
|
RTW_PRINT_SEL(sel, MAC_FMT" %6u\n"
|
||
|
, MAC_ARG(src)
|
||
|
, age_ms < 999999 ? age_ms : 999999
|
||
|
);
|
||
|
}
|
||
|
|
||
|
idx++;
|
||
|
} while (wgptr);
|
||
|
}
|
||
|
|
||
|
static struct rtw_wds_gptr *rtw_wds_gptr_new(_adapter *adapter, const u8 *src)
|
||
|
{
|
||
|
struct rtw_wds_gptr *new_wgptr;
|
||
|
|
||
|
new_wgptr = rtw_zmalloc(sizeof(struct rtw_wds_gptr));
|
||
|
if (!new_wgptr)
|
||
|
return NULL;
|
||
|
|
||
|
new_wgptr->adapter = adapter;
|
||
|
_rtw_memcpy(new_wgptr->src, src, ETH_ALEN);
|
||
|
new_wgptr->last_update = rtw_get_current_time();
|
||
|
|
||
|
return new_wgptr;
|
||
|
}
|
||
|
|
||
|
static struct rtw_wds_gptr *rtw_wds_gptr_add(_adapter *adapter, const u8 *src)
|
||
|
{
|
||
|
struct rtw_wds_gptr_table *tbl = adapter->wds_gpt_records;
|
||
|
struct rtw_wds_gptr *wgptr, *new_wgptr;
|
||
|
int ret;
|
||
|
|
||
|
if (!tbl)
|
||
|
return ERR_PTR(-ENOTSUPP);
|
||
|
|
||
|
if (ATOMIC_INC_UNLESS(&adapter->wds_gpt_record_num, RTW_WDS_MAX_PATHS) == 0)
|
||
|
return ERR_PTR(-ENOSPC);
|
||
|
|
||
|
new_wgptr = rtw_wds_gptr_new(adapter, src);
|
||
|
if (!new_wgptr)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
do {
|
||
|
ret = rtw_rhashtable_lookup_insert_fast(&tbl->rhead,
|
||
|
&new_wgptr->rhash,
|
||
|
rtw_wds_gptr_rht_params);
|
||
|
|
||
|
if (ret == -EEXIST)
|
||
|
wgptr = rtw_rhashtable_lookup_fast(&tbl->rhead,
|
||
|
src,
|
||
|
rtw_wds_gptr_rht_params);
|
||
|
|
||
|
} while (unlikely(ret == -EEXIST && !wgptr));
|
||
|
|
||
|
if (ret && ret != -EEXIST)
|
||
|
return ERR_PTR(ret);
|
||
|
|
||
|
/* At this point either new_wgptr was added, or we found a
|
||
|
* matching entry already in the table; in the latter case
|
||
|
* free the unnecessary new entry.
|
||
|
*/
|
||
|
if (ret == -EEXIST) {
|
||
|
rtw_mfree(new_wgptr, sizeof(struct rtw_wds_gptr));
|
||
|
new_wgptr = wgptr;
|
||
|
}
|
||
|
|
||
|
return new_wgptr;
|
||
|
}
|
||
|
|
||
|
bool rtw_rx_wds_gptr_check(_adapter *adapter, const u8 *src)
|
||
|
{
|
||
|
struct rtw_wds_gptr *wgptr;
|
||
|
bool ret = 0;
|
||
|
|
||
|
rtw_rcu_read_lock();
|
||
|
|
||
|
wgptr = rtw_wds_gptr_lookup(adapter, src);
|
||
|
if (wgptr)
|
||
|
ret = rtw_time_after(wgptr->last_update + RTW_WDS_GPTR_EXPIRE, rtw_get_current_time());
|
||
|
|
||
|
rtw_rcu_read_unlock();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void rtw_tx_wds_gptr_update(_adapter *adapter, const u8 *src)
|
||
|
{
|
||
|
struct rtw_wds_gptr *wgptr;
|
||
|
|
||
|
rtw_rcu_read_lock();
|
||
|
wgptr = rtw_wds_gptr_lookup(adapter, src);
|
||
|
if (!wgptr)
|
||
|
rtw_wds_gptr_add(adapter, src);
|
||
|
else
|
||
|
wgptr->last_update = rtw_get_current_time();
|
||
|
rtw_rcu_read_unlock();
|
||
|
}
|
||
|
|
||
|
static void __rtw_wds_gptr_del(struct rtw_wds_gptr_table *tbl, struct rtw_wds_gptr *wgptr)
|
||
|
{
|
||
|
rtw_rhashtable_remove_fast(&tbl->rhead, &wgptr->rhash, rtw_wds_gptr_rht_params);
|
||
|
rtw_wds_gptr_free_rcu(tbl, wgptr);
|
||
|
}
|
||
|
|
||
|
void rtw_wds_gptr_expire(_adapter *adapter)
|
||
|
{
|
||
|
struct rtw_wds_gptr_table *tbl = adapter->wds_gpt_records;
|
||
|
struct rtw_wds_gptr *wgptr;
|
||
|
rtw_rhashtable_iter iter;
|
||
|
int ret;
|
||
|
|
||
|
if (!tbl)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_enter(&tbl->rhead, &iter);
|
||
|
if (ret)
|
||
|
return;
|
||
|
|
||
|
ret = rtw_rhashtable_walk_start(&iter);
|
||
|
if (ret && ret != -EAGAIN)
|
||
|
goto out;
|
||
|
|
||
|
while ((wgptr = rtw_rhashtable_walk_next(&iter))) {
|
||
|
if (IS_ERR(wgptr) && PTR_ERR(wgptr) == -EAGAIN)
|
||
|
continue;
|
||
|
if (IS_ERR(wgptr))
|
||
|
break;
|
||
|
if (rtw_time_after(rtw_get_current_time(), wgptr->last_update + RTW_WDS_GPTR_EXPIRE))
|
||
|
__rtw_wds_gptr_del(tbl, wgptr);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
rtw_rhashtable_walk_stop(&iter);
|
||
|
rtw_rhashtable_walk_exit(&iter);
|
||
|
}
|
||
|
|
||
|
int rtw_wds_gptr_tbl_init(_adapter *adapter)
|
||
|
{
|
||
|
struct rtw_wds_gptr_table *tbl;
|
||
|
int ret;
|
||
|
|
||
|
tbl = rtw_wds_gptr_table_alloc();
|
||
|
if (!tbl)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
rtw_rhashtable_init(&tbl->rhead, &rtw_wds_gptr_rht_params);
|
||
|
|
||
|
ATOMIC_SET(&adapter->wds_gpt_record_num, 0);
|
||
|
adapter->wds_gpt_records = tbl;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void rtw_wds_gptr_tbl_unregister(_adapter *adapter)
|
||
|
{
|
||
|
if (adapter->wds_gpt_records) {
|
||
|
rtw_wds_gptr_table_free(adapter->wds_gpt_records);
|
||
|
adapter->wds_gpt_records = NULL;
|
||
|
}
|
||
|
}
|
||
|
#endif /* defined(CONFIG_RTW_WDS) */
|
||
|
|