android13/external/wifi_driver/aic8800/aic8800_fdrv/rwnx_mu_group.c

660 lines
18 KiB
C

/**
******************************************************************************
*
* @file rwnx_mu_group.c
*
* Copyright (C) RivieraWaves 2016-2019
*
******************************************************************************
*/
#include "rwnx_defs.h"
#include "rwnx_msg_tx.h"
#include "rwnx_events.h"
/**
* rwnx_mu_group_sta_init - Initialize group information for a STA
*
* @sta: Sta to initialize
*/
void rwnx_mu_group_sta_init(struct rwnx_sta *sta,
const struct ieee80211_vht_cap *vht_cap)
{
sta->group_info.map = 0;
sta->group_info.cnt = 0;
sta->group_info.active.next = LIST_POISON1;
sta->group_info.update.next = LIST_POISON1;
sta->group_info.last_update = 0;
sta->group_info.traffic = 0;
sta->group_info.group = 0;
if (!vht_cap ||
!(vht_cap->vht_cap_info & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE)) {
sta->group_info.map = RWNX_SU_GROUP;
}
}
/**
* rwnx_mu_group_sta_del - Remove a sta from all MU group
*
* @rwnx_hw: main driver data
* @sta: STA to remove
*
* Remove one sta from all the MU groups it belongs to.
*/
void rwnx_mu_group_sta_del(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta)
{
struct rwnx_mu_info *mu = &rwnx_hw->mu;
int i, j, group_id;
bool lock_taken;
u64 map;
lock_taken = (down_interruptible(&mu->lock) == 0);
group_sta_for_each(sta, group_id, map) {
struct rwnx_mu_group *group = rwnx_mu_group_from_id(mu, group_id);
for (i = 0; i < CONFIG_USER_MAX; i++) {
if (group->users[i] == sta) {
group->users[i] = NULL;
group->user_cnt--;
/* Don't keep group with only one user */
if (group->user_cnt == 1) {
for (j = 0; j < CONFIG_USER_MAX; j++) {
if (group->users[j]) {
group->users[j]->group_info.cnt--;
group->users[j]->group_info.map &= ~BIT_ULL(group->group_id);
if (group->users[j]->group_info.group == group_id)
group->users[j]->group_info.group = 0;
group->user_cnt--;
break;
}
}
mu->group_cnt--;
trace_mu_group_delete(group->group_id);
} else {
trace_mu_group_update(group);
}
break;
}
}
WARN((i == CONFIG_USER_MAX), "sta %d doesn't belongs to group %d",
sta->sta_idx, group_id);
}
sta->group_info.map = 0;
sta->group_info.cnt = 0;
sta->group_info.traffic = 0;
if (sta->group_info.active.next != LIST_POISON1)
list_del(&sta->group_info.active);
if (sta->group_info.update.next != LIST_POISON1)
list_del(&sta->group_info.update);
if (lock_taken)
up(&mu->lock);
}
/**
* rwnx_mu_group_sta_get_map - Get the list of group a STA belongs to
*
* @sta: pointer to the sta
*
* @return the list of group a STA belongs to as a bitfield
*/
u64 rwnx_mu_group_sta_get_map(struct rwnx_sta *sta)
{
if (sta)
return sta->group_info.map;
return 0;
}
/**
* rwnx_mu_group_sta_get_pos - Get sta position in a group
*
* @rwnx_hw: main driver data
* @sta: pointer to the sta
* @group_id: Group id
*
* @return the positon of @sta in group @group_id or -1 if the sta
* doesn't belongs to the group (or group id is invalid)
*/
int rwnx_mu_group_sta_get_pos(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
int group_id)
{
struct rwnx_mu_group *group;
int i;
group = rwnx_mu_group_from_id(&rwnx_hw->mu, group_id);
if (!group)
return -1;
for (i = 0; i < CONFIG_USER_MAX; i++) {
if (group->users[i] == sta)
return i;
}
WARN(1, "sta %d doesn't belongs to group %d",
sta->sta_idx, group_id);
return -1;
}
/**
* rwnx_mu_group_move_head - Move (or add) one element at the top of a list
*
* @list: list pointer
* @elem: element to move (or add) at the top of @list
*
*/
static inline
void rwnx_mu_group_move_head(struct list_head *list, struct list_head *elem)
{
if (elem->next != LIST_POISON1) {
__list_del_entry(elem);
}
list_add(elem, list);
}
/**
* rwnx_mu_group_remove_users - Remove all the users of a group
*
* @mu: pointer on MU info
* @group: pointer on group to remove users from
*
* Loop over all users one one group and remove this group from their
* map (and count).
* Each users is also added to the update_sta list, so that group info
* will be resent to fw for this user.
*/
static inline
void rwnx_mu_group_remove_users(struct rwnx_mu_info *mu,
struct rwnx_mu_group *group)
{
struct rwnx_sta *sta;
int i, group_id = group->group_id;
for (i = 0; i < CONFIG_USER_MAX; i++) {
if (group->users[i]) {
sta = group->users[i];
group->users[i] = NULL;
sta->group_info.cnt--;
sta->group_info.map &= ~BIT_ULL(group_id);
rwnx_mu_group_move_head(&mu->update_sta,
&sta->group_info.update);
}
}
if (group->user_cnt)
mu->group_cnt--;
group->user_cnt = 0;
trace_mu_group_delete(group_id);
}
/**
* rwnx_mu_group_add_users - Add users to a group
*
* @mu: pointer on MU info
* @group: pointer on group to add users in
* @nb_user: number of users to ad
* @users: table of user to add
*
* Add @nb_users to @group (which may already have users)
* Each new users is added to the first free position.
* It is assume that @group has at least @nb_user free position. If it is not
* case it only add the number of users needed to complete the group.
* Each users (effectively added to @group) is also added to the update_sta
* list, so that group info will be resent to fw for this user.
*/
static inline
void rwnx_mu_group_add_users(struct rwnx_mu_info *mu,
struct rwnx_mu_group *group,
int nb_user, struct rwnx_sta **users)
{
int i, j, group_id = group->group_id;
if (!group->user_cnt)
mu->group_cnt++;
j = 0;
for (i = 0; i < nb_user ; i++) {
for (; j < CONFIG_USER_MAX ; j++) {
if (group->users[j] == NULL) {
group->users[j] = users[i];
users[i]->group_info.cnt++;
users[i]->group_info.map |= BIT_ULL(group_id);
rwnx_mu_group_move_head(&(mu->update_sta),
&(users[i]->group_info.update));
group->user_cnt++;
j++;
break;
}
WARN(j == (CONFIG_USER_MAX - 1),
"Too many user for group %d (nb_user=%d)",
group_id, group->user_cnt + nb_user - i);
}
}
trace_mu_group_update(group);
}
/**
* rwnx_mu_group_create_one - create on group with a specific group of user
*
* @mu: pointer on MU info
* @nb_user: number of user to include in the group (<= CONFIG_USER_MAX)
* @users: table of users
*
* Try to create a new group with a specific group of users.
* 1- First it checks if a group containing all this users already exists.
*
* 2- Then it checks if it is possible to complete a group which already
* contains at least one user.
*
* 3- Finally it create a new group. To do so, it take take the last group of
* the active_groups list, remove all its current users and add the new ones
*
* In all cases, the group selected is moved at the top of the active_groups
* list
*
* @return 1 if a new group has been created and 0 otherwise
*/
static
int rwnx_mu_group_create_one(struct rwnx_mu_info *mu, int nb_user,
struct rwnx_sta **users, int *nb_group_left)
{
int i, group_id;
struct rwnx_mu_group *group;
u64 group_match;
u64 group_avail;
group_match = users[0]->group_info.map;
group_avail = users[0]->group_info.map;
for (i = 1; i < nb_user ; i++) {
group_match &= users[i]->group_info.map;
group_avail |= users[i]->group_info.map;
}
if (group_match) {
/* a group (or more) with all the users already exist */
group_id = RWNX_GET_FIRST_GROUP_ID(group_match);
group = rwnx_mu_group_from_id(mu, group_id);
rwnx_mu_group_move_head(&mu->active_groups, &group->list);
return 0;
}
#if CONFIG_USER_MAX > 2
if (group_avail) {
/* check if we can complete a group */
struct rwnx_sta *users2[CONFIG_USER_MAX];
int nb_user2;
group_for_each(group_id, group_avail) {
group = rwnx_mu_group_from_id(mu, group_id);
if (group->user_cnt == CONFIG_USER_MAX)
continue;
nb_user2 = 0;
for (i = 0; i < nb_user ; i++) {
if (!(users[i]->group_info.map & BIT_ULL(group_id))) {
users2[nb_user2] = users[i];
nb_user2++;
}
}
if ((group->user_cnt + nb_user2) <= CONFIG_USER_MAX) {
rwnx_mu_group_add_users(mu, group, nb_user2, users2);
rwnx_mu_group_move_head(&mu->active_groups, &group->list);
return 0;
}
}
}
#endif /* CONFIG_USER_MAX > 2*/
/* create a new group */
group = list_last_entry(&mu->active_groups, struct rwnx_mu_group, list);
rwnx_mu_group_remove_users(mu, group);
rwnx_mu_group_add_users(mu, group, nb_user, users);
rwnx_mu_group_move_head(&mu->active_groups, &group->list);
(*nb_group_left)--;
return 1;
}
/**
* rwnx_mu_group_create - Create new groups containing one specific sta
*
* @mu: pointer on MU info
* @sta: sta to add in each group
* @nb_group_left: maximum number to new group allowed. (updated on exit)
*
* This will try to create "all the possible" group with a specific sta being
* a member of all these group.
* The function simply loops over the @active_sta list (starting from @sta).
* When it has (CONFIG_USER_MAX - 1) users it try to create a new group with
* these users (plus @sta).
* Loops end when there is no more users, or no more new group is allowed
*
*/
static
void rwnx_mu_group_create(struct rwnx_mu_info *mu, struct rwnx_sta *sta,
int *nb_group_left)
{
struct rwnx_sta *user_sta = sta;
struct rwnx_sta *users[CONFIG_USER_MAX];
int nb_user = 1;
users[0] = sta;
while (*nb_group_left) {
list_for_each_entry_continue(user_sta, &mu->active_sta, group_info.active) {
users[nb_user] = user_sta;
if (++nb_user == CONFIG_USER_MAX) {
break;
}
}
if (nb_user > 1) {
if (rwnx_mu_group_create_one(mu, nb_user, users, nb_group_left))
(*nb_group_left)--;
if (nb_user < CONFIG_USER_MAX)
break;
else
nb_user = 1;
} else
break;
}
}
/**
* rwnx_mu_group_work - process function of the "group_work"
*
* The work is scheduled when several sta (MU beamformee capable) are active.
* When called, the @active_sta contains the list of the active sta (starting
* from the most recent one), and @active_groups is the list of all possible
* groups ordered so that the first one is the most recently used.
*
* This function will create new groups, starting from group containing the
* most "active" sta.
* For example if the list of sta is :
* sta8 -> sta3 -> sta4 -> sta7 -> sta1
* and the number of user per group is 3, it will create grooups :
* - sta8 / sta3 / sta4
* - sta8 / sta7 / sta1
* - sta3 / sta4 / sta7
* - sta3 / sta1
* - sta4 / sta7 / sta1
* - sta7 / sta1
*
* To create new group, the least used group are first selected.
* It is only allowed to create NX_MU_GROUP_MAX per iteration.
*
* Once groups have been updated, mu group information is update to the fw.
* To do so it use the @update_sta list to know which sta has been affected.
* As it is necessary to wait for fw confirmation before using this new group
* MU is temporarily disabled during group update
*
* Work is then rescheduled.
*
* At the end of the function, both @active_sta and @update_sta list are empty.
*
* Note:
* - This is still a WIP, and will require more tuning
* - not all combinations are created, to avoid to much processing.
* - reschedule delay should be adaptative
*/
void rwnx_mu_group_work(struct work_struct *ws)
{
struct delayed_work *dw = container_of(ws, struct delayed_work, work);
struct rwnx_mu_info *mu = container_of(dw, struct rwnx_mu_info, group_work);
struct rwnx_hw *rwnx_hw = container_of(mu, struct rwnx_hw, mu);
struct rwnx_sta *sta, *next;
int nb_group_left = NX_MU_GROUP_MAX;
if (WARN(!rwnx_hw->mod_params->mutx,
"In group formation work, but mutx disabled"))
return;
if (down_interruptible(&mu->lock) != 0)
return;
mu->update_count++;
if (!mu->update_count)
mu->update_count++;
list_for_each_entry_safe(sta, next, &mu->active_sta, group_info.active) {
if (nb_group_left)
rwnx_mu_group_create(mu, sta, &nb_group_left);
sta->group_info.last_update = mu->update_count;
list_del(&sta->group_info.active);
}
if (!list_empty(&mu->update_sta)) {
list_for_each_entry_safe(sta, next, &mu->update_sta, group_info.update) {
rwnx_send_mu_group_update_req(rwnx_hw, sta);
list_del(&sta->group_info.update);
}
}
mu->next_group_select = jiffies;
rwnx_mu_group_sta_select(rwnx_hw);
up(&mu->lock);
return;
}
/**
* rwnx_mu_group_init - Initialize MU groups
*
* @rwnx_hw: main driver data
*
* Initialize all MU group
*/
void rwnx_mu_group_init(struct rwnx_hw *rwnx_hw)
{
struct rwnx_mu_info *mu = &rwnx_hw->mu;
int i;
INIT_LIST_HEAD(&mu->active_groups);
INIT_LIST_HEAD(&mu->active_sta);
INIT_LIST_HEAD(&mu->update_sta);
for (i = 0; i < NX_MU_GROUP_MAX; i++) {
int j;
mu->groups[i].user_cnt = 0;
mu->groups[i].group_id = i + 1;
for (j = 0; j < CONFIG_USER_MAX; j++) {
mu->groups[i].users[j] = NULL;
}
list_add(&mu->groups[i].list, &mu->active_groups);
}
mu->update_count = 1;
mu->group_cnt = 0;
mu->next_group_select = jiffies;
INIT_DELAYED_WORK(&mu->group_work, rwnx_mu_group_work);
sema_init(&mu->lock, 1);
}
/**
* rwnx_mu_set_active_sta - mark a STA as active
*
* @rwnx_hw: main driver data
* @sta: pointer to the sta
* @traffic: Number of buffers to add in the sta's traffic counter
*
* If @sta is MU beamformee capable (and MU-MIMO tx is enabled) move the
* sta at the top of the @active_sta list.
* It also schedule the group_work if not already scheduled and the list
* contains more than one sta.
*
* If a STA was already in the list during the last group update
* (i.e. sta->group_info.last_update == mu->update_count) it is not added
* back to the list until a sta that wasn't active during the last update is
* added. This is to avoid scheduling group update with a list of sta that
* were all already in the list during previous update.
*
* It is called with mu->lock taken.
*/
void rwnx_mu_set_active_sta(struct rwnx_hw *rwnx_hw, struct rwnx_sta *sta,
int traffic)
{
struct rwnx_mu_info *mu = &rwnx_hw->mu;
if (!sta || (sta->group_info.map & RWNX_SU_GROUP))
return;
sta->group_info.traffic += traffic;
if ((sta->group_info.last_update != mu->update_count) ||
!list_empty(&mu->active_sta)) {
rwnx_mu_group_move_head(&mu->active_sta, &sta->group_info.active);
if (!delayed_work_pending(&mu->group_work) &&
!list_is_singular(&mu->active_sta)) {
schedule_delayed_work(&mu->group_work,
msecs_to_jiffies(RWNX_MU_GROUP_INTERVAL));
}
}
}
/**
* rwnx_mu_set_active_group - mark a MU group as active
*
* @rwnx_hw: main driver data
* @group_id: Group id
*
* move a group at the top of the @active_groups list
*/
void rwnx_mu_set_active_group(struct rwnx_hw *rwnx_hw, int group_id)
{
struct rwnx_mu_info *mu = &rwnx_hw->mu;
struct rwnx_mu_group *group = rwnx_mu_group_from_id(mu, group_id);
rwnx_mu_group_move_head(&mu->active_groups, &group->list);
}
/**
* rwnx_mu_group_sta_select - Select the best group for MU stas
*
* @rwnx_hw: main driver data
*
* For each MU capable client of AP interfaces this function tries to select
* the best group to use.
*
* In first pass, gather information from all stations to form statistics
* for each group for the previous @RWNX_MU_GROUP_SELECT_INTERVAL interval:
* - number of buffers transmitted
* - number of user
*
* Then groups with more than 2 active users, are assigned after being ordered
* by traffic :
* - group with highest traffic is selected: set this group for all its users
* - update nb_users for all others group (as one sta may be in several groups)
* - select the next group that have still mor than 2 users and assign it.
* - continue until all group are processed
*
*/
void rwnx_mu_group_sta_select(struct rwnx_hw *rwnx_hw)
{
struct rwnx_mu_info *mu = &rwnx_hw->mu;
int nb_users[NX_MU_GROUP_MAX + 1];
int traffic[NX_MU_GROUP_MAX + 1];
int order[NX_MU_GROUP_MAX + 1];
struct rwnx_sta *sta;
struct rwnx_vif *vif;
struct list_head *head;
u64 map;
int i, j, update, group_id, tmp, cnt = 0;
if (!mu->group_cnt || time_before(jiffies, mu->next_group_select))
return;
list_for_each_entry(vif, &rwnx_hw->vifs, list) {
if (RWNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP)
continue;
#ifdef CONFIG_RWNX_FULLMAC
head = &vif->ap.sta_list;
#else
head = &vif->stations;
#endif /* CONFIG_RWNX_FULLMAC */
memset(nb_users, 0, sizeof(nb_users));
memset(traffic, 0, sizeof(traffic));
list_for_each_entry(sta, head, list) {
int sta_traffic = sta->group_info.traffic;
/* reset statistics for next selection */
sta->group_info.traffic = 0;
if (sta->group_info.group)
trace_mu_group_selection(sta, 0);
sta->group_info.group = 0;
if (sta->group_info.cnt == 0 ||
sta_traffic < RWNX_MU_GROUP_MIN_TRAFFIC)
continue;
group_sta_for_each(sta, group_id, map) {
nb_users[group_id]++;
traffic[group_id] += sta_traffic;
/* list group with 2 users or more */
if (nb_users[group_id] == 2)
order[cnt++] = group_id;
}
}
/* reorder list of group with more that 2 users */
update = 1;
while (update) {
update = 0;
for (i = 0; i < cnt - 1; i++) {
if (traffic[order[i]] < traffic[order[i + 1]]) {
tmp = order[i];
order[i] = order[i + 1];
order[i + 1] = tmp;
update = 1;
}
}
}
/* now assign group in traffic order */
for (i = 0; i < cnt; i++) {
struct rwnx_mu_group *group;
group_id = order[i];
if (nb_users[group_id] < 2)
continue;
group = rwnx_mu_group_from_id(mu, group_id);
for (j = 0; j < CONFIG_USER_MAX; j++) {
if (group->users[j]) {
trace_mu_group_selection(group->users[j], group_id);
group->users[j]->group_info.group = group_id;
group_sta_for_each(group->users[j], tmp, map) {
if (group_id != tmp)
nb_users[tmp]--;
}
}
}
}
}
mu->next_group_select = jiffies +
msecs_to_jiffies(RWNX_MU_GROUP_SELECT_INTERVAL);
mu->next_group_select |= 1;
}