| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Implementation of mac80211 API. |
| * |
| * Copyright (c) 2017-2019, Silicon Laboratories, Inc. |
| * Copyright (c) 2010, ST-Ericsson |
| */ |
| #include <net/mac80211.h> |
| |
| #include "sta.h" |
| #include "wfx.h" |
| #include "key.h" |
| #include "scan.h" |
| #include "hif_tx_mib.h" |
| |
| #define TXOP_UNIT 32 |
| |
| int wfx_fwd_probe_req(struct wfx_vif *wvif, bool enable) |
| { |
| wvif->fwd_probe_req = enable; |
| return hif_set_rx_filter(wvif, wvif->filter_bssid, |
| wvif->fwd_probe_req); |
| } |
| |
| static int wfx_set_tim_impl(struct wfx_vif *wvif, bool aid0_bit_set) |
| { |
| struct sk_buff *skb; |
| struct hif_ie_flags target_frame = { |
| .beacon = 1, |
| }; |
| u16 tim_offset, tim_length; |
| u8 *tim_ptr; |
| |
| skb = ieee80211_beacon_get_tim(wvif->wdev->hw, wvif->vif, |
| &tim_offset, &tim_length); |
| if (!skb) |
| return -ENOENT; |
| tim_ptr = skb->data + tim_offset; |
| |
| if (tim_offset && tim_length >= 6) { |
| /* Ignore DTIM count from mac80211: |
| * firmware handles DTIM internally. |
| */ |
| tim_ptr[2] = 0; |
| |
| /* Set/reset aid0 bit */ |
| if (aid0_bit_set) |
| tim_ptr[4] |= 1; |
| else |
| tim_ptr[4] &= ~1; |
| } |
| |
| hif_update_ie(wvif, &target_frame, tim_ptr, tim_length); |
| dev_kfree_skb(skb); |
| |
| return 0; |
| } |
| |
| static void wfx_mcast_start_work(struct work_struct *work) |
| { |
| struct wfx_vif *wvif = container_of(work, struct wfx_vif, mcast_start_work); |
| |
| cancel_work_sync(&wvif->mcast_stop_work); |
| if (!wvif->aid0_bit_set) { |
| wfx_tx_lock_flush(wvif->wdev); |
| wfx_set_tim_impl(wvif, true); |
| wvif->aid0_bit_set = true; |
| mod_timer(&wvif->mcast_timeout, TU_TO_JIFFIES(1000)); |
| wfx_tx_unlock(wvif->wdev); |
| } |
| } |
| |
| static void wfx_mcast_stop_work(struct work_struct *work) |
| { |
| struct wfx_vif *wvif = container_of(work, struct wfx_vif, mcast_stop_work); |
| |
| if (wvif->aid0_bit_set) { |
| del_timer_sync(&wvif->mcast_timeout); |
| wfx_tx_lock_flush(wvif->wdev); |
| wvif->aid0_bit_set = false; |
| wfx_set_tim_impl(wvif, false); |
| wfx_tx_unlock(wvif->wdev); |
| } |
| } |
| |
| static void wfx_mcast_timeout(struct timer_list *t) |
| { |
| struct wfx_vif *wvif = from_timer(wvif, t, mcast_timeout); |
| |
| dev_warn(wvif->wdev->dev, "multicast delivery timeout\n"); |
| spin_lock_bh(&wvif->ps_state_lock); |
| wvif->mcast_tx = wvif->aid0_bit_set && wvif->mcast_buffered; |
| if (wvif->mcast_tx) |
| wfx_bh_request_tx(wvif->wdev); |
| spin_unlock_bh(&wvif->ps_state_lock); |
| } |
| |
| int wfx_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) |
| { |
| int i; |
| struct wfx_dev *wdev = hw->priv; |
| struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv; |
| // FIXME: parameters are set by kernel juste after interface_add. |
| // Keep struct hif_req_edca_queue_params blank? |
| struct hif_req_edca_queue_params default_edca_params[] = { |
| [IEEE80211_AC_VO] = { |
| .queue_id = HIF_QUEUE_ID_VOICE, |
| .aifsn = 2, |
| .cw_min = 3, |
| .cw_max = 7, |
| .tx_op_limit = TXOP_UNIT * 47, |
| }, |
| [IEEE80211_AC_VI] = { |
| .queue_id = HIF_QUEUE_ID_VIDEO, |
| .aifsn = 2, |
| .cw_min = 7, |
| .cw_max = 15, |
| .tx_op_limit = TXOP_UNIT * 94, |
| }, |
| [IEEE80211_AC_BE] = { |
| .queue_id = HIF_QUEUE_ID_BESTEFFORT, |
| .aifsn = 3, |
| .cw_min = 15, |
| .cw_max = 1023, |
| .tx_op_limit = TXOP_UNIT * 0, |
| }, |
| [IEEE80211_AC_BK] = { |
| .queue_id = HIF_QUEUE_ID_BACKGROUND, |
| .aifsn = 7, |
| .cw_min = 15, |
| .cw_max = 1023, |
| .tx_op_limit = TXOP_UNIT * 0, |
| }, |
| }; |
| |
| if (wfx_api_older_than(wdev, 2, 0)) { |
| default_edca_params[IEEE80211_AC_BE].queue_id = HIF_QUEUE_ID_BACKGROUND; |
| default_edca_params[IEEE80211_AC_BK].queue_id = HIF_QUEUE_ID_BESTEFFORT; |
| } |
| |
| mutex_lock(&wdev->conf_mutex); |
| |
| for (i = 0; i < ARRAY_SIZE(wdev->vif); i++) { |
| if (!wdev->vif[i]) { |
| wdev->vif[i] = vif; |
| wvif->id = i; |
| break; |
| } |
| } |
| if (i == ARRAY_SIZE(wdev->vif)) { |
| mutex_unlock(&wdev->conf_mutex); |
| return -EOPNOTSUPP; |
| } |
| wvif->vif = vif; |
| wvif->wdev = wdev; |
| |
| INIT_WORK(&wvif->link_id_work, wfx_link_id_work); |
| INIT_DELAYED_WORK(&wvif->link_id_gc_work, wfx_link_id_gc_work); |
| |
| spin_lock_init(&wvif->ps_state_lock); |
| |
| INIT_WORK(&wvif->mcast_start_work, wfx_mcast_start_work); |
| INIT_WORK(&wvif->mcast_stop_work, wfx_mcast_stop_work); |
| timer_setup(&wvif->mcast_timeout, wfx_mcast_timeout, 0); |
| |
| wvif->wep_default_key_id = -1; |
| INIT_WORK(&wvif->wep_key_work, wfx_wep_key_work); |
| |
| sema_init(&wvif->scan.lock, 1); |
| INIT_WORK(&wvif->scan.work, wfx_scan_work); |
| INIT_DELAYED_WORK(&wvif->scan.timeout, wfx_scan_timeout); |
| |
| mutex_unlock(&wdev->conf_mutex); |
| BUG_ON(ARRAY_SIZE(default_edca_params) != ARRAY_SIZE(wvif->edca.params)); |
| for (i = 0; i < IEEE80211_NUM_ACS; i++) |
| memcpy(&wvif->edca.params[i], &default_edca_params[i], sizeof(default_edca_params[i])); |
| tx_policy_init(wvif); |
| return 0; |
| } |
| |
| void wfx_remove_interface(struct ieee80211_hw *hw, |
| struct ieee80211_vif *vif) |
| { |
| struct wfx_vif *wvif = (struct wfx_vif *) vif->drv_priv; |
| |
| wfx_tx_queues_wait_empty_vif(wvif); |
| cancel_delayed_work_sync(&wvif->link_id_gc_work); |
| del_timer_sync(&wvif->mcast_timeout); |
| } |
| |
| int wfx_start(struct ieee80211_hw *hw) |
| { |
| return 0; |
| } |
| |
| void wfx_stop(struct ieee80211_hw *hw) |
| { |
| struct wfx_dev *wdev = hw->priv; |
| |
| wfx_tx_lock_flush(wdev); |
| mutex_lock(&wdev->conf_mutex); |
| wfx_tx_queues_clear(wdev); |
| mutex_unlock(&wdev->conf_mutex); |
| wfx_tx_unlock(wdev); |
| WARN(atomic_read(&wdev->tx_lock), "tx_lock is locked"); |
| } |