diff options
Diffstat (limited to 'drivers/net/wireless/zd1211rw/zd_mac.c')
| -rw-r--r-- | drivers/net/wireless/zd1211rw/zd_mac.c | 573 |
1 files changed, 455 insertions, 118 deletions
diff --git a/drivers/net/wireless/zd1211rw/zd_mac.c b/drivers/net/wireless/zd1211rw/zd_mac.c index 43307bd42a6..e7af261e919 100644 --- a/drivers/net/wireless/zd1211rw/zd_mac.c +++ b/drivers/net/wireless/zd1211rw/zd_mac.c @@ -16,8 +16,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * along with this program; if not, see <http://www.gnu.org/licenses/>. */ #include <linux/netdevice.h> @@ -138,6 +137,12 @@ static const struct ieee80211_channel zd_channels[] = { static void housekeeping_init(struct zd_mac *mac); static void housekeeping_enable(struct zd_mac *mac); static void housekeeping_disable(struct zd_mac *mac); +static void beacon_init(struct zd_mac *mac); +static void beacon_enable(struct zd_mac *mac); +static void beacon_disable(struct zd_mac *mac); +static void set_rts_cts(struct zd_mac *mac, unsigned int short_preamble); +static int zd_mac_config_beacon(struct ieee80211_hw *hw, + struct sk_buff *beacon, bool in_intr); static int zd_reg2alpha2(u8 regdomain, char *alpha2) { @@ -154,6 +159,22 @@ static int zd_reg2alpha2(u8 regdomain, char *alpha2) return 1; } +static int zd_check_signal(struct ieee80211_hw *hw, int signal) +{ + struct zd_mac *mac = zd_hw_mac(hw); + + dev_dbg_f_cond(zd_mac_dev(mac), signal < 0 || signal > 100, + "%s: signal value from device not in range 0..100, " + "but %d.\n", __func__, signal); + + if (signal < 0) + signal = 0; + else if (signal > 100) + signal = 100; + + return signal; +} + int zd_mac_preinit_hw(struct ieee80211_hw *hw) { int r; @@ -231,6 +252,26 @@ static int set_rx_filter(struct zd_mac *mac) return zd_iowrite32(&mac->chip, CR_RX_FILTER, filter); } +static int set_mac_and_bssid(struct zd_mac *mac) +{ + int r; + + if (!mac->vif) + return -1; + + r = zd_write_mac_addr(&mac->chip, mac->vif->addr); + if (r) + return r; + + /* Vendor driver after setting MAC either sets BSSID for AP or + * filter for other modes. + */ + if (mac->type != NL80211_IFTYPE_AP) + return set_rx_filter(mac); + else + return zd_write_bssid(&mac->chip, mac->vif->addr); +} + static int set_mc_hash(struct zd_mac *mac) { struct zd_mc_hash hash; @@ -238,7 +279,7 @@ static int set_mc_hash(struct zd_mac *mac) return zd_chip_set_multicast_hash(&mac->chip, &hash); } -static int zd_op_start(struct ieee80211_hw *hw) +int zd_op_start(struct ieee80211_hw *hw) { struct zd_mac *mac = zd_hw_mac(hw); struct zd_chip *chip = &mac->chip; @@ -264,9 +305,19 @@ static int zd_op_start(struct ieee80211_hw *hw) r = set_mc_hash(mac); if (r) goto disable_int; + + /* Wait after setting the multicast hash table and powering on + * the radio otherwise interface bring up will fail. This matches + * what the vendor driver did. + */ + msleep(10); + r = zd_chip_switch_radio_on(chip); - if (r < 0) + if (r < 0) { + dev_err(zd_chip_dev(chip), + "%s: failed to set radio on\n", __func__); goto disable_int; + } r = zd_chip_enable_rxtx(chip); if (r < 0) goto disable_radio; @@ -275,6 +326,8 @@ static int zd_op_start(struct ieee80211_hw *hw) goto disable_rxtx; housekeeping_enable(mac); + beacon_enable(mac); + set_bit(ZD_DEVICE_RUNNING, &mac->flags); return 0; disable_rxtx: zd_chip_disable_rxtx(chip); @@ -286,19 +339,22 @@ out: return r; } -static void zd_op_stop(struct ieee80211_hw *hw) +void zd_op_stop(struct ieee80211_hw *hw) { struct zd_mac *mac = zd_hw_mac(hw); struct zd_chip *chip = &mac->chip; struct sk_buff *skb; struct sk_buff_head *ack_wait_queue = &mac->ack_wait_queue; + clear_bit(ZD_DEVICE_RUNNING, &mac->flags); + /* The order here deliberately is a little different from the open() * method, since we need to make sure there is no opportunity for RX * frames to be processed by mac80211 after we have stopped it. */ zd_chip_disable_rxtx(chip); + beacon_disable(mac); housekeeping_disable(mac); flush_workqueue(zd_workqueue); @@ -311,6 +367,66 @@ static void zd_op_stop(struct ieee80211_hw *hw) dev_kfree_skb_any(skb); } +int zd_restore_settings(struct zd_mac *mac) +{ + struct sk_buff *beacon; + struct zd_mc_hash multicast_hash; + unsigned int short_preamble; + int r, beacon_interval, beacon_period; + u8 channel; + + dev_dbg_f(zd_mac_dev(mac), "\n"); + + spin_lock_irq(&mac->lock); + multicast_hash = mac->multicast_hash; + short_preamble = mac->short_preamble; + beacon_interval = mac->beacon.interval; + beacon_period = mac->beacon.period; + channel = mac->channel; + spin_unlock_irq(&mac->lock); + + r = set_mac_and_bssid(mac); + if (r < 0) { + dev_dbg_f(zd_mac_dev(mac), "set_mac_and_bssid failed, %d\n", r); + return r; + } + + r = zd_chip_set_channel(&mac->chip, channel); + if (r < 0) { + dev_dbg_f(zd_mac_dev(mac), "zd_chip_set_channel failed, %d\n", + r); + return r; + } + + set_rts_cts(mac, short_preamble); + + r = zd_chip_set_multicast_hash(&mac->chip, &multicast_hash); + if (r < 0) { + dev_dbg_f(zd_mac_dev(mac), + "zd_chip_set_multicast_hash failed, %d\n", r); + return r; + } + + if (mac->type == NL80211_IFTYPE_MESH_POINT || + mac->type == NL80211_IFTYPE_ADHOC || + mac->type == NL80211_IFTYPE_AP) { + if (mac->vif != NULL) { + beacon = ieee80211_beacon_get(mac->hw, mac->vif); + if (beacon) + zd_mac_config_beacon(mac->hw, beacon, false); + } + + zd_set_beacon_interval(&mac->chip, beacon_interval, + beacon_period, mac->type); + + spin_lock_irq(&mac->lock); + mac->beacon.last_update = jiffies; + spin_unlock_irq(&mac->lock); + } + + return 0; +} + /** * zd_mac_tx_status - reports tx status of a packet if required * @hw - a &struct ieee80211_hw pointer @@ -368,7 +484,7 @@ static void zd_mac_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb, if (i<IEEE80211_TX_MAX_RATES) info->status.rates[i].idx = -1; /* terminate */ - info->status.ack_signal = ackssi; + info->status.ack_signal = zd_check_signal(hw, ackssi); ieee80211_tx_status_irqsafe(hw, skb); } @@ -416,9 +532,8 @@ void zd_mac_tx_failed(struct urb *urb) tx_hdr = (struct ieee80211_hdr *)skb->data; /* we skip all frames not matching the reported destination */ - if (unlikely(memcmp(tx_hdr->addr1, tx_status->mac, ETH_ALEN))) { + if (unlikely(!ether_addr_equal(tx_hdr->addr1, tx_status->mac))) continue; - } /* we skip all frames not matching the reported final rate */ @@ -571,67 +686,178 @@ static void cs_set_control(struct zd_mac *mac, struct zd_ctrlset *cs, /* FIXME: Management frame? */ } -static int zd_mac_config_beacon(struct ieee80211_hw *hw, struct sk_buff *beacon) +static bool zd_mac_match_cur_beacon(struct zd_mac *mac, struct sk_buff *beacon) +{ + if (!mac->beacon.cur_beacon) + return false; + + if (mac->beacon.cur_beacon->len != beacon->len) + return false; + + return !memcmp(beacon->data, mac->beacon.cur_beacon->data, beacon->len); +} + +static void zd_mac_free_cur_beacon_locked(struct zd_mac *mac) +{ + ZD_ASSERT(mutex_is_locked(&mac->chip.mutex)); + + kfree_skb(mac->beacon.cur_beacon); + mac->beacon.cur_beacon = NULL; +} + +static void zd_mac_free_cur_beacon(struct zd_mac *mac) +{ + mutex_lock(&mac->chip.mutex); + zd_mac_free_cur_beacon_locked(mac); + mutex_unlock(&mac->chip.mutex); +} + +static int zd_mac_config_beacon(struct ieee80211_hw *hw, struct sk_buff *beacon, + bool in_intr) { struct zd_mac *mac = zd_hw_mac(hw); - int r; + int r, ret, num_cmds, req_pos = 0; u32 tmp, j = 0; /* 4 more bytes for tail CRC */ u32 full_len = beacon->len + 4; + unsigned long end_jiffies, message_jiffies; + struct zd_ioreq32 *ioreqs; + + mutex_lock(&mac->chip.mutex); + + /* Check if hw already has this beacon. */ + if (zd_mac_match_cur_beacon(mac, beacon)) { + r = 0; + goto out_nofree; + } + + /* Alloc memory for full beacon write at once. */ + num_cmds = 1 + zd_chip_is_zd1211b(&mac->chip) + full_len; + ioreqs = kmalloc(num_cmds * sizeof(struct zd_ioreq32), GFP_KERNEL); + if (!ioreqs) { + r = -ENOMEM; + goto out_nofree; + } - r = zd_iowrite32(&mac->chip, CR_BCN_FIFO_SEMAPHORE, 0); + r = zd_iowrite32_locked(&mac->chip, 0, CR_BCN_FIFO_SEMAPHORE); if (r < 0) - return r; - r = zd_ioread32(&mac->chip, CR_BCN_FIFO_SEMAPHORE, &tmp); + goto out; + r = zd_ioread32_locked(&mac->chip, &tmp, CR_BCN_FIFO_SEMAPHORE); if (r < 0) - return r; + goto release_sema; + if (in_intr && tmp & 0x2) { + r = -EBUSY; + goto release_sema; + } + end_jiffies = jiffies + HZ / 2; /*~500ms*/ + message_jiffies = jiffies + HZ / 10; /*~100ms*/ while (tmp & 0x2) { - r = zd_ioread32(&mac->chip, CR_BCN_FIFO_SEMAPHORE, &tmp); + r = zd_ioread32_locked(&mac->chip, &tmp, CR_BCN_FIFO_SEMAPHORE); if (r < 0) - return r; - if ((++j % 100) == 0) { - printk(KERN_ERR "CR_BCN_FIFO_SEMAPHORE not ready\n"); - if (j >= 500) { - printk(KERN_ERR "Giving up beacon config.\n"); - return -ETIMEDOUT; + goto release_sema; + if (time_is_before_eq_jiffies(message_jiffies)) { + message_jiffies = jiffies + HZ / 10; + dev_err(zd_mac_dev(mac), + "CR_BCN_FIFO_SEMAPHORE not ready\n"); + if (time_is_before_eq_jiffies(end_jiffies)) { + dev_err(zd_mac_dev(mac), + "Giving up beacon config.\n"); + r = -ETIMEDOUT; + goto reset_device; } } - msleep(1); + msleep(20); } - r = zd_iowrite32(&mac->chip, CR_BCN_FIFO, full_len - 1); - if (r < 0) - return r; + ioreqs[req_pos].addr = CR_BCN_FIFO; + ioreqs[req_pos].value = full_len - 1; + req_pos++; if (zd_chip_is_zd1211b(&mac->chip)) { - r = zd_iowrite32(&mac->chip, CR_BCN_LENGTH, full_len - 1); - if (r < 0) - return r; + ioreqs[req_pos].addr = CR_BCN_LENGTH; + ioreqs[req_pos].value = full_len - 1; + req_pos++; } for (j = 0 ; j < beacon->len; j++) { - r = zd_iowrite32(&mac->chip, CR_BCN_FIFO, - *((u8 *)(beacon->data + j))); - if (r < 0) - return r; + ioreqs[req_pos].addr = CR_BCN_FIFO; + ioreqs[req_pos].value = *((u8 *)(beacon->data + j)); + req_pos++; } for (j = 0; j < 4; j++) { - r = zd_iowrite32(&mac->chip, CR_BCN_FIFO, 0x0); - if (r < 0) - return r; + ioreqs[req_pos].addr = CR_BCN_FIFO; + ioreqs[req_pos].value = 0x0; + req_pos++; } - r = zd_iowrite32(&mac->chip, CR_BCN_FIFO_SEMAPHORE, 1); - if (r < 0) - return r; + BUG_ON(req_pos != num_cmds); + + r = zd_iowrite32a_locked(&mac->chip, ioreqs, num_cmds); + +release_sema: + /* + * Try very hard to release device beacon semaphore, as otherwise + * device/driver can be left in unusable state. + */ + end_jiffies = jiffies + HZ / 2; /*~500ms*/ + ret = zd_iowrite32_locked(&mac->chip, 1, CR_BCN_FIFO_SEMAPHORE); + while (ret < 0) { + if (in_intr || time_is_before_eq_jiffies(end_jiffies)) { + ret = -ETIMEDOUT; + break; + } + + msleep(20); + ret = zd_iowrite32_locked(&mac->chip, 1, CR_BCN_FIFO_SEMAPHORE); + } + + if (ret < 0) + dev_err(zd_mac_dev(mac), "Could not release " + "CR_BCN_FIFO_SEMAPHORE!\n"); + if (r < 0 || ret < 0) { + if (r >= 0) + r = ret; + + /* We don't know if beacon was written successfully or not, + * so clear current. */ + zd_mac_free_cur_beacon_locked(mac); + + goto out; + } + + /* Beacon has now been written successfully, update current. */ + zd_mac_free_cur_beacon_locked(mac); + mac->beacon.cur_beacon = beacon; + beacon = NULL; /* 802.11b/g 2.4G CCK 1Mb * 802.11a, not yet implemented, uses different values (see GPL vendor * driver) */ - return zd_iowrite32(&mac->chip, CR_BCN_PLCP_CFG, 0x00000400 | - (full_len << 19)); + r = zd_iowrite32_locked(&mac->chip, 0x00000400 | (full_len << 19), + CR_BCN_PLCP_CFG); +out: + kfree(ioreqs); +out_nofree: + kfree_skb(beacon); + mutex_unlock(&mac->chip.mutex); + + return r; + +reset_device: + zd_mac_free_cur_beacon_locked(mac); + kfree_skb(beacon); + + mutex_unlock(&mac->chip.mutex); + kfree(ioreqs); + + /* semaphore stuck, reset device to avoid fw freeze later */ + dev_warn(zd_mac_dev(mac), "CR_BCN_FIFO_SEMAPHORE stuck, " + "resetting device..."); + usb_queue_reset_device(mac->chip.usb.intf); + + return r; } static int fill_ctrlset(struct zd_mac *mac, @@ -648,6 +874,14 @@ static int fill_ctrlset(struct zd_mac *mac, ZD_ASSERT(frag_len <= 0xffff); + /* + * Firmware computes the duration itself (for all frames except PSPoll) + * and needs the field set to 0 at input, otherwise firmware messes up + * duration_id and sets bits 14 and 15 on. + */ + if (!ieee80211_is_pspoll(hdr->frame_control)) + hdr->duration_id = 0; + txrate = ieee80211_get_tx_rate(mac->hw, info); cs->modulation = txrate->hw_value; @@ -701,7 +935,9 @@ static int fill_ctrlset(struct zd_mac *mac, * control block of the skbuff will be initialized. If necessary the incoming * mac80211 queues will be stopped. */ -static int zd_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb) +static void zd_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) { struct zd_mac *mac = zd_hw_mac(hw); struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); @@ -716,11 +952,10 @@ static int zd_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb) r = zd_usb_tx(&mac->chip.usb, skb); if (r) goto fail; - return 0; + return; fail: dev_kfree_skb(skb); - return 0; } /** @@ -761,7 +996,7 @@ static int filter_ack(struct ieee80211_hw *hw, struct ieee80211_hdr *rx_hdr, continue; tx_hdr = (struct ieee80211_hdr *)skb->data; - if (likely(!memcmp(tx_hdr->addr2, rx_hdr->addr1, ETH_ALEN))) + if (likely(ether_addr_equal(tx_hdr->addr2, rx_hdr->addr1))) { found = 1; break; @@ -779,6 +1014,13 @@ static int filter_ack(struct ieee80211_hw *hw, struct ieee80211_hdr *rx_hdr, mac->ack_pending = 1; mac->ack_signal = stats->signal; + + /* Prevent pending tx-packet on AP-mode */ + if (mac->type == NL80211_IFTYPE_AP) { + skb = __skb_dequeue(q); + zd_mac_tx_status(hw, skb, mac->ack_signal, NULL); + mac->ack_pending = 0; + } } spin_unlock_irqrestore(&q->lock, flags); @@ -827,7 +1069,7 @@ int zd_mac_rx(struct ieee80211_hw *hw, const u8 *buffer, unsigned int length) stats.freq = zd_channels[_zd_chip_get_channel(&mac->chip) - 1].center_freq; stats.band = IEEE80211_BAND_2GHZ; - stats.signal = status->signal_strength; + stats.signal = zd_check_signal(hw, status->signal_strength); rate = zd_rx_rate(buffer, status); @@ -882,13 +1124,16 @@ static int zd_op_add_interface(struct ieee80211_hw *hw, case NL80211_IFTYPE_MESH_POINT: case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_AP: mac->type = vif->type; break; default: return -EOPNOTSUPP; } - return zd_write_mac_addr(&mac->chip, vif->addr); + mac->vif = vif; + + return set_mac_and_bssid(mac); } static void zd_op_remove_interface(struct ieee80211_hw *hw, @@ -896,8 +1141,11 @@ static void zd_op_remove_interface(struct ieee80211_hw *hw, { struct zd_mac *mac = zd_hw_mac(hw); mac->type = NL80211_IFTYPE_UNSPECIFIED; - zd_set_beacon_interval(&mac->chip, 0); + mac->vif = NULL; + zd_set_beacon_interval(&mac->chip, 0, 0, NL80211_IFTYPE_UNSPECIFIED); zd_write_mac_addr(&mac->chip, NULL); + + zd_mac_free_cur_beacon(mac); } static int zd_op_config(struct ieee80211_hw *hw, u32 changed) @@ -905,49 +1153,65 @@ static int zd_op_config(struct ieee80211_hw *hw, u32 changed) struct zd_mac *mac = zd_hw_mac(hw); struct ieee80211_conf *conf = &hw->conf; - return zd_chip_set_channel(&mac->chip, conf->channel->hw_value); + spin_lock_irq(&mac->lock); + mac->channel = conf->chandef.chan->hw_value; + spin_unlock_irq(&mac->lock); + + return zd_chip_set_channel(&mac->chip, conf->chandef.chan->hw_value); } -static void zd_process_intr(struct work_struct *work) +static void zd_beacon_done(struct zd_mac *mac) { - u16 int_status; - struct zd_mac *mac = container_of(work, struct zd_mac, process_intr); + struct sk_buff *skb, *beacon; - int_status = le16_to_cpu(*(__le16 *)(mac->intr_buffer+4)); - if (int_status & INT_CFG_NEXT_BCN) - dev_dbg_f_limit(zd_mac_dev(mac), "INT_CFG_NEXT_BCN\n"); - else - dev_dbg_f(zd_mac_dev(mac), "Unsupported interrupt\n"); - - zd_chip_enable_hwint(&mac->chip); -} + if (!test_bit(ZD_DEVICE_RUNNING, &mac->flags)) + return; + if (!mac->vif || mac->vif->type != NL80211_IFTYPE_AP) + return; + /* + * Send out buffered broad- and multicast frames. + */ + while (!ieee80211_queue_stopped(mac->hw, 0)) { + skb = ieee80211_get_buffered_bc(mac->hw, mac->vif); + if (!skb) + break; + zd_op_tx(mac->hw, NULL, skb); + } -static void set_multicast_hash_handler(struct work_struct *work) -{ - struct zd_mac *mac = - container_of(work, struct zd_mac, set_multicast_hash_work); - struct zd_mc_hash hash; + /* + * Fetch next beacon so that tim_count is updated. + */ + beacon = ieee80211_beacon_get(mac->hw, mac->vif); + if (beacon) + zd_mac_config_beacon(mac->hw, beacon, true); spin_lock_irq(&mac->lock); - hash = mac->multicast_hash; + mac->beacon.last_update = jiffies; spin_unlock_irq(&mac->lock); - - zd_chip_set_multicast_hash(&mac->chip, &hash); } -static void set_rx_filter_handler(struct work_struct *work) +static void zd_process_intr(struct work_struct *work) { - struct zd_mac *mac = - container_of(work, struct zd_mac, set_rx_filter_work); - int r; + u16 int_status; + unsigned long flags; + struct zd_mac *mac = container_of(work, struct zd_mac, process_intr); - dev_dbg_f(zd_mac_dev(mac), "\n"); - r = set_rx_filter(mac); - if (r) - dev_err(zd_mac_dev(mac), "set_rx_filter_handler error %d\n", r); + spin_lock_irqsave(&mac->lock, flags); + int_status = le16_to_cpu(*(__le16 *)(mac->intr_buffer + 4)); + spin_unlock_irqrestore(&mac->lock, flags); + + if (int_status & INT_CFG_NEXT_BCN) { + /*dev_dbg_f_limit(zd_mac_dev(mac), "INT_CFG_NEXT_BCN\n");*/ + zd_beacon_done(mac); + } else { + dev_dbg_f(zd_mac_dev(mac), "Unsupported interrupt\n"); + } + + zd_chip_enable_hwint(&mac->chip); } + static u64 zd_op_prepare_multicast(struct ieee80211_hw *hw, struct netdev_hw_addr_list *mc_list) { @@ -979,6 +1243,7 @@ static void zd_op_configure_filter(struct ieee80211_hw *hw, }; struct zd_mac *mac = zd_hw_mac(hw); unsigned long flags; + int r; /* Only deal with supported flags */ changed_flags &= SUPPORTED_FIF_FLAGS; @@ -1000,11 +1265,13 @@ static void zd_op_configure_filter(struct ieee80211_hw *hw, mac->multicast_hash = hash; spin_unlock_irqrestore(&mac->lock, flags); - /* XXX: these can be called here now, can sleep now! */ - queue_work(zd_workqueue, &mac->set_multicast_hash_work); + zd_chip_set_multicast_hash(&mac->chip, &hash); - if (changed_flags & FIF_CONTROL) - queue_work(zd_workqueue, &mac->set_rx_filter_work); + if (changed_flags & FIF_CONTROL) { + r = set_rx_filter(mac); + if (r) + dev_err(zd_mac_dev(mac), "set_rx_filter error %d\n", r); + } /* no handling required for FIF_OTHER_BSS as we don't currently * do BSSID filtering */ @@ -1016,20 +1283,9 @@ static void zd_op_configure_filter(struct ieee80211_hw *hw, * time. */ } -static void set_rts_cts_work(struct work_struct *work) +static void set_rts_cts(struct zd_mac *mac, unsigned int short_preamble) { - struct zd_mac *mac = - container_of(work, struct zd_mac, set_rts_cts_work); - unsigned long flags; - unsigned int short_preamble; - mutex_lock(&mac->chip.mutex); - - spin_lock_irqsave(&mac->lock, flags); - mac->updating_rts_rate = 0; - short_preamble = mac->short_preamble; - spin_unlock_irqrestore(&mac->lock, flags); - zd_chip_set_rts_cts_rate_locked(&mac->chip, short_preamble); mutex_unlock(&mac->chip.mutex); } @@ -1040,33 +1296,41 @@ static void zd_op_bss_info_changed(struct ieee80211_hw *hw, u32 changes) { struct zd_mac *mac = zd_hw_mac(hw); - unsigned long flags; int associated; dev_dbg_f(zd_mac_dev(mac), "changes: %x\n", changes); if (mac->type == NL80211_IFTYPE_MESH_POINT || - mac->type == NL80211_IFTYPE_ADHOC) { + mac->type == NL80211_IFTYPE_ADHOC || + mac->type == NL80211_IFTYPE_AP) { associated = true; if (changes & BSS_CHANGED_BEACON) { struct sk_buff *beacon = ieee80211_beacon_get(hw, vif); if (beacon) { - zd_mac_config_beacon(hw, beacon); - kfree_skb(beacon); + zd_chip_disable_hwint(&mac->chip); + zd_mac_config_beacon(hw, beacon, false); + zd_chip_enable_hwint(&mac->chip); } } if (changes & BSS_CHANGED_BEACON_ENABLED) { - u32 interval; + u16 interval = 0; + u8 period = 0; + + if (bss_conf->enable_beacon) { + period = bss_conf->dtim_period; + interval = bss_conf->beacon_int; + } - if (bss_conf->enable_beacon) - interval = BCN_MODE_IBSS | - bss_conf->beacon_int; - else - interval = 0; + spin_lock_irq(&mac->lock); + mac->beacon.period = period; + mac->beacon.interval = interval; + mac->beacon.last_update = jiffies; + spin_unlock_irq(&mac->lock); - zd_set_beacon_interval(&mac->chip, interval); + zd_set_beacon_interval(&mac->chip, interval, period, + mac->type); } } else associated = is_valid_ether_addr(bss_conf->bssid); @@ -1078,19 +1342,15 @@ static void zd_op_bss_info_changed(struct ieee80211_hw *hw, /* TODO: do hardware bssid filtering */ if (changes & BSS_CHANGED_ERP_PREAMBLE) { - spin_lock_irqsave(&mac->lock, flags); + spin_lock_irq(&mac->lock); mac->short_preamble = bss_conf->use_short_preamble; - if (!mac->updating_rts_rate) { - mac->updating_rts_rate = 1; - /* FIXME: should disable TX here, until work has - * completed and RTS_CTS reg is updated */ - queue_work(zd_workqueue, &mac->set_rts_cts_work); - } - spin_unlock_irqrestore(&mac->lock, flags); + spin_unlock_irq(&mac->lock); + + set_rts_cts(mac, bss_conf->use_short_preamble); } } -static u64 zd_op_get_tsf(struct ieee80211_hw *hw) +static u64 zd_op_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { struct zd_mac *mac = zd_hw_mac(hw); return zd_chip_get_tsf(&mac->chip); @@ -1138,12 +1398,15 @@ struct ieee80211_hw *zd_mac_alloc_hw(struct usb_interface *intf) hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &mac->band; hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | - IEEE80211_HW_SIGNAL_UNSPEC; + IEEE80211_HW_SIGNAL_UNSPEC | + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_MFP_CAPABLE; hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_MESH_POINT) | BIT(NL80211_IFTYPE_STATION) | - BIT(NL80211_IFTYPE_ADHOC); + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP); hw->max_signal = 100; hw->queues = 1; @@ -1160,15 +1423,86 @@ struct ieee80211_hw *zd_mac_alloc_hw(struct usb_interface *intf) zd_chip_init(&mac->chip, hw, intf); housekeeping_init(mac); - INIT_WORK(&mac->set_multicast_hash_work, set_multicast_hash_handler); - INIT_WORK(&mac->set_rts_cts_work, set_rts_cts_work); - INIT_WORK(&mac->set_rx_filter_work, set_rx_filter_handler); + beacon_init(mac); INIT_WORK(&mac->process_intr, zd_process_intr); SET_IEEE80211_DEV(hw, &intf->dev); return hw; } +#define BEACON_WATCHDOG_DELAY round_jiffies_relative(HZ) + +static void beacon_watchdog_handler(struct work_struct *work) +{ + struct zd_mac *mac = + container_of(work, struct zd_mac, beacon.watchdog_work.work); + struct sk_buff *beacon; + unsigned long timeout; + int interval, period; + + if (!test_bit(ZD_DEVICE_RUNNING, &mac->flags)) + goto rearm; + if (mac->type != NL80211_IFTYPE_AP || !mac->vif) + goto rearm; + + spin_lock_irq(&mac->lock); + interval = mac->beacon.interval; + period = mac->beacon.period; + timeout = mac->beacon.last_update + + msecs_to_jiffies(interval * 1024 / 1000) * 3; + spin_unlock_irq(&mac->lock); + + if (interval > 0 && time_is_before_jiffies(timeout)) { + dev_dbg_f(zd_mac_dev(mac), "beacon interrupt stalled, " + "restarting. " + "(interval: %d, dtim: %d)\n", + interval, period); + + zd_chip_disable_hwint(&mac->chip); + + beacon = ieee80211_beacon_get(mac->hw, mac->vif); + if (beacon) { + zd_mac_free_cur_beacon(mac); + + zd_mac_config_beacon(mac->hw, beacon, false); + } + + zd_set_beacon_interval(&mac->chip, interval, period, mac->type); + + zd_chip_enable_hwint(&mac->chip); + + spin_lock_irq(&mac->lock); + mac->beacon.last_update = jiffies; + spin_unlock_irq(&mac->lock); + } + +rearm: + queue_delayed_work(zd_workqueue, &mac->beacon.watchdog_work, + BEACON_WATCHDOG_DELAY); +} + +static void beacon_init(struct zd_mac *mac) +{ + INIT_DELAYED_WORK(&mac->beacon.watchdog_work, beacon_watchdog_handler); +} + +static void beacon_enable(struct zd_mac *mac) +{ + dev_dbg_f(zd_mac_dev(mac), "\n"); + + mac->beacon.last_update = jiffies; + queue_delayed_work(zd_workqueue, &mac->beacon.watchdog_work, + BEACON_WATCHDOG_DELAY); +} + +static void beacon_disable(struct zd_mac *mac) +{ + dev_dbg_f(zd_mac_dev(mac), "\n"); + cancel_delayed_work_sync(&mac->beacon.watchdog_work); + + zd_mac_free_cur_beacon(mac); +} + #define LINK_LED_WORK_DELAY HZ static void link_led_handler(struct work_struct *work) @@ -1179,6 +1513,9 @@ static void link_led_handler(struct work_struct *work) int is_associated; int r; + if (!test_bit(ZD_DEVICE_RUNNING, &mac->flags)) + goto requeue; + spin_lock_irq(&mac->lock); is_associated = mac->associated; spin_unlock_irq(&mac->lock); @@ -1188,6 +1525,7 @@ static void link_led_handler(struct work_struct *work) if (r) dev_dbg_f(zd_mac_dev(mac), "zd_chip_control_leds error %d\n", r); +requeue: queue_delayed_work(zd_workqueue, &mac->housekeeping.link_led_work, LINK_LED_WORK_DELAY); } @@ -1207,7 +1545,6 @@ static void housekeeping_enable(struct zd_mac *mac) static void housekeeping_disable(struct zd_mac *mac) { dev_dbg_f(zd_mac_dev(mac), "\n"); - cancel_rearming_delayed_workqueue(zd_workqueue, - &mac->housekeeping.link_led_work); + cancel_delayed_work_sync(&mac->housekeeping.link_led_work); zd_chip_control_leds(&mac->chip, ZD_LED_OFF); } |
