b43: Fix upload of beacon packets to the hardware
This fixes uploading of the beacon data and writing of the
TIM and DTIM offsets.
Signed-off-by: Michael Buesch <mb@bu3sch.de>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/drivers/net/wireless/b43/b43.h b/drivers/net/wireless/b43/b43.h
index 82bff51..c8295b3 100644
--- a/drivers/net/wireless/b43/b43.h
+++ b/drivers/net/wireless/b43/b43.h
@@ -651,6 +651,12 @@
u8 nr_devs;
bool radiotap_enabled;
+
+ /* The beacon we are currently using (AP or IBSS mode).
+ * This beacon stuff is protected by the irq_lock. */
+ struct sk_buff *current_beacon;
+ bool beacon0_uploaded;
+ bool beacon1_uploaded;
};
/* Pointers to the firmware data and meta information about it. */
@@ -745,9 +751,6 @@
u8 max_nr_keys;
struct b43_key key[58];
- /* Cached beacon template while uploading the template. */
- struct sk_buff *cached_beacon;
-
/* Firmware data */
struct b43_firmware fw;
diff --git a/drivers/net/wireless/b43/main.c b/drivers/net/wireless/b43/main.c
index af3d24c..9f6647c 100644
--- a/drivers/net/wireless/b43/main.c
+++ b/drivers/net/wireless/b43/main.c
@@ -1148,15 +1148,58 @@
u16 ram_offset,
u16 shm_size_offset, u8 rate)
{
- int len;
- const u8 *data;
+ int i, len;
+ const struct ieee80211_mgmt *bcn;
+ const u8 *ie;
+ bool tim_found = 0;
- B43_WARN_ON(!dev->cached_beacon);
- len = min((size_t) dev->cached_beacon->len,
+ bcn = (const struct ieee80211_mgmt *)(dev->wl->current_beacon->data);
+ len = min((size_t) dev->wl->current_beacon->len,
0x200 - sizeof(struct b43_plcp_hdr6));
- data = (const u8 *)(dev->cached_beacon->data);
- b43_write_template_common(dev, data,
+
+ b43_write_template_common(dev, (const u8 *)bcn,
len, ram_offset, shm_size_offset, rate);
+
+ /* Find the position of the TIM and the DTIM_period value
+ * and write them to SHM. */
+ ie = bcn->u.beacon.variable;
+ for (i = 0; i < len - 2; ) {
+ uint8_t ie_id, ie_len;
+
+ ie_id = ie[i];
+ ie_len = ie[i + 1];
+ if (ie_id == 5) {
+ u16 tim_position;
+ u16 dtim_period;
+ /* This is the TIM Information Element */
+
+ /* Check whether the ie_len is in the beacon data range. */
+ if (len < ie_len + 2 + i)
+ break;
+ /* A valid TIM is at least 4 bytes long. */
+ if (ie_len < 4)
+ break;
+ tim_found = 1;
+
+ tim_position = sizeof(struct b43_plcp_hdr6);
+ tim_position += offsetof(struct ieee80211_mgmt, u.beacon.variable);
+ tim_position += i;
+
+ dtim_period = ie[i + 3];
+
+ b43_shm_write16(dev, B43_SHM_SHARED,
+ B43_SHM_SH_TIMBPOS, tim_position);
+ b43_shm_write16(dev, B43_SHM_SHARED,
+ B43_SHM_SH_DTIMPER, dtim_period);
+ break;
+ }
+ i += ie_len + 2;
+ }
+ if (!tim_found) {
+ b43warn(dev->wl, "Did not find a valid TIM IE in "
+ "the beacon template packet. AP or IBSS operation "
+ "may be broken.\n");
+ }
}
static void b43_write_probe_resp_plcp(struct b43_wldev *dev,
@@ -1184,40 +1227,43 @@
* 2) Patching duration field
* 3) Stripping TIM
*/
-static u8 *b43_generate_probe_resp(struct b43_wldev *dev,
- u16 * dest_size, u8 rate)
+static const u8 * b43_generate_probe_resp(struct b43_wldev *dev,
+ u16 *dest_size, u8 rate)
{
const u8 *src_data;
u8 *dest_data;
u16 src_size, elem_size, src_pos, dest_pos;
__le16 dur;
struct ieee80211_hdr *hdr;
+ size_t ie_start;
- B43_WARN_ON(!dev->cached_beacon);
- src_size = dev->cached_beacon->len;
- src_data = (const u8 *)dev->cached_beacon->data;
+ src_size = dev->wl->current_beacon->len;
+ src_data = (const u8 *)dev->wl->current_beacon->data;
- if (unlikely(src_size < 0x24)) {
- b43dbg(dev->wl, "b43_generate_probe_resp: " "invalid beacon\n");
+ /* Get the start offset of the variable IEs in the packet. */
+ ie_start = offsetof(struct ieee80211_mgmt, u.probe_resp.variable);
+ B43_WARN_ON(ie_start != offsetof(struct ieee80211_mgmt, u.beacon.variable));
+
+ if (B43_WARN_ON(src_size < ie_start))
return NULL;
- }
dest_data = kmalloc(src_size, GFP_ATOMIC);
if (unlikely(!dest_data))
return NULL;
- /* 0x24 is offset of first variable-len Information-Element
- * in beacon frame.
- */
- memcpy(dest_data, src_data, 0x24);
- src_pos = dest_pos = 0x24;
- for (; src_pos < src_size - 2; src_pos += elem_size) {
+ /* Copy the static data and all Information Elements, except the TIM. */
+ memcpy(dest_data, src_data, ie_start);
+ src_pos = ie_start;
+ dest_pos = ie_start;
+ for ( ; src_pos < src_size - 2; src_pos += elem_size) {
elem_size = src_data[src_pos + 1] + 2;
- if (src_data[src_pos] != 0x05) { /* TIM */
- memcpy(dest_data + dest_pos, src_data + src_pos,
- elem_size);
- dest_pos += elem_size;
+ if (src_data[src_pos] == 5) {
+ /* This is the TIM. */
+ continue;
}
+ memcpy(dest_data + dest_pos, src_data + src_pos,
+ elem_size);
+ dest_pos += elem_size;
}
*dest_size = dest_pos;
hdr = (struct ieee80211_hdr *)dest_data;
@@ -1237,11 +1283,10 @@
u16 ram_offset,
u16 shm_size_offset, u8 rate)
{
- u8 *probe_resp_data;
+ const u8 *probe_resp_data;
u16 size;
- B43_WARN_ON(!dev->cached_beacon);
- size = dev->cached_beacon->len;
+ size = dev->wl->current_beacon->len;
probe_resp_data = b43_generate_probe_resp(dev, &size, rate);
if (unlikely(!probe_resp_data))
return;
@@ -1260,39 +1305,26 @@
kfree(probe_resp_data);
}
-static int b43_refresh_cached_beacon(struct b43_wldev *dev,
- struct sk_buff *beacon)
+/* Asynchronously update the packet templates in template RAM. */
+static void b43_update_templates(struct b43_wl *wl, struct sk_buff *beacon)
{
- if (dev->cached_beacon)
- kfree_skb(dev->cached_beacon);
- dev->cached_beacon = beacon;
+ unsigned long flags;
- return 0;
-}
+ /* This is the top half of the ansynchronous beacon update.
+ * The bottom half is the beacon IRQ.
+ * Beacon update must be asynchronous to avoid sending an
+ * invalid beacon. This can happen for example, if the firmware
+ * transmits a beacon while we are updating it. */
-static void b43_update_templates(struct b43_wldev *dev)
-{
- u32 cmd;
+ spin_lock_irqsave(&wl->irq_lock, flags);
- B43_WARN_ON(!dev->cached_beacon);
+ if (wl->current_beacon)
+ dev_kfree_skb_any(wl->current_beacon);
+ wl->current_beacon = beacon;
+ wl->beacon0_uploaded = 0;
+ wl->beacon1_uploaded = 0;
- b43_write_beacon_template(dev, 0x68, 0x18, B43_CCK_RATE_1MB);
- b43_write_beacon_template(dev, 0x468, 0x1A, B43_CCK_RATE_1MB);
- b43_write_probe_resp_template(dev, 0x268, 0x4A, B43_CCK_RATE_11MB);
-
- cmd = b43_read32(dev, B43_MMIO_MACCMD);
- cmd |= B43_MACCMD_BEACON0_VALID | B43_MACCMD_BEACON1_VALID;
- b43_write32(dev, B43_MMIO_MACCMD, cmd);
-}
-
-static void b43_refresh_templates(struct b43_wldev *dev, struct sk_buff *beacon)
-{
- int err;
-
- err = b43_refresh_cached_beacon(dev, beacon);
- if (unlikely(err))
- return;
- b43_update_templates(dev);
+ spin_unlock_irqrestore(&wl->irq_lock, flags);
}
static void b43_set_ssid(struct b43_wldev *dev, const u8 * ssid, u8 ssid_len)
@@ -1328,33 +1360,34 @@
static void handle_irq_beacon(struct b43_wldev *dev)
{
- u32 status;
+ struct b43_wl *wl = dev->wl;
+ u32 cmd;
- if (!b43_is_mode(dev->wl, IEEE80211_IF_TYPE_AP))
+ if (!b43_is_mode(wl, IEEE80211_IF_TYPE_AP))
return;
- dev->irq_savedstate &= ~B43_IRQ_BEACON;
- status = b43_read32(dev, B43_MMIO_MACCMD);
+ /* This is the bottom half of the asynchronous beacon update. */
- if (!dev->cached_beacon || ((status & 0x1) && (status & 0x2))) {
- /* ACK beacon IRQ. */
- b43_write32(dev, B43_MMIO_GEN_IRQ_REASON, B43_IRQ_BEACON);
- dev->irq_savedstate |= B43_IRQ_BEACON;
- if (dev->cached_beacon)
- kfree_skb(dev->cached_beacon);
- dev->cached_beacon = NULL;
- return;
+ cmd = b43_read32(dev, B43_MMIO_MACCMD);
+ if (!(cmd & B43_MACCMD_BEACON0_VALID)) {
+ if (!wl->beacon0_uploaded) {
+ b43_write_beacon_template(dev, 0x68, 0x18,
+ B43_CCK_RATE_1MB);
+ b43_write_probe_resp_template(dev, 0x268, 0x4A,
+ B43_CCK_RATE_11MB);
+ wl->beacon0_uploaded = 1;
+ }
+ cmd |= B43_MACCMD_BEACON0_VALID;
}
- if (!(status & 0x1)) {
- b43_write_beacon_template(dev, 0x68, 0x18, B43_CCK_RATE_1MB);
- status |= 0x1;
- b43_write32(dev, B43_MMIO_MACCMD, status);
+ if (!(cmd & B43_MACCMD_BEACON1_VALID)) {
+ if (!wl->beacon1_uploaded) {
+ b43_write_beacon_template(dev, 0x468, 0x1A,
+ B43_CCK_RATE_1MB);
+ wl->beacon1_uploaded = 1;
+ }
+ cmd |= B43_MACCMD_BEACON1_VALID;
}
- if (!(status & 0x2)) {
- b43_write_beacon_template(dev, 0x468, 0x1A, B43_CCK_RATE_1MB);
- status |= 0x2;
- b43_write32(dev, B43_MMIO_MACCMD, status);
- }
+ b43_write32(dev, B43_MMIO_MACCMD, cmd);
}
static void handle_irq_ucode_debug(struct b43_wldev *dev)
@@ -2949,7 +2982,7 @@
B43_WARN_ON(conf->type != IEEE80211_IF_TYPE_AP);
b43_set_ssid(dev, conf->ssid, conf->ssid_len);
if (conf->beacon)
- b43_refresh_templates(dev, conf->beacon);
+ b43_update_templates(wl, conf->beacon);
}
b43_write_mac_bssid_templates(dev);
}
@@ -3295,6 +3328,11 @@
kfree(phy->tssi2dbm);
kfree(phy->lo_control);
phy->lo_control = NULL;
+ if (dev->wl->current_beacon) {
+ dev_kfree_skb_any(dev->wl->current_beacon);
+ dev->wl->current_beacon = NULL;
+ }
+
ssb_device_disable(dev->dev, 0);
ssb_bus_may_powerdown(dev->dev->bus);
}
@@ -3556,6 +3594,34 @@
return err;
}
+static int b43_op_beacon_set_tim(struct ieee80211_hw *hw, int aid, int set)
+{
+ struct b43_wl *wl = hw_to_b43_wl(hw);
+ struct sk_buff *beacon;
+
+ /* We could modify the existing beacon and set the aid bit in
+ * the TIM field, but that would probably require resizing and
+ * moving of data within the beacon template.
+ * Simply request a new beacon and let mac80211 do the hard work. */
+ beacon = ieee80211_beacon_get(hw, wl->vif, NULL);
+ if (unlikely(!beacon))
+ return -ENOMEM;
+ b43_update_templates(wl, beacon);
+
+ return 0;
+}
+
+static int b43_op_ibss_beacon_update(struct ieee80211_hw *hw,
+ struct sk_buff *beacon,
+ struct ieee80211_tx_control *ctl)
+{
+ struct b43_wl *wl = hw_to_b43_wl(hw);
+
+ b43_update_templates(wl, beacon);
+
+ return 0;
+}
+
static const struct ieee80211_ops b43_hw_ops = {
.tx = b43_op_tx,
.conf_tx = b43_op_conf_tx,
@@ -3570,6 +3636,8 @@
.start = b43_op_start,
.stop = b43_op_stop,
.set_retry_limit = b43_op_set_retry_limit,
+ .set_tim = b43_op_beacon_set_tim,
+ .beacon_update = b43_op_ibss_beacon_update,
};
/* Hard-reset the chip. Do not call this directly.