Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Lorenzo Bianconi | 4846 | 80.34% | 77 | 58.78% |
Stanislaw Gruszka | 711 | 11.79% | 23 | 17.56% |
Felix Fietkau | 419 | 6.95% | 22 | 16.79% |
Markus Theil | 23 | 0.38% | 1 | 0.76% |
Stanislaw W. Gruszka | 10 | 0.17% | 1 | 0.76% |
Allen Pais | 7 | 0.12% | 1 | 0.76% |
Breno Leitão | 5 | 0.08% | 1 | 0.76% |
Kees Cook | 4 | 0.07% | 1 | 0.76% |
Ryder Lee | 3 | 0.05% | 2 | 1.53% |
Gustavo A. R. Silva | 2 | 0.03% | 1 | 0.76% |
Sujuan Chen | 2 | 0.03% | 1 | 0.76% |
Total | 6032 | 131 |
// SPDX-License-Identifier: ISC /* * Copyright (C) 2018 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com> */ #include <linux/module.h> #include "mt76.h" #include "usb_trace.h" #include "dma.h" #define MT_VEND_REQ_MAX_RETRY 10 #define MT_VEND_REQ_TOUT_MS 300 static bool disable_usb_sg; module_param_named(disable_usb_sg, disable_usb_sg, bool, 0644); MODULE_PARM_DESC(disable_usb_sg, "Disable usb scatter-gather support"); int __mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type, u16 val, u16 offset, void *buf, size_t len) { struct usb_interface *uintf = to_usb_interface(dev->dev); struct usb_device *udev = interface_to_usbdev(uintf); unsigned int pipe; int i, ret; lockdep_assert_held(&dev->usb.usb_ctrl_mtx); pipe = (req_type & USB_DIR_IN) ? usb_rcvctrlpipe(udev, 0) : usb_sndctrlpipe(udev, 0); for (i = 0; i < MT_VEND_REQ_MAX_RETRY; i++) { if (test_bit(MT76_REMOVED, &dev->phy.state)) return -EIO; ret = usb_control_msg(udev, pipe, req, req_type, val, offset, buf, len, MT_VEND_REQ_TOUT_MS); if (ret == -ENODEV) set_bit(MT76_REMOVED, &dev->phy.state); if (ret >= 0 || ret == -ENODEV) return ret; usleep_range(5000, 10000); } dev_err(dev->dev, "vendor request req:%02x off:%04x failed:%d\n", req, offset, ret); return ret; } EXPORT_SYMBOL_GPL(__mt76u_vendor_request); int mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type, u16 val, u16 offset, void *buf, size_t len) { int ret; mutex_lock(&dev->usb.usb_ctrl_mtx); ret = __mt76u_vendor_request(dev, req, req_type, val, offset, buf, len); trace_usb_reg_wr(dev, offset, val); mutex_unlock(&dev->usb.usb_ctrl_mtx); return ret; } EXPORT_SYMBOL_GPL(mt76u_vendor_request); u32 ___mt76u_rr(struct mt76_dev *dev, u8 req, u8 req_type, u32 addr) { struct mt76_usb *usb = &dev->usb; u32 data = ~0; int ret; ret = __mt76u_vendor_request(dev, req, req_type, addr >> 16, addr, usb->data, sizeof(__le32)); if (ret == sizeof(__le32)) data = get_unaligned_le32(usb->data); trace_usb_reg_rr(dev, addr, data); return data; } EXPORT_SYMBOL_GPL(___mt76u_rr); static u32 __mt76u_rr(struct mt76_dev *dev, u32 addr) { u8 req; switch (addr & MT_VEND_TYPE_MASK) { case MT_VEND_TYPE_EEPROM: req = MT_VEND_READ_EEPROM; break; case MT_VEND_TYPE_CFG: req = MT_VEND_READ_CFG; break; default: req = MT_VEND_MULTI_READ; break; } return ___mt76u_rr(dev, req, USB_DIR_IN | USB_TYPE_VENDOR, addr & ~MT_VEND_TYPE_MASK); } static u32 mt76u_rr(struct mt76_dev *dev, u32 addr) { u32 ret; mutex_lock(&dev->usb.usb_ctrl_mtx); ret = __mt76u_rr(dev, addr); mutex_unlock(&dev->usb.usb_ctrl_mtx); return ret; } void ___mt76u_wr(struct mt76_dev *dev, u8 req, u8 req_type, u32 addr, u32 val) { struct mt76_usb *usb = &dev->usb; put_unaligned_le32(val, usb->data); __mt76u_vendor_request(dev, req, req_type, addr >> 16, addr, usb->data, sizeof(__le32)); trace_usb_reg_wr(dev, addr, val); } EXPORT_SYMBOL_GPL(___mt76u_wr); static void __mt76u_wr(struct mt76_dev *dev, u32 addr, u32 val) { u8 req; switch (addr & MT_VEND_TYPE_MASK) { case MT_VEND_TYPE_CFG: req = MT_VEND_WRITE_CFG; break; default: req = MT_VEND_MULTI_WRITE; break; } ___mt76u_wr(dev, req, USB_DIR_OUT | USB_TYPE_VENDOR, addr & ~MT_VEND_TYPE_MASK, val); } static void mt76u_wr(struct mt76_dev *dev, u32 addr, u32 val) { mutex_lock(&dev->usb.usb_ctrl_mtx); __mt76u_wr(dev, addr, val); mutex_unlock(&dev->usb.usb_ctrl_mtx); } static u32 mt76u_rmw(struct mt76_dev *dev, u32 addr, u32 mask, u32 val) { mutex_lock(&dev->usb.usb_ctrl_mtx); val |= __mt76u_rr(dev, addr) & ~mask; __mt76u_wr(dev, addr, val); mutex_unlock(&dev->usb.usb_ctrl_mtx); return val; } static void mt76u_copy(struct mt76_dev *dev, u32 offset, const void *data, int len) { struct mt76_usb *usb = &dev->usb; const u8 *val = data; int ret; int current_batch_size; int i = 0; /* Assure that always a multiple of 4 bytes are copied, * otherwise beacons can be corrupted. * See: "mt76: round up length on mt76_wr_copy" * Commit 850e8f6fbd5d0003b0 */ len = round_up(len, 4); mutex_lock(&usb->usb_ctrl_mtx); while (i < len) { current_batch_size = min_t(int, usb->data_len, len - i); memcpy(usb->data, val + i, current_batch_size); ret = __mt76u_vendor_request(dev, MT_VEND_MULTI_WRITE, USB_DIR_OUT | USB_TYPE_VENDOR, 0, offset + i, usb->data, current_batch_size); if (ret < 0) break; i += current_batch_size; } mutex_unlock(&usb->usb_ctrl_mtx); } void mt76u_read_copy(struct mt76_dev *dev, u32 offset, void *data, int len) { struct mt76_usb *usb = &dev->usb; int i = 0, batch_len, ret; u8 *val = data; len = round_up(len, 4); mutex_lock(&usb->usb_ctrl_mtx); while (i < len) { batch_len = min_t(int, usb->data_len, len - i); ret = __mt76u_vendor_request(dev, MT_VEND_READ_EXT, USB_DIR_IN | USB_TYPE_VENDOR, (offset + i) >> 16, offset + i, usb->data, batch_len); if (ret < 0) break; memcpy(val + i, usb->data, batch_len); i += batch_len; } mutex_unlock(&usb->usb_ctrl_mtx); } EXPORT_SYMBOL_GPL(mt76u_read_copy); void mt76u_single_wr(struct mt76_dev *dev, const u8 req, const u16 offset, const u32 val) { mutex_lock(&dev->usb.usb_ctrl_mtx); __mt76u_vendor_request(dev, req, USB_DIR_OUT | USB_TYPE_VENDOR, val & 0xffff, offset, NULL, 0); __mt76u_vendor_request(dev, req, USB_DIR_OUT | USB_TYPE_VENDOR, val >> 16, offset + 2, NULL, 0); mutex_unlock(&dev->usb.usb_ctrl_mtx); } EXPORT_SYMBOL_GPL(mt76u_single_wr); static int mt76u_req_wr_rp(struct mt76_dev *dev, u32 base, const struct mt76_reg_pair *data, int len) { struct mt76_usb *usb = &dev->usb; mutex_lock(&usb->usb_ctrl_mtx); while (len > 0) { __mt76u_wr(dev, base + data->reg, data->value); len--; data++; } mutex_unlock(&usb->usb_ctrl_mtx); return 0; } static int mt76u_wr_rp(struct mt76_dev *dev, u32 base, const struct mt76_reg_pair *data, int n) { if (test_bit(MT76_STATE_MCU_RUNNING, &dev->phy.state)) return dev->mcu_ops->mcu_wr_rp(dev, base, data, n); else return mt76u_req_wr_rp(dev, base, data, n); } static int mt76u_req_rd_rp(struct mt76_dev *dev, u32 base, struct mt76_reg_pair *data, int len) { struct mt76_usb *usb = &dev->usb; mutex_lock(&usb->usb_ctrl_mtx); while (len > 0) { data->value = __mt76u_rr(dev, base + data->reg); len--; data++; } mutex_unlock(&usb->usb_ctrl_mtx); return 0; } static int mt76u_rd_rp(struct mt76_dev *dev, u32 base, struct mt76_reg_pair *data, int n) { if (test_bit(MT76_STATE_MCU_RUNNING, &dev->phy.state)) return dev->mcu_ops->mcu_rd_rp(dev, base, data, n); else return mt76u_req_rd_rp(dev, base, data, n); } static bool mt76u_check_sg(struct mt76_dev *dev) { struct usb_interface *uintf = to_usb_interface(dev->dev); struct usb_device *udev = interface_to_usbdev(uintf); return (!disable_usb_sg && udev->bus->sg_tablesize > 0 && udev->bus->no_sg_constraint); } static int mt76u_set_endpoints(struct usb_interface *intf, struct mt76_usb *usb) { struct usb_host_interface *intf_desc = intf->cur_altsetting; struct usb_endpoint_descriptor *ep_desc; int i, in_ep = 0, out_ep = 0; for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) { ep_desc = &intf_desc->endpoint[i].desc; if (usb_endpoint_is_bulk_in(ep_desc) && in_ep < __MT_EP_IN_MAX) { usb->in_ep[in_ep] = usb_endpoint_num(ep_desc); in_ep++; } else if (usb_endpoint_is_bulk_out(ep_desc) && out_ep < __MT_EP_OUT_MAX) { usb->out_ep[out_ep] = usb_endpoint_num(ep_desc); out_ep++; } } if (in_ep != __MT_EP_IN_MAX || out_ep != __MT_EP_OUT_MAX) return -EINVAL; return 0; } static int mt76u_fill_rx_sg(struct mt76_dev *dev, struct mt76_queue *q, struct urb *urb, int nsgs) { int i; for (i = 0; i < nsgs; i++) { void *data; int offset; data = mt76_get_page_pool_buf(q, &offset, q->buf_size); if (!data) break; sg_set_page(&urb->sg[i], virt_to_head_page(data), q->buf_size, offset); } if (i < nsgs) { int j; for (j = nsgs; j < urb->num_sgs; j++) mt76_put_page_pool_buf(sg_virt(&urb->sg[j]), false); urb->num_sgs = i; } urb->num_sgs = max_t(int, i, urb->num_sgs); urb->transfer_buffer_length = urb->num_sgs * q->buf_size; sg_init_marker(urb->sg, urb->num_sgs); return i ? : -ENOMEM; } static int mt76u_refill_rx(struct mt76_dev *dev, struct mt76_queue *q, struct urb *urb, int nsgs) { enum mt76_rxq_id qid = q - &dev->q_rx[MT_RXQ_MAIN]; int offset; if (qid == MT_RXQ_MAIN && dev->usb.sg_en) return mt76u_fill_rx_sg(dev, q, urb, nsgs); urb->transfer_buffer_length = q->buf_size; urb->transfer_buffer = mt76_get_page_pool_buf(q, &offset, q->buf_size); return urb->transfer_buffer ? 0 : -ENOMEM; } static int mt76u_urb_alloc(struct mt76_dev *dev, struct mt76_queue_entry *e, int sg_max_size) { unsigned int size = sizeof(struct urb); if (dev->usb.sg_en) size += sg_max_size * sizeof(struct scatterlist); e->urb = kzalloc(size, GFP_KERNEL); if (!e->urb) return -ENOMEM; usb_init_urb(e->urb); if (dev->usb.sg_en && sg_max_size > 0) e->urb->sg = (struct scatterlist *)(e->urb + 1); return 0; } static int mt76u_rx_urb_alloc(struct mt76_dev *dev, struct mt76_queue *q, struct mt76_queue_entry *e) { enum mt76_rxq_id qid = q - &dev->q_rx[MT_RXQ_MAIN]; int err, sg_size; sg_size = qid == MT_RXQ_MAIN ? MT_RX_SG_MAX_SIZE : 0; err = mt76u_urb_alloc(dev, e, sg_size); if (err) return err; return mt76u_refill_rx(dev, q, e->urb, sg_size); } static void mt76u_urb_free(struct urb *urb) { int i; for (i = 0; i < urb->num_sgs; i++) mt76_put_page_pool_buf(sg_virt(&urb->sg[i]), false); if (urb->transfer_buffer) mt76_put_page_pool_buf(urb->transfer_buffer, false); usb_free_urb(urb); } static void mt76u_fill_bulk_urb(struct mt76_dev *dev, int dir, int index, struct urb *urb, usb_complete_t complete_fn, void *context) { struct usb_interface *uintf = to_usb_interface(dev->dev); struct usb_device *udev = interface_to_usbdev(uintf); unsigned int pipe; if (dir == USB_DIR_IN) pipe = usb_rcvbulkpipe(udev, dev->usb.in_ep[index]); else pipe = usb_sndbulkpipe(udev, dev->usb.out_ep[index]); urb->dev = udev; urb->pipe = pipe; urb->complete = complete_fn; urb->context = context; } static struct urb * mt76u_get_next_rx_entry(struct mt76_queue *q) { struct urb *urb = NULL; unsigned long flags; spin_lock_irqsave(&q->lock, flags); if (q->queued > 0) { urb = q->entry[q->tail].urb; q->tail = (q->tail + 1) % q->ndesc; q->queued--; } spin_unlock_irqrestore(&q->lock, flags); return urb; } static int mt76u_get_rx_entry_len(struct mt76_dev *dev, u8 *data, u32 data_len) { u16 dma_len, min_len; dma_len = get_unaligned_le16(data); if (dev->drv->drv_flags & MT_DRV_RX_DMA_HDR) return dma_len; min_len = MT_DMA_HDR_LEN + MT_RX_RXWI_LEN + MT_FCE_INFO_LEN; if (data_len < min_len || !dma_len || dma_len + MT_DMA_HDR_LEN > data_len || (dma_len & 0x3)) return -EINVAL; return dma_len; } static struct sk_buff * mt76u_build_rx_skb(struct mt76_dev *dev, void *data, int len, int buf_size) { int head_room, drv_flags = dev->drv->drv_flags; struct sk_buff *skb; head_room = drv_flags & MT_DRV_RX_DMA_HDR ? 0 : MT_DMA_HDR_LEN; if (SKB_WITH_OVERHEAD(buf_size) < head_room + len) { struct page *page; /* slow path, not enough space for data and * skb_shared_info */ skb = alloc_skb(MT_SKB_HEAD_LEN, GFP_ATOMIC); if (!skb) return NULL; skb_put_data(skb, data + head_room, MT_SKB_HEAD_LEN); data += head_room + MT_SKB_HEAD_LEN; page = virt_to_head_page(data); skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, data - page_address(page), len - MT_SKB_HEAD_LEN, buf_size); return skb; } /* fast path */ skb = build_skb(data, buf_size); if (!skb) return NULL; skb_reserve(skb, head_room); __skb_put(skb, len); return skb; } static int mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb, int buf_size) { u8 *data = urb->num_sgs ? sg_virt(&urb->sg[0]) : urb->transfer_buffer; int data_len = urb->num_sgs ? urb->sg[0].length : urb->actual_length; int len, nsgs = 1, head_room, drv_flags = dev->drv->drv_flags; struct sk_buff *skb; if (!test_bit(MT76_STATE_INITIALIZED, &dev->phy.state)) return 0; len = mt76u_get_rx_entry_len(dev, data, urb->actual_length); if (len < 0) return 0; head_room = drv_flags & MT_DRV_RX_DMA_HDR ? 0 : MT_DMA_HDR_LEN; data_len = min_t(int, len, data_len - head_room); if (len == data_len && dev->drv->rx_check && !dev->drv->rx_check(dev, data, data_len)) return 0; skb = mt76u_build_rx_skb(dev, data, data_len, buf_size); if (!skb) return 0; len -= data_len; while (len > 0 && nsgs < urb->num_sgs) { data_len = min_t(int, len, urb->sg[nsgs].length); skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, sg_page(&urb->sg[nsgs]), urb->sg[nsgs].offset, data_len, buf_size); len -= data_len; nsgs++; } skb_mark_for_recycle(skb); dev->drv->rx_skb(dev, MT_RXQ_MAIN, skb, NULL); return nsgs; } static void mt76u_complete_rx(struct urb *urb) { struct mt76_dev *dev = dev_get_drvdata(&urb->dev->dev); struct mt76_queue *q = urb->context; unsigned long flags; trace_rx_urb(dev, urb); switch (urb->status) { case -ECONNRESET: case -ESHUTDOWN: case -ENOENT: case -EPROTO: return; default: dev_err_ratelimited(dev->dev, "rx urb failed: %d\n", urb->status); fallthrough; case 0: break; } spin_lock_irqsave(&q->lock, flags); if (WARN_ONCE(q->entry[q->head].urb != urb, "rx urb mismatch")) goto out; q->head = (q->head + 1) % q->ndesc; q->queued++; mt76_worker_schedule(&dev->usb.rx_worker); out: spin_unlock_irqrestore(&q->lock, flags); } static int mt76u_submit_rx_buf(struct mt76_dev *dev, enum mt76_rxq_id qid, struct urb *urb) { int ep = qid == MT_RXQ_MAIN ? MT_EP_IN_PKT_RX : MT_EP_IN_CMD_RESP; mt76u_fill_bulk_urb(dev, USB_DIR_IN, ep, urb, mt76u_complete_rx, &dev->q_rx[qid]); trace_submit_urb(dev, urb); return usb_submit_urb(urb, GFP_ATOMIC); } static void mt76u_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q) { int qid = q - &dev->q_rx[MT_RXQ_MAIN]; struct urb *urb; int err, count; while (true) { urb = mt76u_get_next_rx_entry(q); if (!urb) break; count = mt76u_process_rx_entry(dev, urb, q->buf_size); if (count > 0) { err = mt76u_refill_rx(dev, q, urb, count); if (err < 0) break; } mt76u_submit_rx_buf(dev, qid, urb); } if (qid == MT_RXQ_MAIN) { local_bh_disable(); mt76_rx_poll_complete(dev, MT_RXQ_MAIN, NULL); local_bh_enable(); } } static void mt76u_rx_worker(struct mt76_worker *w) { struct mt76_usb *usb = container_of(w, struct mt76_usb, rx_worker); struct mt76_dev *dev = container_of(usb, struct mt76_dev, usb); int i; rcu_read_lock(); mt76_for_each_q_rx(dev, i) mt76u_process_rx_queue(dev, &dev->q_rx[i]); rcu_read_unlock(); } static int mt76u_submit_rx_buffers(struct mt76_dev *dev, enum mt76_rxq_id qid) { struct mt76_queue *q = &dev->q_rx[qid]; unsigned long flags; int i, err = 0; spin_lock_irqsave(&q->lock, flags); for (i = 0; i < q->ndesc; i++) { err = mt76u_submit_rx_buf(dev, qid, q->entry[i].urb); if (err < 0) break; } q->head = q->tail = 0; q->queued = 0; spin_unlock_irqrestore(&q->lock, flags); return err; } static int mt76u_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid) { struct mt76_queue *q = &dev->q_rx[qid]; int i, err; err = mt76_create_page_pool(dev, q); if (err) return err; spin_lock_init(&q->lock); q->entry = devm_kcalloc(dev->dev, MT_NUM_RX_ENTRIES, sizeof(*q->entry), GFP_KERNEL); if (!q->entry) return -ENOMEM; q->ndesc = MT_NUM_RX_ENTRIES; q->buf_size = PAGE_SIZE; for (i = 0; i < q->ndesc; i++) { err = mt76u_rx_urb_alloc(dev, q, &q->entry[i]); if (err < 0) return err; } return mt76u_submit_rx_buffers(dev, qid); } int mt76u_alloc_mcu_queue(struct mt76_dev *dev) { return mt76u_alloc_rx_queue(dev, MT_RXQ_MCU); } EXPORT_SYMBOL_GPL(mt76u_alloc_mcu_queue); static void mt76u_free_rx_queue(struct mt76_dev *dev, struct mt76_queue *q) { int i; for (i = 0; i < q->ndesc; i++) { if (!q->entry[i].urb) continue; mt76u_urb_free(q->entry[i].urb); q->entry[i].urb = NULL; } page_pool_destroy(q->page_pool); q->page_pool = NULL; } static void mt76u_free_rx(struct mt76_dev *dev) { int i; mt76_worker_teardown(&dev->usb.rx_worker); mt76_for_each_q_rx(dev, i) mt76u_free_rx_queue(dev, &dev->q_rx[i]); } void mt76u_stop_rx(struct mt76_dev *dev) { int i; mt76_worker_disable(&dev->usb.rx_worker); mt76_for_each_q_rx(dev, i) { struct mt76_queue *q = &dev->q_rx[i]; int j; for (j = 0; j < q->ndesc; j++) usb_poison_urb(q->entry[j].urb); } } EXPORT_SYMBOL_GPL(mt76u_stop_rx); int mt76u_resume_rx(struct mt76_dev *dev) { int i; mt76_for_each_q_rx(dev, i) { struct mt76_queue *q = &dev->q_rx[i]; int err, j; for (j = 0; j < q->ndesc; j++) usb_unpoison_urb(q->entry[j].urb); err = mt76u_submit_rx_buffers(dev, i); if (err < 0) return err; } mt76_worker_enable(&dev->usb.rx_worker); return 0; } EXPORT_SYMBOL_GPL(mt76u_resume_rx); static void mt76u_status_worker(struct mt76_worker *w) { struct mt76_usb *usb = container_of(w, struct mt76_usb, status_worker); struct mt76_dev *dev = container_of(usb, struct mt76_dev, usb); struct mt76_queue_entry entry; struct mt76_queue *q; int i; if (!test_bit(MT76_STATE_RUNNING, &dev->phy.state)) return; for (i = 0; i <= MT_TXQ_PSD; i++) { q = dev->phy.q_tx[i]; if (!q) continue; while (q->queued > 0) { if (!q->entry[q->tail].done) break; entry = q->entry[q->tail]; q->entry[q->tail].done = false; mt76_queue_tx_complete(dev, q, &entry); } if (!q->queued) wake_up(&dev->tx_wait); mt76_worker_schedule(&dev->tx_worker); } if (dev->drv->tx_status_data && !test_and_set_bit(MT76_READING_STATS, &dev->phy.state)) queue_work(dev->wq, &dev->usb.stat_work); } static void mt76u_tx_status_data(struct work_struct *work) { struct mt76_usb *usb; struct mt76_dev *dev; u8 update = 1; u16 count = 0; usb = container_of(work, struct mt76_usb, stat_work); dev = container_of(usb, struct mt76_dev, usb); while (true) { if (test_bit(MT76_REMOVED, &dev->phy.state)) break; if (!dev->drv->tx_status_data(dev, &update)) break; count++; } if (count && test_bit(MT76_STATE_RUNNING, &dev->phy.state)) queue_work(dev->wq, &usb->stat_work); else clear_bit(MT76_READING_STATS, &dev->phy.state); } static void mt76u_complete_tx(struct urb *urb) { struct mt76_dev *dev = dev_get_drvdata(&urb->dev->dev); struct mt76_queue_entry *e = urb->context; if (mt76u_urb_error(urb)) dev_err(dev->dev, "tx urb failed: %d\n", urb->status); e->done = true; mt76_worker_schedule(&dev->usb.status_worker); } static int mt76u_tx_setup_buffers(struct mt76_dev *dev, struct sk_buff *skb, struct urb *urb) { urb->transfer_buffer_length = skb->len; if (!dev->usb.sg_en) { urb->transfer_buffer = skb->data; return 0; } sg_init_table(urb->sg, MT_TX_SG_MAX_SIZE); urb->num_sgs = skb_to_sgvec(skb, urb->sg, 0, skb->len); if (!urb->num_sgs) return -ENOMEM; return urb->num_sgs; } static int mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q, enum mt76_txq_id qid, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_sta *sta) { struct mt76_tx_info tx_info = { .skb = skb, }; struct mt76_dev *dev = phy->dev; u16 idx = q->head; int err; if (q->queued == q->ndesc) return -ENOSPC; skb->prev = skb->next = NULL; err = dev->drv->tx_prepare_skb(dev, NULL, qid, wcid, sta, &tx_info); if (err < 0) return err; err = mt76u_tx_setup_buffers(dev, tx_info.skb, q->entry[idx].urb); if (err < 0) return err; mt76u_fill_bulk_urb(dev, USB_DIR_OUT, q->ep, q->entry[idx].urb, mt76u_complete_tx, &q->entry[idx]); q->head = (q->head + 1) % q->ndesc; q->entry[idx].skb = tx_info.skb; q->entry[idx].wcid = 0xffff; q->queued++; return idx; } static void mt76u_tx_kick(struct mt76_dev *dev, struct mt76_queue *q) { struct urb *urb; int err; while (q->first != q->head) { urb = q->entry[q->first].urb; trace_submit_urb(dev, urb); err = usb_submit_urb(urb, GFP_ATOMIC); if (err < 0) { if (err == -ENODEV) set_bit(MT76_REMOVED, &dev->phy.state); else dev_err(dev->dev, "tx urb submit failed:%d\n", err); break; } q->first = (q->first + 1) % q->ndesc; } } static void mt76u_ac_to_hwq(struct mt76_dev *dev, struct mt76_queue *q, u8 qid) { u8 ac = qid < IEEE80211_NUM_ACS ? qid : IEEE80211_AC_BE; switch (mt76_chip(dev)) { case 0x7663: { static const u8 lmac_queue_map[] = { /* ac to lmac mapping */ [IEEE80211_AC_BK] = 0, [IEEE80211_AC_BE] = 1, [IEEE80211_AC_VI] = 2, [IEEE80211_AC_VO] = 4, }; q->hw_idx = lmac_queue_map[ac]; q->ep = q->hw_idx + 1; break; } case 0x7961: case 0x7925: q->hw_idx = mt76_ac_to_hwq(ac); q->ep = qid == MT_TXQ_PSD ? MT_EP_OUT_HCCA : q->hw_idx + 1; break; default: q->hw_idx = mt76_ac_to_hwq(ac); q->ep = q->hw_idx + 1; break; } } static int mt76u_alloc_tx(struct mt76_dev *dev) { int i; for (i = 0; i <= MT_TXQ_PSD; i++) { struct mt76_queue *q; int j, err; q = devm_kzalloc(dev->dev, sizeof(*q), GFP_KERNEL); if (!q) return -ENOMEM; spin_lock_init(&q->lock); mt76u_ac_to_hwq(dev, q, i); dev->phy.q_tx[i] = q; q->entry = devm_kcalloc(dev->dev, MT_NUM_TX_ENTRIES, sizeof(*q->entry), GFP_KERNEL); if (!q->entry) return -ENOMEM; q->ndesc = MT_NUM_TX_ENTRIES; for (j = 0; j < q->ndesc; j++) { err = mt76u_urb_alloc(dev, &q->entry[j], MT_TX_SG_MAX_SIZE); if (err < 0) return err; } } return 0; } static void mt76u_free_tx(struct mt76_dev *dev) { int i; mt76_worker_teardown(&dev->usb.status_worker); for (i = 0; i <= MT_TXQ_PSD; i++) { struct mt76_queue *q; int j; q = dev->phy.q_tx[i]; if (!q) continue; for (j = 0; j < q->ndesc; j++) { usb_free_urb(q->entry[j].urb); q->entry[j].urb = NULL; } } } void mt76u_stop_tx(struct mt76_dev *dev) { int ret; mt76_worker_disable(&dev->usb.status_worker); ret = wait_event_timeout(dev->tx_wait, !mt76_has_tx_pending(&dev->phy), HZ / 5); if (!ret) { struct mt76_queue_entry entry; struct mt76_queue *q; int i, j; dev_err(dev->dev, "timed out waiting for pending tx\n"); for (i = 0; i <= MT_TXQ_PSD; i++) { q = dev->phy.q_tx[i]; if (!q) continue; for (j = 0; j < q->ndesc; j++) usb_kill_urb(q->entry[j].urb); } mt76_worker_disable(&dev->tx_worker); /* On device removal we maight queue skb's, but mt76u_tx_kick() * will fail to submit urb, cleanup those skb's manually. */ for (i = 0; i <= MT_TXQ_PSD; i++) { q = dev->phy.q_tx[i]; if (!q) continue; while (q->queued > 0) { entry = q->entry[q->tail]; q->entry[q->tail].done = false; mt76_queue_tx_complete(dev, q, &entry); } } mt76_worker_enable(&dev->tx_worker); } cancel_work_sync(&dev->usb.stat_work); clear_bit(MT76_READING_STATS, &dev->phy.state); mt76_worker_enable(&dev->usb.status_worker); mt76_tx_status_check(dev, true); } EXPORT_SYMBOL_GPL(mt76u_stop_tx); void mt76u_queues_deinit(struct mt76_dev *dev) { mt76u_stop_rx(dev); mt76u_stop_tx(dev); mt76u_free_rx(dev); mt76u_free_tx(dev); } EXPORT_SYMBOL_GPL(mt76u_queues_deinit); int mt76u_alloc_queues(struct mt76_dev *dev) { int err; err = mt76u_alloc_rx_queue(dev, MT_RXQ_MAIN); if (err < 0) return err; return mt76u_alloc_tx(dev); } EXPORT_SYMBOL_GPL(mt76u_alloc_queues); static const struct mt76_queue_ops usb_queue_ops = { .tx_queue_skb = mt76u_tx_queue_skb, .kick = mt76u_tx_kick, }; int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf, struct mt76_bus_ops *ops) { struct usb_device *udev = interface_to_usbdev(intf); struct mt76_usb *usb = &dev->usb; int err; INIT_WORK(&usb->stat_work, mt76u_tx_status_data); usb->data_len = usb_maxpacket(udev, usb_sndctrlpipe(udev, 0)); if (usb->data_len < 32) usb->data_len = 32; usb->data = devm_kmalloc(dev->dev, usb->data_len, GFP_KERNEL); if (!usb->data) return -ENOMEM; mutex_init(&usb->usb_ctrl_mtx); dev->bus = ops; dev->queue_ops = &usb_queue_ops; dev_set_drvdata(&udev->dev, dev); usb->sg_en = mt76u_check_sg(dev); err = mt76u_set_endpoints(intf, usb); if (err < 0) return err; err = mt76_worker_setup(dev->hw, &usb->rx_worker, mt76u_rx_worker, "usb-rx"); if (err) return err; err = mt76_worker_setup(dev->hw, &usb->status_worker, mt76u_status_worker, "usb-status"); if (err) return err; sched_set_fifo_low(usb->rx_worker.task); sched_set_fifo_low(usb->status_worker.task); return 0; } EXPORT_SYMBOL_GPL(__mt76u_init); int mt76u_init(struct mt76_dev *dev, struct usb_interface *intf) { static struct mt76_bus_ops bus_ops = { .rr = mt76u_rr, .wr = mt76u_wr, .rmw = mt76u_rmw, .read_copy = mt76u_read_copy, .write_copy = mt76u_copy, .wr_rp = mt76u_wr_rp, .rd_rp = mt76u_rd_rp, .type = MT76_BUS_USB, }; return __mt76u_init(dev, intf, &bus_ops); } EXPORT_SYMBOL_GPL(mt76u_init); MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>"); MODULE_DESCRIPTION("MediaTek MT76x USB helpers"); MODULE_LICENSE("Dual BSD/GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1