--- a/net80211/ieee80211_scan.c +++ b/net80211/ieee80211_scan.c @@ -97,6 +97,123 @@ struct scan_state { static void scan_restart_pwrsav(unsigned long); static void scan_next(unsigned long); +spinlock_t channel_lock = SPIN_LOCK_UNLOCKED; +static LIST_HEAD(channels_inuse); + +struct channel_inuse { + struct list_head list; + struct ieee80211com *ic; + u16 freq; + u8 bw; +}; + +static inline u32 +get_signal(u8 bw, u8 distance) +{ + u32 v; + + /* signal = 1 - (distance / bw)^2 [scale: 100] */ + v = 100 * distance / bw; + v = (100 - ((v * v) / 100)); + return v; +} + +static u32 +get_overlap(u16 f1, u16 f2, u8 b1, u8 b2) +{ + u32 v; + u16 d, c; + + /* add offsets for sidechannel interference */ + b1 += (b1 / 5); + b2 += (b2 / 5); + + /* use only one direction */ + b1 /= 2; + b2 /= 2; + + if (f1 + b1 < f2 - b2) + return 0; + + d = f2 - f1; + c = d * b1 / (b1 + b2); + v = get_signal(b1, c); + + return v * v / 100; +} + +static u8 +get_channel_bw(struct ieee80211_channel *c) +{ + switch(c->ic_flags & ( + IEEE80211_CHAN_HALF | + IEEE80211_CHAN_QUARTER | + IEEE80211_CHAN_TURBO | + IEEE80211_CHAN_STURBO)) { + case IEEE80211_CHAN_QUARTER: + return 5; + case IEEE80211_CHAN_HALF: + return 10; + case IEEE80211_CHAN_TURBO: + case IEEE80211_CHAN_STURBO: + return 40; + default: + return 20; + } +} + +/* must be called with channel_lock held */ +u32 +ieee80211_scan_get_bias(struct ieee80211_channel *c) +{ + struct channel_inuse *ch; + u8 bw = get_channel_bw(c); + u32 bias = 0; + + list_for_each_entry(ch, &channels_inuse, list) { + if (ch->freq == c->ic_freq) { + bias += 50; + continue; + } + if (c->ic_freq < ch->freq) + bias += get_overlap(c->ic_freq, ch->freq, bw, ch->bw); + else + bias += get_overlap(ch->freq, c->ic_freq, ch->bw, bw); + } + return bias; +} +EXPORT_SYMBOL(ieee80211_scan_get_bias); + +/* must be called with channel_lock held */ +void +ieee80211_scan_set_bss_channel(struct ieee80211com *ic, struct ieee80211_channel *c) +{ + unsigned long flags; + struct channel_inuse *ch; + + list_for_each_entry(ch, &channels_inuse, list) { + if (ch->ic == ic) + goto found; + } + ch = NULL; +found: + if (c && (c != IEEE80211_CHAN_ANYC)) { + if (!ch) { + ch = kmalloc(sizeof(struct channel_inuse), GFP_ATOMIC); + ch->ic = ic; + INIT_LIST_HEAD(&ch->list); + list_add(&ch->list, &channels_inuse); + } + ch->freq = c->ic_freq; + ch->bw = get_channel_bw(c); + } else if (ch) { + list_del(&ch->list); + kfree(ch); + } +} +EXPORT_SYMBOL(ieee80211_scan_set_bss_channel); + + void ieee80211_scan_attach(struct ieee80211com *ic) { @@ -1169,7 +1286,7 @@ ieee80211_scan_dfs_action(struct ieee802 IEEE80211_RADAR_CHANCHANGE_TBTT_COUNT; ic->ic_flags |= IEEE80211_F_CHANSWITCH; } else { - + unsigned long flags; IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, "%s: directly switching to channel " "%3d (%4d MHz)\n", __func__, @@ -1180,6 +1297,9 @@ ieee80211_scan_dfs_action(struct ieee802 * change the channel here. */ change_channel(ic, new_channel); ic->ic_bsschan = new_channel; + spin_lock_irqsave(&channel_lock, flags); + ieee80211_scan_set_bss_channel(ic, ic->ic_bsschan); + spin_unlock_irqrestore(&channel_lock, flags); if (vap->iv_bss) vap->iv_bss->ni_chan = new_channel; } --- a/net80211/ieee80211_scan.h +++ b/net80211/ieee80211_scan.h @@ -35,6 +35,7 @@ #define IEEE80211_SCAN_MAX IEEE80211_CHAN_MAX +extern spinlock_t channel_lock; struct ieee80211_scanner; struct ieee80211_scan_entry; @@ -116,6 +117,8 @@ void ieee80211_scan_flush(struct ieee802 struct ieee80211_scan_entry; typedef int ieee80211_scan_iter_func(void *, const struct ieee80211_scan_entry *); int ieee80211_scan_iterate(struct ieee80211com *, ieee80211_scan_iter_func *, void *); +u32 ieee80211_scan_get_bias(struct ieee80211_channel *c); +void ieee80211_scan_set_bss_channel(struct ieee80211com *ic, struct ieee80211_channel *c); /* * Parameters supplied when adding/updating an entry in a --- a/net80211/ieee80211.c +++ b/net80211/ieee80211.c @@ -373,8 +373,16 @@ void ieee80211_ifdetach(struct ieee80211com *ic) { struct ieee80211vap *vap; + unsigned long flags; int count; + /* mark the channel as no longer in use */ + ic->ic_bsschan = IEEE80211_CHAN_ANYC; + spin_lock_irqsave(&channel_lock, flags); + ieee80211_scan_set_bss_channel(ic, ic->ic_bsschan); + spin_unlock_irqrestore(&channel_lock, flags); + + /* bring down all vaps */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { ieee80211_stop(vap->iv_dev); --- a/net80211/ieee80211_input.c +++ b/net80211/ieee80211_input.c @@ -2775,6 +2775,7 @@ static void ieee80211_doth_switch_channel(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; + unsigned long flags; IEEE80211_DPRINTF(vap, IEEE80211_MSG_DOTH, "%s: Channel switch to %3d (%4d MHz) NOW!\n", @@ -2797,6 +2798,9 @@ ieee80211_doth_switch_channel(struct iee ic->ic_curchan = ic->ic_bsschan = vap->iv_csa_chan; ic->ic_set_channel(ic); + spin_lock_irqsave(&channel_lock, flags); + ieee80211_scan_set_bss_channel(ic, ic->ic_bsschan); + spin_unlock_irqrestore(&channel_lock, flags); } static void --- a/net80211/ieee80211_node.c +++ b/net80211/ieee80211_node.c @@ -308,6 +308,7 @@ ieee80211_create_ibss(struct ieee80211va { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; + unsigned long flags; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: creating ibss on channel %u\n", __func__, @@ -386,6 +387,9 @@ ieee80211_create_ibss(struct ieee80211va ic->ic_bsschan = chan; ieee80211_node_set_chan(ic, ni); ic->ic_curmode = ieee80211_chan2mode(chan); + spin_lock_irqsave(&channel_lock, flags); + ieee80211_scan_set_bss_channel(ic, ic->ic_bsschan); + spin_unlock_irqrestore(&channel_lock, flags); /* Update country ie information */ ieee80211_build_countryie(ic); @@ -622,6 +626,7 @@ ieee80211_sta_join1(struct ieee80211_nod struct ieee80211vap *vap = selbs->ni_vap; struct ieee80211com *ic = selbs->ni_ic; struct ieee80211_node *obss; + unsigned long flags; int canreassoc; if (vap->iv_opmode == IEEE80211_M_IBSS) { @@ -650,6 +655,9 @@ ieee80211_sta_join1(struct ieee80211_nod ic->ic_curchan = ic->ic_bsschan; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_set_channel(ic); + spin_lock_irqsave(&channel_lock, flags); + ieee80211_scan_set_bss_channel(ic, ic->ic_bsschan); + spin_unlock_irqrestore(&channel_lock, flags); /* * Set the erp state (mostly the slot time) to deal with * the auto-select case; this should be redundant if the --- a/net80211/ieee80211_proto.c +++ b/net80211/ieee80211_proto.c @@ -1231,6 +1231,7 @@ ieee80211_dturbo_switch(struct ieee80211 struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); #endif struct ieee80211_channel *chan; + unsigned long flags; chan = ieee80211_find_channel(ic, ic->ic_bsschan->ic_freq, newflags); if (chan == NULL) { /* XXX should not happen */ @@ -1249,6 +1250,9 @@ ieee80211_dturbo_switch(struct ieee80211 ic->ic_bsschan = chan; ic->ic_curchan = chan; ic->ic_set_channel(ic); + spin_lock_irqsave(&channel_lock, flags); + ieee80211_scan_set_bss_channel(ic, ic->ic_bsschan); + spin_unlock_irqrestore(&channel_lock, flags); /* NB: do not need to reset ERP state because in sta mode */ } EXPORT_SYMBOL(ieee80211_dturbo_switch); --- a/net80211/ieee80211_wireless.c +++ b/net80211/ieee80211_wireless.c @@ -4076,8 +4076,13 @@ ieee80211_ioctl_setchanlist(struct net_d if (nchan == 0) /* no valid channels, disallow */ return -EINVAL; if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && /* XXX */ - isclr(chanlist, ic->ic_bsschan->ic_ieee)) + isclr(chanlist, ic->ic_bsschan->ic_ieee)) { + unsigned long flags; ic->ic_bsschan = IEEE80211_CHAN_ANYC; /* invalidate */ + spin_lock_irqsave(&channel_lock, flags); + ieee80211_scan_set_bss_channel(ic, ic->ic_bsschan); + spin_unlock_irqrestore(&channel_lock, flags); + } memcpy(ic->ic_chan_active, chanlist, sizeof(ic->ic_chan_active)); /* update Supported Channels information element */ --- a/net80211/ieee80211_scan_ap.c +++ b/net80211/ieee80211_scan_ap.c @@ -208,9 +208,15 @@ ap_start(struct ieee80211_scan_state *ss struct ieee80211com *ic = NULL; int i; unsigned int mode = 0; + unsigned long sflags; SCAN_AP_LOCK_IRQ(as); ic = vap->iv_ic; + + spin_lock_irqsave(&channel_lock, sflags); + ieee80211_scan_set_bss_channel(ic, NULL); + spin_unlock_irqrestore(&channel_lock, sflags); + /* Determine mode flags to match, or leave zero for auto mode */ ss->ss_last = 0; ieee80211_scan_add_channels(ic, ss, vap->iv_des_mode); @@ -423,8 +429,10 @@ pc_cmp_idletime(struct ieee80211_channel if (!a->ic_idletime || !b->ic_idletime) return 0; - /* a is better than b (return < 0) when a has more idle time than b */ - return b->ic_idletime - a->ic_idletime; + /* a is better than b (return < 0) when a has more idle and less bias time than b */ + return + ((100 - (u32) a->ic_idletime) + ieee80211_scan_get_bias(a)) - + ((100 - (u32) b->ic_idletime) + ieee80211_scan_get_bias(b)); } @@ -575,6 +583,7 @@ ap_end(struct ieee80211_scan_state *ss, struct ap_state *as = ss->ss_priv; struct ieee80211_channel *bestchan = NULL; struct ieee80211com *ic = NULL; + unsigned long sflags; int res = 1; SCAN_AP_LOCK_IRQ(as); @@ -586,8 +595,11 @@ ap_end(struct ieee80211_scan_state *ss, /* record stats for the channel that was scanned last */ ic->ic_set_channel(ic); + spin_lock_irqsave(&channel_lock, sflags); + ieee80211_scan_set_bss_channel(ic, NULL); bestchan = pick_channel(ss, vap, flags); if (bestchan == NULL) { + spin_unlock_irqrestore(&channel_lock, sflags); if (ss->ss_last > 0) { /* no suitable channel, should not happen */ printk(KERN_ERR "%s: %s: no suitable channel! " @@ -606,6 +618,7 @@ ap_end(struct ieee80211_scan_state *ss, bestchan->ic_freq, bestchan->ic_flags & ~IEEE80211_CHAN_TURBO)) == NULL) { /* should never happen ?? */ + spin_unlock_irqrestore(&channel_lock, sflags); SCAN_AP_UNLOCK_IRQ_EARLY(as); return 0; } @@ -618,6 +631,9 @@ ap_end(struct ieee80211_scan_state *ss, as->as_action = action; as->as_selbss = se; + ieee80211_scan_set_bss_channel(ic, bestchan); + spin_unlock_irqrestore(&channel_lock, sflags); + /* Must defer action to avoid possible recursive call through * 80211 state machine, which would result in recursive * locking. */