/* $OpenBSD: if_urtwn.c,v 1.16 2011/02/10 17:26:40 jakemsr Exp $ */ /*- * Copyright (c) 2010 Damien Bergamini * Copyright (c) 2014 Kevin Lo * Copyright (c) 2016 Andriy Voskoboinyk * * Permission to use, copy, modify, and 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static device_probe_t rtwn_pci_probe; static device_attach_t rtwn_pci_attach; static device_detach_t rtwn_pci_detach; static device_shutdown_t rtwn_pci_shutdown; static device_suspend_t rtwn_pci_suspend; static device_resume_t rtwn_pci_resume; static int rtwn_pci_alloc_rx_list(struct rtwn_softc *); static void rtwn_pci_reset_rx_list(struct rtwn_softc *); static void rtwn_pci_free_rx_list(struct rtwn_softc *); static int rtwn_pci_alloc_tx_list(struct rtwn_softc *, int); static void rtwn_pci_reset_tx_list(struct rtwn_softc *, struct ieee80211vap *, int); static void rtwn_pci_free_tx_list(struct rtwn_softc *, int); static void rtwn_pci_reset_lists(struct rtwn_softc *, struct ieee80211vap *); static int rtwn_pci_fw_write_block(struct rtwn_softc *, const uint8_t *, uint16_t, int); static uint16_t rtwn_pci_get_qmap(struct rtwn_softc *); static void rtwn_pci_set_desc_addr(struct rtwn_softc *); static void rtwn_pci_attach_methods(struct rtwn_softc *); static int matched_chip = RTWN_CHIP_MAX_PCI; static int rtwn_pci_probe(device_t dev) { const struct rtwn_pci_ident *ident; for (ident = rtwn_pci_ident_table; ident->name != NULL; ident++) { if (pci_get_vendor(dev) == ident->vendor && pci_get_device(dev) == ident->device) { matched_chip = ident->chip; device_set_desc(dev, ident->name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int rtwn_pci_alloc_rx_list(struct rtwn_softc *sc) { struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); struct rtwn_rx_ring *rx_ring = &pc->rx_ring; struct rtwn_rx_data *rx_data; bus_size_t size; int i, error; /* Allocate Rx descriptors. */ size = sizeof(struct r92ce_rx_stat) * RTWN_PCI_RX_LIST_COUNT; error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, size, 1, size, 0, NULL, NULL, &rx_ring->desc_dmat); if (error != 0) { device_printf(sc->sc_dev, "could not create rx desc DMA tag\n"); goto fail; } error = bus_dmamem_alloc(rx_ring->desc_dmat, (void **)&rx_ring->desc, BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, &rx_ring->desc_map); if (error != 0) { device_printf(sc->sc_dev, "could not allocate rx desc\n"); goto fail; } error = bus_dmamap_load(rx_ring->desc_dmat, rx_ring->desc_map, rx_ring->desc, size, rtwn_pci_dma_map_addr, &rx_ring->paddr, 0); if (error != 0) { device_printf(sc->sc_dev, "could not load rx desc DMA map\n"); goto fail; } bus_dmamap_sync(rx_ring->desc_dmat, rx_ring->desc_map, BUS_DMASYNC_PREWRITE); /* Create RX buffer DMA tag. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, MCLBYTES, 0, NULL, NULL, &rx_ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "could not create rx buf DMA tag\n"); goto fail; } /* Allocate Rx buffers. */ for (i = 0; i < RTWN_PCI_RX_LIST_COUNT; i++) { rx_data = &rx_ring->rx_data[i]; error = bus_dmamap_create(rx_ring->data_dmat, 0, &rx_data->map); if (error != 0) { device_printf(sc->sc_dev, "could not create rx buf DMA map\n"); goto fail; } rx_data->m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (rx_data->m == NULL) { device_printf(sc->sc_dev, "could not allocate rx mbuf\n"); error = ENOMEM; goto fail; } error = bus_dmamap_load(rx_ring->data_dmat, rx_data->map, mtod(rx_data->m, void *), MCLBYTES, rtwn_pci_dma_map_addr, &rx_data->paddr, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "could not load rx buf DMA map"); goto fail; } rtwn_pci_setup_rx_desc(pc, &rx_ring->desc[i], rx_data->paddr, MCLBYTES, i); } rx_ring->cur = 0; return (0); fail: rtwn_pci_free_rx_list(sc); return (error); } static void rtwn_pci_reset_rx_list(struct rtwn_softc *sc) { struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); struct rtwn_rx_ring *rx_ring = &pc->rx_ring; struct rtwn_rx_data *rx_data; int i; for (i = 0; i < RTWN_PCI_RX_LIST_COUNT; i++) { rx_data = &rx_ring->rx_data[i]; rtwn_pci_setup_rx_desc(pc, &rx_ring->desc[i], rx_data->paddr, MCLBYTES, i); } rx_ring->cur = 0; } static void rtwn_pci_free_rx_list(struct rtwn_softc *sc) { struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); struct rtwn_rx_ring *rx_ring = &pc->rx_ring; struct rtwn_rx_data *rx_data; int i; if (rx_ring->desc_dmat != NULL) { if (rx_ring->desc != NULL) { bus_dmamap_sync(rx_ring->desc_dmat, rx_ring->desc_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(rx_ring->desc_dmat, rx_ring->desc_map); bus_dmamem_free(rx_ring->desc_dmat, rx_ring->desc, rx_ring->desc_map); rx_ring->desc = NULL; } bus_dma_tag_destroy(rx_ring->desc_dmat); rx_ring->desc_dmat = NULL; } for (i = 0; i < RTWN_PCI_RX_LIST_COUNT; i++) { rx_data = &rx_ring->rx_data[i]; if (rx_data->m != NULL) { bus_dmamap_sync(rx_ring->data_dmat, rx_data->map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(rx_ring->data_dmat, rx_data->map); m_freem(rx_data->m); rx_data->m = NULL; } bus_dmamap_destroy(rx_ring->data_dmat, rx_data->map); rx_data->map = NULL; } if (rx_ring->data_dmat != NULL) { bus_dma_tag_destroy(rx_ring->data_dmat); rx_ring->data_dmat = NULL; } } static int rtwn_pci_alloc_tx_list(struct rtwn_softc *sc, int qid) { struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); struct rtwn_tx_ring *tx_ring = &pc->tx_ring[qid]; bus_size_t size; int i, error; size = sc->txdesc_len * RTWN_PCI_TX_LIST_COUNT; error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), PAGE_SIZE, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, size, 1, size, 0, NULL, NULL, &tx_ring->desc_dmat); if (error != 0) { device_printf(sc->sc_dev, "could not create tx ring DMA tag\n"); goto fail; } error = bus_dmamem_alloc(tx_ring->desc_dmat, &tx_ring->desc, BUS_DMA_NOWAIT | BUS_DMA_ZERO, &tx_ring->desc_map); if (error != 0) { device_printf(sc->sc_dev, "can't map tx ring DMA memory\n"); goto fail; } error = bus_dmamap_load(tx_ring->desc_dmat, tx_ring->desc_map, tx_ring->desc, size, rtwn_pci_dma_map_addr, &tx_ring->paddr, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "could not load desc DMA map\n"); goto fail; } bus_dmamap_sync(tx_ring->desc_dmat, tx_ring->desc_map, BUS_DMASYNC_PREWRITE); error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, MCLBYTES, 0, NULL, NULL, &tx_ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "could not create tx buf DMA tag\n"); goto fail; } for (i = 0; i < RTWN_PCI_TX_LIST_COUNT; i++) { struct rtwn_tx_data *tx_data = &tx_ring->tx_data[i]; void *tx_desc = (uint8_t *)tx_ring->desc + sc->txdesc_len * i; uint32_t next_desc_addr = tx_ring->paddr + sc->txdesc_len * ((i + 1) % RTWN_PCI_TX_LIST_COUNT); rtwn_pci_setup_tx_desc(pc, tx_desc, next_desc_addr); error = bus_dmamap_create(tx_ring->data_dmat, 0, &tx_data->map); if (error != 0) { device_printf(sc->sc_dev, "could not create tx buf DMA map\n"); return (error); } tx_data->m = NULL; tx_data->ni = NULL; } return (0); fail: rtwn_pci_free_tx_list(sc, qid); return (error); } static void rtwn_pci_reset_tx_list(struct rtwn_softc *sc, struct ieee80211vap *vap, int qid) { struct rtwn_vap *uvp = RTWN_VAP(vap); struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); struct rtwn_tx_ring *tx_ring = &pc->tx_ring[qid]; int i, id; id = (uvp != NULL ? uvp->id : RTWN_VAP_ID_INVALID); for (i = 0; i < RTWN_PCI_TX_LIST_COUNT; i++) { struct rtwn_tx_data *tx_data = &tx_ring->tx_data[i]; if (vap == NULL || (tx_data->ni == NULL && (tx_data->id == id || id == RTWN_VAP_ID_INVALID)) || (tx_data->ni != NULL && tx_data->ni->ni_vap == vap)) { void *tx_desc = (uint8_t *)tx_ring->desc + sc->txdesc_len * i; rtwn_pci_copy_tx_desc(pc, tx_desc, NULL); if (tx_data->m != NULL) { bus_dmamap_sync(tx_ring->data_dmat, tx_data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(tx_ring->data_dmat, tx_data->map); m_freem(tx_data->m); tx_data->m = NULL; } if (tx_data->ni != NULL) { ieee80211_free_node(tx_data->ni); tx_data->ni = NULL; } } } bus_dmamap_sync(tx_ring->desc_dmat, tx_ring->desc_map, BUS_DMASYNC_POSTWRITE); sc->qfullmsk &= ~(1 << qid); tx_ring->queued = 0; tx_ring->last = tx_ring->cur = 0; } static void rtwn_pci_free_tx_list(struct rtwn_softc *sc, int qid) { struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); struct rtwn_tx_ring *tx_ring = &pc->tx_ring[qid]; struct rtwn_tx_data *tx_data; int i; if (tx_ring->desc_dmat != NULL) { if (tx_ring->desc != NULL) { bus_dmamap_sync(tx_ring->desc_dmat, tx_ring->desc_map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(tx_ring->desc_dmat, tx_ring->desc_map); bus_dmamem_free(tx_ring->desc_dmat, tx_ring->desc, tx_ring->desc_map); } bus_dma_tag_destroy(tx_ring->desc_dmat); } for (i = 0; i < RTWN_PCI_TX_LIST_COUNT; i++) { tx_data = &tx_ring->tx_data[i]; if (tx_data->m != NULL) { bus_dmamap_sync(tx_ring->data_dmat, tx_data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(tx_ring->data_dmat, tx_data->map); m_freem(tx_data->m); tx_data->m = NULL; } } if (tx_ring->data_dmat != NULL) { bus_dma_tag_destroy(tx_ring->data_dmat); tx_ring->data_dmat = NULL; } sc->qfullmsk &= ~(1 << qid); tx_ring->queued = 0; tx_ring->last = tx_ring->cur = 0; } static void rtwn_pci_reset_lists(struct rtwn_softc *sc, struct ieee80211vap *vap) { int i; for (i = 0; i < RTWN_PCI_NTXQUEUES; i++) rtwn_pci_reset_tx_list(sc, vap, i); if (vap == NULL) { sc->qfullmsk = 0; rtwn_pci_reset_rx_list(sc); } } static int rtwn_pci_fw_write_block(struct rtwn_softc *sc, const uint8_t *buf, uint16_t reg, int mlen) { int i; for (i = 0; i < mlen; i++) rtwn_pci_write_1(sc, reg++, buf[i]); /* NB: cannot fail */ return (0); } static uint16_t rtwn_pci_get_qmap(struct rtwn_softc *sc) { struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); KASSERT(pc->pc_qmap != 0, ("%s: qmap is not set!\n", __func__)); return (pc->pc_qmap); } static void rtwn_pci_set_desc_addr(struct rtwn_softc *sc) { struct rtwn_pci_softc *pc = RTWN_PCI_SOFTC(sc); RTWN_DPRINTF(sc, RTWN_DEBUG_RESET, "%s: addresses:\n" "bk: %08jX, be: %08jX, vi: %08jX, vo: %08jX\n" "bcn: %08jX, mgt: %08jX, high: %08jX, rx: %08jX\n", __func__, (uintmax_t)pc->tx_ring[RTWN_PCI_BK_QUEUE].paddr, (uintmax_t)pc->tx_ring[RTWN_PCI_BE_QUEUE].paddr, (uintmax_t)pc->tx_ring[RTWN_PCI_VI_QUEUE].paddr, (uintmax_t)pc->tx_ring[RTWN_PCI_VO_QUEUE].paddr, (uintmax_t)pc->tx_ring[RTWN_PCI_BEACON_QUEUE].paddr, (uintmax_t)pc->tx_ring[RTWN_PCI_MGNT_QUEUE].paddr, (uintmax_t)pc->tx_ring[RTWN_PCI_HIGH_QUEUE].paddr, (uintmax_t)pc->rx_ring.paddr); /* Set Tx Configuration Register. */ rtwn_pci_write_4(sc, R92C_TCR, pc->tcr); /* Configure Tx DMA. */ rtwn_pci_write_4(sc, R92C_BKQ_DESA, pc->tx_ring[RTWN_PCI_BK_QUEUE].paddr); rtwn_pci_write_4(sc, R92C_BEQ_DESA, pc->tx_ring[RTWN_PCI_BE_QUEUE].paddr); rtwn_pci_write_4(sc, R92C_VIQ_DESA, pc->tx_ring[RTWN_PCI_VI_QUEUE].paddr); rtwn_pci_write_4(sc, R92C_VOQ_DESA, pc->tx_ring[RTWN_PCI_VO_QUEUE].paddr); rtwn_pci_write_4(sc, R92C_BCNQ_DESA, pc->tx_ring[RTWN_PCI_BEACON_QUEUE].paddr); rtwn_pci_write_4(sc, R92C_MGQ_DESA, pc->tx_ring[RTWN_PCI_MGNT_QUEUE].paddr); rtwn_pci_write_4(sc, R92C_HQ_DESA, pc->tx_ring[RTWN_PCI_HIGH_QUEUE].paddr); /* Configure Rx DMA. */ rtwn_pci_write_4(sc, R92C_RX_DESA, pc->rx_ring.paddr); } static void rtwn_pci_attach_methods(struct rtwn_softc *sc) { sc->sc_write_1 = rtwn_pci_write_1; sc->sc_write_2 = rtwn_pci_write_2; sc->sc_write_4 = rtwn_pci_write_4; sc->sc_read_1 = rtwn_pci_read_1; sc->sc_read_2 = rtwn_pci_read_2; sc->sc_read_4 = rtwn_pci_read_4; sc->sc_delay = rtwn_pci_delay; sc->sc_tx_start = rtwn_pci_tx_start; sc->sc_reset_lists = rtwn_pci_reset_lists; sc->sc_abort_xfers = rtwn_nop_softc; sc->sc_fw_write_block = rtwn_pci_fw_write_block; sc->sc_get_qmap = rtwn_pci_get_qmap; sc->sc_set_desc_addr = rtwn_pci_set_desc_addr; sc->sc_drop_incorrect_tx = rtwn_nop_softc; } static int rtwn_pci_attach(device_t dev) { struct rtwn_pci_softc *pc = device_get_softc(dev); struct rtwn_softc *sc = &pc->pc_sc; struct ieee80211com *ic = &sc->sc_ic; uint32_t lcsr; int cap_off, i, error, rid; if (matched_chip >= RTWN_CHIP_MAX_PCI) return (ENXIO); /* * Get the offset of the PCI Express Capability Structure in PCI * Configuration Space. */ error = pci_find_cap(dev, PCIY_EXPRESS, &cap_off); if (error != 0) { device_printf(dev, "PCIe capability structure not found!\n"); return (error); } /* Enable bus-mastering. */ pci_enable_busmaster(dev); rid = PCIR_BAR(2); pc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (pc->mem == NULL) { device_printf(dev, "can't map mem space\n"); return (ENOMEM); } pc->pc_st = rman_get_bustag(pc->mem); pc->pc_sh = rman_get_bushandle(pc->mem); /* Install interrupt handler. */ rid = 1; if (pci_alloc_msi(dev, &rid) == 0) rid = 1; else rid = 0; pc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | (rid != 0 ? 0 : RF_SHAREABLE)); if (pc->irq == NULL) { device_printf(dev, "can't map interrupt\n"); goto detach; } /* Disable PCIe Active State Power Management (ASPM). */ lcsr = pci_read_config(dev, cap_off + PCIER_LINK_CTL, 4); lcsr &= ~PCIEM_LINK_CTL_ASPMC; pci_write_config(dev, cap_off + PCIER_LINK_CTL, lcsr, 4); sc->sc_dev = dev; ic->ic_name = device_get_nameunit(dev); /* Need to be initialized early. */ rtwn_sysctlattach(sc); mtx_init(&sc->sc_mtx, ic->ic_name, MTX_NETWORK_LOCK, MTX_DEF); rtwn_pci_attach_methods(sc); /* XXX something similar to USB_GET_DRIVER_INFO() */ rtwn_pci_attach_private(pc, matched_chip); /* Allocate Tx/Rx buffers. */ error = rtwn_pci_alloc_rx_list(sc); if (error != 0) { device_printf(dev, "could not allocate Rx buffers, error %d\n", error); goto detach; } for (i = 0; i < RTWN_PCI_NTXQUEUES; i++) { error = rtwn_pci_alloc_tx_list(sc, i); if (error != 0) { device_printf(dev, "could not allocate Tx buffers, error %d\n", error); goto detach; } } /* Generic attach. */ error = rtwn_attach(sc); if (error != 0) goto detach; /* * Hook our interrupt after all initialization is complete. */ error = bus_setup_intr(dev, pc->irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, rtwn_pci_intr, sc, &pc->pc_ih); if (error != 0) { device_printf(dev, "can't establish interrupt, error %d\n", error); goto detach; } return (0); detach: rtwn_pci_detach(dev); /* failure */ return (ENXIO); } static int rtwn_pci_detach(device_t dev) { struct rtwn_pci_softc *pc = device_get_softc(dev); struct rtwn_softc *sc = &pc->pc_sc; int i; /* Generic detach. */ rtwn_detach(sc); /* Uninstall interrupt handler. */ if (pc->irq != NULL) { bus_teardown_intr(dev, pc->irq, pc->pc_ih); bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(pc->irq), pc->irq); pci_release_msi(dev); } /* Free Tx/Rx buffers. */ for (i = 0; i < RTWN_PCI_NTXQUEUES; i++) rtwn_pci_free_tx_list(sc, i); rtwn_pci_free_rx_list(sc); if (pc->mem != NULL) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(pc->mem), pc->mem); rtwn_detach_private(sc); mtx_destroy(&sc->sc_mtx); return (0); } static int rtwn_pci_shutdown(device_t self) { struct rtwn_pci_softc *pc = device_get_softc(self); ieee80211_stop_all(&pc->pc_sc.sc_ic); return (0); } static int rtwn_pci_suspend(device_t self) { struct rtwn_pci_softc *pc = device_get_softc(self); rtwn_suspend(&pc->pc_sc); return (0); } static int rtwn_pci_resume(device_t self) { struct rtwn_pci_softc *pc = device_get_softc(self); rtwn_resume(&pc->pc_sc); return (0); } static device_method_t rtwn_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rtwn_pci_probe), DEVMETHOD(device_attach, rtwn_pci_attach), DEVMETHOD(device_detach, rtwn_pci_detach), DEVMETHOD(device_shutdown, rtwn_pci_shutdown), DEVMETHOD(device_suspend, rtwn_pci_suspend), DEVMETHOD(device_resume, rtwn_pci_resume), DEVMETHOD_END }; static driver_t rtwn_pci_driver = { "rtwn", rtwn_pci_methods, sizeof(struct rtwn_pci_softc) }; static devclass_t rtwn_pci_devclass; DRIVER_MODULE(rtwn_pci, pci, rtwn_pci_driver, rtwn_pci_devclass, NULL, NULL); MODULE_VERSION(rtwn_pci, 1); MODULE_DEPEND(rtwn_pci, pci, 1, 1, 1); MODULE_DEPEND(rtwn_pci, wlan, 1, 1, 1); MODULE_DEPEND(rtwn_pci, rtwn, 2, 2, 2);