d80211: Fix inconsistent sta_lock usage
Subsystems (2)
PATCH 1/1 d80211: Fix inconsistent sta_lock usage
Hacking a bit on rt2x00 to make it work in master and ad-hoc mode, lockdep popped up on some hostapd ioctls, pointing out remaining inconsistencies related to sta_lock:
1. sta_lock holders must always be protected against softirq
2. bss_tim_set/clear must not be called with sta_lock held, rather an unprotected variant
3. ieee80211_ioctl_remove_sta is not already holding the lock when calling sta_info_free
As I was not sure if sta_info_remove_aid_ptr needs lock protection or not, I played safe and moved it always under the lock. Please correct me if this is overkill.
[Sorry, patch is against rt2x00 CVS. I'm lacking time and bandwidth to pull the d80211 git repos and rebase.]
--- ieee80211/ieee80211_i.h | 24 ++++++++++++++++++------ ieee80211/ieee80211_ioctl.c | 4 +++- ieee80211/sta_info.c | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-)
ieee80211/ieee80211_ioctl.c +3 -1
@@ -286,7 +286,9 @@ static int ieee80211_ioctl_add_sta(strucif (sta->dev != dev) {/* Binding STA to a new interface, so remove all references to * the old BSS. */ +spin_lock_bh (&local->sta_lock);sta_info_remove_aid_ptr (sta); +spin_unlock_bh (&local->sta_lock); }/* TODO
@@ -360,7 +362,7 @@ static int ieee80211_ioctl_remove_sta(st sta =sta_info_get (local, param->sta_addr);if (sta) {sta_info_put (sta); -sta_info_free (sta,1 ); +sta_info_free (sta,0 ); }return sta ?0 : -ENOENT ;
ieee80211/ieee80211_i.h +18 -6
@@ -565,20 +565,32 @@ struct sta_attribute {ssize_t (*store)(struct sta_info *,const char *buf,size_t count); }; +static inline void __bss_tim_set (struct ieee80211_local *local, +struct ieee80211_if_ap *bss,int aid) +{ + bss->tim[(aid)/8 ] |=1 <<((aid) %8 ); +} +static inline void bss_tim_set (struct ieee80211_local *local,struct ieee80211_if_ap *bss,int aid) { -spin_lock (&local->sta_lock); - bss->tim[(aid)/8 ] |=1 <<((aid) %8 ); -spin_unlock (&local->sta_lock); +spin_lock_bh (&local->sta_lock); +__bss_tim_set (local, bss, aid); +spin_unlock_bh (&local->sta_lock); +} + +static inline void __bss_tim_clear (struct ieee80211_local *local, +struct ieee80211_if_ap *bss,int aid) +{ + bss->tim[(aid)/8 ] &= !(1 <<((aid) %8 )); }static inline void bss_tim_clear (struct ieee80211_local *local,struct ieee80211_if_ap *bss,int aid) { -spin_lock (&local->sta_lock); - bss->tim[(aid)/8 ] &= !(1 <<((aid) %8 )); -spin_unlock (&local->sta_lock); +spin_lock_bh (&local->sta_lock); +__bss_tim_clear (local, bss, aid); +spin_unlock_bh (&local->sta_lock); }/* ieee80211.c */
ieee80211/sta_info.c +1 -1
@@ -439,7 +439,7 @@ void sta_info_remove_aid_ptr(struct sta_ sdata->local->ops->set_tim (local_to_hw (sdata->local), sta->aid,0 );if (sdata->bss) -bss_tim_clear (sdata->local, sdata->bss, sta->aid); +__bss_tim_clear (sdata->local, sdata->bss, sta->aid); }
Quoted 10 lines · Jan Kiszka
Hacking a bit on rt2x00 to make it work in master and ad-hoc mode, lockdep popped up on some hostapd ioctls, pointing out remaining inconsistencies related to
sta_lock:1.
sta_lockholders must always be protected against softirq
2. bss_tim_set/clear must not be called withsta_lockheld, rather an unprotected variant
3.ieee80211_ioctl_remove_stais not already holding the lock when callingsta_info_free
Quoted 8 lines
As I was not sure if
sta_info_remove_aid_ptrneeds lock protection or not, I played safe and moved it always under the lock. Please correct me if this is overkill.Signed-off-by Jan Kiszka <[email protected]>[Sorry, patch is against rt2x00 CVS. I'm lacking time and bandwidth to pull the d80211 git repos and rebase.]
To make it easier for everybody, here is the same patch only this time applied to the dscape git tree. ;)
---
net/d80211/ieee80211_i.h +0 -0
@@ -558,20 +558,32 @@ struct sta_attribute {
ssize_t (*store)(struct sta_info *, const char *buf, size_t count); };
+static inline void __bss_tim_set(struct ieee80211_local *local,
+ struct ieee80211_if_ap *bss, int aid)
+{
+ bss->tim[(aid)/8] |= 1<<((aid) % 8);
+}
+
static inline void bss_tim_set(struct ieee80211_local *local,
struct ieee80211_if_ap *bss, int aid)
{
- spin_lock(&local->sta_lock);
- bss->tim[(aid)/8] |= 1<<((aid) % 8);
- spin_unlock(&local->sta_lock);
+ spin_lock_bh(&local->sta_lock);
+ __bss_tim_set(local, bss, aid);
+ spin_unlock_bh(&local->sta_lock);
+}
+
+static inline void __bss_tim_clear(struct ieee80211_local *local,
+ struct ieee80211_if_ap *bss, int aid)
+{
+ bss->tim[(aid)/8] &= !(1<<((aid) % 8));
}
static inline void bss_tim_clear(struct ieee80211_local *local,
struct ieee80211_if_ap *bss, int aid)
{
- spin_lock(&local->sta_lock);
- bss->tim[(aid)/8] &= !(1<<((aid) % 8));
- spin_unlock(&local->sta_lock);
+ spin_lock_bh(&local->sta_lock);
+ __bss_tim_clear(local, bss, aid);
+ spin_unlock_bh(&local->sta_lock);
}
/* ieee80211.c */
net/d80211/ieee80211_ioctl.c +0 -0
@@ -285,7 +285,9 @@ static int ieee80211_ioctl_add_sta(struct net_device *dev,
if (sta->dev != dev) {
/* Binding STA to a new interface, so remove all references to
* the old BSS. */
+ spin_lock_bh(&local->sta_lock);
sta_info_remove_aid_ptr(sta);
+ spin_unlock_bh(&local->sta_lock);
}
/* TODO
@@ -359,7 +361,7 @@ static int ieee80211_ioctl_remove_sta(struct
net_device *dev,
sta = sta_info_get(local, param->sta_addr);
if (sta) {
sta_info_put(sta);
- sta_info_free(sta, 1);
+ sta_info_free(sta, 0);
}
return sta ? 0 : -ENOENT;net/d80211/sta_info.c +0 -0
@@ -439,7 +439,7 @@ void sta_info_remove_aid_ptr(struct sta_info *sta)
sdata->local->ops->set_tim(local_to_hw(sdata->local), sta->aid, 0); if (sdata->bss)
- bss_tim_clear(sdata->local, sdata->bss, sta->aid);
+ __bss_tim_clear(sdata->local, sdata->bss, sta->aid);
}
Review
Quoted 5 lines · Ivo Van Doorn
(quoted diff) +5
-0+static inline void __bss_tim_set(struct ieee80211_local *local, + struct ieee80211_if_ap *bss, int aid) +{ + bss->tim[(aid)/8] |= 1<<((aid) % 8); +}
This really screams to be converted to __set_bit. Also the local argument is entirely unused. I'd probaby not even add a helper for this but just opencode it as:
__set_bit(&bss->time, aid);
Review
Quoted 4 lines
(quoted diff) +4
-0+static inline void __bss_tim_clear(struct ieee80211_local *local, + struct ieee80211_if_ap *bss, int aid) +{ + bss->tim[(aid)/8] &= !(1<<((aid) % 8));
Similarly this should just be __clear_bit
Quoted 11 lines · Christoph Hellwig
Review
Quoted 5 lines · Ivo Van Doorn
(quoted diff) +5
-0+static inline void __bss_tim_set(struct ieee80211_local *local, + struct ieee80211_if_ap *bss, int aid) +{ + bss->tim[(aid)/8] |= 1<<((aid) % 8); +}This really screams to be converted to
__set_bit. Also the local argument is entirely unused. I'd probaby not even add a helper for this but just opencode it as:
Quoted 8 lines
__set_bit(&bss->time, aid);Review
Quoted 4 lines
(quoted diff) +4
-0+static inline void __bss_tim_clear(struct ieee80211_local *local, + struct ieee80211_if_ap *bss, int aid) +{ + bss->tim[(aid)/8] &= !(1<<((aid) % 8));Similarly this should just be
__clear_bit
This patch uses the __set_bit and __clear_but as suggested by Christoph. It also removes the local argument since it was unused.
Signed-off-by Ivo van Doorn <[email protected]>
---
net/d80211/ieee80211_i.h +8 -4
@@ -560,19 +560,23 @@ struct sta_attribute {ssize_t (*store)(struct sta_info *,const char *buf,size_t count); }; +#define __bss_tim_set (__bss, __aid) __set_bit((__aid), &(__bss)->tim) +static inline void bss_tim_set (struct ieee80211_local *local,struct ieee80211_if_ap *bss,int aid) { -spin_lock (&local->sta_lock); - bss->tim[(aid)/8 ] |=1 <<((aid) %8 ); -spin_unlock (&local->sta_lock); +spin_lock_bh (&local->sta_lock); +__bss_tim_set (bss, aid); +spin_unlock_bh (&local->sta_lock); } +#define __bss_tim_clear (__bss, __aid) __clear_bit((__aid), &(__bss)->tim) +static inline void bss_tim_clear (struct ieee80211_local *local,struct ieee80211_if_ap *bss,int aid) {spin_lock (&local->sta_lock); - bss->tim[(aid)/8 ] &= !(1 <<((aid) %8 )); +__bss_tim_clear (bss, aid);spin_unlock (&local->sta_lock); }
net/d80211/ieee80211_ioctl.c +3 -1
@@ -286,7 +286,9 @@ static int ieee80211_ioctl_add_sta(struct net_device *dev,if (sta->dev != dev) {/* Binding STA to a new interface, so remove all references to * the old BSS. */ +spin_lock_bh (&local->sta_lock);sta_info_remove_aid_ptr (sta); +spin_unlock_bh (&local->sta_lock); }/* TODO
@@ -360,7 +362,7 @@ static int ieee80211_ioctl_remove_sta(struct net_device *dev, sta =sta_info_get (local, param->sta_addr);if (sta) {sta_info_put (sta); -sta_info_free (sta,1 ); +sta_info_free (sta,0 ); }return sta ?0 : -ENOENT ;
net/d80211/sta_info.c +1 -1
@@ -439,7 +439,7 @@ void sta_info_remove_aid_ptr(struct sta_info *sta) sdata->local->ops->set_tim (local_to_hw (sdata->local), sta->aid,0 );if (sdata->bss) -bss_tim_clear (sdata->local, sdata->bss, sta->aid); +__bss_tim_clear (sdata->bss, sta->aid); }
Review
Quoted 2 lines · Ivo van Doorn
(quoted diff) +2
-0+#define __bss_tim_set(__bss, __aid) __set_bit((__aid), &(__bss)->tim) +
__set/clear_bit demands unsigned long, tim is u8. That causes quite some warnings here.
...
net/d80211/ieee80211_i.h struct sta_attribute { · line 580
Quoted 7 lines
static inline void bss_tim_clear(struct
ieee80211_local*local,net/d80211/ieee80211_i.h +1
-1struct ieee80211_if_ap *bss, int aid) {spin_lock (&local->sta_lock); - bss->tim[(aid)/8 ] &= !(1 <<((aid) %8 )); +__bss_tim_clear (bss, aid);spin_unlock (&local->sta_lock);
Probably forgotten: we need _bh here as well.
Jan
Quoted 2 lines · Ivo van Doorn
This patch uses the
__set_bitand__clear_butas suggested by Christoph. It also removes the local argument since it was unused.
NACK. This breaks spec compliance for drivers that use the TIM in their beacon frames.
johannes
Quoted 7 lines · Johannes Berg
Quoted 2 lines · Ivo van Doorn
This patch uses the
__set_bitand__clear_butas suggested by Christoph. It also removes the local argument since it was unused.NACK. This breaks spec compliance for drivers that use the TIM in their beacon frames.
Which, in fact, is the only point we keep track of the TIM, note that we never actually test if bits there are set, the stack has better ways of keeping track of the pending traffic.
johannes
Quoted 7 lines · Johannes Berg
Quoted 2 lines · Ivo van Doorn
This patch uses the
__set_bitand__clear_butas suggested by Christoph. It also removes the local argument since it was unused.NACK. This breaks spec compliance for drivers that use the TIM in their beacon frames.
Bit ordering, I see. Then go for my original patch + comments why this is open-coded in __bss_tim_set/clear + removed unused arguments from those functions, OK?
Jan
Quoted 6 lines · Jan Kiszka
Quoted 5 lines · Johannes Berg
Quoted 2 lines · Ivo van Doorn
This patch uses the
__set_bitand__clear_butas suggested by Christoph. It also removes the local argument since it was unused.
Quoted 6 lines
Quoted 2 lines
NACK. This breaks spec compliance for drivers that use the TIM in their beacon frames.
Bit ordering, I see. Then go for my original patch + comments why this is open-coded in __bss_tim_set/clear + removed unused arguments from those functions, OK?
Sounds good to me, care to send a new patch?
johannes
Quoted 5 lines
Quoted 3 lines
Bit ordering, I see. Then go for my original patch + comments why this is open-coded in __bss_tim_set/clear + removed unused arguments from those functions, OK?
Sounds good to me, care to send a new patch?
This patch returns to the original format for setting and clearing the tim bit, as the IEEE specs mandate. (Comment has been added to prevent future attempts to use the __set_bit and __clear_bit)
And the local argument has been removed.
Signed-off-by Jan Kiszka <[email protected]>
Signed-off-by Ivo van Doorn <[email protected]>
---
net/d80211/ieee80211_i.h +0 -0
@@ -558,20 +558,38 @@ struct sta_attribute {
ssize_t (*store)(struct sta_info *, const char *buf, size_t count); };
+static inline void __bss_tim_set(struct ieee80211_if_ap *bss, int aid) +{
+ /*
+ * This format has ben mandated by the IEEE specifications,
+ * so this line may not be changed to use the __set_bit() format.
+ */
+ bss->tim[(aid)/8] |= 1<<((aid) % 8);
+}
+
static inline void bss_tim_set(struct ieee80211_local *local,
struct ieee80211_if_ap *bss, int aid)
{
- spin_lock(&local->sta_lock);
- bss->tim[(aid)/8] |= 1<<((aid) % 8);
- spin_unlock(&local->sta_lock);
+ spin_lock_bh(&local->sta_lock);
+ __bss_tim_set(bss, aid);
+ spin_unlock_bh(&local->sta_lock);
+}
+
+static inline void __bss_tim_clear(struct ieee80211_if_ap *bss, int aid) +{
+ /*
+ * This format has ben mandated by the IEEE specifications,
+ * so this line may not be changed to use the __clear_bit() format.
+ */
+ bss->tim[(aid)/8] &= !(1<<((aid) % 8));
}
static inline void bss_tim_clear(struct ieee80211_local *local,
struct ieee80211_if_ap *bss, int aid)
{
- spin_lock(&local->sta_lock);
- bss->tim[(aid)/8] &= !(1<<((aid) % 8));
- spin_unlock(&local->sta_lock);
+ spin_lock_bh(&local->sta_lock);
+ __bss_tim_clear(bss, aid);
+ spin_unlock_bh(&local->sta_lock);
}
/* ieee80211.c */
net/d80211/ieee80211_ioctl.c +0 -0
@@ -285,7 +285,9 @@ static int ieee80211_ioctl_add_sta(struct net_device *dev,
if (sta->dev != dev) {
/* Binding STA to a new interface, so remove all references to
* the old BSS. */
+ spin_lock_bh(&local->sta_lock);
sta_info_remove_aid_ptr(sta);
+ spin_unlock_bh(&local->sta_lock);
}
/* TODO
@@ -359,7 +361,7 @@ static int ieee80211_ioctl_remove_sta(struct
net_device *dev,
sta = sta_info_get(local, param->sta_addr);
if (sta) {
sta_info_put(sta);
- sta_info_free(sta, 1);
+ sta_info_free(sta, 0);
}
return sta ? 0 : -ENOENT;net/d80211/sta_info.c +0 -0
@@ -439,7 +439,7 @@ void sta_info_remove_aid_ptr(struct sta_info *sta)
sdata->local->ops->set_tim(local_to_hw(sdata->local), sta->aid, 0); if (sdata->bss)
- bss_tim_clear(sdata->local, sdata->bss, sta->aid);
+ __bss_tim_clear(sdata->bss, sta->aid);
}
Review static int ieee80211_ioctl_remove_sta(struct · line 361
Quoted 3 lines · Ivo Van Doorn
[...]
(quoted diff) +0
-0@@ -359,7 +361,7 @@ static int ieee80211_ioctl_remove_sta(struct net_device *dev,
The patch is line-wrapped here. Applied, but please try to fix your mail client settings.
Thanks for the patch,
Jiri
Signature · 2 lines
Jiri Benc SUSE Labs
Quoted 1 line · Christoph Hellwig
This really screams to be converted to
__set_bit.
Whoops, I should really have jumped into this thread earlier but somehow missed it.
This cannot be converted to __set_bit because the IEEE specs mandate this format.
We can insert a comment that the format must stay like this :)
You have to realise that the TIM is sent as-is with the 0th bit indicating multicast traffic (IIRC) and each of the other bits indicating traffic for that AID.
johannes