mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-30 21:44:27 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			348 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| --- 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. */
 |