diff options
Diffstat (limited to 'virtio_wifi.c')
-rw-r--r-- | virtio_wifi.c | 917 |
1 files changed, 0 insertions, 917 deletions
diff --git a/virtio_wifi.c b/virtio_wifi.c deleted file mode 100644 index 9002b57..0000000 --- a/virtio_wifi.c +++ /dev/null @@ -1,917 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ - -/* - * A virtio mac80211 WLAN driver that intendes to facilitates - * Wifi TX/RX between guest system and QEMU emulator. - */ - -#include "defconfig_test.h" - -#include <linux/debugfs.h> -#include <linux/interrupt.h> -#include <linux/ktime.h> -#include <linux/list.h> -#include <linux/module.h> -#include <linux/netdevice.h> -#include <linux/platform_device.h> -#include <linux/scatterlist.h> -#include <linux/slab.h> -#include <linux/virtio.h> -#include <linux/virtio_config.h> -#include <linux/virtio_types.h> -#include <net/mac80211.h> - -#include "virtio_wifi.h" - -#define VIRTIO_WIFI_RX_PADDING (NET_IP_ALIGN + NET_SKB_PAD) -#define IEEE80211_HEADER_MIN_LEN 24 -#define IEEE80211_HEADER_LEN 32 - -#define CHAN2G(_freq) { \ - .band = NL80211_BAND_2GHZ, \ - .center_freq = (_freq), \ - .hw_value = (_freq), \ - .max_power = 20, \ -} - -#define CHAN5G(_freq) { \ - .band = NL80211_BAND_5GHZ, \ - .center_freq = (_freq), \ - .hw_value = (_freq), .max_power = 20, \ -} - -static const int napi_weight = NAPI_POLL_WEIGHT; - -static const struct ieee80211_channel virtio_wifi_channels_2ghz[] = { - CHAN2G(2412), /* Channel 1 */ - CHAN2G(2417), /* Channel 2 */ - CHAN2G(2422), /* Channel 3 */ - CHAN2G(2427), /* Channel 4 */ - CHAN2G(2432), /* Channel 5 */ - CHAN2G(2437), /* Channel 6 */ - CHAN2G(2442), /* Channel 7 */ - CHAN2G(2447), /* Channel 8 */ - CHAN2G(2452), /* Channel 9 */ - CHAN2G(2457), /* Channel 10 */ - CHAN2G(2462), /* Channel 11 */ - CHAN2G(2467), /* Channel 12 */ - CHAN2G(2472), /* Channel 13 */ - CHAN2G(2484), /* Channel 14 */ -}; - -static const struct ieee80211_channel virtio_wifi_channels_5ghz[] = { - CHAN5G(5180), /* Channel 36 */ -}; - -static const struct ieee80211_rate virtio_wifi_rates[] = { - { .bitrate = 10 }, - { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, - { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, - { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, - { .bitrate = 60 }, - { .bitrate = 90 }, - { .bitrate = 120 }, - { .bitrate = 180 }, - { .bitrate = 240 }, - { .bitrate = 360 }, - { .bitrate = 480 }, - { .bitrate = 540 } -}; - -struct virtio_wifi_config { - /* The config defining mac address */ - __u8 mac[ETH_ALEN]; - /* Default maximum transmit unit advice */ - __u16 mtu; - -} __packed; - -/* Internal representation of a send virtqueue */ -struct send_queue { - /* Virtqueue associated with this send _queue */ - struct virtqueue *vq; - - /* TX: fragments + linear part + virtio header */ - struct scatterlist sg[MAX_SKB_FRAGS + 2]; - - struct napi_struct napi; -}; - -/* Internal representation of a receive virtqueue */ -struct receive_queue { - /* Virtqueue associated with this receive_queue */ - struct virtqueue *vq; - - /* RX: fragments + linear part + virtio header */ - struct scatterlist sg[MAX_SKB_FRAGS + 2]; - - /* Page frag for packet buffer allocation. */ - struct page_frag alloc_frag; - - struct napi_struct napi; -}; - -struct virtio_wifi_info { - struct virtio_device *vdev; - struct ieee80211_hw *hw; - struct virtqueue *cvq; - struct send_queue *sq_sta; - struct send_queue *sq_p2p; - struct receive_queue *rq_sta; - struct net_device napi_dev; - unsigned int status; - int hdr_len; - int max_queue_pairs; - unsigned int rx_filter; - struct mutex mutex; - struct ieee80211_channel channels_2ghz[ - ARRAY_SIZE(virtio_wifi_channels_2ghz)]; - struct ieee80211_channel channels_5ghz[ - ARRAY_SIZE(virtio_wifi_channels_5ghz)]; - struct ieee80211_rate rates[ - ARRAY_SIZE(virtio_wifi_rates)]; - struct ieee80211_supported_band bands[NUM_NL80211_BANDS]; - struct ieee80211_channel *channel; - bool started; - bool associated; - /* Work struct for refilling if we run low on memory. */ - struct delayed_work refill_work; - struct hrtimer beacon_timer; - /* beacon interval in us */ - u64 beacon_int; -}; - -static inline u64 virtio_wifi_get_tsf_raw(void) -{ - return ktime_to_us(ktime_get_boottime()); -} - -static void virtqueue_napi_schedule(struct napi_struct *napi, - struct virtqueue *vq) -{ - if (napi_schedule_prep(napi)) { - virtqueue_disable_cb(vq); - __napi_schedule(napi); - } -} - -static void virtqueue_napi_complete(struct napi_struct *napi, - struct virtqueue *vq, - int processed) -{ - int opaque; - - opaque = virtqueue_enable_cb_prepare(vq); - if (napi_complete_done(napi, processed)) { - if (unlikely(virtqueue_poll(vq, opaque))) - virtqueue_napi_schedule(napi, vq); - } else - virtqueue_disable_cb(vq); -} - -static void virtio_wifi_napi_enable(struct virtqueue *vq, - struct napi_struct *napi) -{ - napi_enable(napi); - - /* If all buffers were filled by other side before we napi_enabled, we - * won't get another interrupt, so process any outstanding packets now. - * Call local_bh_enable after to trigger softIRQ processing. - */ - local_bh_disable(); - virtqueue_napi_schedule(napi, vq); - local_bh_enable(); -} - -static void free_old_xmit_skbs(struct send_queue *sq) -{ - struct sk_buff *skb = NULL; - unsigned int len; - - while ((skb = virtqueue_get_buf(sq->vq, &len)) != NULL) - dev_consume_skb_any(skb); -} - -static int xmit_skb(struct send_queue *sq, struct sk_buff *skb) -{ - int num_sg; - - sg_init_table(sq->sg, skb_shinfo(skb)->nr_frags + 1); - num_sg = skb_to_sgvec(skb, sq->sg, 0, skb->len); - if (unlikely(num_sg < 0)) - return num_sg; - - return virtqueue_add_outbuf(sq->vq, sq->sg, num_sg, skb, GFP_ATOMIC); -} - -static void virtio_wifi_tx(struct ieee80211_hw *hw, - struct ieee80211_tx_control *control, - struct sk_buff *skb) -{ - struct virtio_wifi_info *vi = hw->priv; - struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); - struct ieee80211_channel *channel; - int err; - struct sk_buff* copy; - struct send_queue *sq; - bool kick = !netdev_xmit_more(); - - channel = vi->channel; - if (WARN_ON(skb->len < 10) || !vi->started) { - ieee80211_free_txskb(hw, skb); - return; - } - - if (WARN(!channel, "TX w/o channel - queue = %d\n", txi->hw_queue)) { - ieee80211_free_txskb(hw, skb); - return; - } - - if (ieee80211_hw_check(hw, SUPPORTS_RC_TABLE)) - ieee80211_get_tx_rates(txi->control.vif, control->sta, skb, - txi->control.rates, - ARRAY_SIZE(txi->control.rates)); - - if (txi->control.vif->type == NL80211_IFTYPE_STATION) - sq = vi->sq_sta; - else - sq = vi->sq_p2p; - free_old_xmit_skbs(sq); - copy = skb_copy(skb, GFP_ATOMIC); - err = xmit_skb(sq, copy); - if (unlikely(err)) { - dev_kfree_skb_any(copy); - return; - } - ieee80211_tx_info_clear_status(txi); - /* frame was transmitted at most favorable rate at first attempt */ - txi->control.rates[0].count = 1; - txi->control.rates[1].idx = -1; - // Always assume that tx is acked. - txi->flags |= IEEE80211_TX_STAT_ACK; - ieee80211_tx_status_ni(hw, skb); - skb_orphan(copy); - nf_reset_ct(copy); - - /* - * If running out of space, stop queue to avoid getting packets that we - * are then unable to transmit. - */ - if (sq->vq->num_free < 2 + MAX_SKB_FRAGS) - ieee80211_stop_queues(hw); - - if (kick) - virtqueue_kick(sq->vq); -} - -/* - * Returns false if we couldn't fill entirely (OOM). - * - * Normally run in the receive path, but can also be run from virtio_wifi_start - * before we're receiving packets, or from refill_work which is - * careful to disable receiving (using napi_disable). - */ -static bool try_fill_recv(struct virtio_wifi_info *vi, - struct receive_queue *rq, - gfp_t gfp) -{ - int err, len; - bool oom; - char *buf; - struct page_frag *alloc_frag; - - do { - alloc_frag = &rq->alloc_frag; - len = VIRTIO_WIFI_RX_PADDING + IEEE80211_MAX_FRAME_LEN; - - len = SKB_DATA_ALIGN(len) + SKB_DATA_ALIGN( - sizeof(struct skb_shared_info)); - if (unlikely(!skb_page_frag_refill(len, alloc_frag, gfp))) { - err = -ENOMEM; - return false; - } - buf = (char *)page_address(alloc_frag->page) + - alloc_frag->offset; - get_page(alloc_frag->page); - alloc_frag->offset += len; - sg_init_one(rq->sg, buf + VIRTIO_WIFI_RX_PADDING, - vi->hdr_len + IEEE80211_MAX_FRAME_LEN); - err = virtqueue_add_inbuf(rq->vq, rq->sg, 1, buf, gfp); - if (err < 0) - put_page(virt_to_head_page(buf)); - if (err) - return false; - } while (rq->vq->num_free); - virtqueue_kick(rq->vq); - oom = err == -ENOMEM; - - return !oom; -} - -static int virtio_wifi_start(struct ieee80211_hw *hw) -{ - struct virtio_wifi_info *vi = hw->priv; - - mutex_lock(&vi->mutex); - vi->started = true; - mutex_unlock(&vi->mutex); - - if (!try_fill_recv(vi, vi->rq_sta, GFP_KERNEL)) - schedule_delayed_work(&vi->refill_work, 0); - - virtio_wifi_napi_enable(vi->rq_sta->vq, &vi->rq_sta->napi); - return 0; -} - -static void virtio_wifi_stop(struct ieee80211_hw *hw) -{ - struct virtio_wifi_info *vi = hw->priv; - - mutex_lock(&vi->mutex); - vi->started = false; - mutex_unlock(&vi->mutex); - cancel_delayed_work_sync(&vi->refill_work); - napi_disable(&vi->rq_sta->napi); - hrtimer_cancel(&vi->beacon_timer); -} - -static int virtio_wifi_add_interface(struct ieee80211_hw *hw, - struct ieee80211_vif *vif) -{ - struct virtio_wifi_info *vi = hw->priv; - int ret = 0; - - mutex_lock(&vi->mutex); - vif->cab_queue = 0; - vif->hw_queue[IEEE80211_AC_VO] = 0; - vif->hw_queue[IEEE80211_AC_VI] = 1; - vif->hw_queue[IEEE80211_AC_BE] = 2; - vif->hw_queue[IEEE80211_AC_BK] = 3; - mutex_unlock(&vi->mutex); - return ret; -} - -static int virtio_wifi_change_interface(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - enum nl80211_iftype newtype, - bool newp2p) -{ - /* - * interface may change from non-AP to AP in - * which case this needs to be set up again - */ - vif->cab_queue = 0; - - return 0; -} - -static int virtio_wifi_hwconfig(struct ieee80211_hw *hw, u32 changed) -{ - // TODO (wdu@) Implement channel switch based on hw->conf - struct virtio_wifi_info *vi = hw->priv; - struct ieee80211_conf *conf = &hw->conf; - - mutex_lock(&vi->mutex); - vi->channel = conf->chandef.chan; - mutex_unlock(&vi->mutex); - if (!vi->started || !vi->beacon_int) - hrtimer_cancel(&vi->beacon_timer); - else if (!hrtimer_is_queued(&vi->beacon_timer)) { - u64 tsf = virtio_wifi_get_tsf_raw(); - u32 bcn_int = vi->beacon_int; - u64 until_tbtt = bcn_int - do_div(tsf, bcn_int); - - hrtimer_start(&vi->beacon_timer, ns_to_ktime( - until_tbtt * NSEC_PER_USEC), HRTIMER_MODE_REL); - } - - return 0; -} - -static void virtio_wifi_bss_info_changed(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - struct ieee80211_bss_conf *info, - u32 changed) -{ - struct virtio_wifi_info *vi = hw->priv; - - if (changed & BSS_CHANGED_ASSOC) { - mutex_lock(&vi->mutex); - if (info->assoc) - vi->associated = true; - else - vi->associated = false; - mutex_unlock(&vi->mutex); - } - - if (changed & BSS_CHANGED_BEACON_ENABLED) { - if (vi->started && !hrtimer_is_queued(&vi->beacon_timer) && - info->enable_beacon) { - u64 tsf, until_tbtt; - u32 bcn_int; - - vi->beacon_int = info->beacon_int * 1024; - tsf = virtio_wifi_get_tsf_raw(); - bcn_int = vi->beacon_int; - until_tbtt = bcn_int - do_div(tsf, bcn_int); - hrtimer_start(&vi->beacon_timer, ns_to_ktime( - until_tbtt * 1000), HRTIMER_MODE_REL_SOFT); - } - } -} - -static void virtio_wifi_remove_interface(struct ieee80211_hw *hw, - struct ieee80211_vif *vif) -{ - // Un-implemented -} - -static void virtio_wifi_configure_filter(struct ieee80211_hw *hw, - unsigned int changed_flags, - unsigned int *total_flags, - u64 multicast) -{ - struct virtio_wifi_info *vi = hw->priv; - - vi->rx_filter = 0; - if (*total_flags & FIF_ALLMULTI) - vi->rx_filter |= FIF_ALLMULTI; - *total_flags = vi->rx_filter; -} - -static void virtio_wifi_flush(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - u32 queues, - bool drop) -{ - // Un-implemented -} - -static int virtio_wifi_ampdu_action(struct ieee80211_hw *hw, - struct ieee80211_vif *vif, - struct ieee80211_ampdu_params *params) -{ - struct ieee80211_sta *sta = params->sta; - enum ieee80211_ampdu_mlme_action action = params->action; - u16 tid = params->tid; - - switch (action) { - case IEEE80211_AMPDU_TX_START: - ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); - break; - case IEEE80211_AMPDU_TX_STOP_CONT: - case IEEE80211_AMPDU_TX_STOP_FLUSH: - case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: - ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); - break; - case IEEE80211_AMPDU_TX_OPERATIONAL: - break; - case IEEE80211_AMPDU_RX_START: - case IEEE80211_AMPDU_RX_STOP: - break; - default: - return -EOPNOTSUPP; - } - return 0; -} - -static const struct ieee80211_ops virtio_wifi_ops = { - .start = virtio_wifi_start, - .stop = virtio_wifi_stop, - .tx = virtio_wifi_tx, - .add_interface = virtio_wifi_add_interface, - .change_interface = virtio_wifi_change_interface, - .remove_interface = virtio_wifi_remove_interface, - .config = virtio_wifi_hwconfig, - .bss_info_changed = virtio_wifi_bss_info_changed, - .configure_filter = virtio_wifi_configure_filter, - .flush = virtio_wifi_flush, - .ampdu_action = virtio_wifi_ampdu_action, -}; - -static void virtio_wifi_refill_work(struct work_struct *work) -{ - struct virtio_wifi_info *vi = - container_of(work, struct virtio_wifi_info, refill_work.work); - bool still_empty; - - napi_disable(&vi->rq_sta->napi); - still_empty = !try_fill_recv(vi, vi->rq_sta, GFP_KERNEL); - virtio_wifi_napi_enable(vi->rq_sta->vq, &vi->rq_sta->napi); - /* In theory, this can happen: if we don't get any buffers in - * we will *never* try to fill again. - */ - if (still_empty) - schedule_delayed_work(&vi->refill_work, HZ / 2); -} - -static int receive_buf(struct virtio_wifi_info *vi, - struct receive_queue *rq, - void *buf, - unsigned int len) -{ - struct ieee80211_rx_status rx_status; - struct ieee80211_hdr *hdr; - unsigned int header_offset = VIRTIO_WIFI_RX_PADDING; - unsigned int headroom = vi->hdr_len + header_offset; - unsigned int buflen = SKB_DATA_ALIGN(IEEE80211_MAX_FRAME_LEN); - struct page *page = virt_to_head_page(buf); - struct sk_buff *skb = build_skb(buf, buflen); - - if (!skb) { - put_page(page); - return -1; - } - - skb_reserve(skb, headroom); - skb_put(skb, len); - skb->ip_summed = CHECKSUM_UNNECESSARY; - skb->protocol = htons(ETH_P_802_2); - hdr = (void *)skb->data; - - if (skb->len < IEEE80211_HEADER_MIN_LEN) { - put_page(page); - return 0; - } - - if (ieee80211_is_probe_resp(hdr->frame_control)) { - struct ieee80211_mgmt *mgmt = - (struct ieee80211_mgmt *)skb->data; - - mgmt->u.probe_resp.timestamp = - cpu_to_le64(virtio_wifi_get_tsf_raw()); - } - memset(&rx_status, 0, sizeof(rx_status)); - rx_status.flag |= RX_FLAG_MACTIME_END; - rx_status.mactime = virtio_wifi_get_tsf_raw(); - rx_status.freq = vi->channel->center_freq; - rx_status.band = vi->channel->band; - rx_status.bw = RATE_INFO_BW_20; - rx_status.signal = -50; - memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); - ieee80211_rx(vi->hw, skb); - return len; -} - -static int virtio_wifi_receive(struct receive_queue *rq, int budget) -{ - struct virtio_wifi_info *vi = rq->vq->vdev->priv; - unsigned int len, received = 0; - void *buf; - - while (received < budget && - (buf = virtqueue_get_buf(rq->vq, &len)) != NULL) { - if (vi->started) - receive_buf(vi, rq, buf, len); - received++; - } - if (rq->vq->num_free > virtqueue_get_vring_size(rq->vq) / 2) { - if (!try_fill_recv(vi, rq, GFP_ATOMIC)) - schedule_delayed_work(&vi->refill_work, 0); - } - return received; -} - -static int virtio_wifi_poll(struct napi_struct *napi, int budget) -{ - struct receive_queue *rq = container_of(napi, - struct receive_queue, napi); - struct virtio_wifi_info *vi = rq->vq->vdev->priv; - unsigned int received; - - if (vi->sq_sta->vq->num_free >= 2 + MAX_SKB_FRAGS && - vi->sq_p2p->vq->num_free >= 2 + MAX_SKB_FRAGS) - ieee80211_wake_queues(vi->hw); - received = virtio_wifi_receive(rq, budget); - - /* Out of packets? */ - if (received < budget) - virtqueue_napi_complete(napi, rq->vq, received); - return received; -} - -static void virtio_wifi_rx_completed(struct virtqueue *vq) -{ - struct virtio_wifi_info *vi = vq->vdev->priv; - struct receive_queue *rq = vi->rq_sta; - - virtqueue_napi_schedule(&rq->napi, vq); -} - -static int virtio_wifi_init_vqs(struct virtio_wifi_info *vi) -{ - int err; - const int kTotalVqs = 4; - struct virtqueue *vqs[kTotalVqs]; - const char * const names[] = { "rx_sta", "tx_sta", "cvq", "tx_p2p" }; - vq_callback_t *callbacks[] = { - virtio_wifi_rx_completed, - NULL, - NULL, - NULL, - }; - vi->rq_sta = kzalloc(sizeof(*vi->rq_sta), GFP_KERNEL); - if (!vi->rq_sta) - goto err_rq; - vi->sq_sta = kzalloc(sizeof(*vi->sq_sta), GFP_KERNEL); - if (!vi->sq_sta) - goto err_sq; - vi->sq_p2p = kzalloc(sizeof(*vi->sq_p2p), GFP_KERNEL); - if (!vi->sq_p2p) - goto err_sq_p2p; - - netif_napi_add(&vi->napi_dev, &vi->rq_sta->napi, virtio_wifi_poll, - napi_weight); - sg_init_table(vi->rq_sta->sg, ARRAY_SIZE(vi->rq_sta->sg)); - sg_init_table(vi->sq_sta->sg, ARRAY_SIZE(vi->sq_sta->sg)); - sg_init_table(vi->sq_p2p->sg, ARRAY_SIZE(vi->sq_p2p->sg)); - - err = vi->vdev->config->find_vqs(vi->vdev, kTotalVqs, - vqs, callbacks, names, NULL, NULL); - if (!err) { - vi->rq_sta->vq = vqs[0]; - vi->sq_sta->vq = vqs[1]; - vi->cvq = vqs[2]; - vi->sq_p2p->vq = vqs[3]; - return 0; - } -err_sq_p2p: - kfree(vi->sq_p2p); -err_sq: - kfree(vi->sq_sta); -err_rq: - kfree(vi->rq_sta); - return -ENOMEM; -} - -static void virtio_wifi_del_vqs(struct virtio_wifi_info *vi) -{ - struct virtio_device *vdev = vi->vdev; - - vdev->config->del_vqs(vdev); - - kfree(vi->rq_sta); - kfree(vi->sq_sta); - kfree(vi->sq_p2p); -} - -static void virtio_wifi_remove(struct virtio_device *vdev) -{ - struct virtio_wifi_info *vi = vdev->priv; - - vi->vdev->config->reset(vi->vdev); - virtio_wifi_del_vqs(vi); - cancel_delayed_work_sync(&vi->refill_work); - ieee80211_free_hw(vi->hw); -} - -static int virtio_wifi_init_modes(struct virtio_wifi_info *vi) -{ - enum nl80211_band band; - - memcpy(vi->channels_2ghz, virtio_wifi_channels_2ghz, - sizeof(virtio_wifi_channels_2ghz)); - memcpy(vi->channels_5ghz, virtio_wifi_channels_5ghz, - sizeof(virtio_wifi_channels_5ghz)); - memcpy(vi->rates, virtio_wifi_rates, sizeof(virtio_wifi_rates)); - - for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) { - struct ieee80211_supported_band *sband = &vi->bands[band]; - - switch (band) { - case NL80211_BAND_2GHZ: - sband->channels = - (struct ieee80211_channel *)vi->channels_2ghz; - sband->n_channels = ARRAY_SIZE(vi->channels_2ghz); - sband->bitrates = (struct ieee80211_rate *)vi->rates; - sband->n_bitrates = ARRAY_SIZE(vi->rates); - break; - case NL80211_BAND_5GHZ: - sband->channels = - (struct ieee80211_channel *)vi->channels_5ghz; - sband->n_channels = ARRAY_SIZE(vi->channels_5ghz); - sband->bitrates = - (struct ieee80211_rate *)vi->rates + 4; - sband->n_bitrates = ARRAY_SIZE(vi->rates) - 4; - break; - default: - continue; - } - sband->ht_cap.ht_supported = true; - sband->ht_cap.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | - IEEE80211_HT_CAP_GRN_FLD | - IEEE80211_HT_CAP_SGI_20 | - IEEE80211_HT_CAP_SGI_40 | - IEEE80211_HT_CAP_DSSSCCK40; - sband->ht_cap.ampdu_factor = 0x3; - sband->ht_cap.ampdu_density = 0x6; - memset(&sband->ht_cap.mcs, 0, sizeof(sband->ht_cap.mcs)); - sband->ht_cap.mcs.rx_mask[0] = 0xff; - sband->ht_cap.mcs.rx_mask[1] = 0xff; - sband->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; - vi->hw->wiphy->bands[band] = sband; - } - return 0; -} - -static void virtio_wifi_beacon_tx(void *arg, u8 *mac, struct ieee80211_vif *vif) -{ - struct virtio_wifi_info *vi = arg; - struct send_queue *sq = vi->sq_p2p; - struct ieee80211_hw *hw = vi->hw; - struct ieee80211_tx_info *info; - struct ieee80211_mgmt *mgmt; - struct sk_buff *skb; - int err; - - if (vif->type != NL80211_IFTYPE_AP && - vif->type != NL80211_IFTYPE_MESH_POINT && - vif->type != NL80211_IFTYPE_ADHOC) - return; - - skb = ieee80211_beacon_get(hw, vif); - if (skb == NULL) - return; - info = IEEE80211_SKB_CB(skb); - if (ieee80211_hw_check(hw, SUPPORTS_RC_TABLE)) - ieee80211_get_tx_rates(vif, NULL, skb, info->control.rates, - ARRAY_SIZE(info->control.rates)); - mgmt = (struct ieee80211_mgmt *)skb->data; - /* fake header transmission time */ - mgmt->u.beacon.timestamp = virtio_wifi_get_tsf_raw(); - err = xmit_skb(sq, skb); - if (unlikely(err)) { - dev_kfree_skb_any(skb); - return; - } - skb_orphan(skb); - nf_reset_ct(skb); - - if (vif->csa_active && ieee80211_csa_is_complete(vif)) - ieee80211_csa_finish(vif); -} - -static enum hrtimer_restart virtio_wifi_beacon(struct hrtimer *timer) -{ - struct virtio_wifi_info *vi = - container_of(timer, struct virtio_wifi_info, beacon_timer); - struct ieee80211_hw *hw = vi->hw; - u64 bcn_int = vi->beacon_int; - ktime_t next_bcn; - - if (!vi->started) - return HRTIMER_NORESTART; - - ieee80211_iterate_active_interfaces_atomic( - hw, IEEE80211_IFACE_ITER_NORMAL, - virtio_wifi_beacon_tx, vi); - hrtimer_forward(&vi->beacon_timer, hrtimer_get_expires(timer), - ns_to_ktime(bcn_int * NSEC_PER_USEC)); - return HRTIMER_RESTART; -} - -static const struct ieee80211_iface_limit virtio_wifi_if_limit[] = { - { .max = 1, .types = BIT(NL80211_IFTYPE_ADHOC) }, - { .max = 2048, - .types = BIT(NL80211_IFTYPE_STATION) | - BIT(NL80211_IFTYPE_P2P_CLIENT) | - BIT(NL80211_IFTYPE_AP) | - BIT(NL80211_IFTYPE_P2P_GO) }, - /* must be last, see hwsim_if_comb */ - { .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) } -}; - -static const struct ieee80211_iface_combination virtio_wifi_ifcomb[] = { - { - .limits = virtio_wifi_if_limit, - .n_limits = ARRAY_SIZE(virtio_wifi_if_limit), - .max_interfaces = 2048, - .num_different_channels = 1, - .radar_detect_widths = - BIT(NL80211_CHAN_WIDTH_20_NOHT) | - BIT(NL80211_CHAN_WIDTH_20) | - BIT(NL80211_CHAN_WIDTH_40) | - BIT(NL80211_CHAN_WIDTH_80) | - BIT(NL80211_CHAN_WIDTH_160), - }, -}; - -static int virtio_wifi_probe(struct virtio_device *vdev) -{ - int err; - u8 macaddr[ETH_ALEN]; - struct ieee80211_hw *hw; - struct virtio_wifi_info *vi; - - hw = ieee80211_alloc_hw(sizeof(*vi), &virtio_wifi_ops); - err = 1; - if (!hw) - goto err_init_ieee80211; - - vi = hw->priv; - vdev->priv = vi; - vi->vdev = vdev; - vi->hdr_len = 0; - vi->hw = hw; - mutex_init(&vi->mutex); - init_dummy_netdev(&vi->napi_dev); - INIT_DELAYED_WORK(&vi->refill_work, virtio_wifi_refill_work); - netif_set_real_num_tx_queues(&vi->napi_dev, 1); - netif_set_real_num_rx_queues(&vi->napi_dev, 1); - err = virtio_wifi_init_vqs(vi); - if (err) - goto err_init_vq; - - SET_IEEE80211_DEV(hw, &vdev->dev); - ieee80211_hw_set(hw, SIGNAL_DBM); - ieee80211_hw_set(hw, CONNECTION_MONITOR); - - hw->wiphy->interface_modes = - BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP) | - BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) | - BIT(NL80211_IFTYPE_P2P_DEVICE) | BIT(NL80211_IFTYPE_ADHOC); - hw->queues = 5; - hw->offchannel_tx_hw_queue = 4; - virtio_wifi_init_modes(vi); - hw->wiphy->iface_combinations = virtio_wifi_ifcomb; - hw->wiphy->n_iface_combinations = ARRAY_SIZE(virtio_wifi_ifcomb); - - virtio_cread_bytes(vdev, offsetof(struct virtio_wifi_config, mac), - macaddr, ETH_ALEN); - - SET_IEEE80211_PERM_ADDR(hw, macaddr); - - hrtimer_init(&vi->beacon_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_SOFT); - vi->beacon_timer.function = virtio_wifi_beacon; - hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS | - WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | WIPHY_FLAG_AP_UAPSD | - WIPHY_FLAG_HAS_CHANNEL_SWITCH; - hw->wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR | - NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | - NL80211_FEATURE_STATIC_SMPS | - NL80211_FEATURE_DYNAMIC_SMPS; - - err = ieee80211_register_hw(hw); - if (err) { - pr_debug("virtio wifi: could not register device\n"); - goto err_register_hw; - } - virtio_device_ready(vdev); - return 0; - -err_register_hw: - ieee80211_free_hw(hw); -err_init_vq: - cancel_delayed_work_sync(&vi->refill_work); - virtio_wifi_del_vqs(vi); - kfree(vi); -err_init_ieee80211: - return err; -} - -static void virtio_wifi_config_changed(struct virtio_device *vdev) -{ -} - -static int virtio_wifi_validate(struct virtio_device *vdev) -{ - return 0; -} - -static struct virtio_device_id id_table[] = { - { VIRTIO_ID_MAC80211_WLAN, VIRTIO_DEV_ANY_ID }, - { 0 }, -}; - -static unsigned int features[] = { - /* none */ -}; - -static struct virtio_driver virtio_wifi_driver = { - .feature_table = features, - .feature_table_size = ARRAY_SIZE(features), - .driver.name = KBUILD_MODNAME, - .driver.owner = THIS_MODULE, - .id_table = id_table, - .validate = virtio_wifi_validate, - .probe = virtio_wifi_probe, - .remove = virtio_wifi_remove, - .config_changed = virtio_wifi_config_changed, -}; - -static __init int virtio_wifi_driver_init(void) -{ - return register_virtio_driver(&virtio_wifi_driver); -} -module_init(virtio_wifi_driver_init); - -static __exit void virtio_wifi_driver_exit(void) -{ - unregister_virtio_driver(&virtio_wifi_driver); -} -module_exit(virtio_wifi_driver_exit); - -MODULE_DEVICE_TABLE(virtio, id_table); -MODULE_DESCRIPTION("A virtio MAC80211 WLAN driver for emulated wifi on Emulator."); -MODULE_AUTHOR("Google, Inc."); -MODULE_LICENSE("GPL v2"); |