mac80211: allow configure_filter callback to sleep
Over time, a whole bunch of drivers have come up
with their own scheme to delay the configure_filter
operation to a workqueue. To be able to simplify
things, allow configure_filter to sleep, and add
a new prepare_multicast callback that drivers that
need the multicast address list implement. This new
callback must be atomic, but most drivers either
don't care or just calculate a hash which can be
done atomically and then uploaded to the hardware
non-atomically.
A cursory look suggests that at76c50x-usb, ar9170,
mwl8k (which is actually very broken now), rt2x00,
wl1251, wl1271 and zd1211 should make use of this
new capability.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/ath/ar9170/main.c b/drivers/net/wireless/ath/ar9170/main.c
index ea8c941..6a9462e 100644
--- a/drivers/net/wireless/ath/ar9170/main.c
+++ b/drivers/net/wireless/ath/ar9170/main.c
@@ -2100,10 +2100,29 @@
mutex_unlock(&ar->mutex);
}
+static u64 ar9170_op_prepare_multicast(struct ieee80211_hw *hw, int mc_count,
+ struct dev_addr_list *mclist)
+{
+ u64 mchash;
+ int i;
+
+ /* always get broadcast frames */
+ mchash = 1ULL << (0xff >> 2);
+
+ for (i = 0; i < mc_count; i++) {
+ if (WARN_ON(!mclist))
+ break;
+ mchash |= 1ULL << (mclist->dmi_addr[5] >> 2);
+ mclist = mclist->next;
+ }
+
+ return mchash;
+}
+
static void ar9170_op_configure_filter(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *new_flags,
- int mc_count, struct dev_mc_list *mclist)
+ u64 multicast)
{
struct ar9170 *ar = hw->priv;
@@ -2116,24 +2135,11 @@
* then checking the error flags, later.
*/
- if (changed_flags & FIF_ALLMULTI) {
- if (*new_flags & FIF_ALLMULTI) {
- ar->want_mc_hash = ~0ULL;
- } else {
- u64 mchash;
- int i;
+ if (changed_flags & FIF_ALLMULTI && *new_flags & FIF_ALLMULTI)
+ multicast = ~0ULL;
- /* always get broadcast frames */
- mchash = 1ULL << (0xff >> 2);
-
- for (i = 0; i < mc_count; i++) {
- if (WARN_ON(!mclist))
- break;
- mchash |= 1ULL << (mclist->dmi_addr[5] >> 2);
- mclist = mclist->next;
- }
- ar->want_mc_hash = mchash;
- }
+ if (multicast != ar->want_mc_hash) {
+ ar->want_mc_hash = multicast;
set_bit(AR9170_FILTER_CHANGED_MULTICAST, &ar->filter_changed);
}
@@ -2543,6 +2549,7 @@
.add_interface = ar9170_op_add_interface,
.remove_interface = ar9170_op_remove_interface,
.config = ar9170_op_config,
+ .prepare_multicast = ar9170_op_prepare_multicast,
.configure_filter = ar9170_op_configure_filter,
.conf_tx = ar9170_conf_tx,
.bss_info_changed = ar9170_op_bss_info_changed,
diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
index 2b3cf39..3951b5b 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -229,10 +229,12 @@
static void ath5k_remove_interface(struct ieee80211_hw *hw,
struct ieee80211_if_init_conf *conf);
static int ath5k_config(struct ieee80211_hw *hw, u32 changed);
+static u64 ath5k_prepare_multicast(struct ieee80211_hw *hw,
+ int mc_count, struct dev_addr_list *mc_list);
static void ath5k_configure_filter(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *new_flags,
- int mc_count, struct dev_mc_list *mclist);
+ u64 multicast);
static int ath5k_set_key(struct ieee80211_hw *hw,
enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
@@ -260,6 +262,7 @@
.add_interface = ath5k_add_interface,
.remove_interface = ath5k_remove_interface,
.config = ath5k_config,
+ .prepare_multicast = ath5k_prepare_multicast,
.configure_filter = ath5k_configure_filter,
.set_key = ath5k_set_key,
.get_stats = ath5k_get_stats,
@@ -2853,6 +2856,37 @@
return ret;
}
+static u64 ath5k_prepare_multicast(struct ieee80211_hw *hw,
+ int mc_count, struct dev_addr_list *mclist)
+{
+ u32 mfilt[2], val;
+ int i;
+ u8 pos;
+
+ mfilt[0] = 0;
+ mfilt[1] = 1;
+
+ for (i = 0; i < mc_count; i++) {
+ if (!mclist)
+ break;
+ /* calculate XOR of eight 6-bit values */
+ val = get_unaligned_le32(mclist->dmi_addr + 0);
+ pos = (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val;
+ val = get_unaligned_le32(mclist->dmi_addr + 3);
+ pos ^= (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val;
+ pos &= 0x3f;
+ mfilt[pos / 32] |= (1 << (pos % 32));
+ /* XXX: we might be able to just do this instead,
+ * but not sure, needs testing, if we do use this we'd
+ * neet to inform below to not reset the mcast */
+ /* ath5k_hw_set_mcast_filterindex(ah,
+ * mclist->dmi_addr[5]); */
+ mclist = mclist->next;
+ }
+
+ return ((u64)(mfilt[1]) << 32) | mfilt[0];
+}
+
#define SUPPORTED_FIF_FLAGS \
FIF_PROMISC_IN_BSS | FIF_ALLMULTI | FIF_FCSFAIL | \
FIF_PLCPFAIL | FIF_CONTROL | FIF_OTHER_BSS | \
@@ -2878,16 +2912,14 @@
static void ath5k_configure_filter(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *new_flags,
- int mc_count, struct dev_mc_list *mclist)
+ u64 multicast)
{
struct ath5k_softc *sc = hw->priv;
struct ath5k_hw *ah = sc->ah;
- u32 mfilt[2], val, rfilt;
- u8 pos;
- int i;
+ u32 mfilt[2], rfilt;
- mfilt[0] = 0;
- mfilt[1] = 0;
+ mfilt[0] = multicast;
+ mfilt[1] = multicast >> 32;
/* Only deal with supported flags */
changed_flags &= SUPPORTED_FIF_FLAGS;
@@ -2913,24 +2945,6 @@
if (*new_flags & FIF_ALLMULTI) {
mfilt[0] = ~0;
mfilt[1] = ~0;
- } else {
- for (i = 0; i < mc_count; i++) {
- if (!mclist)
- break;
- /* calculate XOR of eight 6-bit values */
- val = get_unaligned_le32(mclist->dmi_addr + 0);
- pos = (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val;
- val = get_unaligned_le32(mclist->dmi_addr + 3);
- pos ^= (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val;
- pos &= 0x3f;
- mfilt[pos / 32] |= (1 << (pos % 32));
- /* XXX: we might be able to just do this instead,
- * but not sure, needs testing, if we do use this we'd
- * neet to inform below to not reset the mcast */
- /* ath5k_hw_set_mcast_filterindex(ah,
- * mclist->dmi_addr[5]); */
- mclist = mclist->next;
- }
}
/* This is the best we can do */
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c
index 3e09b9a..2f9c149 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -2394,8 +2394,7 @@
static void ath9k_configure_filter(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *total_flags,
- int mc_count,
- struct dev_mc_list *mclist)
+ u64 multicast)
{
struct ath_wiphy *aphy = hw->priv;
struct ath_softc *sc = aphy->sc;