* [PATCH v6 2/3] mt76: add common code shared between multiple chipsets
From: Felix Fietkau @ 2017-10-06 11:02 UTC (permalink / raw)
To: linux-wireless; +Cc: kvalo
In-Reply-To: <20171006110249.31013-1-nbd@nbd.name>
This will be used by drivers for MT76x2e, MT7603e and MT7628
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/debugfs.c | 76 ++++
drivers/net/wireless/mediatek/mt76/dma.c | 451 +++++++++++++++++++++++
drivers/net/wireless/mediatek/mt76/dma.h | 38 ++
drivers/net/wireless/mediatek/mt76/eeprom.c | 112 ++++++
drivers/net/wireless/mediatek/mt76/mac80211.c | 344 +++++++++++++++++
drivers/net/wireless/mediatek/mt76/mmio.c | 61 +++
drivers/net/wireless/mediatek/mt76/mt76.h | 355 ++++++++++++++++++
drivers/net/wireless/mediatek/mt76/trace.c | 23 ++
drivers/net/wireless/mediatek/mt76/trace.h | 71 ++++
drivers/net/wireless/mediatek/mt76/tx.c | 511 ++++++++++++++++++++++++++
drivers/net/wireless/mediatek/mt76/util.c | 78 ++++
drivers/net/wireless/mediatek/mt76/util.h | 44 +++
12 files changed, 2164 insertions(+)
create mode 100644 drivers/net/wireless/mediatek/mt76/debugfs.c
create mode 100644 drivers/net/wireless/mediatek/mt76/dma.c
create mode 100644 drivers/net/wireless/mediatek/mt76/dma.h
create mode 100644 drivers/net/wireless/mediatek/mt76/eeprom.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mac80211.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mmio.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76.h
create mode 100644 drivers/net/wireless/mediatek/mt76/trace.c
create mode 100644 drivers/net/wireless/mediatek/mt76/trace.h
create mode 100644 drivers/net/wireless/mediatek/mt76/tx.c
create mode 100644 drivers/net/wireless/mediatek/mt76/util.c
create mode 100644 drivers/net/wireless/mediatek/mt76/util.h
diff --git a/drivers/net/wireless/mediatek/mt76/debugfs.c b/drivers/net/wireless/mediatek/mt76/debugfs.c
new file mode 100644
index 000000000000..cfe32e53efc1
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/debugfs.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "mt76.h"
+
+static int
+mt76_reg_set(void *data, u64 val)
+{
+ struct mt76_dev *dev = data;
+
+ dev->bus->wr(dev, dev->debugfs_reg, val);
+ return 0;
+}
+
+static int
+mt76_reg_get(void *data, u64 *val)
+{
+ struct mt76_dev *dev = data;
+
+ *val = dev->bus->rr(dev, dev->debugfs_reg);
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(fops_regval, mt76_reg_get, mt76_reg_set, "0x%08llx\n");
+
+static int
+mt76_queues_read(struct seq_file *s, void *data)
+{
+ struct mt76_dev *dev = dev_get_drvdata(s->private);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dev->q_tx); i++) {
+ struct mt76_queue *q = &dev->q_tx[i];
+
+ if (!q->ndesc)
+ continue;
+
+ seq_printf(s,
+ "%d: queued=%d head=%d tail=%d swq_queued=%d\n",
+ i, q->queued, q->head, q->tail, q->swq_queued);
+ }
+
+ return 0;
+}
+
+struct dentry *mt76_register_debugfs(struct mt76_dev *dev)
+{
+ struct dentry *dir;
+
+ dir = debugfs_create_dir("mt76", dev->hw->wiphy->debugfsdir);
+ if (!dir)
+ return NULL;
+
+ debugfs_create_u32("regidx", S_IRUSR | S_IWUSR, dir, &dev->debugfs_reg);
+ debugfs_create_file("regval", S_IRUSR | S_IWUSR, dir, dev,
+ &fops_regval);
+ debugfs_create_blob("eeprom", S_IRUSR, dir, &dev->eeprom);
+ if (dev->otp.data)
+ debugfs_create_blob("otp", S_IRUSR, dir, &dev->otp);
+ debugfs_create_devm_seqfile(dev->dev, "queues", dir, mt76_queues_read);
+
+ return dir;
+}
+EXPORT_SYMBOL_GPL(mt76_register_debugfs);
diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
new file mode 100644
index 000000000000..ec4880cdc610
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/dma-mapping.h>
+#include "mt76.h"
+#include "dma.h"
+
+#define DMA_DUMMY_TXWI ((void *) ~0)
+
+static int
+mt76_dma_alloc_queue(struct mt76_dev *dev, struct mt76_queue *q)
+{
+ int size;
+ int i;
+
+ spin_lock_init(&q->lock);
+ INIT_LIST_HEAD(&q->swq);
+
+ size = q->ndesc * sizeof(struct mt76_desc);
+ q->desc = dmam_alloc_coherent(dev->dev, size, &q->desc_dma, GFP_KERNEL);
+ if (!q->desc)
+ return -ENOMEM;
+
+ size = q->ndesc * sizeof(*q->entry);
+ q->entry = devm_kzalloc(dev->dev, size, GFP_KERNEL);
+ if (!q->entry)
+ return -ENOMEM;
+
+ /* clear descriptors */
+ for (i = 0; i < q->ndesc; i++)
+ q->desc[i].ctrl = cpu_to_le32(MT_DMA_CTL_DMA_DONE);
+
+ iowrite32(q->desc_dma, &q->regs->desc_base);
+ iowrite32(0, &q->regs->cpu_idx);
+ iowrite32(0, &q->regs->dma_idx);
+ iowrite32(q->ndesc, &q->regs->ring_size);
+
+ return 0;
+}
+
+static int
+mt76_dma_add_buf(struct mt76_dev *dev, struct mt76_queue *q,
+ struct mt76_queue_buf *buf, int nbufs, u32 info,
+ struct sk_buff *skb, void *txwi)
+{
+ struct mt76_desc *desc;
+ u32 ctrl;
+ int i, idx = -1;
+
+ if (txwi)
+ q->entry[q->head].txwi = DMA_DUMMY_TXWI;
+
+ for (i = 0; i < nbufs; i += 2, buf += 2) {
+ u32 buf0 = buf[0].addr, buf1 = 0;
+
+ ctrl = FIELD_PREP(MT_DMA_CTL_SD_LEN0, buf[0].len);
+ if (i < nbufs - 1) {
+ buf1 = buf[1].addr;
+ ctrl |= FIELD_PREP(MT_DMA_CTL_SD_LEN1, buf[1].len);
+ }
+
+ if (i == nbufs - 1)
+ ctrl |= MT_DMA_CTL_LAST_SEC0;
+ else if (i == nbufs - 2)
+ ctrl |= MT_DMA_CTL_LAST_SEC1;
+
+ idx = q->head;
+ q->head = (q->head + 1) % q->ndesc;
+
+ desc = &q->desc[idx];
+
+ WRITE_ONCE(desc->buf0, cpu_to_le32(buf0));
+ WRITE_ONCE(desc->buf1, cpu_to_le32(buf1));
+ WRITE_ONCE(desc->info, cpu_to_le32(info));
+ WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl));
+
+ q->queued++;
+ }
+
+ q->entry[idx].txwi = txwi;
+ q->entry[idx].skb = skb;
+
+ return idx;
+}
+
+static void
+mt76_dma_tx_cleanup_idx(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ struct mt76_queue_entry *prev_e)
+{
+ struct mt76_queue_entry *e = &q->entry[idx];
+ __le32 __ctrl = READ_ONCE(q->desc[idx].ctrl);
+ u32 ctrl = le32_to_cpu(__ctrl);
+
+ if (!e->txwi || !e->skb) {
+ __le32 addr = READ_ONCE(q->desc[idx].buf0);
+ u32 len = FIELD_GET(MT_DMA_CTL_SD_LEN0, ctrl);
+
+ dma_unmap_single(dev->dev, le32_to_cpu(addr), len,
+ DMA_TO_DEVICE);
+ }
+
+ if (!(ctrl & MT_DMA_CTL_LAST_SEC0)) {
+ __le32 addr = READ_ONCE(q->desc[idx].buf1);
+ u32 len = FIELD_GET(MT_DMA_CTL_SD_LEN1, ctrl);
+
+ dma_unmap_single(dev->dev, le32_to_cpu(addr), len,
+ DMA_TO_DEVICE);
+ }
+
+ if (e->txwi == DMA_DUMMY_TXWI)
+ e->txwi = NULL;
+
+ *prev_e = *e;
+ memset(e, 0, sizeof(*e));
+}
+
+static void
+mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q)
+{
+ q->head = ioread32(&q->regs->dma_idx);
+ q->tail = q->head;
+ iowrite32(q->head, &q->regs->cpu_idx);
+}
+
+static void
+mt76_dma_tx_cleanup(struct mt76_dev *dev, enum mt76_txq_id qid, bool flush)
+{
+ struct mt76_queue *q = &dev->q_tx[qid];
+ struct mt76_queue_entry entry;
+ bool wake = false;
+ int last;
+
+ if (!q->ndesc)
+ return;
+
+ spin_lock_bh(&q->lock);
+ if (flush)
+ last = -1;
+ else
+ last = ioread32(&q->regs->dma_idx);
+
+ while (q->queued && q->tail != last) {
+ mt76_dma_tx_cleanup_idx(dev, q, q->tail, &entry);
+ if (entry.schedule)
+ q->swq_queued--;
+
+ if (entry.skb)
+ dev->drv->tx_complete_skb(dev, q, &entry, flush);
+
+ if (entry.txwi) {
+ mt76_put_txwi(dev, entry.txwi);
+ wake = true;
+ }
+
+ q->tail = (q->tail + 1) % q->ndesc;
+ q->queued--;
+
+ if (!flush && q->tail == last)
+ last = ioread32(&q->regs->dma_idx);
+ }
+
+ if (!flush)
+ mt76_txq_schedule(dev, q);
+ else
+ mt76_dma_sync_idx(dev, q);
+
+ wake = wake && qid < IEEE80211_NUM_ACS && q->queued < q->ndesc - 8;
+ spin_unlock_bh(&q->lock);
+
+ if (wake)
+ ieee80211_wake_queue(dev->hw, qid);
+}
+
+static void *
+mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ int *len, u32 *info, bool *more)
+{
+ struct mt76_queue_entry *e = &q->entry[idx];
+ struct mt76_desc *desc = &q->desc[idx];
+ dma_addr_t buf_addr;
+ void *buf = e->buf;
+ int buf_len = SKB_WITH_OVERHEAD(q->buf_size);
+
+ buf_addr = READ_ONCE(desc->buf0);
+ if (len) {
+ u32 ctl = READ_ONCE(desc->ctrl);
+ *len = FIELD_GET(MT_DMA_CTL_SD_LEN0, ctl);
+ *more = !(ctl & MT_DMA_CTL_LAST_SEC0);
+ }
+
+ if (info)
+ *info = le32_to_cpu(desc->info);
+
+ dma_unmap_single(dev->dev, buf_addr, buf_len, DMA_FROM_DEVICE);
+ e->buf = NULL;
+
+ return buf;
+}
+
+static void *
+mt76_dma_dequeue(struct mt76_dev *dev, struct mt76_queue *q, bool flush,
+ int *len, u32 *info, bool *more)
+{
+ int idx = q->tail;
+
+ *more = false;
+ if (!q->queued)
+ return NULL;
+
+ if (!flush && !(q->desc[idx].ctrl & cpu_to_le32(MT_DMA_CTL_DMA_DONE)))
+ return NULL;
+
+ q->tail = (q->tail + 1) % q->ndesc;
+ q->queued--;
+
+ return mt76_dma_get_buf(dev, q, idx, len, info, more);
+}
+
+static void
+mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q)
+{
+ iowrite32(q->head, &q->regs->cpu_idx);
+}
+
+static int
+mt76_dma_rx_fill(struct mt76_dev *dev, struct mt76_queue *q, bool napi)
+{
+ dma_addr_t addr;
+ void *buf;
+ int frames = 0;
+ int len = SKB_WITH_OVERHEAD(q->buf_size);
+ int offset = q->buf_offset;
+ int idx;
+ void *(*alloc)(unsigned int fragsz);
+
+ if (napi)
+ alloc = napi_alloc_frag;
+ else
+ alloc = netdev_alloc_frag;
+
+ spin_lock_bh(&q->lock);
+
+ while (q->queued < q->ndesc - 1) {
+ struct mt76_queue_buf qbuf;
+
+ buf = alloc(q->buf_size);
+ if (!buf)
+ break;
+
+ addr = dma_map_single(dev->dev, buf, len, DMA_FROM_DEVICE);
+ if (dma_mapping_error(dev->dev, addr)) {
+ skb_free_frag(buf);
+ break;
+ }
+
+ qbuf.addr = addr + offset;
+ qbuf.len = len - offset;
+ idx = mt76_dma_add_buf(dev, q, &qbuf, 1, 0, buf, NULL);
+ frames++;
+ }
+
+ if (frames)
+ mt76_dma_kick_queue(dev, q);
+
+ spin_unlock_bh(&q->lock);
+
+ return frames;
+}
+
+static void
+mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q)
+{
+ void *buf;
+ bool more;
+
+ spin_lock_bh(&q->lock);
+ do {
+ buf = mt76_dma_dequeue(dev, q, true, NULL, NULL, &more);
+ if (!buf)
+ break;
+
+ skb_free_frag(buf);
+ } while (1);
+ spin_unlock_bh(&q->lock);
+}
+
+static void
+mt76_dma_rx_reset(struct mt76_dev *dev, enum mt76_rxq_id qid)
+{
+ struct mt76_queue *q = &dev->q_rx[qid];
+ int i;
+
+ for (i = 0; i < q->ndesc; i++)
+ q->desc[i].ctrl &= ~cpu_to_le32(MT_DMA_CTL_DMA_DONE);
+
+ mt76_dma_rx_cleanup(dev, q);
+ mt76_dma_sync_idx(dev, q);
+ mt76_dma_rx_fill(dev, q, false);
+}
+
+static void
+mt76_add_fragment(struct mt76_dev *dev, struct mt76_queue *q, void *data,
+ int len, bool more)
+{
+ struct page *page = virt_to_head_page(data);
+ int offset = data - page_address(page);
+ struct sk_buff *skb = q->rx_head;
+
+ offset += q->buf_offset;
+ skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, offset, len,
+ q->buf_size);
+
+ if (more)
+ return;
+
+ q->rx_head = NULL;
+ dev->drv->rx_skb(dev, q - dev->q_rx, skb);
+}
+
+static int
+mt76_dma_rx_process(struct mt76_dev *dev, struct mt76_queue *q, int budget)
+{
+ struct sk_buff *skb;
+ unsigned char *data;
+ int len;
+ int done = 0;
+ bool more;
+
+ while (done < budget) {
+ u32 info;
+
+ data = mt76_dma_dequeue(dev, q, false, &len, &info, &more);
+ if (!data)
+ break;
+
+ if (q->rx_head) {
+ mt76_add_fragment(dev, q, data, len, more);
+ continue;
+ }
+
+ skb = build_skb(data, q->buf_size);
+ if (!skb) {
+ skb_free_frag(data);
+ continue;
+ }
+
+ skb_reserve(skb, q->buf_offset);
+ if (skb->tail + len > skb->end) {
+ dev_kfree_skb(skb);
+ continue;
+ }
+
+ if (q == &dev->q_rx[MT_RXQ_MCU]) {
+ u32 *rxfce = (u32 *) skb->cb;
+ *rxfce = info;
+ }
+
+ __skb_put(skb, len);
+ done++;
+
+ if (more) {
+ q->rx_head = skb;
+ continue;
+ }
+
+ dev->drv->rx_skb(dev, q - dev->q_rx, skb);
+ }
+
+ mt76_dma_rx_fill(dev, q, true);
+ return done;
+}
+
+static int
+mt76_dma_rx_poll(struct napi_struct *napi, int budget)
+{
+ struct mt76_dev *dev;
+ int qid, done;
+
+ dev = container_of(napi->dev, struct mt76_dev, napi_dev);
+ qid = napi - dev->napi;
+
+ done = mt76_dma_rx_process(dev, &dev->q_rx[qid], budget);
+ if (done < budget) {
+ napi_complete(napi);
+ dev->drv->rx_poll_complete(dev, qid);
+ }
+ mt76_rx_complete(dev, qid);
+
+ return done;
+}
+
+static int
+mt76_dma_init(struct mt76_dev *dev)
+{
+ int i;
+
+ init_dummy_netdev(&dev->napi_dev);
+
+ for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++) {
+ netif_napi_add(&dev->napi_dev, &dev->napi[i], mt76_dma_rx_poll,
+ 64);
+ mt76_dma_rx_fill(dev, &dev->q_rx[i], false);
+ skb_queue_head_init(&dev->rx_skb[i]);
+ napi_enable(&dev->napi[i]);
+ }
+
+ return 0;
+}
+
+static const struct mt76_queue_ops mt76_dma_ops = {
+ .init = mt76_dma_init,
+ .alloc = mt76_dma_alloc_queue,
+ .add_buf = mt76_dma_add_buf,
+ .tx_cleanup = mt76_dma_tx_cleanup,
+ .rx_reset = mt76_dma_rx_reset,
+ .kick = mt76_dma_kick_queue,
+};
+
+int mt76_dma_attach(struct mt76_dev *dev)
+{
+ dev->queue_ops = &mt76_dma_ops;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mt76_dma_attach);
+
+void mt76_dma_cleanup(struct mt76_dev *dev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dev->q_tx); i++)
+ mt76_dma_tx_cleanup(dev, i, true);
+
+ for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++) {
+ netif_napi_del(&dev->napi[i]);
+ mt76_dma_rx_cleanup(dev, &dev->q_rx[i]);
+ }
+}
+EXPORT_SYMBOL_GPL(mt76_dma_cleanup);
diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h
new file mode 100644
index 000000000000..1dad39697929
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/dma.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef __MT76_DMA_H
+#define __MT76_DMA_H
+
+#define MT_RING_SIZE 0x10
+
+#define MT_DMA_CTL_SD_LEN1 GENMASK(13, 0)
+#define MT_DMA_CTL_LAST_SEC1 BIT(14)
+#define MT_DMA_CTL_BURST BIT(15)
+#define MT_DMA_CTL_SD_LEN0 GENMASK(29, 16)
+#define MT_DMA_CTL_LAST_SEC0 BIT(30)
+#define MT_DMA_CTL_DMA_DONE BIT(31)
+
+struct mt76_desc {
+ __le32 buf0;
+ __le32 ctrl;
+ __le32 buf1;
+ __le32 info;
+} __packed __aligned(4);
+
+int mt76_dma_attach(struct mt76_dev *dev);
+void mt76_dma_cleanup(struct mt76_dev *dev);
+
+#endif
diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c
new file mode 100644
index 000000000000..530e5593765c
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/eeprom.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/etherdevice.h>
+#include "mt76.h"
+
+static int
+mt76_get_of_eeprom(struct mt76_dev *dev, int len)
+{
+#if defined(CONFIG_OF) && defined(CONFIG_MTD)
+ struct device_node *np = dev->dev->of_node;
+ struct mtd_info *mtd;
+ const __be32 *list;
+ const char *part;
+ phandle phandle;
+ int offset = 0;
+ int size;
+ size_t retlen;
+ int ret;
+
+ if (!np)
+ return -ENOENT;
+
+ list = of_get_property(np, "mediatek,mtd-eeprom", &size);
+ if (!list)
+ return -ENOENT;
+
+ phandle = be32_to_cpup(list++);
+ if (!phandle)
+ return -ENOENT;
+
+ np = of_find_node_by_phandle(phandle);
+ if (!np)
+ return -EINVAL;
+
+ part = of_get_property(np, "label", NULL);
+ if (!part)
+ part = np->name;
+
+ mtd = get_mtd_device_nm(part);
+ if (IS_ERR(mtd))
+ return PTR_ERR(mtd);
+
+ if (size <= sizeof(*list))
+ return -EINVAL;
+
+ offset = be32_to_cpup(list);
+ ret = mtd_read(mtd, offset, len, &retlen, dev->eeprom.data);
+ put_mtd_device(mtd);
+ if (ret)
+ return ret;
+
+ if (retlen < len)
+ return -EINVAL;
+
+ return 0;
+#else
+ return -ENOENT;
+#endif
+}
+
+void
+mt76_eeprom_override(struct mt76_dev *dev)
+{
+#ifdef CONFIG_OF
+ struct device_node *np = dev->dev->of_node;
+ const u8 *mac;
+
+ if (!np)
+ return;
+
+ mac = of_get_mac_address(np);
+ if (mac)
+ memcpy(dev->macaddr, mac, ETH_ALEN);
+#endif
+
+ if (!is_valid_ether_addr(dev->macaddr)) {
+ eth_random_addr(dev->macaddr);
+ dev_info(dev->dev,
+ "Invalid MAC address, using random address %pM\n",
+ dev->macaddr);
+ }
+}
+EXPORT_SYMBOL_GPL(mt76_eeprom_override);
+
+int
+mt76_eeprom_init(struct mt76_dev *dev, int len)
+{
+ dev->eeprom.size = len;
+ dev->eeprom.data = devm_kzalloc(dev->dev, len, GFP_KERNEL);
+ if (!dev->eeprom.data)
+ return -ENOMEM;
+
+ return !mt76_get_of_eeprom(dev, len);
+}
+EXPORT_SYMBOL_GPL(mt76_eeprom_init);
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
new file mode 100644
index 000000000000..c757254c15d8
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "mt76.h"
+
+#define CHAN2G(_idx, _freq) { \
+ .band = NL80211_BAND_2GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_idx), \
+ .max_power = 30, \
+}
+
+#define CHAN5G(_idx, _freq) { \
+ .band = NL80211_BAND_5GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_idx), \
+ .max_power = 30, \
+}
+
+static const struct ieee80211_channel mt76_channels_2ghz[] = {
+ CHAN2G(1, 2412),
+ CHAN2G(2, 2417),
+ CHAN2G(3, 2422),
+ CHAN2G(4, 2427),
+ CHAN2G(5, 2432),
+ CHAN2G(6, 2437),
+ CHAN2G(7, 2442),
+ CHAN2G(8, 2447),
+ CHAN2G(9, 2452),
+ CHAN2G(10, 2457),
+ CHAN2G(11, 2462),
+ CHAN2G(12, 2467),
+ CHAN2G(13, 2472),
+ CHAN2G(14, 2484),
+};
+
+static const struct ieee80211_channel mt76_channels_5ghz[] = {
+ CHAN5G(36, 5180),
+ CHAN5G(40, 5200),
+ CHAN5G(44, 5220),
+ CHAN5G(48, 5240),
+
+ CHAN5G(52, 5260),
+ CHAN5G(56, 5280),
+ CHAN5G(60, 5300),
+ CHAN5G(64, 5320),
+
+ CHAN5G(100, 5500),
+ CHAN5G(104, 5520),
+ CHAN5G(108, 5540),
+ CHAN5G(112, 5560),
+ CHAN5G(116, 5580),
+ CHAN5G(120, 5600),
+ CHAN5G(124, 5620),
+ CHAN5G(128, 5640),
+ CHAN5G(132, 5660),
+ CHAN5G(136, 5680),
+ CHAN5G(140, 5700),
+
+ CHAN5G(149, 5745),
+ CHAN5G(153, 5765),
+ CHAN5G(157, 5785),
+ CHAN5G(161, 5805),
+ CHAN5G(165, 5825),
+};
+
+static int
+mt76_init_sband(struct mt76_dev *dev, struct mt76_sband *msband,
+ const struct ieee80211_channel *chan, int n_chan,
+ struct ieee80211_rate *rates, int n_rates, bool vht)
+{
+ struct ieee80211_supported_band *sband = &msband->sband;
+ struct ieee80211_sta_ht_cap *ht_cap;
+ struct ieee80211_sta_vht_cap *vht_cap;
+ void *chanlist;
+ u16 mcs_map;
+ int size;
+
+ size = n_chan * sizeof(*chan);
+ chanlist = devm_kmemdup(dev->dev, chan, size, GFP_KERNEL);
+ if (!chanlist)
+ return -ENOMEM;
+
+ msband->chan = devm_kzalloc(dev->dev, n_chan * sizeof(*msband->chan),
+ GFP_KERNEL);
+ if (!msband->chan)
+ return -ENOMEM;
+
+ sband->channels = chanlist;
+ sband->n_channels = n_chan;
+ sband->bitrates = rates;
+ sband->n_bitrates = n_rates;
+ dev->chandef.chan = &sband->channels[0];
+
+ ht_cap = &sband->ht_cap;
+ ht_cap->ht_supported = true;
+ 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_TX_STBC |
+ (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT);
+
+ ht_cap->mcs.rx_mask[0] = 0xff;
+ ht_cap->mcs.rx_mask[1] = 0xff;
+ ht_cap->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
+ ht_cap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
+ ht_cap->ampdu_density = IEEE80211_HT_MPDU_DENSITY_4;
+
+ if (!vht)
+ return 0;
+
+ vht_cap = &sband->vht_cap;
+ vht_cap->vht_supported = true;
+
+ mcs_map = (IEEE80211_VHT_MCS_SUPPORT_0_9 << (0 * 2)) |
+ (IEEE80211_VHT_MCS_SUPPORT_0_9 << (1 * 2)) |
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << (2 * 2)) |
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << (3 * 2)) |
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << (4 * 2)) |
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << (5 * 2)) |
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << (6 * 2)) |
+ (IEEE80211_VHT_MCS_NOT_SUPPORTED << (7 * 2));
+
+ vht_cap->vht_mcs.rx_mcs_map = cpu_to_le16(mcs_map);
+ vht_cap->vht_mcs.tx_mcs_map = cpu_to_le16(mcs_map);
+ vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC |
+ IEEE80211_VHT_CAP_TXSTBC |
+ IEEE80211_VHT_CAP_RXSTBC_1 |
+ IEEE80211_VHT_CAP_SHORT_GI_80;
+
+ return 0;
+}
+
+static int
+mt76_init_sband_2g(struct mt76_dev *dev, struct ieee80211_rate *rates,
+ int n_rates)
+{
+ dev->hw->wiphy->bands[NL80211_BAND_2GHZ] = &dev->sband_2g.sband;
+
+ return mt76_init_sband(dev, &dev->sband_2g,
+ mt76_channels_2ghz,
+ ARRAY_SIZE(mt76_channels_2ghz),
+ rates, n_rates, false);
+}
+
+static int
+mt76_init_sband_5g(struct mt76_dev *dev, struct ieee80211_rate *rates,
+ int n_rates, bool vht)
+{
+ dev->hw->wiphy->bands[NL80211_BAND_5GHZ] = &dev->sband_5g.sband;
+
+ return mt76_init_sband(dev, &dev->sband_5g,
+ mt76_channels_5ghz,
+ ARRAY_SIZE(mt76_channels_5ghz),
+ rates, n_rates, vht);
+}
+
+static void
+mt76_check_sband(struct mt76_dev *dev, int band)
+{
+ struct ieee80211_supported_band *sband = dev->hw->wiphy->bands[band];
+ bool found = false;
+ int i;
+
+ if (!sband)
+ return;
+
+ for (i = 0; i < sband->n_channels; i++) {
+ if (sband->channels[i].flags & IEEE80211_CHAN_DISABLED)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (found)
+ return;
+
+ sband->n_channels = 0;
+ dev->hw->wiphy->bands[band] = NULL;
+}
+
+int mt76_register_device(struct mt76_dev *dev, bool vht,
+ struct ieee80211_rate *rates, int n_rates)
+{
+ struct ieee80211_hw *hw = dev->hw;
+ struct wiphy *wiphy = hw->wiphy;
+ int ret;
+
+ dev_set_drvdata(dev->dev, dev);
+
+ spin_lock_init(&dev->lock);
+ spin_lock_init(&dev->cc_lock);
+ INIT_LIST_HEAD(&dev->txwi_cache);
+
+ SET_IEEE80211_DEV(hw, dev->dev);
+ SET_IEEE80211_PERM_ADDR(hw, dev->macaddr);
+
+ wiphy->interface_modes =
+ BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP) |
+#ifdef CONFIG_MAC80211_MESH
+ BIT(NL80211_IFTYPE_MESH_POINT) |
+#endif
+ BIT(NL80211_IFTYPE_ADHOC);
+
+ wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR;
+
+ hw->txq_data_size = sizeof(struct mt76_txq);
+ hw->max_tx_fragments = 16;
+
+ ieee80211_hw_set(hw, SIGNAL_DBM);
+ ieee80211_hw_set(hw, PS_NULLFUNC_STACK);
+ ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING);
+ ieee80211_hw_set(hw, AMPDU_AGGREGATION);
+ ieee80211_hw_set(hw, SUPPORTS_RC_TABLE);
+ ieee80211_hw_set(hw, SUPPORT_FAST_XMIT);
+ ieee80211_hw_set(hw, SUPPORTS_CLONED_SKBS);
+ ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU);
+ ieee80211_hw_set(hw, TX_AMSDU);
+ ieee80211_hw_set(hw, TX_FRAG_LIST);
+ ieee80211_hw_set(hw, MFP_CAPABLE);
+
+ if (dev->cap.has_2ghz) {
+ ret = mt76_init_sband_2g(dev, rates, n_rates);
+ if (ret)
+ return ret;
+ }
+
+ if (dev->cap.has_5ghz) {
+ ret = mt76_init_sband_5g(dev, rates + 4, n_rates - 4, vht);
+ if (ret)
+ return ret;
+ }
+
+ wiphy_read_of_freq_limits(dev->hw->wiphy);
+ mt76_check_sband(dev, NL80211_BAND_2GHZ);
+ mt76_check_sband(dev, NL80211_BAND_5GHZ);
+
+ return ieee80211_register_hw(hw);
+}
+EXPORT_SYMBOL_GPL(mt76_register_device);
+
+void mt76_unregister_device(struct mt76_dev *dev)
+{
+ struct ieee80211_hw *hw = dev->hw;
+
+ ieee80211_unregister_hw(hw);
+ mt76_tx_free(dev);
+}
+EXPORT_SYMBOL_GPL(mt76_unregister_device);
+
+void mt76_rx(struct mt76_dev *dev, enum mt76_rxq_id q, struct sk_buff *skb)
+{
+ if (!test_bit(MT76_STATE_RUNNING, &dev->state)) {
+ dev_kfree_skb(skb);
+ return;
+ }
+
+ __skb_queue_tail(&dev->rx_skb[q], skb);
+}
+EXPORT_SYMBOL_GPL(mt76_rx);
+
+void mt76_set_channel(struct mt76_dev *dev)
+{
+ struct ieee80211_hw *hw = dev->hw;
+ struct cfg80211_chan_def *chandef = &hw->conf.chandef;
+ struct mt76_channel_state *state;
+ bool offchannel = hw->conf.flags & IEEE80211_CONF_OFFCHANNEL;
+
+ if (dev->drv->update_survey)
+ dev->drv->update_survey(dev);
+
+ dev->chandef = *chandef;
+
+ if (!offchannel)
+ dev->main_chan = chandef->chan;
+
+ if (chandef->chan != dev->main_chan) {
+ state = mt76_channel_state(dev, chandef->chan);
+ memset(state, 0, sizeof(*state));
+ }
+}
+EXPORT_SYMBOL_GPL(mt76_set_channel);
+
+int mt76_get_survey(struct ieee80211_hw *hw, int idx,
+ struct survey_info *survey)
+{
+ struct mt76_dev *dev = hw->priv;
+ struct mt76_sband *sband;
+ struct ieee80211_channel *chan;
+ struct mt76_channel_state *state;
+ int ret = 0;
+
+ if (idx == 0 && dev->drv->update_survey)
+ dev->drv->update_survey(dev);
+
+ sband = &dev->sband_2g;
+ if (idx >= sband->sband.n_channels) {
+ idx -= sband->sband.n_channels;
+ sband = &dev->sband_5g;
+ }
+
+ if (idx >= sband->sband.n_channels)
+ return -ENOENT;
+
+ chan = &sband->sband.channels[idx];
+ state = mt76_channel_state(dev, chan);
+
+ memset(survey, 0, sizeof(*survey));
+ survey->channel = chan;
+ survey->filled = SURVEY_INFO_TIME | SURVEY_INFO_TIME_BUSY;
+ if (chan == dev->main_chan)
+ survey->filled |= SURVEY_INFO_IN_USE;
+
+ spin_lock_bh(&dev->cc_lock);
+ survey->time = div_u64(state->cc_active, 1000);
+ survey->time_busy = div_u64(state->cc_busy, 1000);
+ spin_unlock_bh(&dev->cc_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mt76_get_survey);
+
+void mt76_rx_complete(struct mt76_dev *dev, enum mt76_rxq_id q)
+{
+ struct sk_buff *skb;
+
+ while ((skb = __skb_dequeue(&dev->rx_skb[q])) != NULL)
+ ieee80211_rx_napi(dev->hw, NULL, skb, &dev->napi[q]);
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mmio.c b/drivers/net/wireless/mediatek/mt76/mmio.c
new file mode 100644
index 000000000000..09a14dead6e3
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mmio.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "mt76.h"
+#include "trace.h"
+
+static u32 mt76_mmio_rr(struct mt76_dev *dev, u32 offset)
+{
+ u32 val;
+
+ val = ioread32(dev->regs + offset);
+ trace_reg_rr(dev, offset, val);
+
+ return val;
+}
+
+static void mt76_mmio_wr(struct mt76_dev *dev, u32 offset, u32 val)
+{
+ trace_reg_wr(dev, offset, val);
+ iowrite32(val, dev->regs + offset);
+}
+
+static u32 mt76_mmio_rmw(struct mt76_dev *dev, u32 offset, u32 mask, u32 val)
+{
+ val |= mt76_mmio_rr(dev, offset) & ~mask;
+ mt76_mmio_wr(dev, offset, val);
+ return val;
+}
+
+static void mt76_mmio_copy(struct mt76_dev *dev, u32 offset, const void *data,
+ int len)
+{
+ __iowrite32_copy(dev->regs + offset, data, len >> 2);
+}
+
+void mt76_mmio_init(struct mt76_dev *dev, void __iomem *regs)
+{
+ static const struct mt76_bus_ops mt76_mmio_ops = {
+ .rr = mt76_mmio_rr,
+ .rmw = mt76_mmio_rmw,
+ .wr = mt76_mmio_wr,
+ .copy = mt76_mmio_copy,
+ };
+
+ dev->bus = &mt76_mmio_ops;
+ dev->regs = regs;
+}
+EXPORT_SYMBOL_GPL(mt76_mmio_init);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
new file mode 100644
index 000000000000..f94f78a85106
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __MT76_H
+#define __MT76_H
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <net/mac80211.h>
+#include "util.h"
+
+#define MT_RX_RING_SIZE 128
+#define MT_TX_RING_SIZE 256
+#define MT_MCU_RING_SIZE 32
+#define MT_RX_BUF_SIZE 2048
+
+struct mt76_dev;
+
+struct mt76_bus_ops {
+ u32 (*rr)(struct mt76_dev *dev, u32 offset);
+ void (*wr)(struct mt76_dev *dev, u32 offset, u32 val);
+ u32 (*rmw)(struct mt76_dev *dev, u32 offset, u32 mask, u32 val);
+ void (*copy)(struct mt76_dev *dev, u32 offset, const void *data,
+ int len);
+};
+
+enum mt76_txq_id {
+ MT_TXQ_VO = IEEE80211_AC_VO,
+ MT_TXQ_VI = IEEE80211_AC_VI,
+ MT_TXQ_BE = IEEE80211_AC_BE,
+ MT_TXQ_BK = IEEE80211_AC_BK,
+ MT_TXQ_PSD,
+ MT_TXQ_MCU,
+ MT_TXQ_BEACON,
+ MT_TXQ_CAB,
+ __MT_TXQ_MAX
+};
+
+enum mt76_rxq_id {
+ MT_RXQ_MAIN,
+ MT_RXQ_MCU,
+ __MT_RXQ_MAX
+};
+
+struct mt76_queue_buf {
+ dma_addr_t addr;
+ int len;
+};
+
+struct mt76_queue_entry {
+ union {
+ void *buf;
+ struct sk_buff *skb;
+ };
+ struct mt76_txwi_cache *txwi;
+ bool schedule;
+};
+
+struct mt76_queue_regs {
+ u32 desc_base;
+ u32 ring_size;
+ u32 cpu_idx;
+ u32 dma_idx;
+} __packed __aligned(4);
+
+struct mt76_queue {
+ struct mt76_queue_regs __iomem *regs;
+
+ spinlock_t lock;
+ struct mt76_queue_entry *entry;
+ struct mt76_desc *desc;
+
+ struct list_head swq;
+ int swq_queued;
+
+ u16 head;
+ u16 tail;
+ int ndesc;
+ int queued;
+ int buf_size;
+
+ u8 buf_offset;
+ u8 hw_idx;
+
+ dma_addr_t desc_dma;
+ struct sk_buff *rx_head;
+};
+
+struct mt76_queue_ops {
+ int (*init)(struct mt76_dev *dev);
+
+ int (*alloc)(struct mt76_dev *dev, struct mt76_queue *q);
+
+ int (*add_buf)(struct mt76_dev *dev, struct mt76_queue *q,
+ struct mt76_queue_buf *buf, int nbufs, u32 info,
+ struct sk_buff *skb, void *txwi);
+
+ void *(*dequeue)(struct mt76_dev *dev, struct mt76_queue *q, bool flush,
+ int *len, u32 *info, bool *more);
+
+ void (*rx_reset)(struct mt76_dev *dev, enum mt76_rxq_id qid);
+
+ void (*tx_cleanup)(struct mt76_dev *dev, enum mt76_txq_id qid,
+ bool flush);
+
+ void (*kick)(struct mt76_dev *dev, struct mt76_queue *q);
+};
+
+struct mt76_wcid {
+ u8 idx;
+ u8 hw_key_idx;
+
+ __le16 tx_rate;
+ bool tx_rate_set;
+ u8 tx_rate_nss;
+ s8 max_txpwr_adj;
+};
+
+struct mt76_txq {
+ struct list_head list;
+ struct mt76_queue *hwq;
+ struct mt76_wcid *wcid;
+
+ struct sk_buff_head retry_q;
+
+ u16 agg_ssn;
+ bool send_bar;
+ bool aggr;
+};
+
+struct mt76_txwi_cache {
+ u32 txwi[8];
+ dma_addr_t dma_addr;
+ struct list_head list;
+};
+
+enum {
+ MT76_STATE_INITIALIZED,
+ MT76_STATE_RUNNING,
+ MT76_SCANNING,
+ MT76_RESET,
+};
+
+struct mt76_hw_cap {
+ bool has_2ghz;
+ bool has_5ghz;
+};
+
+struct mt76_driver_ops {
+ u16 txwi_size;
+
+ void (*update_survey)(struct mt76_dev *dev);
+
+ int (*tx_prepare_skb)(struct mt76_dev *dev, void *txwi_ptr,
+ struct sk_buff *skb, struct mt76_queue *q,
+ struct mt76_wcid *wcid,
+ struct ieee80211_sta *sta, u32 *tx_info);
+
+ void (*tx_complete_skb)(struct mt76_dev *dev, struct mt76_queue *q,
+ struct mt76_queue_entry *e, bool flush);
+
+ void (*rx_skb)(struct mt76_dev *dev, enum mt76_rxq_id q,
+ struct sk_buff *skb);
+
+ void (*rx_poll_complete)(struct mt76_dev *dev, enum mt76_rxq_id q);
+};
+
+struct mt76_channel_state {
+ u64 cc_active;
+ u64 cc_busy;
+};
+
+struct mt76_sband {
+ struct ieee80211_supported_band sband;
+ struct mt76_channel_state *chan;
+};
+
+struct mt76_dev {
+ struct ieee80211_hw *hw;
+ struct cfg80211_chan_def chandef;
+ struct ieee80211_channel *main_chan;
+
+ spinlock_t lock;
+ spinlock_t cc_lock;
+ const struct mt76_bus_ops *bus;
+ const struct mt76_driver_ops *drv;
+ void __iomem *regs;
+ struct device *dev;
+
+ struct net_device napi_dev;
+ struct napi_struct napi[__MT_RXQ_MAX];
+ struct sk_buff_head rx_skb[__MT_RXQ_MAX];
+
+ struct list_head txwi_cache;
+ struct mt76_queue q_tx[__MT_TXQ_MAX];
+ struct mt76_queue q_rx[__MT_RXQ_MAX];
+ const struct mt76_queue_ops *queue_ops;
+
+ u8 macaddr[ETH_ALEN];
+ u32 rev;
+ unsigned long state;
+
+ struct mt76_sband sband_2g;
+ struct mt76_sband sband_5g;
+ struct debugfs_blob_wrapper eeprom;
+ struct debugfs_blob_wrapper otp;
+ struct mt76_hw_cap cap;
+
+ u32 debugfs_reg;
+};
+
+enum mt76_phy_type {
+ MT_PHY_TYPE_CCK,
+ MT_PHY_TYPE_OFDM,
+ MT_PHY_TYPE_HT,
+ MT_PHY_TYPE_HT_GF,
+ MT_PHY_TYPE_VHT,
+};
+
+struct mt76_rate_power {
+ union {
+ struct {
+ s8 cck[4];
+ s8 ofdm[8];
+ s8 ht[16];
+ s8 vht[10];
+ };
+ s8 all[38];
+ };
+};
+
+#define mt76_rr(dev, ...) (dev)->mt76.bus->rr(&((dev)->mt76), __VA_ARGS__)
+#define mt76_wr(dev, ...) (dev)->mt76.bus->wr(&((dev)->mt76), __VA_ARGS__)
+#define mt76_rmw(dev, ...) (dev)->mt76.bus->rmw(&((dev)->mt76), __VA_ARGS__)
+#define mt76_wr_copy(dev, ...) (dev)->mt76.bus->copy(&((dev)->mt76), __VA_ARGS__)
+
+#define mt76_set(dev, offset, val) mt76_rmw(dev, offset, 0, val)
+#define mt76_clear(dev, offset, val) mt76_rmw(dev, offset, val, 0)
+
+#define mt76_get_field(_dev, _reg, _field) \
+ FIELD_GET(_field, mt76_rr(dev, _reg))
+
+#define mt76_rmw_field(_dev, _reg, _field, _val) \
+ mt76_rmw(_dev, _reg, _field, FIELD_PREP(_field, _val))
+
+#define mt76_hw(dev) (dev)->mt76.hw
+
+bool __mt76_poll(struct mt76_dev *dev, u32 offset, u32 mask, u32 val,
+ int timeout);
+
+#define mt76_poll(dev, ...) __mt76_poll(&((dev)->mt76), __VA_ARGS__)
+
+bool __mt76_poll_msec(struct mt76_dev *dev, u32 offset, u32 mask, u32 val,
+ int timeout);
+
+#define mt76_poll_msec(dev, ...) __mt76_poll_msec(&((dev)->mt76), __VA_ARGS__)
+
+void mt76_mmio_init(struct mt76_dev *dev, void __iomem *regs);
+
+static inline u16 mt76_chip(struct mt76_dev *dev)
+{
+ return dev->rev >> 16;
+}
+
+static inline u16 mt76_rev(struct mt76_dev *dev)
+{
+ return dev->rev & 0xffff;
+}
+
+#define mt76xx_chip(dev) mt76_chip(&((dev)->mt76))
+#define mt76xx_rev(dev) mt76_rev(&((dev)->mt76))
+
+#define mt76_init_queues(dev) (dev)->mt76.queue_ops->init(&((dev)->mt76))
+#define mt76_queue_alloc(dev, ...) (dev)->mt76.queue_ops->alloc(&((dev)->mt76), __VA_ARGS__)
+#define mt76_queue_add_buf(dev, ...) (dev)->mt76.queue_ops->add_buf(&((dev)->mt76), __VA_ARGS__)
+#define mt76_queue_rx_reset(dev, ...) (dev)->mt76.queue_ops->rx_reset(&((dev)->mt76), __VA_ARGS__)
+#define mt76_queue_tx_cleanup(dev, ...) (dev)->mt76.queue_ops->tx_cleanup(&((dev)->mt76), __VA_ARGS__)
+#define mt76_queue_kick(dev, ...) (dev)->mt76.queue_ops->kick(&((dev)->mt76), __VA_ARGS__)
+
+static inline struct mt76_channel_state *
+mt76_channel_state(struct mt76_dev *dev, struct ieee80211_channel *c)
+{
+ struct mt76_sband *msband;
+ int idx;
+
+ if (c->band == NL80211_BAND_2GHZ)
+ msband = &dev->sband_2g;
+ else
+ msband = &dev->sband_5g;
+
+ idx = c - &msband->sband.channels[0];
+ return &msband->chan[idx];
+}
+
+int mt76_register_device(struct mt76_dev *dev, bool vht,
+ struct ieee80211_rate *rates, int n_rates);
+void mt76_unregister_device(struct mt76_dev *dev);
+
+struct dentry *mt76_register_debugfs(struct mt76_dev *dev);
+
+int mt76_eeprom_init(struct mt76_dev *dev, int len);
+void mt76_eeprom_override(struct mt76_dev *dev);
+
+static inline struct ieee80211_txq *
+mtxq_to_txq(struct mt76_txq *mtxq)
+{
+ void *ptr = mtxq;
+
+ return container_of(ptr, struct ieee80211_txq, drv_priv);
+}
+
+int mt76_tx_queue_skb(struct mt76_dev *dev, struct mt76_queue *q,
+ struct sk_buff *skb, struct mt76_wcid *wcid,
+ struct ieee80211_sta *sta);
+
+void mt76_rx(struct mt76_dev *dev, enum mt76_rxq_id q, struct sk_buff *skb);
+void mt76_tx(struct mt76_dev *dev, struct ieee80211_sta *sta,
+ struct mt76_wcid *wcid, struct sk_buff *skb);
+void mt76_txq_init(struct mt76_dev *dev, struct ieee80211_txq *txq);
+void mt76_txq_remove(struct mt76_dev *dev, struct ieee80211_txq *txq);
+void mt76_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq);
+void mt76_stop_tx_queues(struct mt76_dev *dev, struct ieee80211_sta *sta,
+ bool send_bar);
+void mt76_txq_schedule(struct mt76_dev *dev, struct mt76_queue *hwq);
+void mt76_txq_schedule_all(struct mt76_dev *dev);
+void mt76_release_buffered_frames(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta,
+ u16 tids, int nframes,
+ enum ieee80211_frame_release_type reason,
+ bool more_data);
+void mt76_set_channel(struct mt76_dev *dev);
+int mt76_get_survey(struct ieee80211_hw *hw, int idx,
+ struct survey_info *survey);
+
+/* internal */
+void mt76_tx_free(struct mt76_dev *dev);
+void mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t);
+void mt76_rx_complete(struct mt76_dev *dev, enum mt76_rxq_id q);
+
+#endif
diff --git a/drivers/net/wireless/mediatek/mt76/trace.c b/drivers/net/wireless/mediatek/mt76/trace.c
new file mode 100644
index 000000000000..ea4ab8729ae4
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/trace.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/module.h>
+
+#ifndef __CHECKER__
+#define CREATE_TRACE_POINTS
+#include "trace.h"
+
+#endif
diff --git a/drivers/net/wireless/mediatek/mt76/trace.h b/drivers/net/wireless/mediatek/mt76/trace.h
new file mode 100644
index 000000000000..ea30895933c5
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/trace.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if !defined(__MT76_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define __MT76_TRACE_H
+
+#include <linux/tracepoint.h>
+#include "mt76.h"
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mt76
+
+#define MAXNAME 32
+#define DEV_ENTRY __array(char, wiphy_name, 32)
+#define DEV_ASSIGN strlcpy(__entry->wiphy_name, wiphy_name(dev->hw->wiphy), MAXNAME)
+#define DEV_PR_FMT "%s"
+#define DEV_PR_ARG __entry->wiphy_name
+
+#define REG_ENTRY __field(u32, reg) __field(u32, val)
+#define REG_ASSIGN __entry->reg = reg; __entry->val = val
+#define REG_PR_FMT " %04x=%08x"
+#define REG_PR_ARG __entry->reg, __entry->val
+
+DECLARE_EVENT_CLASS(dev_reg_evt,
+ TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val),
+ TP_ARGS(dev, reg, val),
+ TP_STRUCT__entry(
+ DEV_ENTRY
+ REG_ENTRY
+ ),
+ TP_fast_assign(
+ DEV_ASSIGN;
+ REG_ASSIGN;
+ ),
+ TP_printk(
+ DEV_PR_FMT REG_PR_FMT,
+ DEV_PR_ARG, REG_PR_ARG
+ )
+);
+
+DEFINE_EVENT(dev_reg_evt, reg_rr,
+ TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val),
+ TP_ARGS(dev, reg, val)
+);
+
+DEFINE_EVENT(dev_reg_evt, reg_wr,
+ TP_PROTO(struct mt76_dev *dev, u32 reg, u32 val),
+ TP_ARGS(dev, reg, val)
+);
+
+#endif
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+
+#include <trace/define_trace.h>
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
new file mode 100644
index 000000000000..45bf0b17f7cf
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "mt76.h"
+
+static struct mt76_txwi_cache *
+mt76_alloc_txwi(struct mt76_dev *dev)
+{
+ struct mt76_txwi_cache *t;
+ dma_addr_t addr;
+ int size;
+
+ size = (sizeof(*t) + L1_CACHE_BYTES - 1) & ~(L1_CACHE_BYTES - 1);
+ t = devm_kzalloc(dev->dev, size, GFP_ATOMIC);
+ if (!t)
+ return NULL;
+
+ addr = dma_map_single(dev->dev, &t->txwi, sizeof(t->txwi),
+ DMA_TO_DEVICE);
+ t->dma_addr = addr;
+
+ return t;
+}
+
+static struct mt76_txwi_cache *
+__mt76_get_txwi(struct mt76_dev *dev)
+{
+ struct mt76_txwi_cache *t = NULL;
+
+ spin_lock_bh(&dev->lock);
+ if (!list_empty(&dev->txwi_cache)) {
+ t = list_first_entry(&dev->txwi_cache, struct mt76_txwi_cache,
+ list);
+ list_del(&t->list);
+ }
+ spin_unlock_bh(&dev->lock);
+
+ return t;
+}
+
+static struct mt76_txwi_cache *
+mt76_get_txwi(struct mt76_dev *dev)
+{
+ struct mt76_txwi_cache *t = __mt76_get_txwi(dev);
+
+ if (t)
+ return t;
+
+ return mt76_alloc_txwi(dev);
+}
+
+void
+mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t)
+{
+ if (!t)
+ return;
+
+ spin_lock_bh(&dev->lock);
+ list_add(&t->list, &dev->txwi_cache);
+ spin_unlock_bh(&dev->lock);
+}
+
+void mt76_tx_free(struct mt76_dev *dev)
+{
+ struct mt76_txwi_cache *t;
+
+ while ((t = __mt76_get_txwi(dev)) != NULL)
+ dma_unmap_single(dev->dev, t->dma_addr, sizeof(t->txwi),
+ DMA_TO_DEVICE);
+}
+
+static int
+mt76_txq_get_qid(struct ieee80211_txq *txq)
+{
+ if (!txq->sta)
+ return MT_TXQ_BE;
+
+ return txq->ac;
+}
+
+int mt76_tx_queue_skb(struct mt76_dev *dev, struct mt76_queue *q,
+ struct sk_buff *skb, struct mt76_wcid *wcid,
+ struct ieee80211_sta *sta)
+{
+ struct mt76_queue_entry e;
+ struct mt76_txwi_cache *t;
+ struct mt76_queue_buf buf[32];
+ struct sk_buff *iter;
+ dma_addr_t addr;
+ int len;
+ u32 tx_info = 0;
+ int n, ret;
+
+ t = mt76_get_txwi(dev);
+ if (!t) {
+ ieee80211_free_txskb(dev->hw, skb);
+ return -ENOMEM;
+ }
+
+ dma_sync_single_for_cpu(dev->dev, t->dma_addr, sizeof(t->txwi),
+ DMA_TO_DEVICE);
+ ret = dev->drv->tx_prepare_skb(dev, &t->txwi, skb, q, wcid, sta,
+ &tx_info);
+ dma_sync_single_for_device(dev->dev, t->dma_addr, sizeof(t->txwi),
+ DMA_TO_DEVICE);
+ if (ret < 0)
+ goto free;
+
+ len = skb->len - skb->data_len;
+ addr = dma_map_single(dev->dev, skb->data, len, DMA_TO_DEVICE);
+ if (dma_mapping_error(dev->dev, addr)) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ n = 0;
+ buf[n].addr = t->dma_addr;
+ buf[n++].len = dev->drv->txwi_size;
+ buf[n].addr = addr;
+ buf[n++].len = len;
+
+ skb_walk_frags(skb, iter) {
+ if (n == ARRAY_SIZE(buf))
+ goto unmap;
+
+ addr = dma_map_single(dev->dev, iter->data, iter->len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(dev->dev, addr))
+ goto unmap;
+
+ buf[n].addr = addr;
+ buf[n++].len = iter->len;
+ }
+
+ if (q->queued + (n + 1) / 2 >= q->ndesc - 1)
+ goto unmap;
+
+ return dev->queue_ops->add_buf(dev, q, buf, n, tx_info, skb, t);
+
+unmap:
+ ret = -ENOMEM;
+ for (n--; n > 0; n--)
+ dma_unmap_single(dev->dev, buf[n].addr, buf[n].len,
+ DMA_TO_DEVICE);
+
+free:
+ e.skb = skb;
+ e.txwi = t;
+ dev->drv->tx_complete_skb(dev, q, &e, true);
+ mt76_put_txwi(dev, t);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mt76_tx_queue_skb);
+
+void
+mt76_tx(struct mt76_dev *dev, struct ieee80211_sta *sta,
+ struct mt76_wcid *wcid, struct sk_buff *skb)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct mt76_queue *q;
+ int qid = skb_get_queue_mapping(skb);
+
+ if (WARN_ON(qid >= MT_TXQ_PSD)) {
+ qid = MT_TXQ_BE;
+ skb_set_queue_mapping(skb, qid);
+ }
+
+ if (!wcid->tx_rate_set)
+ ieee80211_get_tx_rates(info->control.vif, sta, skb,
+ info->control.rates, 1);
+
+ q = &dev->q_tx[qid];
+
+ spin_lock_bh(&q->lock);
+ mt76_tx_queue_skb(dev, q, skb, wcid, sta);
+ dev->queue_ops->kick(dev, q);
+
+ if (q->queued > q->ndesc - 8)
+ ieee80211_stop_queue(dev->hw, skb_get_queue_mapping(skb));
+ spin_unlock_bh(&q->lock);
+}
+EXPORT_SYMBOL_GPL(mt76_tx);
+
+static struct sk_buff *
+mt76_txq_dequeue(struct mt76_dev *dev, struct mt76_txq *mtxq, bool ps)
+{
+ struct ieee80211_txq *txq = mtxq_to_txq(mtxq);
+ struct sk_buff *skb;
+
+ skb = skb_dequeue(&mtxq->retry_q);
+ if (skb) {
+ u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
+
+ if (ps && skb_queue_empty(&mtxq->retry_q))
+ ieee80211_sta_set_buffered(txq->sta, tid, false);
+
+ return skb;
+ }
+
+ skb = ieee80211_tx_dequeue(dev->hw, txq);
+ if (!skb)
+ return NULL;
+
+ return skb;
+}
+
+static void
+mt76_check_agg_ssn(struct mt76_txq *mtxq, struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+
+ if (!ieee80211_is_data_qos(hdr->frame_control))
+ return;
+
+ mtxq->agg_ssn = le16_to_cpu(hdr->seq_ctrl) + 0x10;
+}
+
+static void
+mt76_queue_ps_skb(struct mt76_dev *dev, struct ieee80211_sta *sta,
+ struct sk_buff *skb, bool last)
+{
+ struct mt76_wcid *wcid = (struct mt76_wcid *) sta->drv_priv;
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct mt76_queue *hwq = &dev->q_tx[MT_TXQ_PSD];
+
+ info->control.flags |= IEEE80211_TX_CTRL_PS_RESPONSE;
+ if (last)
+ info->flags |= IEEE80211_TX_STATUS_EOSP;
+
+ mt76_skb_set_moredata(skb, !last);
+ mt76_tx_queue_skb(dev, hwq, skb, wcid, sta);
+}
+
+void
+mt76_release_buffered_frames(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
+ u16 tids, int nframes,
+ enum ieee80211_frame_release_type reason,
+ bool more_data)
+{
+ struct mt76_dev *dev = hw->priv;
+ struct sk_buff *last_skb = NULL;
+ struct mt76_queue *hwq = &dev->q_tx[MT_TXQ_PSD];
+ int i;
+
+ spin_lock_bh(&hwq->lock);
+ for (i = 0; tids && nframes; i++, tids >>= 1) {
+ struct ieee80211_txq *txq = sta->txq[i];
+ struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv;
+ struct sk_buff *skb;
+
+ if (!(tids & 1))
+ continue;
+
+ do {
+ skb = mt76_txq_dequeue(dev, mtxq, true);
+ if (!skb)
+ break;
+
+ if (mtxq->aggr)
+ mt76_check_agg_ssn(mtxq, skb);
+
+ nframes--;
+ if (last_skb)
+ mt76_queue_ps_skb(dev, sta, last_skb, false);
+
+ last_skb = skb;
+ } while (nframes);
+ }
+
+ if (last_skb) {
+ mt76_queue_ps_skb(dev, sta, last_skb, true);
+ dev->queue_ops->kick(dev, hwq);
+ }
+ spin_unlock_bh(&hwq->lock);
+}
+EXPORT_SYMBOL_GPL(mt76_release_buffered_frames);
+
+static int
+mt76_txq_send_burst(struct mt76_dev *dev, struct mt76_queue *hwq,
+ struct mt76_txq *mtxq, bool *empty)
+{
+ struct ieee80211_txq *txq = mtxq_to_txq(mtxq);
+ struct ieee80211_tx_info *info;
+ struct mt76_wcid *wcid = mtxq->wcid;
+ struct sk_buff *skb = NULL;
+ int n_frames = 1, limit;
+ struct ieee80211_tx_rate tx_rate;
+ bool ampdu;
+ bool probe;
+ int idx;
+
+ skb = mt76_txq_dequeue(dev, mtxq, false);
+ if (!skb) {
+ *empty = true;
+ return 0;
+ }
+
+ info = IEEE80211_SKB_CB(skb);
+ if (!wcid->tx_rate_set)
+ ieee80211_get_tx_rates(txq->vif, txq->sta, skb,
+ info->control.rates, 1);
+ tx_rate = info->control.rates[0];
+
+ probe = (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE);
+ ampdu = IEEE80211_SKB_CB(skb)->flags & IEEE80211_TX_CTL_AMPDU;
+ limit = ampdu ? 16 : 3;
+
+ if (ampdu)
+ mt76_check_agg_ssn(mtxq, skb);
+
+ idx = mt76_tx_queue_skb(dev, hwq, skb, wcid, txq->sta);
+
+ if (idx < 0)
+ return idx;
+
+ do {
+ bool cur_ampdu;
+
+ if (probe)
+ break;
+
+ skb = mt76_txq_dequeue(dev, mtxq, false);
+ if (!skb) {
+ *empty = true;
+ break;
+ }
+
+ cur_ampdu = info->flags & IEEE80211_TX_CTL_AMPDU;
+
+ if (ampdu != cur_ampdu ||
+ (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)) {
+ skb_queue_tail(&mtxq->retry_q, skb);
+ break;
+ }
+
+ info = IEEE80211_SKB_CB(skb);
+ info->control.rates[0] = tx_rate;
+
+ if (cur_ampdu)
+ mt76_check_agg_ssn(mtxq, skb);
+
+ idx = mt76_tx_queue_skb(dev, hwq, skb, wcid, txq->sta);
+ if (idx < 0)
+ return idx;
+
+ n_frames++;
+ } while (n_frames < limit);
+
+ if (!probe) {
+ hwq->swq_queued++;
+ hwq->entry[idx].schedule = true;
+ }
+
+ dev->queue_ops->kick(dev, hwq);
+
+ return n_frames;
+}
+
+static int
+mt76_txq_schedule_list(struct mt76_dev *dev, struct mt76_queue *hwq)
+{
+ struct mt76_txq *mtxq, *mtxq_last;
+ int len = 0;
+
+restart:
+ mtxq_last = list_last_entry(&hwq->swq, struct mt76_txq, list);
+ while (!list_empty(&hwq->swq)) {
+ bool empty = false;
+ int cur;
+
+ mtxq = list_first_entry(&hwq->swq, struct mt76_txq, list);
+ if (mtxq->send_bar && mtxq->aggr) {
+ struct ieee80211_txq *txq = mtxq_to_txq(mtxq);
+ struct ieee80211_sta *sta = txq->sta;
+ struct ieee80211_vif *vif = txq->vif;
+ u16 agg_ssn = mtxq->agg_ssn;
+ u8 tid = txq->tid;
+
+ mtxq->send_bar = false;
+ spin_unlock_bh(&hwq->lock);
+ ieee80211_send_bar(vif, sta->addr, tid, agg_ssn);
+ spin_lock_bh(&hwq->lock);
+ goto restart;
+ }
+
+ list_del_init(&mtxq->list);
+
+ cur = mt76_txq_send_burst(dev, hwq, mtxq, &empty);
+ if (!empty)
+ list_add_tail(&mtxq->list, &hwq->swq);
+
+ if (cur < 0)
+ return cur;
+
+ len += cur;
+
+ if (mtxq == mtxq_last)
+ break;
+ }
+
+ return len;
+}
+
+void mt76_txq_schedule(struct mt76_dev *dev, struct mt76_queue *hwq)
+{
+ int len;
+
+ if (test_bit(MT76_SCANNING, &dev->state) ||
+ test_bit(MT76_RESET, &dev->state))
+ return;
+
+ do {
+ if (hwq->swq_queued >= 4 || list_empty(&hwq->swq))
+ break;
+
+ len = mt76_txq_schedule_list(dev, hwq);
+ } while (len > 0);
+}
+EXPORT_SYMBOL_GPL(mt76_txq_schedule);
+
+void mt76_txq_schedule_all(struct mt76_dev *dev)
+{
+ int i;
+
+ for (i = 0; i <= MT_TXQ_BK; i++) {
+ struct mt76_queue *q = &dev->q_tx[i];
+
+ spin_lock_bh(&q->lock);
+ mt76_txq_schedule(dev, q);
+ spin_unlock_bh(&q->lock);
+ }
+}
+EXPORT_SYMBOL_GPL(mt76_txq_schedule_all);
+
+void mt76_stop_tx_queues(struct mt76_dev *dev, struct ieee80211_sta *sta,
+ bool send_bar)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+ struct ieee80211_txq *txq = sta->txq[i];
+ struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv;
+
+ spin_lock_bh(&mtxq->hwq->lock);
+ mtxq->send_bar = mtxq->aggr && send_bar;
+ if (!list_empty(&mtxq->list))
+ list_del_init(&mtxq->list);
+ spin_unlock_bh(&mtxq->hwq->lock);
+ }
+}
+EXPORT_SYMBOL_GPL(mt76_stop_tx_queues);
+
+void mt76_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq)
+{
+ struct mt76_dev *dev = hw->priv;
+ struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv;
+ struct mt76_queue *hwq = mtxq->hwq;
+
+ spin_lock_bh(&hwq->lock);
+ if (list_empty(&mtxq->list))
+ list_add_tail(&mtxq->list, &hwq->swq);
+ mt76_txq_schedule(dev, hwq);
+ spin_unlock_bh(&hwq->lock);
+}
+EXPORT_SYMBOL_GPL(mt76_wake_tx_queue);
+
+void mt76_txq_remove(struct mt76_dev *dev, struct ieee80211_txq *txq)
+{
+ struct mt76_txq *mtxq;
+ struct mt76_queue *hwq;
+ struct sk_buff *skb;
+
+ if (!txq)
+ return;
+
+ mtxq = (struct mt76_txq *) txq->drv_priv;
+ hwq = mtxq->hwq;
+
+ spin_lock_bh(&hwq->lock);
+ if (!list_empty(&mtxq->list))
+ list_del(&mtxq->list);
+ spin_unlock_bh(&hwq->lock);
+
+ while ((skb = skb_dequeue(&mtxq->retry_q)) != NULL)
+ ieee80211_free_txskb(dev->hw, skb);
+}
+EXPORT_SYMBOL_GPL(mt76_txq_remove);
+
+void mt76_txq_init(struct mt76_dev *dev, struct ieee80211_txq *txq)
+{
+ struct mt76_txq *mtxq = (struct mt76_txq *) txq->drv_priv;
+
+ INIT_LIST_HEAD(&mtxq->list);
+ skb_queue_head_init(&mtxq->retry_q);
+
+ mtxq->hwq = &dev->q_tx[mt76_txq_get_qid(txq)];
+}
+EXPORT_SYMBOL_GPL(mt76_txq_init);
diff --git a/drivers/net/wireless/mediatek/mt76/util.c b/drivers/net/wireless/mediatek/mt76/util.c
new file mode 100644
index 000000000000..0c35b8db58cd
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/util.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include "mt76.h"
+
+bool __mt76_poll(struct mt76_dev *dev, u32 offset, u32 mask, u32 val,
+ int timeout)
+{
+ u32 cur;
+
+ timeout /= 10;
+ do {
+ cur = dev->bus->rr(dev, offset) & mask;
+ if (cur == val)
+ return true;
+
+ udelay(10);
+ } while (timeout-- > 0);
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(__mt76_poll);
+
+bool __mt76_poll_msec(struct mt76_dev *dev, u32 offset, u32 mask, u32 val,
+ int timeout)
+{
+ u32 cur;
+
+ timeout /= 10;
+ do {
+ cur = dev->bus->rr(dev, offset) & mask;
+ if (cur == val)
+ return true;
+
+ usleep_range(10000, 20000);
+ } while (timeout-- > 0);
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(__mt76_poll_msec);
+
+int mt76_wcid_alloc(unsigned long *mask, int size)
+{
+ int i, idx = 0, cur;
+
+ for (i = 0; i < size / BITS_PER_LONG; i++) {
+ idx = ffs(~mask[i]);
+ if (!idx)
+ continue;
+
+ idx--;
+ cur = i * BITS_PER_LONG + idx;
+ if (cur >= size)
+ break;
+
+ mask[i] |= BIT(idx);
+ return cur;
+ }
+
+ return -1;
+}
+EXPORT_SYMBOL_GPL(mt76_wcid_alloc);
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/mediatek/mt76/util.h b/drivers/net/wireless/mediatek/mt76/util.h
new file mode 100644
index 000000000000..018d475504a2
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/util.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __MT76_UTIL_H
+#define __MT76_UTIL_H
+
+#include <linux/skbuff.h>
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+
+#define MT76_INCR(_var, _size) \
+ _var = (((_var) + 1) % _size)
+
+int mt76_wcid_alloc(unsigned long *mask, int size);
+
+static inline void
+mt76_wcid_free(unsigned long *mask, int idx)
+{
+ mask[idx / BITS_PER_LONG] &= ~BIT(idx % BITS_PER_LONG);
+}
+
+static inline void
+mt76_skb_set_moredata(struct sk_buff *skb, bool enable)
+{
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
+
+ if (enable)
+ hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA);
+ else
+ hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_MOREDATA);
+}
+
+#endif
--
2.14.2
^ permalink raw reply related
* [PATCH v6 0/3] mt76: add new wireless driver for MediaTek MT76x2 PCIe chips
From: Felix Fietkau @ 2017-10-06 11:02 UTC (permalink / raw)
To: linux-wireless; +Cc: kvalo
Changes since v5:
- Adjust for mac80211 API changes
- EEPROM parsing fixes
- Ad-hoc mode WPA2 fixes
Changes since v4:
- Cleanups suggested by Stanislaw Gruszka
- Device tree fixes suggested by Rob Herring
- EEPROM MAC address parsing fix
Changes since v3:
- DFS fixes
- stability fixes
- use wiphy_read_of_freq_limits
Changes since v2:
- lots of checkpatch cleanups
- various tx path (and other) fixes
- use the new bitfield API
- documented device tree bindings
Felix Fietkau (3):
Documentation: dt: net: add mt76 wireless device binding
mt76: add common code shared between multiple chipsets
mt76: add driver code for MT76x2e
.../bindings/net/wireless/mediatek,mt76.txt | 24 +
drivers/net/wireless/mediatek/Kconfig | 1 +
drivers/net/wireless/mediatek/Makefile | 1 +
drivers/net/wireless/mediatek/mt76/Kconfig | 10 +
drivers/net/wireless/mediatek/mt76/Makefile | 15 +
drivers/net/wireless/mediatek/mt76/debugfs.c | 76 ++
drivers/net/wireless/mediatek/mt76/dma.c | 451 ++++++++++++
drivers/net/wireless/mediatek/mt76/dma.h | 38 +
drivers/net/wireless/mediatek/mt76/eeprom.c | 112 +++
drivers/net/wireless/mediatek/mt76/mac80211.c | 344 +++++++++
drivers/net/wireless/mediatek/mt76/mmio.c | 61 ++
drivers/net/wireless/mediatek/mt76/mt76.h | 355 ++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2.h | 223 ++++++
drivers/net/wireless/mediatek/mt76/mt76x2_core.c | 88 +++
.../net/wireless/mediatek/mt76/mt76x2_debugfs.c | 133 ++++
drivers/net/wireless/mediatek/mt76/mt76x2_dfs.c | 493 +++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_dfs.h | 80 +++
drivers/net/wireless/mediatek/mt76/mt76x2_dma.c | 184 +++++
drivers/net/wireless/mediatek/mt76/mt76x2_dma.h | 68 ++
drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.c | 644 +++++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.h | 181 +++++
drivers/net/wireless/mediatek/mt76/mt76x2_init.c | 784 +++++++++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_mac.c | 738 +++++++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_mac.h | 189 +++++
drivers/net/wireless/mediatek/mt76/mt76x2_main.c | 534 ++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_mcu.c | 452 ++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_mcu.h | 136 ++++
drivers/net/wireless/mediatek/mt76/mt76x2_pci.c | 109 +++
drivers/net/wireless/mediatek/mt76/mt76x2_phy.c | 691 ++++++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_regs.h | 566 +++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76x2_trace.c | 23 +
drivers/net/wireless/mediatek/mt76/mt76x2_trace.h | 144 ++++
drivers/net/wireless/mediatek/mt76/mt76x2_tx.c | 255 +++++++
drivers/net/wireless/mediatek/mt76/trace.c | 23 +
drivers/net/wireless/mediatek/mt76/trace.h | 71 ++
drivers/net/wireless/mediatek/mt76/tx.c | 511 ++++++++++++++
drivers/net/wireless/mediatek/mt76/util.c | 78 ++
drivers/net/wireless/mediatek/mt76/util.h | 44 ++
38 files changed, 8930 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/wireless/mediatek,mt76.txt
create mode 100644 drivers/net/wireless/mediatek/mt76/Kconfig
create mode 100644 drivers/net/wireless/mediatek/mt76/Makefile
create mode 100644 drivers/net/wireless/mediatek/mt76/debugfs.c
create mode 100644 drivers/net/wireless/mediatek/mt76/dma.c
create mode 100644 drivers/net/wireless/mediatek/mt76/dma.h
create mode 100644 drivers/net/wireless/mediatek/mt76/eeprom.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mac80211.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mmio.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_core.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_debugfs.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_dfs.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_dfs.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_dma.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_dma.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_eeprom.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_init.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_mac.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_mac.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_main.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_mcu.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_mcu.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_pci.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_phy.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_regs.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_trace.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_trace.h
create mode 100644 drivers/net/wireless/mediatek/mt76/mt76x2_tx.c
create mode 100644 drivers/net/wireless/mediatek/mt76/trace.c
create mode 100644 drivers/net/wireless/mediatek/mt76/trace.h
create mode 100644 drivers/net/wireless/mediatek/mt76/tx.c
create mode 100644 drivers/net/wireless/mediatek/mt76/util.c
create mode 100644 drivers/net/wireless/mediatek/mt76/util.h
--
2.11.0
^ permalink raw reply
* Re: [PATCH 2/2] mac80211: only remove AP VLAN frames from TXQ
From: Toke Høiland-Jørgensen @ 2017-10-06 10:30 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: netdev, Michał Kazior, Johannes Berg
In-Reply-To: <20171006095333.26335-2-johannes@sipsolutions.net>
Johannes Berg <johannes@sipsolutions.net> writes:
> From: Johannes Berg <johannes.berg@intel.com>
>
> When removing an AP VLAN interface, mac80211 currently purges
> the entire TXQ for the AP interface. Fix this by using the FQ
> API introduced in the previous patch to filter frames.
>
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Acked-by: Toke H=C3=B8iland-J=C3=B8rgensen <toke@toke.dk>
^ permalink raw reply
* Re: [PATCH 1/2] fq: support filtering a given tin
From: Toke Høiland-Jørgensen @ 2017-10-06 10:30 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: netdev, Michał Kazior, Johannes Berg
In-Reply-To: <20171006095333.26335-1-johannes@sipsolutions.net>
Johannes Berg <johannes@sipsolutions.net> writes:
> From: Johannes Berg <johannes.berg@intel.com>
>
> Add to the FQ API a way to filter a given tin, in order to
> remove frames that fulfil certain criteria according to a
> filter function.
>
> This will be used by mac80211 to remove frames belonging to
> an AP VLAN interface that's being removed.
>
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Acked-by: Toke H=C3=B8iland-J=C3=B8rgensen <toke@toke.dk>
^ permalink raw reply
* Re: converting mac80211 to TXQs entirely
From: Johannes Berg @ 2017-10-06 10:18 UTC (permalink / raw)
To: Toke Høiland-Jørgensen, linux-wireless; +Cc: nbd, Kalle Valo
In-Reply-To: <87376wshqn.fsf@toke.dk>
On Fri, 2017-10-06 at 12:17 +0200, Toke Høiland-Jørgensen wrote:
>
> Do you have any opinion on whether to use ERR_PTR or change the
> function to an int return and pass the pointer as an argument? At
> least ath10k seems to do the latter (returning -ENOENT when no packet
> is available).
I think using an ERR_PTR is nicer, but I have no strong opinions (I
just don't like out parameters all that much and avoid them when
possible)
johannes
^ permalink raw reply
* Re: converting mac80211 to TXQs entirely
From: Toke Høiland-Jørgensen @ 2017-10-06 10:17 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: nbd, Kalle Valo
In-Reply-To: <1507283082.19300.6.camel@sipsolutions.net>
Johannes Berg <johannes@sipsolutions.net> writes:
>> The obvious alternative is to do a token-based airtime scheduler,
>> where the airtime used by one station is immediately divided out to
>> all active stations. But that requires walking over all active
>> stations on every TX completion, and there's a lot of housekeeping to
>> make sure we don't accidentally starve everyone. So I'd prefer to
>> stick with the DRR scheduler :)
>
> Sure, works for me.
>
> I have no objection to defining a special error code (we can always
> use ERR_PTR(-EAGAIN) or something like that, after all) - we just need
> to be careful with driver updates.
Right. I'll see if I can cook up an RFC.
Do you have any opinion on whether to use ERR_PTR or change the function
to an int return and pass the pointer as an argument? At least ath10k
seems to do the latter (returning -ENOENT when no packet is available).
-Toke
^ permalink raw reply
* [PATCH 1/2] fq: support filtering a given tin
From: Johannes Berg @ 2017-10-06 9:53 UTC (permalink / raw)
To: linux-wireless
Cc: Toke Høiland-Jørgensen, netdev, Michał Kazior,
Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
Add to the FQ API a way to filter a given tin, in order to
remove frames that fulfil certain criteria according to a
filter function.
This will be used by mac80211 to remove frames belonging to
an AP VLAN interface that's being removed.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/fq.h | 7 +++++
include/net/fq_impl.h | 72 ++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 69 insertions(+), 10 deletions(-)
diff --git a/include/net/fq.h b/include/net/fq.h
index 6d8521a30c5c..ac944a686840 100644
--- a/include/net/fq.h
+++ b/include/net/fq.h
@@ -90,6 +90,13 @@ typedef void fq_skb_free_t(struct fq *,
struct fq_flow *,
struct sk_buff *);
+/* Return %true to filter (drop) the frame. */
+typedef bool fq_skb_filter_t(struct fq *,
+ struct fq_tin *,
+ struct fq_flow *,
+ struct sk_buff *,
+ void *);
+
typedef struct fq_flow *fq_flow_get_default_t(struct fq *,
struct fq_tin *,
int idx,
diff --git a/include/net/fq_impl.h b/include/net/fq_impl.h
index 4e6131cd3f43..8b237e4afee6 100644
--- a/include/net/fq_impl.h
+++ b/include/net/fq_impl.h
@@ -12,24 +12,22 @@
/* functions that are embedded into includer */
-static struct sk_buff *fq_flow_dequeue(struct fq *fq,
- struct fq_flow *flow)
+static void fq_adjust_removal(struct fq *fq,
+ struct fq_flow *flow,
+ struct sk_buff *skb)
{
struct fq_tin *tin = flow->tin;
- struct fq_flow *i;
- struct sk_buff *skb;
-
- lockdep_assert_held(&fq->lock);
-
- skb = __skb_dequeue(&flow->queue);
- if (!skb)
- return NULL;
tin->backlog_bytes -= skb->len;
tin->backlog_packets--;
flow->backlog -= skb->len;
fq->backlog--;
fq->memory_usage -= skb->truesize;
+}
+
+static void fq_rejigger_backlog(struct fq *fq, struct fq_flow *flow)
+{
+ struct fq_flow *i;
if (flow->backlog == 0) {
list_del_init(&flow->backlogchain);
@@ -43,6 +41,21 @@ static struct sk_buff *fq_flow_dequeue(struct fq *fq,
list_move_tail(&flow->backlogchain,
&i->backlogchain);
}
+}
+
+static struct sk_buff *fq_flow_dequeue(struct fq *fq,
+ struct fq_flow *flow)
+{
+ struct sk_buff *skb;
+
+ lockdep_assert_held(&fq->lock);
+
+ skb = __skb_dequeue(&flow->queue);
+ if (!skb)
+ return NULL;
+
+ fq_adjust_removal(fq, flow, skb);
+ fq_rejigger_backlog(fq, flow);
return skb;
}
@@ -188,6 +201,45 @@ static void fq_tin_enqueue(struct fq *fq,
}
}
+static void fq_flow_filter(struct fq *fq,
+ struct fq_flow *flow,
+ fq_skb_filter_t filter_func,
+ void *filter_data,
+ fq_skb_free_t free_func)
+{
+ struct fq_tin *tin = flow->tin;
+ struct sk_buff *skb, *tmp;
+
+ lockdep_assert_held(&fq->lock);
+
+ skb_queue_walk_safe(&flow->queue, skb, tmp) {
+ if (!filter_func(fq, tin, flow, skb, filter_data))
+ continue;
+
+ __skb_unlink(skb, &flow->queue);
+ fq_adjust_removal(fq, flow, skb);
+ free_func(fq, tin, flow, skb);
+ }
+
+ fq_rejigger_backlog(fq, flow);
+}
+
+static void fq_tin_filter(struct fq *fq,
+ struct fq_tin *tin,
+ fq_skb_filter_t filter_func,
+ void *filter_data,
+ fq_skb_free_t free_func)
+{
+ struct fq_flow *flow;
+
+ lockdep_assert_held(&fq->lock);
+
+ list_for_each_entry(flow, &tin->new_flows, flowchain)
+ fq_flow_filter(fq, flow, filter_func, filter_data, free_func);
+ list_for_each_entry(flow, &tin->old_flows, flowchain)
+ fq_flow_filter(fq, flow, filter_func, filter_data, free_func);
+}
+
static void fq_flow_reset(struct fq *fq,
struct fq_flow *flow,
fq_skb_free_t free_func)
--
2.14.2
^ permalink raw reply related
* [PATCH 2/2] mac80211: only remove AP VLAN frames from TXQ
From: Johannes Berg @ 2017-10-06 9:53 UTC (permalink / raw)
To: linux-wireless
Cc: Toke Høiland-Jørgensen, netdev, Michał Kazior,
Johannes Berg
In-Reply-To: <20171006095333.26335-1-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
When removing an AP VLAN interface, mac80211 currently purges
the entire TXQ for the AP interface. Fix this by using the FQ
API introduced in the previous patch to filter frames.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ieee80211_i.h | 2 ++
net/mac80211/iface.c | 25 +++----------------------
net/mac80211/tx.c | 34 ++++++++++++++++++++++++++++++++++
3 files changed, 39 insertions(+), 22 deletions(-)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 9675814f64db..68f874e73561 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2009,6 +2009,8 @@ void ieee80211_txq_init(struct ieee80211_sub_if_data *sdata,
struct txq_info *txq, int tid);
void ieee80211_txq_purge(struct ieee80211_local *local,
struct txq_info *txqi);
+void ieee80211_txq_remove_vlan(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata);
void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
u16 transaction, u16 auth_alg, u16 status,
const u8 *extra, size_t extra_len, const u8 *bssid,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 2619daa29961..13b16f90e1cf 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -793,9 +793,7 @@ static int ieee80211_open(struct net_device *dev)
static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
bool going_down)
{
- struct ieee80211_sub_if_data *txq_sdata = sdata;
struct ieee80211_local *local = sdata->local;
- struct fq *fq = &local->fq;
unsigned long flags;
struct sk_buff *skb, *tmp;
u32 hw_reconf_flags = 0;
@@ -939,9 +937,6 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP_VLAN:
- txq_sdata = container_of(sdata->bss,
- struct ieee80211_sub_if_data, u.ap);
-
mutex_lock(&local->mtx);
list_del(&sdata->u.vlan.list);
mutex_unlock(&local->mtx);
@@ -998,8 +993,6 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
skb_queue_purge(&sdata->skb_queue);
}
- sdata->bss = NULL;
-
spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
for (i = 0; i < IEEE80211_MAX_QUEUES; i++) {
skb_queue_walk_safe(&local->pending[i], skb, tmp) {
@@ -1012,22 +1005,10 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
}
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
- if (txq_sdata->vif.txq) {
- struct txq_info *txqi = to_txq_info(txq_sdata->vif.txq);
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ ieee80211_txq_remove_vlan(local, sdata);
- /*
- * FIXME FIXME
- *
- * We really shouldn't purge the *entire* txqi since that
- * contains frames for the other AP_VLANs (and possibly
- * the AP itself) as well, but there's no API in FQ now
- * to be able to filter.
- */
-
- spin_lock_bh(&fq->lock);
- ieee80211_txq_purge(local, txqi);
- spin_unlock_bh(&fq->lock);
- }
+ sdata->bss = NULL;
if (local->open_count == 0)
ieee80211_clear_tx_pending(local);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 94826680cf2b..7b8154474b9e 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1396,6 +1396,40 @@ static void ieee80211_txq_enqueue(struct ieee80211_local *local,
fq_flow_get_default_func);
}
+static bool fq_vlan_filter_func(struct fq *fq, struct fq_tin *tin,
+ struct fq_flow *flow, struct sk_buff *skb,
+ void *data)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+ return info->control.vif == data;
+}
+
+void ieee80211_txq_remove_vlan(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata)
+{
+ struct fq *fq = &local->fq;
+ struct txq_info *txqi;
+ struct fq_tin *tin;
+ struct ieee80211_sub_if_data *ap;
+
+ if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP_VLAN))
+ return;
+
+ ap = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap);
+
+ if (!ap->vif.txq)
+ return;
+
+ txqi = to_txq_info(ap->vif.txq);
+ tin = &txqi->tin;
+
+ spin_lock_bh(&fq->lock);
+ fq_tin_filter(fq, tin, fq_vlan_filter_func, &sdata->vif,
+ fq_skb_free_func);
+ spin_unlock_bh(&fq->lock);
+}
+
void ieee80211_txq_init(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta,
struct txq_info *txqi, int tid)
--
2.14.2
^ permalink raw reply related
* Re: [PATCH v2] net/mac80211/mesh_plink: Convert timers to use timer_setup()
From: Johannes Berg @ 2017-10-06 9:49 UTC (permalink / raw)
To: Kees Cook
Cc: David S. Miller, linux-wireless, netdev, Thomas Gleixner,
linux-kernel
In-Reply-To: <20171005173910.GA72335@beast>
On Thu, 2017-10-05 at 10:39 -0700, Kees Cook wrote:
> In preparation for unconditionally passing the struct timer_list
> pointer to
> all timer callbacks, switch to using the new timer_setup() and
> from_timer()
> to pass the timer pointer explicitly. This requires adding a pointer
> back
> to the sta_info since container_of() can't resolve the sta_info.
>
Applied, thanks. (The change did land in net-next, and I merged that in
to merge this)
johannes
^ permalink raw reply
* Re: converting mac80211 to TXQs entirely
From: Johannes Berg @ 2017-10-06 9:44 UTC (permalink / raw)
To: Toke Høiland-Jørgensen, linux-wireless; +Cc: nbd, Kalle Valo
In-Reply-To: <878tgps1o7.fsf@toke.dk>
On Thu, 2017-10-05 at 23:52 +0200, Toke Høiland-Jørgensen wrote:
>
> > I think that's reasonable. I'm not really sure it's *necessary*
> > though? Couldn't mac80211 return NULL, and then simply call
> > wake_tx_queue again when the TXQ becomes eligible for scheduling
> > again? Otherwise the driver might end up doing a lot of polling for
> > it to become eligible again?
>
> Theoretically, but then there would have to be some kind of callback
> or another mechanism to figure out when the queue is ready again. The
> neat thing about DRR scheduling is that we just use the fact that
> there was an attempt to schedule the queue as an opportunity to
> increase that queue's deficit by one quantum. This does give a bit of
> "polling overhead" as you say, but it has been hidden in the
> measurement noise in all my tests.
Ok.
> The obvious alternative is to do a token-based airtime scheduler,
> where the airtime used by one station is immediately divided out to
> all active stations. But that requires walking over all active
> stations on every TX completion, and there's a lot of housekeeping to
> make sure we don't accidentally starve everyone. So I'd prefer to
> stick with the DRR scheduler :)
Sure, works for me.
I have no objection to defining a special error code (we can always use
ERR_PTR(-EAGAIN) or something like that, after all) - we just need to
be careful with driver updates.
Given all these discussions, I'd actually like to put a temporary
restriction on merging new drivers with TXQ support - Felix, I think
you were planning to eventually use this for mt7601u? How far along is
that?
johannes
^ permalink raw reply
* Re: [RFC v2 1/2] fq: support filtering a given tin
From: Johannes Berg @ 2017-10-06 9:42 UTC (permalink / raw)
To: Toke Høiland-Jørgensen, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <87h8vdslpr.fsf@toke.dk>
On Thu, 2017-10-05 at 16:39 +0200, Toke Høiland-Jørgensen wrote:
> Johannes Berg <johannes@sipsolutions.net> writes:
>
> > From: Johannes Berg <johannes.berg@intel.com>
> >
> > Add to the FQ API a way to filter a given tin, in order to
> > remove frames that fulfil certain criteria according to a
> > filter function.
> >
> > This will be used by mac80211 to remove frames belonging to
> > an AP VLAN interface that's being removed.
> >
> > Signed-off-by: Johannes Berg <johannes.berg@intel.com>
>
> Yup, this version looks reasonable to me :)
Do you know if anyone feels responsible for the fq code?
Should I Cc netdev on these patches?
johannes
^ permalink raw reply
* Re: [PATCH] mwifiex: Use put_unaligned_le32
From: Igor Mitsyanko @ 2017-10-05 21:54 UTC (permalink / raw)
To: Himanshu Jha, Brian Norris
Cc: Kalle Valo, amitkarwar, nishants, gbhat, huxm, linux-wireless,
netdev, linux-kernel
In-Reply-To: <20171005190730.GA8043@himanshu-Vostro-3559>
On 10/05/2017 12:07 PM, Himanshu Jha wrote:
>>
>> In this case, the key is CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS. It
>> seems that asm-generic/unaligned.h is set up to include different
>> headers, based on the expected architecture behavior.
>>
> Yes, asm-generic/unaligned.h looks more appopriate and is most generic
> implementation of unaligned accesses and arc specific.
>
Probably the idea is that each ARCH knows exactly what is the best way
to do unaligned access, so common code (like drivers) should include
ARCH-specific asm/unaligned.h only. It will then decide how to do that
and will include asm-generic/unaligned.h itself, if required.
^ permalink raw reply
* Re: converting mac80211 to TXQs entirely
From: Toke Høiland-Jørgensen @ 2017-10-05 21:52 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: nbd
In-Reply-To: <1507221836.2387.66.camel@sipsolutions.net>
Johannes Berg <johannes@sipsolutions.net> writes:
> On Thu, 2017-10-05 at 18:28 +0200, Toke H=C3=B8iland-J=C3=B8rgensen wrote:
>
>> I'm been thinking about how to move the airtime fairness scheduler
>> out of ath9k and into mac80211 (so more drivers can take advantage of
>> it). This will require some changes to the TXQ API that drivers speak
>> to, so wanted to add my thoughts here to make sure it's compatible
>> with your thinking.
>>=20
>> I think the easiest way to have mac80211 handle airtime fairness is
>> to add a way for ieee80211_tx_dequeue() to return some sort of DEFER
>> signal which semantically means "there is no packet for this queue
>> right now, but please keep scheduling it" (which would be the result
>> of a TXQ belonging to a station that has used its airtime quota but
>> still has packets pending). This is different from the current
>> meaning of NULL, which will make the driver stop scheduling that TXQ
>> until it gets a new wake signal.
>
> I think that's reasonable. I'm not really sure it's *necessary*
> though? Couldn't mac80211 return NULL, and then simply call
> wake_tx_queue again when the TXQ becomes eligible for scheduling
> again? Otherwise the driver might end up doing a lot of polling for it
> to become eligible again?
Theoretically, but then there would have to be some kind of callback or
another mechanism to figure out when the queue is ready again. The neat
thing about DRR scheduling is that we just use the fact that there was
an attempt to schedule the queue as an opportunity to increase that
queue's deficit by one quantum. This does give a bit of "polling
overhead" as you say, but it has been hidden in the measurement noise in
all my tests.
The obvious alternative is to do a token-based airtime scheduler, where
the airtime used by one station is immediately divided out to all active
stations. But that requires walking over all active stations on every TX
completion, and there's a lot of housekeeping to make sure we don't
accidentally starve everyone. So I'd prefer to stick with the DRR
scheduler :)
>> And I'm wondering if this "is queue stopped" API could be the same
>> one used for the airtime DEFER case?
>
> I think these are two completely different cases.
>
> The "is queue stopped" I was thinking of would be basically checking
> the variable local->queue_stop_reasons, so that the driver could still
> use the stop_queue API(s). Yeah, this would be very roundabout, but as
> a conversion step I think that'd not be a bad thing.
Ah, I see. Yeah, these are different, and the existing packet/NULL
return is probably sufficient, then :)
>> Oh, and BTW: I see this is on the list of topics for the wireless
>> summit in Seoul. How do I go about signing up for that? I'll be at
>> netdev talking about some of this stuff anyway[1] :)
>
> Just show up there, or you can add yourself to the list on the wiki
> page :)
Cool, I did. See you in Seoul :)
-Toke
^ permalink raw reply
* Re: [PATCH] mwifiex: Use put_unaligned_le32
From: Himanshu Jha @ 2017-10-05 19:07 UTC (permalink / raw)
To: Brian Norris
Cc: Kalle Valo, amitkarwar, nishants, gbhat, huxm, linux-wireless,
netdev, linux-kernel
In-Reply-To: <20171005180248.GA94139@google.com>
On Thu, Oct 05, 2017 at 11:02:50AM -0700, Brian Norris wrote:
> On Thu, Oct 05, 2017 at 08:52:33PM +0530, Himanshu Jha wrote:
> > There are various instances where a function used in file say for eg
> > int func_align (void* a)
> > is used and it is defined in align.h
> > But many files don't *directly* include align.h and rather include
> > any other header which includes align.h
>
> I believe the general rule is that you should included headers for all
> symbols you use, and not rely on implicit includes.
>
> The modification to the general rule is that not all headers are
> intended to be included directly, and in such cases there's likely a
> parent header that is the more appropriate target.
>
> In this case, the key is CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS. It
> seems that asm-generic/unaligned.h is set up to include different
> headers, based on the expected architecture behavior.
>
Yes, asm-generic/unaligned.h looks more appopriate and is most generic
implementation of unaligned accesses and arc specific.
Let's see what Kalle Valo recommends! And then I will send v2 of the
patch.
Thanks for the information!
Himanshu Jha
> I wonder if include/linux/unaligned/access_ok.h should have a safety
> check (e.g., raise an #error if
> !CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS?).
>
> > Is compiling the file the only way to check if apppropriate header is
> > included or is there some other way to check for it.
>
> I believe it's mostly manual. Implicit includes have been a problem for
> anyone who refactors header files.
>
> Brian
^ permalink raw reply
* Re: [PATCH V2] Fix a sleep-in-atomic bug in shash_setkey_unaligned
From: Marcelo Ricardo Leitner @ 2017-10-05 19:07 UTC (permalink / raw)
To: Herbert Xu
Cc: David Miller, luto, baijiaju1990, nhorman, vyasevich, kvalo,
linux-crypto, netdev, linux-sctp, linux-wireless
In-Reply-To: <20171005131631.GA1553@gondor.apana.org.au>
On Thu, Oct 05, 2017 at 09:16:31PM +0800, Herbert Xu wrote:
> On Thu, Oct 05, 2017 at 06:16:20PM +0800, Herbert Xu wrote:
> >
> > That was my point. Functions like sctp_pack_cookie shouldn't be
> > setting the key in the first place. The setkey should happen at
> > the point when the key is generated. That's sctp_endpoint_init
> > which AFAICS only gets called in GFP_KERNEL context.
> >
> > Or is there a code-path where sctp_endpoint_init is called in
> > softirq context?
>
> OK, there are indeed code paths where the key is derived in softirq
> context. Notably sctp_auth_calculate_hmac.
>
> So I think this patch is the correct fix and I will push it upstream
> as well as back to stable.
Okay, thanks.
Marcelo
^ permalink raw reply
* Re: [PATCH] mwifiex: Use put_unaligned_le32
From: Brian Norris @ 2017-10-05 18:02 UTC (permalink / raw)
To: Himanshu Jha
Cc: Kalle Valo, amitkarwar, nishants, gbhat, huxm, linux-wireless,
netdev, linux-kernel
In-Reply-To: <20171005152233.GA6250@himanshu-Vostro-3559>
On Thu, Oct 05, 2017 at 08:52:33PM +0530, Himanshu Jha wrote:
> There are various instances where a function used in file say for eg
> int func_align (void* a)
> is used and it is defined in align.h
> But many files don't *directly* include align.h and rather include
> any other header which includes align.h
I believe the general rule is that you should included headers for all
symbols you use, and not rely on implicit includes.
The modification to the general rule is that not all headers are
intended to be included directly, and in such cases there's likely a
parent header that is the more appropriate target.
In this case, the key is CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS. It
seems that asm-generic/unaligned.h is set up to include different
headers, based on the expected architecture behavior.
I wonder if include/linux/unaligned/access_ok.h should have a safety
check (e.g., raise an #error if
!CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS?).
> Is compiling the file the only way to check if apppropriate header is
> included or is there some other way to check for it.
I believe it's mostly manual. Implicit includes have been a problem for
anyone who refactors header files.
Brian
^ permalink raw reply
* Re: [PATCH 00/18] use ARRAY_SIZE macro
From: J. Bruce Fields @ 2017-10-05 17:57 UTC (permalink / raw)
To: Jérémy Lefaure
Cc: Greg KH, Tobin C. Harding, alsa-devel, nouveau, dri-devel,
dm-devel, brcm80211-dev-list, devel, linux-scsi, linux-rdma,
amd-gfx, Jason Gunthorpe, linux-acpi, linux-video,
intel-wired-lan, linux-media, intel-gfx, ecryptfs, linux-nfs,
linux-raid, openipmi-developer, intel-gvt-dev, devel,
brcm80211-dev-list.pdl, netdev, linux-usb, linux-wireless,
linux-kernel, linux-integrity, jlayton
In-Reply-To: <20171002213312.3f904290@blatinox-laptop.localdomain>
On Mon, Oct 02, 2017 at 09:33:12PM -0400, Jérémy Lefaure wrote:
> On Mon, 2 Oct 2017 15:22:24 -0400
> bfields@fieldses.org (J. Bruce Fields) wrote:
>
> > Mainly I'd just like to know which you're asking for. Do you want me to
> > apply this, or to ACK it so someone else can? If it's sent as a series
> > I tend to assume the latter.
> >
> > But in this case I'm assuming it's the former, so I'll pick up the nfsd
> > one....
> Could you to apply the NFSD patch ("nfsd: use ARRAY_SIZE") with the
> Reviewed-by: Jeff Layton <jlayton@redhat.com>) tag please ?
>
> This patch is an individual patch and it should not have been sent in a
> series (sorry about that).
Applying for 4.15, thanks.--b.
^ permalink raw reply
* [PATCH v2] net/mac80211/mesh_plink: Convert timers to use timer_setup()
From: Kees Cook @ 2017-10-05 17:39 UTC (permalink / raw)
To: Johannes Berg
Cc: David S. Miller, linux-wireless, netdev, Thomas Gleixner,
linux-kernel
In preparation for unconditionally passing the struct timer_list pointer to
all timer callbacks, switch to using the new timer_setup() and from_timer()
to pass the timer pointer explicitly. This requires adding a pointer back
to the sta_info since container_of() can't resolve the sta_info.
Cc: Johannes Berg <johannes@sipsolutions.net>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: linux-wireless@vger.kernel.org
Cc: netdev@vger.kernel.org
Cc: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Kees Cook <keescook@chromium.org>
---
This requires commit 686fef928bba ("timer: Prepare to change timer
callback argument type") in v4.14-rc3, but should be otherwise
stand-alone.
v2:
- make mesh_plink_timer non-static and use it in timer_setup() call directly.
---
net/mac80211/mesh.h | 1 +
net/mac80211/mesh_plink.c | 10 ++++------
net/mac80211/sta_info.c | 4 +++-
net/mac80211/sta_info.h | 2 ++
4 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/net/mac80211/mesh.h b/net/mac80211/mesh.h
index 7e5f271e3c30..465b7853edc0 100644
--- a/net/mac80211/mesh.h
+++ b/net/mac80211/mesh.h
@@ -275,6 +275,7 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
u8 *hw_addr, struct ieee802_11_elems *ie);
bool mesh_peer_accepts_plinks(struct ieee802_11_elems *ie);
u32 mesh_accept_plinks_update(struct ieee80211_sub_if_data *sdata);
+void mesh_plink_timer(struct timer_list *t);
void mesh_plink_broken(struct sta_info *sta);
u32 mesh_plink_deactivate(struct sta_info *sta);
u32 mesh_plink_open(struct sta_info *sta);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index f69c6c38ca43..e79adb4164f3 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -604,8 +604,9 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
ieee80211_mbss_info_change_notify(sdata, changed);
}
-static void mesh_plink_timer(unsigned long data)
+void mesh_plink_timer(struct timer_list *t)
{
+ struct mesh_sta *mesh = from_timer(mesh, t, plink_timer);
struct sta_info *sta;
u16 reason = 0;
struct ieee80211_sub_if_data *sdata;
@@ -617,7 +618,7 @@ static void mesh_plink_timer(unsigned long data)
* del_timer_sync() this timer after having made sure
* it cannot be readded (by deleting the plink.)
*/
- sta = (struct sta_info *) data;
+ sta = mesh->plink_sta;
if (sta->sdata->local->quiescing)
return;
@@ -697,11 +698,8 @@ static void mesh_plink_timer(unsigned long data)
static inline void mesh_plink_timer_set(struct sta_info *sta, u32 timeout)
{
- sta->mesh->plink_timer.expires = jiffies + msecs_to_jiffies(timeout);
- sta->mesh->plink_timer.data = (unsigned long) sta;
- sta->mesh->plink_timer.function = mesh_plink_timer;
sta->mesh->plink_timeout = timeout;
- add_timer(&sta->mesh->plink_timer);
+ mod_timer(&sta->mesh->plink_timer, jiffies + msecs_to_jiffies(timeout));
}
static bool llid_in_use(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 69615016d5bf..6c254a9d5f11 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -329,10 +329,12 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
sta->mesh = kzalloc(sizeof(*sta->mesh), gfp);
if (!sta->mesh)
goto free;
+ sta->mesh->plink_sta = sta;
spin_lock_init(&sta->mesh->plink_lock);
if (ieee80211_vif_is_mesh(&sdata->vif) &&
!sdata->u.mesh.user_mpm)
- init_timer(&sta->mesh->plink_timer);
+ timer_setup(&sta->mesh->plink_timer, mesh_plink_timer,
+ 0);
sta->mesh->nonpeer_pm = NL80211_MESH_POWER_ACTIVE;
}
#endif
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 3acbdfa9f649..21d9760ce5c3 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -344,6 +344,7 @@ DECLARE_EWMA(mesh_fail_avg, 20, 8)
* @plink_state: peer link state
* @plink_timeout: timeout of peer link
* @plink_timer: peer link watch timer
+ * @plink_sta: peer link watch timer's sta_info
* @t_offset: timing offset relative to this host
* @t_offset_setpoint: reference timing offset of this sta to be used when
* calculating clockdrift
@@ -356,6 +357,7 @@ DECLARE_EWMA(mesh_fail_avg, 20, 8)
*/
struct mesh_sta {
struct timer_list plink_timer;
+ struct sta_info *plink_sta;
s64 t_offset;
s64 t_offset_setpoint;
--
2.7.4
--
Kees Cook
Pixel Security
^ permalink raw reply related
* Re: [08/11] ath10k_sdio: common read write
From: Alagu Sankar @ 2017-10-05 17:33 UTC (permalink / raw)
To: Gary Bisson; +Cc: silexcommon, linux-wireless, ath10k
In-Reply-To: <20171005100932.vamkzfulq3dg4iav@t450s.lan>
Hi Gary,
On 2017-10-05 15:39, Gary Bisson wrote:
> Hi Alagu,
>
> On Sat, Sep 30, 2017 at 11:07:45PM +0530, silexcommon@gmail.com wrote:
>> From: Alagu Sankar <alagusankar@silex-india.com>
>>
>> convert different read write functions in sdio hif to bring it under a
>> single read-write path. This helps in having a common dma bounce
>> buffer
>> implementation. Also helps in address modification that is required
>> specific to change in certain mbox addresses of sdio_write.
>>
>> Signed-off-by: Alagu Sankar <alagusankar@silex-india.com>
>> ---
>> drivers/net/wireless/ath/ath10k/sdio.c | 131
>> ++++++++++++++++-----------------
>> 1 file changed, 64 insertions(+), 67 deletions(-)
>>
>> diff --git a/drivers/net/wireless/ath/ath10k/sdio.c
>> b/drivers/net/wireless/ath/ath10k/sdio.c
>> index 77d4fa4..bb6fa67 100644
>> --- a/drivers/net/wireless/ath/ath10k/sdio.c
>> +++ b/drivers/net/wireless/ath/ath10k/sdio.c
>> @@ -36,6 +36,11 @@
>>
>> #define ATH10K_SDIO_DMA_BUF_SIZE (32 * 1024)
>>
>> +static int ath10k_sdio_read(struct ath10k *ar, u32 addr, void *buf,
>> + u32 len, bool incr);
>> +static int ath10k_sdio_write(struct ath10k *ar, u32 addr, const void
>> *buf,
>> + u32 len, bool incr);
>> +
>
> As mentioned by Kalle, u32 needs to be size_t.
Yes, the compiler I used is probably a step older and did not catch
this.
>
>> /* inlined helper functions */
>>
>> /* Macro to check if DMA buffer is WORD-aligned and DMA-able.
>> @@ -152,6 +157,7 @@ static int ath10k_sdio_config(struct ath10k *ar)
>> struct sdio_func *func = ar_sdio->func;
>> unsigned char byte, asyncintdelay = 2;
>> int ret;
>> + u32 addr;
>>
>> ath10k_dbg(ar, ATH10K_DBG_BOOT, "sdio configuration\n");
>>
>> @@ -180,9 +186,8 @@ static int ath10k_sdio_config(struct ath10k *ar)
>> CCCR_SDIO_DRIVER_STRENGTH_ENABLE_C |
>> CCCR_SDIO_DRIVER_STRENGTH_ENABLE_D);
>>
>> - ret = ath10k_sdio_func0_cmd52_wr_byte(func->card,
>> - CCCR_SDIO_DRIVER_STRENGTH_ENABLE_ADDR,
>> - byte);
>> + addr = CCCR_SDIO_DRIVER_STRENGTH_ENABLE_ADDR,
>> + ret = ath10k_sdio_func0_cmd52_wr_byte(func->card, addr, byte);
>
> Not sure this part is needed.
This is to overcome checkpatch warning. Too big a names for the function
and macro
not helping in there. Will have to move it as a separate patch.
>
>> if (ret) {
>> ath10k_warn(ar, "failed to enable driver strength: %d\n", ret);
>> goto out;
>> @@ -233,13 +238,16 @@ static int ath10k_sdio_config(struct ath10k *ar)
>>
>> static int ath10k_sdio_write32(struct ath10k *ar, u32 addr, u32 val)
>> {
>> - struct ath10k_sdio *ar_sdio = ath10k_sdio_priv(ar);
>> - struct sdio_func *func = ar_sdio->func;
>> + __le32 *buf;
>> int ret;
>>
>> - sdio_claim_host(func);
>> + buf = kzalloc(sizeof(*buf), GFP_KERNEL);
>> + if (!buf)
>> + return -ENOMEM;
>>
>> - sdio_writel(func, val, addr, &ret);
>> + *buf = cpu_to_le32(val);
>> +
>> + ret = ath10k_sdio_write(ar, addr, &val, sizeof(val), true);
>
> Shouldn't we use buf instead of val? buf seems pretty useless
> otherwise.
Yes, thanks for pointing this out. will be corrected in v2.
>
> Regards,
> Gary
>
> _______________________________________________
> ath10k mailing list
> ath10k@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/ath10k
^ permalink raw reply
* Re: [PATCH] net/mac80211/mesh_plink: Convert timers to use
From: Kees Cook @ 2017-10-05 17:27 UTC (permalink / raw)
To: Johannes Berg
Cc: LKML, David S. Miller, linux-wireless, Network Development,
Thomas Gleixner
In-Reply-To: <1507186052.2387.11.camel@sipsolutions.net>
On Wed, Oct 4, 2017 at 11:47 PM, Johannes Berg
<johannes@sipsolutions.net> wrote:
> On Wed, 2017-10-04 at 17:49 -0700, Kees Cook wrote:
>> In preparation for unconditionally passing the struct timer_list
>> pointer to all timer callbacks, switch to using the new timer_setup()
>> and from_timer() to pass the timer pointer explicitly. This requires
>> adding a pointer back to the sta_info since container_of() can't
>> resolve the sta_info.
>
> The subject seems to be lacking something ... :-)
Oh wonderful, all the subjects are cut off. *sigh* I wonder which
piece of my workflow broke that...
>> This requires commit 686fef928bba ("timer: Prepare to change timer
>> callback argument type") in v4.14-rc3, but should be otherwise
>> stand-alone.
>
> I still can't apply that because that's not in net-next right now.
Okay, I'll see if Dave brings that into net-next and resend after that.
>> static inline void mesh_plink_timer_set(struct sta_info *sta, u32
>> timeout)
>> {
>> sta->mesh->plink_timer.expires = jiffies +
>> msecs_to_jiffies(timeout);
>> - sta->mesh->plink_timer.data = (unsigned long) sta;
>> - sta->mesh->plink_timer.function = mesh_plink_timer;
>> + sta->mesh->plink_sta = sta;
>> + sta->mesh->plink_timer.function =
>> (TIMER_FUNC_TYPE)mesh_plink_timer;
>> sta->mesh->plink_timeout = timeout;
>> add_timer(&sta->mesh->plink_timer);
>
> Wouldn't it be better to convert this to timer_setup() now?
The problem is that plink_timer is used in several places, and it's
originally initialized in net/mac80211/sta_info.c. The call to
mesh_plink_timer_set() does an update of the function field, so it
didn't look like it could get merged with the timer_setup(), but in
looking again, it seems that this is the _only_ update to
plink_timer.function, so it could probably get collapsed into the
timer_setup() call.
> That add_timer() should probably also be mod_timer() anyway?
Agreed. I'd avoided making those changes in most places, but I can do it here.
>> diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
>> index 69615016d5bf..5e5de9455e4e 100644
>> --- a/net/mac80211/sta_info.c
>> +++ b/net/mac80211/sta_info.c
>> @@ -332,7 +332,7 @@ struct sta_info *sta_info_alloc(struct
>> ieee80211_sub_if_data *sdata,
>> spin_lock_init(&sta->mesh->plink_lock);
>> if (ieee80211_vif_is_mesh(&sdata->vif) &&
>> !sdata->u.mesh.user_mpm)
>> - init_timer(&sta->mesh->plink_timer);
>> + timer_setup(&sta->mesh->plink_timer, NULL,
>> 0);
>> sta->mesh->nonpeer_pm = NL80211_MESH_POWER_ACTIVE;
>> }
>
> You just have to make mesh_plink_timer() non-static, put a prototype
> into mesh.h and then you can use the proper timer_setup() here with the
> function?
>
> Also, the sta->mesh->plink_sta assignment should be here I'd say, no
> point rewriting it all the time.
Sounds good. I'll get it cleaned up.
-Kees
--
Kees Cook
Pixel Security
^ permalink raw reply
* Re: [PATCH 00/11] SDIO support for ath10k
From: Alagu Sankar @ 2017-10-05 17:24 UTC (permalink / raw)
To: Gary Bisson, silexcommon; +Cc: ath10k, linux-wireless, erik.stromdahl
In-Reply-To: <20171005151205.cymirl4wikuw4f7q@t450s.lan>
Hi Gary,
On 05-10-2017 20:42, Gary Bisson wrote:
> Hi Alagu,
>
> First of all, thank you for sharing your patches, this will be a very
> nice improvement to have SDIO QCA9377 working with ath10k.
>
> I've tried your series with Nitrogen7 [1] platform which is supported in
> mainline already. It uses BD-SDMAC [2] which uses the same module as the
> SX-SDMAC.
>
> Below are some questions/remarks I have after the testing.
>
> On Sat, Sep 30, 2017 at 11:07:37PM +0530, silexcommon at gmail.com wrote:
>> From: Alagu Sankar <alagusankar at silex-india.com>
>>
>> This patchset, generated against master-pending branch, enables a fully
>> functional SDIO interface driver for ath10k. Patches have been verified on
>> QCA9377-3 WB396 and Silex's SX-SDCAC reference cards with Station, Access Point
>> and P2P modes.
>>
>> The driver is verified with the firmware WLAN.TF.1.1.1-00061-QCATFSWPZ-1
> Quick question on the firmware, is it the one from Kalle's repository?[3]
>
> If so, where does this firmware comes from? Is 00061 the firmware
> version? So far I've only seen up to v0.0.0.60, see qcacld-2.0 output:
> Host SW:4.5.20.037, FW:0.0.0.60, HW:QCA9377_REV1_1
Yes, it is from
https://github.com/kvalo/ath10k-firmware/tree/master/QCA9377/hw1.0/untested.
I have also used custom firmware from QCA/Silex as used in qcacld-2.0
driver without any issue. You need to use the firmware merger tool from
https://github.com/erstrom/linux-ath/wiki/Firmware to combine the
qwlan30.bin and otp30.bin to generate the firmware-sdio.bin.
>> with the board data from respective SDIO card vendors.
> About the board-sdio.bin, is it just a copy of your bdwlan30.bin?
Yes board-sdio.bin is a copy of bdwlan30.bin
>> Receive performance
>> matches the QCA reference driver when used with SDIO3.0 enabled platforms.
>> iperf tests indicate a downlink UDP of 275Mbit/s and TCP of 150Mbit/s
> Nice performances. Unfortunately I don't get better than 30Mbits/s in TX
> and 50Mbits/s in RX:
> # iperf -c 192.168.1.1
> ------------------------------------------------------------
> Client connecting to 192.168.1.1, TCP port 5001
> TCP window size: 43.8 KByte (default)
> ------------------------------------------------------------
> [ 3] local 192.168.1.115 port 41354 connected with 192.168.1.1 port
> 5001
> [ ID] Interval Transfer Bandwidth
> [ 3] 0.0-10.0 sec 34.9 MBytes 29.2 Mbits/sec
> # iperf -s
> ------------------------------------------------------------
> Server listening on TCP port 5001
> TCP window size: 85.3 KByte (default)
> ------------------------------------------------------------
> [ 4] local 192.168.1.115 port 5001 connected with 192.168.1.1 port
> 50646
> [ ID] Interval Transfer Bandwidth
> [ 4] 0.0-10.0 sec 63.1 MBytes 52.7 Mbits/sec
>
> Do you have any idea why? Note that qcacld-2.0 driver on that same
> platform (same OS) gives the performances you advertize (150Mbits/s).
For some reason, if I use the imx_v6_v7_defconfig as is, performance is
very poor. In fact, the firmware download itself will take about 6
seconds. This can also be seen when you up/down the wlan0 interface. For
the i.MX6 SoloX board, I used the configuration of 4.1.15 as provided by
the BSP from NXP/Freescale. This improved the performance quite a bit.
I haven't had a chance to spend time on this to figure out the
difference and reason. Another difference is that the default device
tree for SoloX did not have the correct settings to support SDIO3.x.
Had to modify them, but did not include the device tree patches here as
it is not meant for this group.
>> This patchset differs from the previous high latency patches, specific to SDIO.
>> HI_ACS_FLAGS_SDIO_REDUCE_TX_COMPL_SET is enabled for HI_ACS. This instructs the
>> firmware to use HTT_T2H_MSG_TYPE_TX_COMPL_IND for outgoing packets. Without
>> this flag, the management frames are not sent out by the firmware. Possibility
>> of management frames being sent via WMI and data frames through the reduced Tx
>> completion needs to be probed further.
>>
>> Further improvements can be done on the transmit path by implementing packet
>> bundle. Scatter Gather is another area of improvement for both Transmit and
>> Receive, but may not work on all platforms
>>
>> Known issues: Surprise removal of the card, when the device is in connected
>> state, delays sdio function remove due to delayed WMI command failures.
>> Existing ath10k framework can not differentiate between a kernel module
>> removae and the surprise removal of teh card.
> Here are some questions:
> - Is it normal to see a warning about board-2.bin, shouldn't it look for
> board-sdio.bin only?
> [ 14.696704] ath10k_sdio mmc1:0001:1: Direct firmware load for
> ath10k/QCA9377/hw1.0/board-2.bin failed with error -2
This was only noticed in the latest update. Like the different firmware
versions, the driver also looks for the board-2.bin now. I have only
tested with the board-sdio.bin
> - Did you have pre-cal and/or cal binaries for your testing?
> [ 14.067159] ath10k_sdio mmc1:0001:1: Direct firmware load for
> ath10k/pre-cal-sdio-mmc1:0001:1.bin failed with error -2
> [ 14.149257] ath10k_sdio mmc1:0001:1: Direct firmware load for
> ath10k/cal-sdio-mmc1:0001:1.bin failed with error -2
No. I did not use the pre-cal and cal binaries.
> Also noticed that the "tx bitrate" output of 'iw link' is wrong in my
> case:
> # iw wlan0 link
> Connected to 00:00:00:00:00:b0 (on wlan0)
> SSID: TPLINK_AC_5G
> freq: 5180
> RX: 72072365 bytes (67934 packets)
> TX: 79084128 bytes (73649 packets)
> signal: -35 dBm
> tx bitrate: 6.0 MBit/s
>
> bss flags: short-slot-time
> dtim period: 2
> beacon int: 100
>
> When connecting using qcacld driver it shows 433MBit/s as expected. Is
> it working properly in your case?
Tx rate is not updated properly. I will include it in the list of known
issues.
> As a FYI, I've built Erik's tree[4] for this testing, should I use
> another tree instead?
I use the Kalle's ath10k tree, but when I last looked, they were pretty
much the same, so it should not be a problem.
> Let me know your thoughts.
>
> Regards,
> Gary
>
> [1] https://boundarydevices.com/product/nitrogen7/
> [2] https://boundarydevices.com/product/bd_sdmac_wifi/
> [3] https://github.com/kvalo/ath10k-firmware/tree/master/QCA9377/hw1.0/untested
> [4] https://github.com/erstrom/linux-ath
>
Regards,
Alagu
^ permalink raw reply
* Re: converting mac80211 to TXQs entirely
From: Johannes Berg @ 2017-10-05 16:43 UTC (permalink / raw)
To: Toke Høiland-Jørgensen, linux-wireless; +Cc: nbd
In-Reply-To: <87efqhsgni.fsf@toke.dk>
On Thu, 2017-10-05 at 18:28 +0200, Toke Høiland-Jørgensen wrote:
> I'm been thinking about how to move the airtime fairness scheduler
> out of ath9k and into mac80211 (so more drivers can take advantage of
> it). This will require some changes to the TXQ API that drivers speak
> to, so wanted to add my thoughts here to make sure it's compatible
> with your thinking.
>
> I think the easiest way to have mac80211 handle airtime fairness is
> to add a way for ieee80211_tx_dequeue() to return some sort of DEFER
> signal which semantically means "there is no packet for this queue
> right now, but please keep scheduling it" (which would be the result
> of a TXQ belonging to a station that has used its airtime quota but
> still has packets pending). This is different from the current
> meaning of NULL, which will make the driver stop scheduling that TXQ
> until it gets a new wake signal.
I think that's reasonable. I'm not really sure it's *necessary* though?
Couldn't mac80211 return NULL, and then simply call wake_tx_queue again
when the TXQ becomes eligible for scheduling again? Otherwise the
driver might end up doing a lot of polling for it to become eligible
again?
I've mostly glossed over a mac80211 scheduler, which is obviously
needed as part of a complete conversion, and it'd just have to
integrate with this new return value.
> The alternative would be to change the API so the driver asks
> mac80211 which TXQ it should pull from next, instead of doing its own
> scheduling as it does now. But I'm not sure that's doable with a sane
> API.
>
> Now, I believe this is related to this point of yours:
>
> > 5) handle non-data frames for vifs
> >
> > Similarly, we need a vif->nondata_txq where we can put probe
> > responses and the (few) other frames that we send before we have a
> > station entry.
> >
> > According to my earlier analysis (previous email) after these steps
> > we have a TXQ for all frames. All of these steps will require
> > certain adjustments in the drivers currently using TXQs (ath9k &
> > ath10k) since they'll be relying on some amount of buffering and
> > queue stop/wake in mac80211 for these frames. We might just have to
> > add some API to ask "is queue stopped" to make the transition here
> > easy.
>
> And I'm wondering if this "is queue stopped" API could be the same
> one used for the airtime DEFER case?
I think these are two completely different cases.
The "is queue stopped" I was thinking of would be basically checking
the variable local->queue_stop_reasons, so that the driver could still
use the stop_queue API(s). Yeah, this would be very roundabout, but as
a conversion step I think that'd not be a bad thing.
A more complete conversion likely wouldn't need this, but would instead
have the driver record its own stop reasons and just stop scheduling
those TXQs that belong to a stopped "class" (it's no longer really a
queue).
(and for mac80211 stop reasons, it would just return NULL and re-
schedule the TXQ when it becomes eligible again)
> Oh, and BTW: I see this is on the list of topics for the wireless
> summit in Seoul. How do I go about signing up for that? I'll be at
> netdev talking about some of this stuff anyway[1] :)
Just show up there, or you can add yourself to the list on the wiki
page :)
johannes
^ permalink raw reply
* Re: converting mac80211 to TXQs entirely
From: Toke Høiland-Jørgensen @ 2017-10-05 16:28 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: nbd
In-Reply-To: <1507217947.2387.60.camel@sipsolutions.net>
Johannes Berg <johannes@sipsolutions.net> writes:
> Part 2 - where can we go from here
>
>
> Of course - as mentioned in the subject - my goal is to just convert
> over to TXQs entirely in mac80211. That would get rid of a LOT of
> special case code, like queueing for aggregation setup, powersave
> buffering (both unicast and multicast), etc.
At first glance this looks like a decent way forward. I'll think about
it some more and comment again later, but for now I just wanted to add
this:
I'm been thinking about how to move the airtime fairness scheduler out
of ath9k and into mac80211 (so more drivers can take advantage of it).
This will require some changes to the TXQ API that drivers speak to, so
wanted to add my thoughts here to make sure it's compatible with your
thinking.
I think the easiest way to have mac80211 handle airtime fairness is to
add a way for ieee80211_tx_dequeue() to return some sort of DEFER signal
which semantically means "there is no packet for this queue right now,
but please keep scheduling it" (which would be the result of a TXQ
belonging to a station that has used its airtime quota but still has
packets pending). This is different from the current meaning of NULL,
which will make the driver stop scheduling that TXQ until it gets a new
wake signal.
The alternative would be to change the API so the driver asks mac80211
which TXQ it should pull from next, instead of doing its own scheduling
as it does now. But I'm not sure that's doable with a sane API.
Now, I believe this is related to this point of yours:
> 5) handle non-data frames for vifs
>
> Similarly, we need a vif->nondata_txq where we can put probe responses
> and the (few) other frames that we send before we have a station entry.
>
> According to my earlier analysis (previous email) after these steps we
> have a TXQ for all frames. All of these steps will require certain
> adjustments in the drivers currently using TXQs (ath9k & ath10k) since
> they'll be relying on some amount of buffering and queue stop/wake in
> mac80211 for these frames. We might just have to add some API to ask
> "is queue stopped" to make the transition here easy.
And I'm wondering if this "is queue stopped" API could be the same one
used for the airtime DEFER case?
Oh, and BTW: I see this is on the list of topics for the wireless summit
in Seoul. How do I go about signing up for that? I'll be at netdev
talking about some of this stuff anyway[1] :)
-Toke
[1] https://www.netdevconf.org/2.2/session.html?jorgensen-wifistack-talk
^ permalink raw reply
* Re: [PATCH] rsi: fix integer overflow warning
From: Joe Perches @ 2017-10-05 16:11 UTC (permalink / raw)
To: David Laight, Arnd Bergmann, Kalle Valo, Prameela Rani Garnepudi,
Amitkumar Karwar
Cc: Pavani Muthyala, Karun Eagalapati, linux-wireless@vger.kernel.org,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <063D6719AE5E284EB5DD2968C1650D6DD008ACEE@AcuExch.aculab.com>
On Thu, 2017-10-05 at 15:12 +0000, David Laight wrote:
> From: Joe Perches
> > Sent: 05 October 2017 13:19
> > On Thu, 2017-10-05 at 14:05 +0200, Arnd Bergmann wrote:
> > > gcc produces a harmless warning about a recently introduced
> > > signed integer overflow:
> > >
> > > drivers/net/wireless/rsi/rsi_91x_hal.c: In function 'rsi_prepare_mgmt_desc':
> > > include/uapi/linux/swab.h:13:15: error: integer overflow in expression [-Werror=overflow]
> > > (((__u16)(x) & (__u16)0x00ffU) << 8) | \
> > > ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
> > > include/uapi/linux/swab.h:104:2: note: in expansion of macro '___constant_swab16'
> > > ___constant_swab16(x) : \
> > > ^~~~~~~~~~~~~~~~~~
> > > include/uapi/linux/byteorder/big_endian.h:34:43: note: in expansion of macro '__swab16'
> > > #define __cpu_to_le16(x) ((__force __le16)__swab16((x)))
> >
> > []
> >
> > > The problem is that the 'mask' value is a signed integer that gets
> > > turned into a negative number when truncated to 16 bits. Making it
> > > an unsigned constant avoids this.
> >
> > I would expect there are more of these.
> >
> > Perhaps this define in include/uapi/linux/swab.h:
> >
> > #define __swab16(x) \
> > (__builtin_constant_p((__u16)(x)) ? \
> > ___constant_swab16(x) : \
> > __fswab16(x))
> >
> > should be
> >
> > #define __swab16(x) \
> > (__builtin_constant_p((__u16)(x)) ? \
> > ___constant_swab16((__u16)(x)) : \
> > __fswab16((__u16)(x)))
>
> You probably don't want the cast in the call to __fswab16() since
> that is likely to generate an explicit and with 0xffff.
> You will likely also get one if the argument is _u16 (not unsigned int).
It would just an explicit vs implicit cast as __fswab16 is
a static inline with a __u16 argument
^ permalink raw reply
* Re: converting mac80211 to TXQs entirely
From: Johannes Berg @ 2017-10-05 15:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Toke Høiland-Jørgensen, nbd
In-Reply-To: <1507205618.2387.19.camel@sipsolutions.net>
Part 2 - where can we go from here
Of course - as mentioned in the subject - my goal is to just convert
over to TXQs entirely in mac80211. That would get rid of a LOT of
special case code, like queueing for aggregation setup, powersave
buffering (both unicast and multicast), etc.
I think the following would be appropriate steps to take
1) convert multicast PS buffering
This is a bit strange today - when multicast PS buffering *isn't*
needed, frames go to TXQ drivers via the vif->txq, but when it *is*
done, then frames are tagged with IEEE80211_TX_CTL_SEND_AFTER_DTIM and
are buffered on ps->bc_buf (if HOST_BROADCAST_PS_BUFFERING is set) and
then handed to the driver through the legacy ->tx() path.
This should be reasonably simple to change - get rid of ps->bc_buf, and
keep the frames on the TXQ, making ieee80211_get_buffered_bc() retrieve
them from there instead. We'd have to tell the driver (it needs to know
in the wake callback) that it has sleeping clients and needs to buffer,
so it can decide whether or not to retrieve immediately (basically, for
TXQ drivers, implementing HOST_BROADCAST_PS_BUFFERING in driver logic:
retrieve immediately if it wants to buffer itself, or keep them there
for a later ieee80211_get_buffered_bc() call if it doesn't).
2) use TXQ for offchannel frames
I'm a bit unsure about this - we never really want to queue offchannel
packets, and if they don't go out immediately then we can basically
drop them. Nevertheless, having everything deal with the TXQ API will
be simpler, and so I think this also should. Perhaps for this we should
have a TXQ but only ever use txqi->frags, so that we never really have
to deal with the FQ stuff for these frames. Then the wake would
basically just pull down the packets and send them immediately.
3) handle monitor mode
I thought this was more complicated, but I think the easiest way to
solve this is to actually just use the local->monitor_sdata->vif.txq.
This requires that mac80211 be changed to always allocate local-
>monitor_sdata, even if WANT_MONITOR_VIF isn't set, and ath9k/ath10k do
something special for this TXQ (and perhaps ath9k should set
WANT_MONITOR_VIF), but that seems reasonable.
4) handle non-data frames for stations
This is probably the trickiest part. I have a patch to add a non-data
TID to the STA TXQs, and that's perhaps the right thing to do - though
I guess those frames should again always go onto txqi->frags so they
don't compete with data frames for resources.
For powersave reasons we'll discuss later, this should probably only
apply to bufferable MMPDUs, with others going to the vif->nondata_txq
(next item).
5) handle non-data frames for vifs
Similarly, we need a vif->nondata_txq where we can put probe responses
and the (few) other frames that we send before we have a station entry.
According to my earlier analysis (previous email) after these steps we
have a TXQ for all frames. All of these steps will require certain
adjustments in the drivers currently using TXQs (ath9k & ath10k) since
they'll be relying on some amount of buffering and queue stop/wake in
mac80211 for these frames. We might just have to add some API to ask
"is queue stopped" to make the transition here easy.
After this, we can start thinking about doing internal cleanups in
mac80211.
6) First thing to do is probably to introduce a compat layer, so that
all drivers go through the TXQs, regardless of whether they handle
that. I have the beginnings of a patch that does that, it basically
requires drivers to set the wake_tx_queue method to a new mac80211
function ieee80211_wake_tx_queue() [so the ops can remain const] when
they don't actually want to deal with TXQs themselves.
This method can then check the queue stop reasons etc. and only service
TXQs that don't have their corresponding queue stopped. We'd also hook
into when the queues get re-enabled, and call the servicing function
from there for such drivers.
My current prototype just calls the existing __ieee80211_tx() but I no
longer think that's a good idea - the idea is that this would
eventually allow us to get rid of tx_pending.
So it'd be better to have ieee80211_wake_tx_queue() just check all the
required things, and once a frame is pulled from a TXQ it's committed
to be given to the driver.
For off-channel, a special case will be needed - dropping the frame
when there's no way to transmit it at the time.
7) Remove all the now-dead code
A lot of code is now dead - we'll never have multiple netdev queues,
all IFF_NOQUEUE etc - remove all the code associated with that
8) convert more infrastructure to TXQs
It gets more vague (starting from 6), but eventually I want to
* get rid of tx_pending (why do we even use both TXQ and tx_pending
for aggregation?)
* do all powersave buffering on TXQs
[this may be tricky for filtered frames, perhaps disallow filtered
for TXQ drivers like ath9k/ath10k, and have a separate per-TXQI list
in the private txq data for ieee80211_wake_tx_queue]
Thoughts?
johannes
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox