diff options
author | Marcus Glocker <mglocker@cvs.openbsd.org> | 2006-08-22 18:12:14 +0000 |
---|---|---|
committer | Marcus Glocker <mglocker@cvs.openbsd.org> | 2006-08-22 18:12:14 +0000 |
commit | 8d37bde677cb65076159a7f16c8b0db2d5e0c051 (patch) | |
tree | b5bc13af72ee5c7685a850e2e8e8e152c4c45dff | |
parent | 7494ab6c1b800af4a75410a82729e68c4295ce60 (diff) |
Very initial import to support the PrismGT chipset (FullMAC for now).
ok deraadt@
-rw-r--r-- | sys/conf/files | 6 | ||||
-rw-r--r-- | sys/dev/ic/pgt.c | 3704 | ||||
-rw-r--r-- | sys/dev/ic/pgtreg.h | 387 | ||||
-rw-r--r-- | sys/dev/ic/pgtvar.h | 205 | ||||
-rw-r--r-- | sys/dev/pci/files.pci | 8 | ||||
-rw-r--r-- | sys/dev/pci/if_pgt_pci.c | 607 |
6 files changed, 4915 insertions, 2 deletions
diff --git a/sys/conf/files b/sys/conf/files index 7ab0cc63726..429f54afb38 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.384 2006/08/06 13:06:58 miod Exp $ +# $OpenBSD: files,v 1.385 2006/08/22 18:12:13 mglocker Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -373,6 +373,10 @@ file dev/ic/acx.c acx file dev/ic/acx111.c acx file dev/ic/acx100.c acx +# Conexant PrismGT +device pgt: ether, ifnet, ifmedia, firmload, wlan +file dev/ic/pgt.c pgt + # Attributes which machine-independent bus support can be attached to. # These should be defined here, because some of these busses can have # devices which provide these attributes, and we'd like to avoid hairy diff --git a/sys/dev/ic/pgt.c b/sys/dev/ic/pgt.c new file mode 100644 index 00000000000..5211a07061f --- /dev/null +++ b/sys/dev/ic/pgt.c @@ -0,0 +1,3704 @@ +/*- + * Copyright (c) 2004 Fujitsu Laboratories of America, Inc. + * Copyright (c) 2004 Brian Fundakowski Feldman + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/socket.h> +#include <sys/mbuf.h> +#include <sys/endian.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> +#include <sys/kthread.h> +#include <sys/time.h> +#include <sys/ioctl.h> + +#include <machine/bus.h> +#include <machine/endian.h> +#include <machine/intr.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/if_dl.h> +#include <net/if_llc.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#endif + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/if_ether.h> +#include <netinet/ip.h> +#endif + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_radiotap.h> + +#include <dev/ic/pgtreg.h> +#include <dev/ic/pgtvar.h> + +#include <dev/ic/if_wireg.h> +#include <dev/ic/if_wi_ieee.h> +#include <dev/ic/if_wivar.h> + +/* + * This is a driver for the Intersil Prism family of 802.11g network cards, + * based upon version 1.2 of the Linux driver and firmware found at + * http://www.prism54.org/. + */ + +/* + * The definitions for this API are _terrible_! + */ +#ifndef BUS_DMASYNC_PREDMA2CPU +#define BUS_DMASYNC_PREDMA2CPU BUS_DMASYNC_PREREAD +#define BUS_DMASYNC_POSTDMA2CPU BUS_DMASYNC_POSTREAD +#define BUS_DMASYNC_PRECPU2DMA BUS_DMASYNC_PREWRITE +#define BUS_DMASYNC_POSTCPU2DMA BUS_DMASYNC_POSTWRITE +#endif + +/* + * hack to get it compiled + */ +/* got from if_wi */ +#define WI_PRISM2_RES_SIZE 62 +#define WI_RID_SCAN_REQ 0xFCE1 +#define WI_RID_SCAN_APS 0x0600 +#define WI_RID_CHANNEL_LIST 0xFD10 +#define WI_RID_SCAN_RES 0xFD88 +#define WI_RID_COMMS_QUALITY 0xFD43 +/* got from FreeBSD */ +#define IEEE80211_WEP_OFF 0 +#define IEEE80211_WEP_ON 1 +#define IEEE80211_WEP_MIXED 2 +#define IEEE80211_IOC_MLME 21 +#define IEEE80211_IOC_AUTHMODE 7 +#define IEEE80211_IOC_WEP 3 +#define IEEE80211_IOC_WEPTXKEY 6 +#define IEEE80211_IOC_WEPKEY 4 +#define IEEE80211_POWERSAVE_ON 1 +#define IEEE80211_POWERSAVE_OFF 0 +#define IEEE80211_MLME_UNAUTHORIZE 5 +#define IEEE80211_MLME_AUTHORIZE 4 + +struct cfdriver pgt_cd = { + NULL, "pgt", DV_IFNET +}; + +void pgt_write_memory_barrier(struct pgt_softc *); +uint32_t pgt_read_4(struct pgt_softc *, uint16_t); +void pgt_write_4(struct pgt_softc *, uint16_t, uint32_t); +void pgt_write_4_flush(struct pgt_softc *, uint16_t, uint32_t); +void pgt_debug_events(struct pgt_softc *, const char *); +uint32_t pgt_queue_frags_pending(struct pgt_softc *, enum pgt_queue); +void pgt_reinit_rx_desc_frag(struct pgt_softc *, struct pgt_desc *); +int pgt_load_tx_desc_frag(struct pgt_softc *, enum pgt_queue, + struct pgt_desc *); +void pgt_unload_tx_desc_frag(struct pgt_softc *, struct pgt_desc *); +void pgt_enter_critical(struct pgt_softc *); +int pgt_try_enter_data_critical(struct pgt_softc *); +void pgt_exit_critical(struct pgt_softc *); +#if 0 +void pgt_try_exit_data_critical(struct pgt_softc *); +#endif +int pgt_upload_firmware(struct pgt_softc *); +void pgt_cleanup_queue(struct pgt_softc *, enum pgt_queue, + struct pgt_frag []); +int pgt_reset(struct pgt_softc *); +void pgt_disable(struct pgt_softc *, unsigned int); +void pgt_kill_kthread(struct pgt_softc *); +void pgt_init_intr(struct pgt_softc *); +void pgt_update_intr(struct pgt_softc *, struct mbuf ***, int); +struct mbuf + *pgt_ieee80211_encap(struct pgt_softc *, struct ether_header *, + struct mbuf *, struct ieee80211_node **); +void pgt_input_frames(struct pgt_softc *, struct mbuf *); +void pgt_wakeup_intr(struct pgt_softc *); +void pgt_sleep_intr(struct pgt_softc *); +void pgt_empty_traps(struct pgt_softc_kthread *); +void pgt_per_device_kthread(void *); +struct mbuf + *pgt_alloc_async(size_t); +void pgt_async_reset(struct pgt_softc *); +void pgt_async_trap(struct pgt_softc *, uint32_t, void *, size_t); +void pgt_async_update(struct pgt_softc *); +//void pgt_poll(struct ifnet *, enum poll_cmd, int); +void pgt_intr(void *); +void pgt_intr_body(struct pgt_softc *, struct mbuf **, int); +void pgt_txdone(struct pgt_softc *, enum pgt_queue); +void pgt_rxdone(struct pgt_softc *, enum pgt_queue); +void pgt_trap_received(struct pgt_softc *, uint32_t, void *, size_t); +void pgt_mgmtrx_completion(struct pgt_softc *, struct pgt_mgmt_desc *); +int pgt_datarx_completion(struct pgt_softc *, enum pgt_queue, + struct mbuf ***, int); +int pgt_oid_get(struct pgt_softc *, enum pgt_oid, void *, size_t); +int pgt_oid_retrieve(struct pgt_softc *, enum pgt_oid, void *, size_t); +int pgt_oid_set(struct pgt_softc *, enum pgt_oid, const void *, size_t); +void pgt_state_dump(struct pgt_softc *); +int pgt_mgmt_request(struct pgt_softc *, struct pgt_mgmt_desc *); +void pgt_desc_transmit(struct pgt_softc *, enum pgt_queue, + struct pgt_desc *, uint16_t, int); +void pgt_maybe_trigger(struct pgt_softc *, enum pgt_queue); +struct ieee80211_node + *pgt_ieee80211_node_alloc(struct ieee80211com *); +void pgt_ieee80211_newassoc(struct ieee80211com *, + struct ieee80211_node *, int); +void pgt_ieee80211_node_free(struct ieee80211com *, + struct ieee80211_node *); +void pgt_ieee80211_node_copy(struct ieee80211com *, + struct ieee80211_node *, + const struct ieee80211_node *); +int pgt_ieee80211_send_mgmt(struct ieee80211com *, + struct ieee80211_node *, int, int); +int pgt_net_attach(struct pgt_softc *); +void pgt_net_detach(struct pgt_softc *); +void pgt_start(struct ifnet *); +void pgt_start_body(struct pgt_softc *, struct ieee80211com *, + struct ifnet *); +int pgt_ioctl(struct ifnet *, u_long, caddr_t); +void pgt_obj_bss2scanres(struct pgt_softc *, + struct pgt_obj_bss *, struct wi_scan_res *, uint32_t); +int pgt_node_set_authorization(struct pgt_softc *, + struct pgt_ieee80211_node *, + enum pin_dot1x_authorization); + +#if 0 +int pgt_do_mlme_sta(struct pgt_softc *, struct ieee80211req_mlme *); +int pgt_do_mlme_hostap(struct pgt_softc *, struct ieee80211req_mlme *); +int pgt_do_mlme_adhoc(struct pgt_softc *, struct ieee80211req_mlme *); +int pgt_80211_set(struct pgt_softc *, struct ieee80211req *); +#endif + +int pgt_wavelan_get(struct pgt_softc *, struct wi_req *); +int pgt_wavelan_set(struct pgt_softc *, struct wi_req *); +void node_mark_active_ap(void *, struct ieee80211_node *); +void node_mark_active_adhoc(void *, struct ieee80211_node *); +void pgt_periodic(struct ifnet *); +int pgt_init(struct ifnet *); +void pgt_update_hw_from_sw(struct pgt_softc *, int, int); +void pgt_update_hw_from_nodes(struct pgt_softc *); +void pgt_hostap_handle_mlme(struct pgt_softc *, uint32_t, + struct pgt_obj_mlme *); +void pgt_update_sw_from_hw(struct pgt_softc *, + struct pgt_async_trap *, struct mbuf *); +int pgt_media_change(struct ifnet *); +void pgt_media_status(struct ifnet *, struct ifmediareq *); +int pgt_new_state(struct ieee80211com *, enum ieee80211_state, int); +int pgt_drain_tx_queue(struct pgt_softc *, enum pgt_queue); + +void +pgt_write_memory_barrier(struct pgt_softc *sc) +{ + bus_space_barrier(sc->sc_iotag, sc->sc_iohandle, 0, 0, + BUS_SPACE_BARRIER_WRITE); +} + +uint32_t +pgt_read_4(struct pgt_softc *sc, uint16_t offset) +{ + return (bus_space_read_4(sc->sc_iotag, sc->sc_iohandle, offset)); +} + +void +pgt_write_4(struct pgt_softc *sc, uint16_t offset, uint32_t value) +{ + bus_space_write_4(sc->sc_iotag, sc->sc_iohandle, offset, value); +} + +/* + * Write out 4 bytes and cause a PCI flush by reading back in on a + * harmless register. + */ +void +pgt_write_4_flush(struct pgt_softc *sc, uint16_t offset, uint32_t value) +{ + bus_space_write_4(sc->sc_iotag, sc->sc_iohandle, offset, value); + (void)bus_space_read_4(sc->sc_iotag, sc->sc_iohandle, PFF_REG_INT_EN); +} + +/* + * Print the state of events in the queues from an interrupt or a trigger. + */ +void +pgt_debug_events(struct pgt_softc *sc, const char *when) +{ +#define COUNT(i) \ + letoh32(sc->sc_cb->pcb_driver_curfrag[i]) - \ + letoh32(sc->sc_cb->pcb_device_curfrag[i]) + if (sc->sc_debug & SC_DEBUG_EVENTS) + printf("%s: ev%s: %u %u %u %u %u %u\n", + sc->sc_dev.dv_xname, when, + COUNT(0), COUNT(1), COUNT(2), COUNT(3), COUNT(4), COUNT(5)); +#undef COUNT +} + +uint32_t +pgt_queue_frags_pending(struct pgt_softc *sc, enum pgt_queue pq) +{ + return (letoh32(sc->sc_cb->pcb_driver_curfrag[pq]) - + letoh32(sc->sc_cb->pcb_device_curfrag[pq])); +} + +void +pgt_reinit_rx_desc_frag(struct pgt_softc *sc, struct pgt_desc *pd) +{ + pd->pd_fragp->pf_addr = htole32((uint32_t)pd->pd_dmaaddr); + pd->pd_fragp->pf_size = htole16(PFF_FRAG_SIZE); + pd->pd_fragp->pf_flags = htole16(0); + /* XXX */ + bus_dmamap_sync(sc->sc_fragdmat, pd->pd_dmam, 0, 0, BUS_DMASYNC_POSTCPU2DMA); +} + +int +pgt_load_tx_desc_frag(struct pgt_softc *sc, enum pgt_queue pq, + struct pgt_desc *pd) +{ + int error; + + /* XXX */ + //error = bus_dmamap_load(sc->sc_fragdmat, pd->pd_dmam, pd->pd_mem, + // PFF_FRAG_SIZE, pgt_load_busaddr, &pd->pd_dmaaddr, 0); + error = bus_dmamap_load(sc->sc_fragdmat, pd->pd_dmam, pd->pd_mem, + PFF_FRAG_SIZE, NULL, BUS_DMA_NOWAIT); + if (error) { + printf("%s: unable to load %s tx DMA: %d\n", + sc->sc_dev.dv_xname, + pgt_queue_is_data(pq) ? "data" : "mgmt", error); + return (error); + } + pd->pd_fragp->pf_addr = htole32((uint32_t)pd->pd_dmaaddr); + pd->pd_fragp->pf_size = htole16(PFF_FRAG_SIZE); + pd->pd_fragp->pf_flags = htole16(0); + /* XXX */ + bus_dmamap_sync(sc->sc_fragdmat, pd->pd_dmam, 0, 0, + BUS_DMASYNC_POSTCPU2DMA); + return (0); +} + +void +pgt_unload_tx_desc_frag(struct pgt_softc *sc, struct pgt_desc *pd) +{ + bus_dmamap_unload(sc->sc_fragdmat, pd->pd_dmam); + pd->pd_dmaaddr = 0; +} + +/* + * The critical lock uses int sc_critical to signify whether there are + * currently transmissions in flight (> 0), is a management in progress + * (<= -1), or the device is not critical (== 0). Management waiters + * prevent further transmissions from contributing to sc_critical -- + * that is, make it monotonically decrease again back to 0 as + * transmissions complete or time out. + */ +void +pgt_enter_critical(struct pgt_softc *sc) +{ + //if (sc->sc_critical < 0 && sc->sc_critical_thread == curthread) { + if (sc->sc_critical < 0) { + sc->sc_critical--; + } else { + while (sc->sc_critical != 0) { + sc->sc_refcnt++; + //cv_wait(&sc->sc_critical_cv, &sc->sc_lock); + sc->sc_refcnt--; + } + sc->sc_critical = -1; + //sc->sc_critical_thread = curthread; + } +} + +void +pgt_exit_critical(struct pgt_softc *sc) +{ + + if (++sc->sc_critical == 0) { + sc->sc_critical_thread = NULL; + //if (sc->sc_critical_cv.cv_waiters == 0 && + if (sc->sc_flags & SC_START_DESIRED) { + sc->sc_flags &= ~SC_START_DESIRED; + if (sc->sc_ic.ic_if.if_flags & IFF_RUNNING) + pgt_start_body(sc, &sc->sc_ic, + &sc->sc_ic.ic_if); + } else { + //cv_signal(&sc->sc_critical_cv); + } + } +} + +int +pgt_try_enter_data_critical(struct pgt_softc *sc) +{ + if (sc->sc_critical <= -1) { + // || sc->sc_critical_cv.cv_waiters > 0) { + /* + * Don't start new data packets if management + * wants a chance. + */ + sc->sc_flags |= SC_START_DESIRED; + return (0); + } else { + sc->sc_critical++; + return (1); + } +} + +#if 0 +void +pgt_try_exit_data_critical(struct pgt_softc *sc) +{ + if (--sc->sc_critical == 0) + cv_signal(&sc->sc_critical_cv); +} +#endif + +int +pgt_upload_firmware(struct pgt_softc *sc) +{ + uint32_t dirreg, reg, fwoff; + uint8_t *ucode; + int fwlen, error; + size_t size; + char *name; + + if (sc->sc_dev_type == PFF_DEV_ISL3877) + name = "isl3877"; + else + name = "isl3890"; /* includes isl3880 */ + + error = loadfirmware(name, &ucode, &size); + + if (error != 0) { + printf("%s: error %d, could not read microcode %s!\n", + sc->sc_dev.dv_xname, error, name); + return (EIO); + } + + if (size & 3) { + printf("%s: bad firmware size %u\n", + sc->sc_dev.dv_xname, size); + free(ucode, M_DEVBUF); + return (EINVAL); + } + + pgt_reboot(sc); + + reg = PFF_FIRMWARE_INTERNAL_OFFSET; + fwoff = 0; + while (fwoff < size) { + u_int32_t *uc = (u_int32_t *)ucode; + + pgt_write_4_flush(sc, PFF_REG_DIR_MEM_BASE, reg); + if (size - fwoff >= PFF_DIRECT_MEMORY_SIZE) + fwlen = PFF_DIRECT_MEMORY_SIZE; + else + fwlen = size - fwoff; + dirreg = PFF_DIRECT_MEMORY_OFFSET; + while (fwlen > 4) { + pgt_write_4(sc, dirreg, uc[fwoff]); + fwoff += 4; + dirreg += 4; + reg += 4; + fwlen -= 4; + } + pgt_write_4_flush(sc, dirreg, uc[fwoff]); + fwoff += 4; + dirreg += 4; + reg += 4; + fwlen -= 4; + } + + reg = pgt_read_4(sc, PFF_REG_CTRL_STAT); + reg &= ~(PFF_CTRL_STAT_RESET | PFF_CTRL_STAT_CLOCKRUN); + reg |= PFF_CTRL_STAT_RAMBOOT; + pgt_write_4_flush(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + reg |= PFF_CTRL_STAT_RESET; + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + reg &= ~PFF_CTRL_STAT_RESET; + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + + free(ucode, M_DEVBUF); + + return (0); +} + +void +pgt_cleanup_queue(struct pgt_softc *sc, enum pgt_queue pq, + struct pgt_frag pqfrags[]) +{ + struct pgt_desc *pd; + unsigned int i; + + sc->sc_cb->pcb_device_curfrag[pq] = htole32(0); + i = 0; + TAILQ_FOREACH(pd, &sc->sc_freeq[pq], pd_link) { + pd->pd_fragnum = i; + pd->pd_fragp = &pqfrags[i]; + if (pgt_queue_is_rx(pq)) + pgt_reinit_rx_desc_frag(sc, pd); + i++; + } + sc->sc_freeq_count[pq] = i; + /* + * The ring buffer describes how many free buffers are available from + * the host (for receive queues) or how many are pending (for + * transmit queues). + */ + if (pgt_queue_is_rx(pq)) + sc->sc_cb->pcb_driver_curfrag[pq] = htole32(i); + else + sc->sc_cb->pcb_driver_curfrag[pq] = htole32(0); +} + +/* + * Turn off interrupts, reset the device (possibly uploading firmware), + * and put everything in a known state. + */ +int +pgt_reset(struct pgt_softc *sc) +{ + int error; + + /* disable all interrupts */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, 0x00000000); + DELAY(PFF_WRITEIO_DELAY); + /* + * Set up the management receive queue, assuming there are no + * requests in progress. + */ + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU | BUS_DMASYNC_PRECPU2DMA); + pgt_cleanup_queue(sc, PFF_QUEUE_DATA_LOW_RX, + &sc->sc_cb->pcb_data_low_rx[0]); + pgt_cleanup_queue(sc, PFF_QUEUE_DATA_LOW_TX, + &sc->sc_cb->pcb_data_low_tx[0]); + pgt_cleanup_queue(sc, PFF_QUEUE_DATA_HIGH_RX, + &sc->sc_cb->pcb_data_high_rx[0]); + pgt_cleanup_queue(sc, PFF_QUEUE_DATA_HIGH_TX, + &sc->sc_cb->pcb_data_high_tx[0]); + pgt_cleanup_queue(sc, PFF_QUEUE_MGMT_RX, + &sc->sc_cb->pcb_mgmt_rx[0]); + pgt_cleanup_queue(sc, PFF_QUEUE_MGMT_TX, + &sc->sc_cb->pcb_mgmt_tx[0]); + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTCPU2DMA | BUS_DMASYNC_PREDMA2CPU); + if (sc->sc_flags & SC_NEEDS_FIRMWARE) { + error = pgt_upload_firmware(sc); + if (error) + return (error); + sc->sc_flags &= ~SC_NEEDS_FIRMWARE; + } + /* upload the control block's DMA address */ + pgt_write_4_flush(sc, PFF_REG_CTRL_BLK_BASE, + htole32((uint32_t)sc->sc_cbdmabusaddr)); + DELAY(PFF_WRITEIO_DELAY); + /* send a reset event */ + pgt_write_4_flush(sc, PFF_REG_DEV_INT, PFF_DEV_INT_RESET); + DELAY(PFF_WRITEIO_DELAY); + /* await only the initialization interrupt */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, PFF_INT_STAT_INIT); + DELAY(PFF_WRITEIO_DELAY); + return (0); +} + +/* + * If we're trying to reset and the device has seemingly not been detached, + * we'll spend a minute seeing if we can't do the reset. + */ +void +pgt_disable(struct pgt_softc *sc, unsigned int flag) +{ + struct ieee80211com *ic; + uint32_t reg; + unsigned int wokeup; + int tries = 6, tryagain; + + ic = &sc->sc_ic; +#ifdef DEVICE_POLLING + mtx_lock(&Giant); +#endif + //mtx_lock(&sc->sc_lock); + if (flag == SC_DYING && sc->sc_flags & SC_DYING) { + while (sc->sc_drainer != NULL); + //(void)msleep(&sc->sc_drainer, &sc->sc_lock, + // PZERO, "pffol1", hz / 10); + //goto out2; + return; + } + if (flag == SC_NEEDS_RESET) { + if (sc->sc_drainer != NULL || sc->sc_flags & SC_GONE) + /* + * Multiple pending resets are not useful. + */ + //goto out2; + return; + } else { + while (sc->sc_drainer != NULL); + //(void)msleep(&sc->sc_drainer, &sc->sc_lock, + // PZERO, "pffol1", hz / 10); + } + ic->ic_if.if_flags &= ~IFF_RUNNING; + sc->sc_flags |= SC_UNINITIALIZED; +#ifdef DEVICE_POLLING + ether_poll_deregister(&ic->ic_if); + /* Turn back on interrupts. */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, PFF_INT_STAT_SOURCES); + DELAY(PFF_WRITEIO_DELAY); +#endif + //sc->sc_drainer = curthread; + sc->sc_flags |= flag; + /* + * The detacher has to wait for all activity to cease (the refcount + * will reach 1 and sc_async_events will have been emptied). If + * we were to drain while doing just a "reset" then this could + * deadlock. + */ + pgt_drain_tx_queue(sc, PFF_QUEUE_DATA_LOW_TX); + pgt_drain_tx_queue(sc, PFF_QUEUE_DATA_HIGH_TX); + pgt_drain_tx_queue(sc, PFF_QUEUE_MGMT_TX); + if (flag == SC_DYING) { + while (sc->sc_refcnt > 1); + //(void)msleep(&sc->sc_drainer, &sc->sc_lock, + // PZERO, "pffol2", hz / 10); + } +trying_again: + tryagain = 0; + /* disable all interrupts */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, 0x00000000); + DELAY(PFF_WRITEIO_DELAY); + //mtx_unlock(&sc->sc_lock); + if (sc->sc_intcookie != NULL) { + //bus_teardown_intr(sc->sc_dev, sc->sc_intres, sc->sc_intcookie); + sc->sc_intcookie = NULL; + } + //mtx_lock(&sc->sc_lock); + reg = pgt_read_4(sc, PFF_REG_CTRL_STAT); + reg &= ~(PFF_CTRL_STAT_RESET | PFF_CTRL_STAT_RAMBOOT); + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + reg |= PFF_CTRL_STAT_RESET; + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + reg &= ~PFF_CTRL_STAT_RESET; + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + do { + wokeup = 0; + /* + * We don't expect to be woken up, just to drop the lock + * and time out. Only tx queues can have anything valid + * on them outside of an interrupt. + */ + while (!TAILQ_EMPTY(&sc->sc_mgmtinprog)) { + struct pgt_mgmt_desc *pmd; + + pmd = TAILQ_FIRST(&sc->sc_mgmtinprog); + TAILQ_REMOVE(&sc->sc_mgmtinprog, pmd, pmd_link); + pmd->pmd_error = ENETRESET; + wakeup_one(pmd); + if (sc->sc_debug & SC_DEBUG_MGMT) + printf("%s: queue: mgmt %p <- 0x%x (drained)\n", + sc->sc_dev.dv_xname, pmd, pmd->pmd_oid); + wokeup++; + } + if (wokeup > 0) { + //(void)msleep(&sc->sc_drainer, &sc->sc_lock, + // PZERO, "pffol3", hz / 10); + if (flag == SC_NEEDS_RESET && sc->sc_flags & SC_DYING) { + sc->sc_flags &= ~flag; + goto out; + } + } + } while (wokeup > 0); + if (flag == SC_NEEDS_RESET && !(sc->sc_flags & SC_GONE)) { + int error; + + printf("%s: resetting\n", sc->sc_dev.dv_xname); + sc->sc_refcnt++; + sc->sc_flags &= ~SC_POWERSAVE; + sc->sc_flags |= SC_NEEDS_FIRMWARE; + //mtx_unlock(&sc->sc_lock); + //error = bus_setup_intr(sc->sc_dev, sc->sc_intres, + // INTR_TYPE_NET | INTR_MPSAFE, pgt_intr, + // &ic->ic_if, &sc->sc_intcookie); + //mtx_lock(&sc->sc_lock); + if (error != 0 || sc->sc_flags & SC_DYING) { + if (error != 0) { + printf("%s: failure establishing irq in " + "reset: %d\n", + sc->sc_dev.dv_xname, error); + sc->sc_flags |= SC_DYING; + } + sc->sc_refcnt--; + goto out; + } + error = pgt_reset(sc); + if (error == 0) { + //(void)msleep(&sc->sc_flags, &sc->sc_lock, PZERO, + // "pffres", hz * 10); + if (sc->sc_flags & SC_UNINITIALIZED) { + printf("%s: not responding\n", + sc->sc_dev.dv_xname); + /* Thud. It was probably removed. */ + if (--tries == 0) + sc->sc_flags |= SC_GONE; + else + tryagain = 1; + } else { + /* await all interrupts */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, + PFF_INT_STAT_SOURCES); + DELAY(PFF_WRITEIO_DELAY); + ic->ic_if.if_flags |= IFF_RUNNING; + } + } + sc->sc_refcnt--; + if (tryagain) + goto trying_again; + sc->sc_flags &= ~flag; + if (ic->ic_if.if_flags & IFF_RUNNING) + pgt_update_hw_from_sw(sc, + ic->ic_state != IEEE80211_S_INIT, + ic->ic_opmode != IEEE80211_M_MONITOR); + } +out: + sc->sc_drainer = NULL; + wakeup(&sc->sc_drainer); +//out2: + //mtx_unlock(&sc->sc_lock); +#ifdef DEVICE_POLLING + mtx_unlock(&Giant); +#endif +} + +void +pgt_kill_kthread(struct pgt_softc *sc) +{ + if (sc->sc_flags & SC_KTHREAD) { + //mtx_lock(&sc->sc_lock); + sc->sc_kthread.sck_exit = 1; + //cv_signal(&sc->sc_kthread.sck_needed); + //msleep(sc->sc_kthread.sck_proc, &sc->sc_lock, PPAUSE | PDROP, + // "pffktc", 0); + } +} + +int +pgt_attach(struct pgt_softc *sc) +{ + int error; + + sc->sc_ic.ic_if.if_softc = sc; + sc->sc_refcnt = 1; + TAILQ_INIT(&sc->sc_mgmtinprog); + TAILQ_INIT(&sc->sc_kthread.sck_traps); + //mtx_init(&sc->sc_lock, device_get_nameunit(dev), MTX_NETWORK_LOCK, + // MTX_DEF); + //cv_init(&sc->sc_critical_cv, "pffccv"); + //cv_init(&sc->sc_kthread.sck_needed, "pffkth"); + //sc->sc_dev = dev; + sc->sc_flags |= SC_NEEDS_FIRMWARE | SC_UNINITIALIZED; + /* + SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, + "0x1:queue,0x2:mgmt,0x4:unexpected,0x8:trigger,0x10:events," + "0x20:power,0x40:trap,0x80:link,0x100:rxannex,0x200:rxfrag," + "0x400:rxether"); + TUNABLE_INT_FETCH("dev.pff.debug", &sc->sc_debug); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "dot1x", CTLFLAG_RW, &sc->sc_dot1x, 0, + "Enable 802.1x authentication mode"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "wds", CTLFLAG_RW, &sc->sc_wds, 0, + "Enable WDS"); + */ + /* + error = kthread_create(pgt_per_device_kthread, sc, + &sc->sc_kthread.sck_proc, 0, 0, device_get_nameunit(dev)); + if (error != 0) { + printf("%s: failure establishing kthread: %d\n", + sc->sc_dev.dv_xname, error); + goto failed; + } + sc->sc_flags |= SC_KTHREAD; + + error = bus_setup_intr(dev, sc->sc_intres, INTR_TYPE_NET | INTR_MPSAFE, + pgt_intr, &sc->sc_ic.ic_if, &sc->sc_intcookie); + if (error != 0) { + printf("%s: failure establishing interrupt: %d\n", + sc->sc_dev.dv_xname, error); + goto failed; + } + */ + //mtx_lock(&sc->sc_lock); + /* reset 802.11 state */ + sc->sc_80211_ioc_wep = IEEE80211_WEP_OFF; + sc->sc_80211_ioc_auth = IEEE80211_AUTH_OPEN; + error = pgt_reset(sc); + if (error == 0) { + sc->sc_refcnt++; + //(void)msleep(&sc->sc_flags, &sc->sc_lock, PZERO, "pffres", hz); + sc->sc_refcnt--; + if (sc->sc_flags & SC_UNINITIALIZED) { + printf("%s: not responding\n", sc->sc_dev.dv_xname); + error = ETIMEDOUT; + } else { + /* await all interrupts */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, + PFF_INT_STAT_SOURCES); + DELAY(PFF_WRITEIO_DELAY); + } + } + //mtx_unlock(&sc->sc_lock); + if (error != 0) + goto failed; + error = pgt_net_attach(sc); + if (error == 0) { + ieee80211_new_state(&sc->sc_ic, IEEE80211_S_INIT, -1); + } else { +failed: + pgt_disable(sc, SC_DYING); + pgt_reboot(sc); + pgt_kill_kthread(sc); + //cv_destroy(&sc->sc_kthread.sck_needed); + //cv_destroy(&sc->sc_critical_cv); + //mtx_destroy(&sc->sc_lock); + } + return (error); +} + +int +pgt_detach(struct pgt_softc *sc) +{ + pgt_net_detach(sc); + sc->sc_flags |= SC_GONE; + pgt_disable(sc, SC_DYING); + pgt_reboot(sc); + pgt_kill_kthread(sc); + //cv_destroy(&sc->sc_kthread.sck_needed); + //cv_destroy(&sc->sc_critical_cv); + //mtx_destroy(&sc->sc_lock); + return (0); +} + +void +pgt_reboot(struct pgt_softc *sc) +{ + uint32_t reg; + + reg = pgt_read_4(sc, PFF_REG_CTRL_STAT); + reg &= ~(PFF_CTRL_STAT_RESET | PFF_CTRL_STAT_RAMBOOT); + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + reg |= PFF_CTRL_STAT_RESET; + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_WRITEIO_DELAY); + reg &= ~PFF_CTRL_STAT_RESET; + pgt_write_4(sc, PFF_REG_CTRL_STAT, reg); + pgt_write_memory_barrier(sc); + DELAY(PFF_RESET_DELAY); +} + +void +pgt_init_intr(struct pgt_softc *sc) +{ + if ((sc->sc_flags & SC_UNINITIALIZED) == 0) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: spurious initialization\n", + sc->sc_dev.dv_xname); + } else { + sc->sc_flags &= ~SC_UNINITIALIZED; + wakeup(&sc->sc_flags); + } +} + +/* + * If called with a NULL last_nextpkt, only the mgmt queue will be checked + * for new packets. + */ +void +pgt_update_intr(struct pgt_softc *sc, struct mbuf ***last_nextpkt, + int max_datarx_count) +{ + /* priority order */ + enum pgt_queue pqs[PFF_QUEUE_COUNT] = { + PFF_QUEUE_MGMT_TX, PFF_QUEUE_MGMT_RX, + PFF_QUEUE_DATA_HIGH_TX, PFF_QUEUE_DATA_HIGH_RX, + PFF_QUEUE_DATA_LOW_TX, PFF_QUEUE_DATA_LOW_RX + }; + uint32_t npend; + unsigned int dirtycount; + int i, prevwasmf; + + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU | BUS_DMASYNC_PRECPU2DMA); + pgt_debug_events(sc, "intr"); + /* + * Check for completion of tx in their dirty queues. + * Check completion of rx into their dirty queues. + */ + for (i = 0; i < PFF_QUEUE_COUNT; i++) { + size_t qdirty, qfree, qtotal; + + qdirty = sc->sc_dirtyq_count[pqs[i]]; + qfree = sc->sc_freeq_count[pqs[i]]; + qtotal = qdirty + qfree; + /* + * We want the wrap-around here. + */ + if (pgt_queue_is_rx(pqs[i])) { + int data; + + data = pgt_queue_is_data(pqs[i]); +#ifdef PFF_BUGGY_INTERRUPT_RECOVERY + if (last_nextpkt == NULL && data) + continue; +#endif + npend = pgt_queue_frags_pending(sc, pqs[i]); + /* + * Receive queues clean up below, so qfree must + * always be qtotal (qdirty is 0). + */ + if (npend > qfree) { + if (sc->sc_flags & SC_DEBUG_UNEXPECTED) + printf("%s: rx queue [%u] overflowed " + "by %u\n", + sc->sc_dev.dv_xname, + pqs[i], npend - qfree); + sc->sc_flags |= SC_INTR_RESET; + break; + } + while (qfree-- > npend) { +#ifdef DEVICE_POLLING + if (data && max_datarx_count != -1) { + if (max_datarx_count-- == 0) + break; + } +#endif + pgt_rxdone(sc, pqs[i]); + } + } else { + npend = pgt_queue_frags_pending(sc, pqs[i]); + if (npend > qdirty) { + if (sc->sc_flags & SC_DEBUG_UNEXPECTED) + printf("%s: tx queue [%u] underflowed " + "by %u\n", + sc->sc_dev.dv_xname, + pqs[i], npend - qdirty); + sc->sc_flags |= SC_INTR_RESET; + break; + } + /* + * If the free queue was empty, or the data transmit + * queue just became empty, wake up any waiters. + */ + if (qdirty > npend) { + if (TAILQ_EMPTY(&sc->sc_freeq[pqs[i]])) + wakeup(&sc->sc_freeq[pqs[i]]); + while (qdirty-- > npend) + pgt_txdone(sc, pqs[i]); + } + } + } + + /* + * This is the deferred completion for received management frames + * and where we queue network frames for stack input. + */ + dirtycount = sc->sc_dirtyq_count[PFF_QUEUE_MGMT_RX]; + while (!TAILQ_EMPTY(&sc->sc_dirtyq[PFF_QUEUE_MGMT_RX])) { + struct pgt_mgmt_desc *pmd; + + pmd = TAILQ_FIRST(&sc->sc_mgmtinprog); + /* + * If there is no mgmt request in progress or the operation + * returned is explicitly a trap, this pmd will essentially + * be ignored. + */ + pgt_mgmtrx_completion(sc, pmd); + } + sc->sc_cb->pcb_driver_curfrag[PFF_QUEUE_MGMT_RX] = + htole32(dirtycount + + letoh32(sc->sc_cb->pcb_driver_curfrag[PFF_QUEUE_MGMT_RX])); + + dirtycount = sc->sc_dirtyq_count[PFF_QUEUE_DATA_HIGH_RX]; + prevwasmf = 0; + while (!TAILQ_EMPTY(&sc->sc_dirtyq[PFF_QUEUE_DATA_HIGH_RX])) + prevwasmf = pgt_datarx_completion(sc, PFF_QUEUE_DATA_HIGH_RX, + last_nextpkt, prevwasmf); + sc->sc_cb->pcb_driver_curfrag[PFF_QUEUE_DATA_HIGH_RX] = + htole32(dirtycount + + letoh32(sc->sc_cb->pcb_driver_curfrag[PFF_QUEUE_DATA_HIGH_RX])); + + dirtycount = sc->sc_dirtyq_count[PFF_QUEUE_DATA_LOW_RX]; + prevwasmf = 0; + while (!TAILQ_EMPTY(&sc->sc_dirtyq[PFF_QUEUE_DATA_LOW_RX])) + prevwasmf = pgt_datarx_completion(sc, PFF_QUEUE_DATA_LOW_RX, + last_nextpkt, prevwasmf); + sc->sc_cb->pcb_driver_curfrag[PFF_QUEUE_DATA_LOW_RX] = + htole32(dirtycount + + letoh32(sc->sc_cb->pcb_driver_curfrag[PFF_QUEUE_DATA_LOW_RX])); + + /* + * Write out what we've finished with. + */ + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTCPU2DMA | BUS_DMASYNC_PREDMA2CPU); +} + +struct mbuf * +pgt_ieee80211_encap(struct pgt_softc *sc, struct ether_header *eh, + struct mbuf *m, struct ieee80211_node **ni) +{ + struct ieee80211com *ic; + struct ieee80211_frame *frame; + struct llc *snap; + + ic = &sc->sc_ic; + M_PREPEND(m, sizeof(frame) + sizeof(snap), M_DONTWAIT); + if (m != NULL) + m = m_pullup(m, sizeof(frame) + sizeof(snap)); + if (m == NULL) + return (m); + frame = mtod(m, struct ieee80211_frame *); + snap = (struct llc *)&frame[1]; + if (ni != NULL) { + if (ic->ic_opmode == IEEE80211_M_STA || + ic->ic_opmode == IEEE80211_M_MONITOR) { + *ni = ieee80211_ref_node(ic->ic_bss); + } else { + *ni = ieee80211_find_node(ic, eh->ether_shost); + /* + * Make up associations for ad-hoc mode. To support + * ad-hoc WPA, we'll need to maintain a bounded + * pool of ad-hoc stations. + */ + if (*ni == NULL && + ic->ic_opmode != IEEE80211_M_HOSTAP) { + *ni = ieee80211_dup_bss(ic, eh->ether_shost); + if (*ni != NULL) { + (*ni)->ni_associd = 1; + ic->ic_newassoc(ic, *ni, 1); + } + } + if (*ni == NULL) { + m_freem(m); + return (NULL); + } + } + (*ni)->ni_inact = 0; + } + snap->llc_dsap = snap->llc_ssap = LLC_SNAP_LSAP; + snap->llc_control = LLC_UI; + snap->llc_snap.org_code[0] = 0; + snap->llc_snap.org_code[1] = 0; + snap->llc_snap.org_code[2] = 0; + snap->llc_snap.ether_type = eh->ether_type; + frame->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA; + /* Doesn't look like much of the 802.11 header is available. */ + *(uint16_t *)frame->i_dur = *(uint16_t *)frame->i_seq = 0; + /* + * Translate the addresses; WDS is not handled. + */ + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + frame->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; + IEEE80211_ADDR_COPY(frame->i_addr1, eh->ether_dhost); + IEEE80211_ADDR_COPY(frame->i_addr2, ic->ic_bss->ni_bssid); + IEEE80211_ADDR_COPY(frame->i_addr3, eh->ether_shost); + break; + case IEEE80211_M_IBSS: + case IEEE80211_M_AHDEMO: + frame->i_fc[1] = IEEE80211_FC1_DIR_NODS; + IEEE80211_ADDR_COPY(frame->i_addr1, eh->ether_dhost); + IEEE80211_ADDR_COPY(frame->i_addr2, eh->ether_shost); + IEEE80211_ADDR_COPY(frame->i_addr3, ic->ic_bss->ni_bssid); + break; + case IEEE80211_M_HOSTAP: + /* HostAP forwarding defaults to being done on firmware. */ + frame->i_fc[1] = IEEE80211_FC1_DIR_TODS; + IEEE80211_ADDR_COPY(frame->i_addr1, ic->ic_bss->ni_bssid); + IEEE80211_ADDR_COPY(frame->i_addr2, eh->ether_shost); + IEEE80211_ADDR_COPY(frame->i_addr3, eh->ether_dhost); + break; + default: + /* + * What format do monitor-mode's frames take? + */ + break; + } + return (m); +} + +void +pgt_input_frames(struct pgt_softc *sc, struct mbuf *m) +{ + struct ether_header eh; + struct ifnet *ifp; + struct ieee80211_channel *chan; + struct ieee80211_node *ni; + struct ieee80211com *ic; + struct pgt_data_frame pdf; + struct pgt_rx_annex *pra; + struct mbuf *next, *m2; + unsigned int n; + uint32_t rstamp; + uint16_t dataoff; + int encrypted; + uint8_t rate, rssi; + ic = &sc->sc_ic; + ifp = &ic->ic_if; + for (next = m; m != NULL; m = next) { + next = m->m_nextpkt; + m->m_nextpkt = NULL; + //if (m->m_flags & M_PROTO2) { + if (m->m_flags) { + /* + * We either ended up losing the previous + * frag, or we're trying to receive more than + * two of them. + */ + ifp->if_ierrors++; + m_freem(m); + continue; + } + if (m->m_flags & M_PROTO1) { + if (next == NULL) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: more frags set, but not " + "found\n", + sc->sc_dev.dv_xname); + ifp->if_ierrors++; + m_freem(m); + continue; + } else { + //if (!(next->m_flags & M_PROTO2)) { + if (!(next->m_flags)) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: more frags set, " + "but next is not one\n", + sc->sc_dev.dv_xname); + ifp->if_ierrors++; + m_freem(m); + continue; + } + /* + * If there are yet more frags after the + * second, we're not touching them here. + */ + //next->m_flags &= ~(M_PROTO1 | M_PROTO2); + } + m->m_flags &= ~M_PROTO1; + m2 = next; + next = m2->m_nextpkt; + m2->m_nextpkt = NULL; + /* Remove any preceding junk from the latter frag. */ + m_adj(m2, *mtod(m2, uint16_t *)); + m_cat(m, m2); + } + dataoff = *mtod(m, uint16_t *); + m_adj(m, 2); + if (dataoff < sizeof(pdf)) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: missing pgt_data_frame header\n", + sc->sc_dev.dv_xname); + ifp->if_ierrors++; + m_freem(m); + continue; + } + bcopy(mtod(m, struct pgt_data_frame *), &pdf, sizeof(pdf)); + m_adj(m, dataoff); + m = m_pullup(m, sizeof(*pra)); + if (m == NULL) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: m_pullup failure\n", + sc->sc_dev.dv_xname); + ifp->if_ierrors++; + continue; + } + pra = mtod(m, struct pgt_rx_annex *); + if (sc->sc_debug & SC_DEBUG_RXANNEX) + printf("%s: rx annex: ? %04x ? %04x " + "len %u clock %u flags %02x ? %02x rate %u ? %02x " + "freq %u ? %04x rssi %u pad %02x%02x%02x\n", + sc->sc_dev.dv_xname, + letoh16(pdf.pdf_unknown), + letoh16(pra->pra_unknown0), + letoh16(pra->pra_length), + letoh32(pra->pra_clock), pra->pra_flags, + pra->pra_unknown1, pra->pra_rate, + pra->pra_unknown2, letoh32(pra->pra_frequency), + pra->pra_unknown3, pra->pra_rssi, + pra->pra_pad[0], pra->pra_pad[1], pra->pra_pad[2]); + if (sc->sc_debug & SC_DEBUG_RXETHER) + printf("%s: rx ether: " + "%02x:%02x:%02x:%02x:%02x:%02x < " + "%02x:%02x:%02x:%02x:%02x:%02x 0x%04x\n", + sc->sc_dev.dv_xname, + pra->pra_ether_dhost[0], pra->pra_ether_dhost[1], + pra->pra_ether_dhost[2], pra->pra_ether_dhost[3], + pra->pra_ether_dhost[4], pra->pra_ether_dhost[5], + pra->pra_ether_shost[0], pra->pra_ether_shost[1], + pra->pra_ether_shost[2], pra->pra_ether_shost[3], + pra->pra_ether_shost[4], pra->pra_ether_shost[5], + ntohs(pra->pra_ether_type)); + /* + * This flag is set if e.g. packet could not be decrypted. + */ + if (pra->pra_flags & PRA_FLAG_BAD) { + ifp->if_ierrors++; + m_freem(m); + continue; + } + /* + * The 16-bit word preceding the received frame contains + * values that seem to have a very non-random distribution + * and possibly follow a periodic distribution. The only + * two values for it that seem to occur for WEP-decrypted + * packets (assuming it is indeed a 16-bit word and not + * something else) are 0x4008 and 0x4808. + * + * Those two values can be found in large runs in the + * histogram that get zero hits over the course of + * hundreds of thousands of samples from an 802.11b + * sender source (ping -f). Further analysis shows + * that the 0x000c bits are always 0x0008 when WEP + * has been used, and never otherwise. The bottom + * two bits seem to not be set in any known + * circumstances. + */ + encrypted = sc->sc_80211_ioc_wep != IEEE80211_WEP_OFF && + (letoh16(pdf.pdf_unknown) & 0xc) == 0x8; + memcpy(eh.ether_dhost, pra->pra_ether_dhost, ETHER_ADDR_LEN); + memcpy(eh.ether_shost, pra->pra_ether_shost, ETHER_ADDR_LEN); + eh.ether_type = pra->pra_ether_type; + /* + * After getting what we want, chop off the annex, then + * turn into something that looks like it really was + * 802.11. + */ + rssi = pra->pra_rssi; + rstamp = letoh32(pra->pra_clock); + rate = pra->pra_rate; + n = ieee80211_mhz2ieee(letoh32(pra->pra_frequency), 0); + if (n <= IEEE80211_CHAN_MAX) + chan = &ic->ic_channels[n]; + else + chan = ic->ic_bss->ni_chan; + /* Send to 802.3 listeners. */ + m_adj(m, sizeof(*pra) - sizeof(eh)); + memcpy(mtod(m, struct ether_header *), &eh, sizeof(eh)); + m_adj(m, sizeof(eh)); + m = pgt_ieee80211_encap(sc, &eh, m, &ni); + if (m != NULL) { + if (sc->sc_drvbpf != NULL) { + struct pgt_ieee80211_radiotap pir; + + bzero(&pir, sizeof(pir)); + pir.pir_header.it_len = htole16(sizeof(pir)); + pir.pir_header.it_present = + htole32(PFF_IEEE80211_RADIOTAP_PRESENT); + if (encrypted) + pir.pir_flags |= + IEEE80211_RADIOTAP_F_WEP; + pir.pir_rate = rate; + pir.pir_channel = htole16(chan->ic_freq); + pir.pir_channel_flags = htole16(chan->ic_flags); + pir.pir_db_antsignal = rssi; + pir.pir_db_antnoise = sc->sc_noise; + //bpf_mtap2(sc->sc_drvbpf, &pir, sizeof(pir), m); + } + ni->ni_rssi = rssi; + ni->ni_rstamp = rstamp; + ieee80211_input(ifp, m, ni, rssi, rstamp); + /* + * The frame may have caused the node to be marked for + * reclamation (e.g. in response to a DEAUTH message) + * so use free_node here instead of unref_node. + */ + if (ni == ic->ic_bss) + ieee80211_unref_node(&ni); + else + ieee80211_release_node(&sc->sc_ic, ni); + } else { + ifp->if_ierrors++; + } + } +} + +void +pgt_wakeup_intr(struct pgt_softc *sc) +{ + int shouldupdate; + int i; + + shouldupdate = 0; + /* Check for any queues being empty before updating. */ + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU); + for (i = 0; !shouldupdate && i < PFF_QUEUE_COUNT; i++) { + if (pgt_queue_is_tx(i)) + shouldupdate = pgt_queue_frags_pending(sc, i); + else + shouldupdate = pgt_queue_frags_pending(sc, i) < + sc->sc_freeq_count[i]; + } + if (!TAILQ_EMPTY(&sc->sc_mgmtinprog)) + shouldupdate = 1; + if (sc->sc_debug & SC_DEBUG_POWER) + printf("%s: wakeup interrupt (update = %d)\n", + sc->sc_dev.dv_xname, shouldupdate); + sc->sc_flags &= ~SC_POWERSAVE; + if (shouldupdate) { + pgt_write_4_flush(sc, PFF_REG_DEV_INT, PFF_DEV_INT_UPDATE); + DELAY(PFF_WRITEIO_DELAY); + } +} + +void +pgt_sleep_intr(struct pgt_softc *sc) +{ + int allowed; + int i; + + allowed = 1; + /* Check for any queues not being empty before allowing. */ + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU); + for (i = 0; allowed && i < PFF_QUEUE_COUNT; i++) { + if (pgt_queue_is_tx(i)) + allowed = pgt_queue_frags_pending(sc, i) == 0; + else + allowed = pgt_queue_frags_pending(sc, i) >= + sc->sc_freeq_count[i]; + } + if (!TAILQ_EMPTY(&sc->sc_mgmtinprog)) + allowed = 0; + if (sc->sc_debug & SC_DEBUG_POWER) + printf("%s: sleep interrupt (allowed = %d)\n", + sc->sc_dev.dv_xname, allowed); + if (allowed && sc->sc_ic.ic_flags & IEEE80211_F_PMGTON) { + sc->sc_flags |= SC_POWERSAVE; + pgt_write_4_flush(sc, PFF_REG_DEV_INT, PFF_DEV_INT_SLEEP); + DELAY(PFF_WRITEIO_DELAY); + } +} + +void +pgt_empty_traps(struct pgt_softc_kthread *sck) +{ + struct pgt_async_trap *pa; + struct mbuf *m; + + while (!TAILQ_EMPTY(&sck->sck_traps)) { + pa = TAILQ_FIRST(&sck->sck_traps); + TAILQ_REMOVE(&sck->sck_traps, pa, pa_link); + m = pa->pa_mbuf; + m_freem(m); + } +} + +void +pgt_per_device_kthread(void *argp) +{ + struct pgt_softc *sc; + struct pgt_softc_kthread *sck; + struct pgt_async_trap *pa; + struct mbuf *m; + + sc = argp; + sck = &sc->sc_kthread; + //mtx_lock(&sc->sc_lock); + while (!sck->sck_exit) { + if (!sck->sck_update && !sck->sck_reset && + TAILQ_EMPTY(&sck->sck_traps)) { + //cv_wait(&sck->sck_needed, &sc->sc_lock); + } + if (sck->sck_reset) { + sck->sck_reset = 0; + sck->sck_update = 0; + pgt_empty_traps(sck); + pgt_enter_critical(sc); + //mtx_unlock(&sc->sc_lock); + pgt_disable(sc, SC_NEEDS_RESET); + //mtx_lock(&sc->sc_lock); + pgt_exit_critical(sc); + } else if (!TAILQ_EMPTY(&sck->sck_traps)) { + pa = TAILQ_FIRST(&sck->sck_traps); + TAILQ_REMOVE(&sck->sck_traps, pa, pa_link); + m = pa->pa_mbuf; + m_adj(m, sizeof(*pa)); + pgt_update_sw_from_hw(sc, pa, m); + m_freem(m); + } else { + sck->sck_update = 0; + pgt_update_sw_from_hw(sc, NULL, NULL); + } + } + pgt_empty_traps(sck); + //mtx_unlock(&sc->sc_lock); + kthread_exit(0); +} + +struct mbuf * +pgt_alloc_async(size_t trapdata) +{ + struct mbuf *m; + size_t total; + + total = trapdata + sizeof(struct pgt_async_trap); + if (total >= MINCLSIZE) + MGETHDR(m, MT_DATA, 0); + else + m = m_get(M_DONTWAIT, MT_DATA); + if (m != NULL) + m->m_len = total; + return (m); +} + +void +pgt_async_reset(struct pgt_softc *sc) +{ + if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET)) + return; + sc->sc_kthread.sck_reset = 1; + //cv_signal(&sc->sc_kthread.sck_needed); +} + +void +pgt_async_trap(struct pgt_softc *sc, uint32_t oid, void *data, size_t len) +{ + struct pgt_async_trap *pa; + struct mbuf *m; + char *p; + + if (sc->sc_flags & SC_DYING) + return; + m = pgt_alloc_async(sizeof(oid) + len); + if (m == NULL) + return; + pa = mtod(m, struct pgt_async_trap *); + p = mtod(m, char *) + sizeof(*pa); + *(uint32_t *)p = oid; + p += sizeof(uint32_t); + memcpy(p, data, len); + pa->pa_mbuf = m; + TAILQ_INSERT_TAIL(&sc->sc_kthread.sck_traps, pa, pa_link); + //cv_signal(&sc->sc_kthread.sck_needed); +} + +void +pgt_async_update(struct pgt_softc *sc) +{ + if (sc->sc_flags & SC_DYING) + return; + sc->sc_kthread.sck_update = 1; + //cv_signal(&sc->sc_kthread.sck_needed); +} + +#ifdef DEVICE_POLLING +void +pgt_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) +{ + struct pgt_softc *sc; + struct mbuf *datarx = NULL; + + sc = ifp->if_softc; + //mtx_lock(&sc->sc_lock); + if (!(ifp->if_capenable & IFCAP_POLLING)) { + ether_poll_deregister(ifp); /* already have Giant, no LOR */ + cmd = POLL_DEREGISTER; + } + if (cmd == POLL_DEREGISTER) { /* final call, enable interrupts */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, PFF_INT_STAT_SOURCES); + DELAY(PFF_WRITEIO_DELAY); + //mtx_unlock(&sc->sc_lock); + return; + } + pgt_intr_body(sc, &datarx, count); + if (cmd == POLL_AND_CHECK_STATUS) { + /* Do more expensive periodic stuff. */ + pgt_async_update(sc); + } + //mtx_unlock(&sc->sc_lock); + /* + * Now that we have unlocked the softc, decode and enter the + * data frames we've received. + */ + if (datarx != NULL) + pgt_input_frames(sc, datarx); + //if (!IFQ_DRV_IS_EMPTY(&sc->sc_ic.ic_if.if_snd)) + // pgt_start(&sc->sc_ic.ic_if); +} +#endif + +void +pgt_intr(void *argp) +{ + struct ifnet *ifp; + struct pgt_softc *sc; + struct mbuf *datarx = NULL; + + ifp = argp; + sc = ifp->if_softc; + //mtx_lock(&sc->sc_lock); +#ifdef DEVICE_POLLING + if (ifp->if_flags & IFF_POLLING) { + //mtx_unlock(&sc->sc_lock); + return; + } + if (ifp->if_capenable & IFCAP_POLLING && + !(sc->sc_flags & SC_UNINITIALIZED) && + ether_poll_register(pgt_poll, ifp)) { + /* Turn off interrupts. */ + pgt_write_4_flush(sc, PFF_REG_INT_EN, 0); + DELAY(PFF_WRITEIO_DELAY); + //mtx_unlock(&sc->sc_lock); + pgt_poll(ifp, POLL_ONLY, 1); + return; + } +#endif + pgt_intr_body(sc, &datarx, -1); + //mtx_unlock(&sc->sc_lock); + /* + * Now that we have unlocked the softc, decode and enter the + * data frames we've received. + */ + if (datarx != NULL) + pgt_input_frames(sc, datarx); + //if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) +// pgt_start(ifp); +} + +void +pgt_intr_body(struct pgt_softc *sc, struct mbuf **datarx, + int max_datarx_count) +{ + uint32_t reg; + + /* + * Here the Linux driver ands in the value of the INT_EN register, + * and masks off everything but the documented interrupt bits. Why? + * + * Unknown bit 0x4000 is set upon initialization, 0x8000000 some + * other times. + */ + if (sc->sc_ic.ic_flags & IEEE80211_F_PMGTON && + sc->sc_flags & SC_POWERSAVE) { + /* + * Don't try handling the interrupt in sleep mode. + */ + reg = pgt_read_4(sc, PFF_REG_CTRL_STAT); + if (reg & PFF_CTRL_STAT_SLEEPMODE) { + //mtx_unlock(&sc->sc_lock); + return; + } + } +#ifdef DEVICE_POLLING + if (sc->sc_ic.ic_if.if_flags & IFF_POLLING) + reg = PFF_INT_STAT_UPDATE; + else +#endif + reg = pgt_read_4(sc, PFF_REG_INT_STAT); + if (reg != 0) { +#ifdef DEVICE_POLLING + if (!(sc->sc_ic.ic_if.if_flags & IFF_POLLING)) +#endif + pgt_write_4_flush(sc, PFF_REG_INT_ACK, reg); + if (reg & PFF_INT_STAT_INIT) + pgt_init_intr(sc); + if (reg & PFF_INT_STAT_UPDATE) { + pgt_update_intr(sc, &datarx, max_datarx_count); + /* + * If we got an update, it's not really asleep. + */ + sc->sc_flags &= ~SC_POWERSAVE; + /* + * Pretend I have any idea what the documentation + * would say, and just give it a shot sending an + * "update" after acknowledging the interrupt + * bits and writing out the new control block. + */ + pgt_write_4_flush(sc, PFF_REG_DEV_INT, + PFF_DEV_INT_UPDATE); + DELAY(PFF_WRITEIO_DELAY); + } + if (reg & PFF_INT_STAT_SLEEP && !(reg & PFF_INT_STAT_WAKEUP)) + pgt_sleep_intr(sc); + if (reg & PFF_INT_STAT_WAKEUP) + pgt_wakeup_intr(sc); + } + if (sc->sc_flags & SC_INTR_RESET) { + sc->sc_flags &= ~SC_INTR_RESET; + pgt_async_reset(sc); + } + if (reg & ~PFF_INT_STAT_SOURCES && sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: unknown interrupt bits %#x " + "(stat %#x)\n", sc->sc_dev.dv_xname, + reg & ~PFF_INT_STAT_SOURCES, + pgt_read_4(sc, PFF_REG_CTRL_STAT)); +} + +void +pgt_txdone(struct pgt_softc *sc, enum pgt_queue pq) +{ + struct pgt_desc *pd; + + pd = TAILQ_FIRST(&sc->sc_dirtyq[pq]); + TAILQ_REMOVE(&sc->sc_dirtyq[pq], pd, pd_link); + sc->sc_dirtyq_count[pq]--; + TAILQ_INSERT_TAIL(&sc->sc_freeq[pq], pd, pd_link); + sc->sc_freeq_count[pq]++; + /* XXX */ + bus_dmamap_sync(sc->sc_fragdmat, pd->pd_dmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU); + /* Management frames want completion information. */ + if (sc->sc_debug & SC_DEBUG_QUEUES) { + printf("%s: queue: tx %u <- [%u]\n", + sc->sc_dev.dv_xname, pd->pd_fragnum, pq); + if (sc->sc_debug & SC_DEBUG_MGMT && pgt_queue_is_mgmt(pq)) { + struct pgt_mgmt_frame *pmf; + + pmf = (struct pgt_mgmt_frame *)pd->pd_mem; + printf("%s: queue: txmgmt %p <- " + "(ver %u, op %u, flags 0x%x)\n", + sc->sc_dev.dv_xname, + pd, pmf->pmf_version, pmf->pmf_operation, + pmf->pmf_flags); + } + } + pgt_unload_tx_desc_frag(sc, pd); + /* + if (pgt_queue_is_data(pq)) + pgt_try_exit_data_critical(sc); + */ +} + +void +pgt_rxdone(struct pgt_softc *sc, enum pgt_queue pq) +{ + struct pgt_desc *pd; + + pd = TAILQ_FIRST(&sc->sc_freeq[pq]); + TAILQ_REMOVE(&sc->sc_freeq[pq], pd, pd_link); + sc->sc_freeq_count[pq]--; + TAILQ_INSERT_TAIL(&sc->sc_dirtyq[pq], pd, pd_link); + sc->sc_dirtyq_count[pq]++; + /* XXX */ + bus_dmamap_sync(sc->sc_fragdmat, pd->pd_dmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU); + if (sc->sc_debug & SC_DEBUG_QUEUES) + printf("%s: queue: rx %u <- [%u]\n", + sc->sc_dev.dv_xname, pd->pd_fragnum, pq); + if (sc->sc_debug & SC_DEBUG_UNEXPECTED && + pd->pd_fragp->pf_flags & ~htole16(PF_FLAG_MF)) + printf("%s: unknown flags on rx [%u]: 0x%x\n", + sc->sc_dev.dv_xname, pq, letoh16(pd->pd_fragp->pf_flags)); +} + +/* + * Traps are generally used for the firmware to report changes in state + * back to the host. Mostly this processes changes in link state, but + * it needs to also be used to initiate WPA and other authentication + * schemes in terms of client (station) or server (access point). + */ +void +pgt_trap_received(struct pgt_softc *sc, uint32_t oid, void *trapdata, + size_t size) +{ + pgt_async_trap(sc, oid, trapdata, size); +} + +/* + * Process a completed management response (all requests should be + * responded to, quickly) or an event (trap). + */ +void +pgt_mgmtrx_completion(struct pgt_softc *sc, struct pgt_mgmt_desc *pmd) +{ + struct pgt_desc *pd; + struct pgt_mgmt_frame *pmf; + uint32_t oid, size; + + pd = TAILQ_FIRST(&sc->sc_dirtyq[PFF_QUEUE_MGMT_RX]); + TAILQ_REMOVE(&sc->sc_dirtyq[PFF_QUEUE_MGMT_RX], pd, pd_link); + sc->sc_dirtyq_count[PFF_QUEUE_MGMT_RX]--; + TAILQ_INSERT_TAIL(&sc->sc_freeq[PFF_QUEUE_MGMT_RX], + pd, pd_link); + sc->sc_freeq_count[PFF_QUEUE_MGMT_RX]++; + if (letoh16(pd->pd_fragp->pf_size) < sizeof(*pmf)) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: mgmt desc too small: %u\n", + sc->sc_dev.dv_xname, + letoh16(pd->pd_fragp->pf_size)); + goto out_nopmd; + } + pmf = (struct pgt_mgmt_frame *)pd->pd_mem; + if (pmf->pmf_version != PMF_VER) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: unknown mgmt version %u\n", + sc->sc_dev.dv_xname, pmf->pmf_version); + goto out_nopmd; + } + if (pmf->pmf_device != PMF_DEV) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: unknown mgmt dev %u\n", + sc->sc_dev.dv_xname, pmf->pmf_device); + goto out; + } + if (pmf->pmf_flags & ~PMF_FLAG_VALID) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: unknown mgmt flags %u\n", + sc->sc_dev.dv_xname, + pmf->pmf_flags & ~PMF_FLAG_VALID); + goto out; + } + if (pmf->pmf_flags & PMF_FLAG_LE) { + oid = letoh32(pmf->pmf_oid); + size = letoh32(pmf->pmf_size); + } else { + oid = betoh32(pmf->pmf_oid); + size = betoh32(pmf->pmf_size); + } + if (pmf->pmf_operation == PMF_OP_TRAP) { + pmd = NULL; /* ignored */ + pgt_trap_received(sc, oid, (char *)pmf + sizeof(*pmf), + min(size, PFF_FRAG_SIZE - sizeof(*pmf))); + goto out_nopmd; + } + if (pmd == NULL) { + if (sc->sc_debug & (SC_DEBUG_UNEXPECTED | SC_DEBUG_MGMT)) + printf("%s: spurious mgmt received " + "(op %u, oid 0x%x, len %u\n", + sc->sc_dev.dv_xname, pmf->pmf_operation, oid, size); + goto out_nopmd; + } + switch (pmf->pmf_operation) { + case PMF_OP_RESPONSE: + pmd->pmd_error = 0; + break; + case PMF_OP_ERROR: + pmd->pmd_error = EPERM; + goto out; + default: + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: unknown mgmt op %u\n", + sc->sc_dev.dv_xname, pmf->pmf_operation); + pmd->pmd_error = EIO; + goto out; + } + if (oid != pmd->pmd_oid) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: mgmt oid changed from 0x%x " + "-> 0x%x\n", + sc->sc_dev.dv_xname, pmd->pmd_oid, oid); + pmd->pmd_oid = oid; + } + if (pmd->pmd_recvbuf != NULL) { + if (size > PFF_FRAG_SIZE) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: mgmt oid 0x%x " + "has bad size %u\n", + sc->sc_dev.dv_xname, oid, size); + pmd->pmd_error = EIO; + goto out; + } + if (size > pmd->pmd_len) + pmd->pmd_error = ENOMEM; + else + memcpy(pmd->pmd_recvbuf, (char *)pmf + sizeof(*pmf), + size); + pmd->pmd_len = size; + } +out: + TAILQ_REMOVE(&sc->sc_mgmtinprog, pmd, pmd_link); +#ifdef DEVICE_POLLING + if (!(sc->sc_ic.ic_if.if_flags & IFF_POLLING)) +#endif + wakeup_one(pmd); + if (sc->sc_debug & SC_DEBUG_MGMT) + printf("%s: queue: mgmt %p <- (op %u, " + "oid 0x%x, len %u)\n", + sc->sc_dev.dv_xname, pmd, pmf->pmf_operation, + pmd->pmd_oid, pmd->pmd_len); +out_nopmd: + pgt_reinit_rx_desc_frag(sc, pd); +} + +/* + * Queue packets for reception and defragmentation. I don't know now + * whether the rx queue being full enough to start, but not finish, + * queueing a fragmented packet, can happen. + */ +int +pgt_datarx_completion(struct pgt_softc *sc, enum pgt_queue pq, + struct mbuf ***last_nextpkt, int prevwasmf) +{ + struct ifnet *ifp; + struct pgt_desc *pd; + struct mbuf *m; + size_t datalen; + uint16_t dataoff; + int morefrags; + + ifp = &sc->sc_ic.ic_if; + pd = TAILQ_FIRST(&sc->sc_dirtyq[pq]); + TAILQ_REMOVE(&sc->sc_dirtyq[pq], pd, pd_link); + sc->sc_dirtyq_count[pq]--; + datalen = letoh16(pd->pd_fragp->pf_size); + dataoff = letoh32(pd->pd_fragp->pf_addr) - pd->pd_dmaaddr; + morefrags = pd->pd_fragp->pf_flags & htole16(PF_FLAG_MF); + if (sc->sc_debug & SC_DEBUG_RXFRAG) + printf("%s: rx frag: len %u memoff %u\n", + sc->sc_dev.dv_xname, datalen, dataoff); + /* Add the (two+?) bytes for the header. */ + datalen += dataoff; + if (datalen > PFF_FRAG_SIZE) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s data rx too big: %u\n", + sc->sc_dev.dv_xname, datalen); + ifp->if_ierrors++; + goto out; + } + /* Add a uint16_t at the beginning containing the actual data offset. */ + if (prevwasmf) { + if (datalen + 2 >= MINCLSIZE) + MGETHDR(m, MT_DATA, 0); + else + m = m_get(M_DONTWAIT, MT_DATA); + if (m == NULL) { + ifp->if_ierrors++; + goto out; + } + //m->m_flags |= M_PROTO2; + bcopy(pd->pd_mem, mtod(m, char *) + 2, datalen); + m->m_len = datalen; + } else { + m = m_devget(pd->pd_mem, datalen, + sizeof(struct ieee80211_frame) + 2, ifp, NULL); + if (m != NULL) + M_PREPEND(m, 2, M_DONTWAIT); + if (m == NULL) { + ifp->if_ierrors++; + goto out; + } + } + *mtod(m, uint16_t *) = dataoff; + if (morefrags) + m->m_flags |= M_PROTO1; + else + /* + * Count non-fragmented packets and the last fragment + * in fragmented packets. + */ + ifp->if_ipackets++; + **last_nextpkt = m; + *last_nextpkt = &m->m_nextpkt; +out: + TAILQ_INSERT_TAIL(&sc->sc_freeq[pq], pd, pd_link); + sc->sc_freeq_count[pq]++; + pgt_reinit_rx_desc_frag(sc, pd); + return (morefrags); +} + +int +pgt_oid_get(struct pgt_softc *sc, enum pgt_oid oid, + void *arg, size_t arglen) +{ + struct pgt_mgmt_desc pmd; + int error; + + bzero(&pmd, sizeof(pmd)); + pmd.pmd_recvbuf = arg; + pmd.pmd_len = arglen; + pmd.pmd_oid = oid; + error = pgt_mgmt_request(sc, &pmd); + if (error == 0) + error = pmd.pmd_error; + if (error != 0 && error != EPERM && sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: failure getting oid 0x%x: %d\n", + sc->sc_dev.dv_xname, + oid, error); + return (error); +} + +int +pgt_oid_retrieve(struct pgt_softc *sc, enum pgt_oid oid, + void *arg, size_t arglen) +{ + struct pgt_mgmt_desc pmd; + int error; + + bzero(&pmd, sizeof(pmd)); + pmd.pmd_sendbuf = arg; + pmd.pmd_recvbuf = arg; + pmd.pmd_len = arglen; + pmd.pmd_oid = oid; + error = pgt_mgmt_request(sc, &pmd); + if (error == 0) + error = pmd.pmd_error; + if (error != 0 && error != EPERM && sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: failure retrieving oid 0x%x: %d\n", + sc->sc_dev.dv_xname, oid, error); + return (error); +} + +int +pgt_oid_set(struct pgt_softc *sc, enum pgt_oid oid, + const void *arg, size_t arglen) +{ + struct pgt_mgmt_desc pmd; + int error; + + bzero(&pmd, sizeof(pmd)); + pmd.pmd_sendbuf = arg; + pmd.pmd_len = arglen; + pmd.pmd_oid = oid; + error = pgt_mgmt_request(sc, &pmd); + if (error == 0) + error = pmd.pmd_error; + if (error != 0 && error != EPERM && sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: failure setting oid 0x%x: %d\n", + sc->sc_dev.dv_xname, oid, error); + return (error); +} + +void +pgt_state_dump(struct pgt_softc *sc) +{ + printf("%s: state dump: control 0x%08x " + "interrupt 0x%08x\n", sc->sc_dev.dv_xname, + pgt_read_4(sc, PFF_REG_CTRL_STAT), + pgt_read_4(sc, PFF_REG_INT_STAT)); + + printf("%s: state dump: driver curfrag[]\n", + sc->sc_dev.dv_xname); + + printf("%s: 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", + sc->sc_dev.dv_xname, + letoh32(sc->sc_cb->pcb_driver_curfrag[0]), + letoh32(sc->sc_cb->pcb_driver_curfrag[1]), + letoh32(sc->sc_cb->pcb_driver_curfrag[2]), + letoh32(sc->sc_cb->pcb_driver_curfrag[3]), + letoh32(sc->sc_cb->pcb_driver_curfrag[4]), + letoh32(sc->sc_cb->pcb_driver_curfrag[5])); + + printf("%s: state dump: device curfrag[]\n", + sc->sc_dev.dv_xname); + + printf("%s: 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", + sc->sc_dev.dv_xname, + letoh32(sc->sc_cb->pcb_device_curfrag[0]), + letoh32(sc->sc_cb->pcb_device_curfrag[1]), + letoh32(sc->sc_cb->pcb_device_curfrag[2]), + letoh32(sc->sc_cb->pcb_device_curfrag[3]), + letoh32(sc->sc_cb->pcb_device_curfrag[4]), + letoh32(sc->sc_cb->pcb_device_curfrag[5])); +} + +int +pgt_mgmt_request(struct pgt_softc *sc, struct pgt_mgmt_desc *pmd) +{ + struct pgt_desc *pd; + struct pgt_mgmt_frame *pmf; + int error, i; + + if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET)) + return (EIO); + if (pmd->pmd_len > PFF_FRAG_SIZE - sizeof(*pmf)) + return (ENOMEM); + pd = TAILQ_FIRST(&sc->sc_freeq[PFF_QUEUE_MGMT_TX]); + if (pd == NULL) + return (ENOMEM); + error = pgt_load_tx_desc_frag(sc, PFF_QUEUE_MGMT_TX, pd); + if (error) + return (error); + pmf = (struct pgt_mgmt_frame *)pd->pd_mem; + pmf->pmf_version = PMF_VER; + /* "get" and "retrieve" operations look the same */ + if (pmd->pmd_recvbuf != NULL) + pmf->pmf_operation = PMF_OP_GET; + else + pmf->pmf_operation = PMF_OP_SET; + pmf->pmf_oid = htobe32(pmd->pmd_oid); + pmf->pmf_device = PMF_DEV; + pmf->pmf_flags = 0; + pmf->pmf_size = htobe32(pmd->pmd_len); + /* "set" and "retrieve" operations both send data */ + if (pmd->pmd_sendbuf != NULL) + memcpy((char *)pmf + sizeof(*pmf), pmd->pmd_sendbuf, + pmd->pmd_len); + else + bzero((char *)pmf + sizeof(*pmf), pmd->pmd_len); + pmd->pmd_error = EINPROGRESS; + TAILQ_INSERT_TAIL(&sc->sc_mgmtinprog, pmd, pmd_link); + if (sc->sc_debug & SC_DEBUG_MGMT) + printf("%s: queue: mgmt %p -> (op %u, " + "oid 0x%x, len %u)\n", sc->sc_dev.dv_xname, + pmd, pmf->pmf_operation, + pmd->pmd_oid, pmd->pmd_len); + pgt_desc_transmit(sc, PFF_QUEUE_MGMT_TX, pd, + sizeof(*pmf) + pmd->pmd_len, 0); + sc->sc_refcnt++; +#ifdef DEVICE_POLLING + /* + * If we're polling, try 1/10th second initially at the smallest + * interval we can sleep for. + */ + for (i = 0; sc->sc_ic.ic_if.if_flags & IFF_POLLING && i < 100; i++) { + pgt_update_intr(sc, NULL, 0); + if (pmd->pmd_error != EINPROGRESS) + goto usedpoll; + /* + if (msleep(pmd, &sc->sc_lock, PZERO, "pffmgp", 1) != + EWOULDBLOCK) + break; + */ + } +#endif + /* + * Try for one second, triggering 10 times. + * + * Do our best to work around seemingly buggy CardBus controllers + * on Soekris 4521 that fail to get interrupts with alarming + * regularity: run as if an interrupt occurred and service every + * queue except for mbuf reception. + */ + i = 0; + do { + /* + if (msleep(pmd, &sc->sc_lock, PZERO, "pffmgm", hz / 10) != + EWOULDBLOCK) + break; + */ + if (pmd->pmd_error != EINPROGRESS) + break; + if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET)) { + pmd->pmd_error = EIO; + TAILQ_REMOVE(&sc->sc_mgmtinprog, pmd, + pmd_link); + break; + } + if (i != 9) + pgt_maybe_trigger(sc, PFF_QUEUE_MGMT_RX); +#ifdef PFF_BUGGY_INTERRUPT_RECOVERY + pgt_update_intr(sc, NULL, 0); +#endif + } while (i++ < 10); +#ifdef DEVICE_POLLING +usedpoll: +#endif + if (pmd->pmd_error == EINPROGRESS) { + printf("%s: timeout waiting for management " + "packet response to 0x%x\n", sc->sc_dev.dv_xname, pmd->pmd_oid); + TAILQ_REMOVE(&sc->sc_mgmtinprog, pmd, + pmd_link); + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + pgt_state_dump(sc); + pgt_async_reset(sc); + error = ETIMEDOUT; + } else { + error = 0; + } + sc->sc_refcnt--; + return (error); +} + +void +pgt_desc_transmit(struct pgt_softc *sc, enum pgt_queue pq, struct pgt_desc *pd, + uint16_t len, int morecoming) +{ + TAILQ_REMOVE(&sc->sc_freeq[pq], pd, pd_link); + sc->sc_freeq_count[pq]--; + TAILQ_INSERT_TAIL(&sc->sc_dirtyq[pq], pd, pd_link); + sc->sc_dirtyq_count[pq]++; + if (sc->sc_debug & SC_DEBUG_QUEUES) + printf("%s: queue: tx %u -> [%u]\n", sc->sc_dev.dv_xname, + pd->pd_fragnum, pq); + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU | BUS_DMASYNC_PRECPU2DMA); + if (morecoming) + pd->pd_fragp->pf_flags |= htole16(PF_FLAG_MF); + pd->pd_fragp->pf_size = htole16(len); + /* XXX */ + bus_dmamap_sync(sc->sc_fragdmat, pd->pd_dmam, 0, 0, + BUS_DMASYNC_POSTCPU2DMA); + sc->sc_cb->pcb_driver_curfrag[pq] = + htole32(letoh32(sc->sc_cb->pcb_driver_curfrag[pq]) + 1); + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTCPU2DMA | BUS_DMASYNC_PREDMA2CPU); + if (!morecoming) + pgt_maybe_trigger(sc, pq); +} + +void +pgt_maybe_trigger(struct pgt_softc *sc, enum pgt_queue pq) +{ + unsigned int tries = 1000000 / PFF_WRITEIO_DELAY; /* one second */ + uint32_t reg; + + if (sc->sc_debug & SC_DEBUG_TRIGGER) + printf("%s: triggered by queue [%u]\n", + sc->sc_dev.dv_xname, pq); + pgt_debug_events(sc, "trig"); + if (sc->sc_flags & SC_POWERSAVE) { + /* Magic values ahoy? */ + if (pgt_read_4(sc, PFF_REG_INT_STAT) == 0xabadface) { + do { + reg = pgt_read_4(sc, PFF_REG_CTRL_STAT); + if (!(reg & PFF_CTRL_STAT_SLEEPMODE)) + DELAY(PFF_WRITEIO_DELAY); + } while (tries-- != 0); + if (!(reg & PFF_CTRL_STAT_SLEEPMODE)) { + if (sc->sc_debug & SC_DEBUG_UNEXPECTED) + printf("%s: timeout triggering from " + "sleep mode\n", + sc->sc_dev.dv_xname); + pgt_async_reset(sc); + return; + } + } + pgt_write_4_flush(sc, PFF_REG_DEV_INT, + PFF_DEV_INT_WAKEUP); + DELAY(PFF_WRITEIO_DELAY); + /* read the status back in */ + (void)pgt_read_4(sc, PFF_REG_CTRL_STAT); + DELAY(PFF_WRITEIO_DELAY); + } else { + pgt_write_4_flush(sc, PFF_REG_DEV_INT, PFF_DEV_INT_UPDATE); + DELAY(PFF_WRITEIO_DELAY); + } +} + +struct ieee80211_node * +pgt_ieee80211_node_alloc(struct ieee80211com *ic) +{ + struct pgt_ieee80211_node *pin; + + pin = malloc(sizeof(*pin), M_DEVBUF, M_NOWAIT); + bzero(pin, sizeof *pin); + if (pin != NULL) + pin->pin_dot1x_auth = PIN_DOT1X_UNAUTHORIZED; + return (struct ieee80211_node *)pin; +} + +void +pgt_ieee80211_newassoc(struct ieee80211com *ic, struct ieee80211_node *ni, + int reallynew) +{ + ieee80211_ref_node(ni); +} + +void +pgt_ieee80211_node_free(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + struct pgt_ieee80211_node *pin; + + pin = (struct pgt_ieee80211_node *)ni; + free(pin, M_DEVBUF); +} + +void +pgt_ieee80211_node_copy(struct ieee80211com *ic, struct ieee80211_node *dst, + const struct ieee80211_node *src) +{ + const struct pgt_ieee80211_node *psrc; + struct pgt_ieee80211_node *pdst; + + psrc = (const struct pgt_ieee80211_node *)src; + pdst = (struct pgt_ieee80211_node *)dst; + bcopy(psrc, pdst, sizeof(*psrc)); +} + +int +pgt_ieee80211_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni, + int type, int arg) +{ + return (EOPNOTSUPP); +} + + +int +pgt_net_attach(struct pgt_softc *sc) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct arpcom *ac = &ic->ic_ac; + struct ifnet *ifp = &ac->ac_if; + struct ieee80211_rateset *rs; + uint8_t rates[IEEE80211_RATE_MAXSIZE]; + struct pgt_obj_buffer psbuffer; + struct pgt_obj_frequencies *freqs; + uint32_t phymode, country; + unsigned int chan, i, j, firstchan = -1; + int error; + + //mtx_lock(&sc->sc_lock); + psbuffer.pob_size = htole32(PFF_FRAG_SIZE * PFF_PSM_BUFFER_FRAME_COUNT); + psbuffer.pob_addr = htole32((uint32_t)sc->sc_psmdmabusaddr); + error = pgt_oid_set(sc, PFF_OID_PSM_BUFFER, &psbuffer, + sizeof(psbuffer)); + if (error == 0) + error = pgt_oid_get(sc, PFF_OID_PHY, &phymode, sizeof(phymode)); + if (error == 0) + error = pgt_oid_get(sc, PFF_OID_MAC_ADDRESS, ac->ac_enaddr, + sizeof(ac->ac_enaddr)); + if (error == 0) + error = pgt_oid_get(sc, PFF_OID_COUNTRY, &country, + sizeof(country)); + //mtx_unlock(&sc->sc_lock); + if (error) + return (error); + + //if_initname(ifp, device_get_name(sc->sc_dev), + // device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST | IFF_RUNNING; + ifp->if_capabilities = IFCAP_VLAN_MTU; +#ifdef DEVICE_POLLING + ifp->if_capabilities |= IFCAP_POLLING; +#endif + //ifp->if_capenable = IFCAP_VLAN_MTU; + ifp->if_start = pgt_start; + ifp->if_ioctl = pgt_ioctl; + ifp->if_watchdog = pgt_periodic; + ifp->if_init = pgt_init; + IFQ_SET_MAXLEN(&ifp->if_snd, PFF_QUEUE_FULL_THRESHOLD); + //ifp->if_snd.ifq_drv_maxlen = PFF_QUEUE_FULL_THRESHOLD; + IFQ_SET_READY(&ifp->if_snd); + + IEEE80211_ADDR_COPY(ic->ic_myaddr, ac->ac_enaddr); + j = sizeof(*freqs) + (IEEE80211_CHAN_MAX + 1) * sizeof(uint16_t); + freqs = malloc(j, M_DEVBUF, M_WAITOK); + //mtx_lock(&sc->sc_lock); + error = pgt_oid_get(sc, PFF_OID_SUPPORTED_FREQUENCIES, freqs, j); + //mtx_unlock(&sc->sc_lock); + if (error) { + free(freqs, M_DEVBUF); + return (error); + } + /* + * Prism hardware likes to report supported frequencies that are + * not actually available for the country of origin. + */ + j = letoh16(freqs->pof_count); + for (i = 0; i < j; i++) { + chan = ieee80211_mhz2ieee(letoh16(freqs->pof_freqlist_mhz[i]), + 0); + if (chan > IEEE80211_CHAN_MAX) { + printf("%s: reported bogus channel (%uMHz)\n", + sc->sc_dev.dv_xname, chan); + free(freqs, M_DEVBUF); + return (EIO); + } + if (letoh16(freqs->pof_freqlist_mhz[i]) < 5000) { + if (!(phymode & htole32(PFF_OID_PHY_2400MHZ))) + continue; + if (country == letoh32(PFF_COUNTRY_USA)) { + if (chan >= 12 && chan <= 14) + continue; + } + if (chan <= 14) + ic->ic_channels[chan].ic_flags |= + IEEE80211_CHAN_B; + ic->ic_channels[chan].ic_flags |= IEEE80211_CHAN_PUREG; + } else { + if (!(phymode & htole32(PFF_OID_PHY_5000MHZ))) + continue; + ic->ic_channels[chan].ic_flags |= IEEE80211_CHAN_A; + } + ic->ic_channels[chan].ic_freq = + letoh16(freqs->pof_freqlist_mhz[i]); + if (firstchan == -1) + firstchan = chan; + } + free(freqs, M_DEVBUF); + if (firstchan == -1) { + printf("%s: no channels found\n", sc->sc_dev.dv_xname); + return (EIO); + } + bzero(rates, sizeof(rates)); + //mtx_lock(&sc->sc_lock); + error = pgt_oid_get(sc, PFF_OID_SUPPORTED_RATES, rates, sizeof(rates)); + //mtx_unlock(&sc->sc_lock); + if (error) + return (error); + for (i = 0; i < sizeof(rates) && rates[i] != 0; i++) { + switch (rates[i]) { + case 2: + case 4: + case 11: + case 22: + case 44: /* maybe */ + if (phymode & htole32(PFF_OID_PHY_2400MHZ)) { + rs = &ic->ic_sup_rates[IEEE80211_MODE_11B]; + rs->rs_rates[rs->rs_nrates++] = rates[i]; + } + default: + if (phymode & htole32(PFF_OID_PHY_2400MHZ)) { + rs = &ic->ic_sup_rates[IEEE80211_MODE_11G]; + rs->rs_rates[rs->rs_nrates++] = rates[i]; + } + if (phymode & htole32(PFF_OID_PHY_5000MHZ)) { + rs = &ic->ic_sup_rates[IEEE80211_MODE_11A]; + rs->rs_rates[rs->rs_nrates++] = rates[i]; + } + rs = &ic->ic_sup_rates[IEEE80211_MODE_AUTO]; + rs->rs_rates[rs->rs_nrates++] = rates[i]; + } + } + ic->ic_caps = IEEE80211_C_WEP | IEEE80211_C_IBSS | IEEE80211_C_PMGT | + IEEE80211_C_HOSTAP | IEEE80211_C_TXPMGT | IEEE80211_C_SHSLOT | + IEEE80211_C_SHPREAMBLE | IEEE80211_C_MONITOR; + ic->ic_phytype = IEEE80211_T_OFDM; /* XXX not really used */ + ic->ic_opmode = IEEE80211_M_STA; + ic->ic_state = IEEE80211_S_INIT; + ic->ic_protmode = IEEE80211_PROT_NONE; + ieee80211_ifattach(ifp); + /* Set up post-attach/pre-lateattach vector functions. */ + ic->ic_newstate = pgt_new_state; + ic->ic_node_alloc = pgt_ieee80211_node_alloc; + ic->ic_newassoc = pgt_ieee80211_newassoc; + ic->ic_node_free = pgt_ieee80211_node_free; + ic->ic_node_copy = pgt_ieee80211_node_copy; + ic->ic_send_mgmt = pgt_ieee80211_send_mgmt; + /* let net80211 handle switching around the media + resetting */ + ieee80211_media_init(ifp, pgt_media_change, pgt_media_status); + //bpfattach2(ifp, DLT_IEEE802_11_RADIO, sizeof(struct ieee80211_frame) + + // sizeof(struct pgt_ieee80211_radiotap), &sc->sc_drvbpf); + /* default to the first channel we know of */ + ic->ic_bss->ni_chan = ic->ic_ibss_chan; + sc->sc_if_flags = ifp->if_flags; + return (0); +} + +void +pgt_net_detach(struct pgt_softc *sc) +{ + ieee80211_new_state(&sc->sc_ic, IEEE80211_S_INIT, -1); + bpfdetach(&sc->sc_ic.ic_if); + ieee80211_ifdetach(&sc->sc_ic.ic_if); +} + +void +pgt_start(struct ifnet *ifp) +{ + struct pgt_softc *sc; + struct ieee80211com *ic; + + sc = ifp->if_softc; + ic = &sc->sc_ic; + //mtx_lock(&sc->sc_lock); + if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET) || + !(ifp->if_flags & IFF_RUNNING) || + ic->ic_state != IEEE80211_S_RUN) { + //mtx_unlock(&sc->sc_lock); + return; + } + pgt_start_body(sc, ic, ifp); + //mtx_unlock(&sc->sc_lock); +} + +/* + * Start data frames. Critical sections surround the boundary of + * management frame transmission / transmission acknowledgement / response + * and data frame transmission / transmission acknowledgement. + */ +void +pgt_start_body(struct pgt_softc *sc, struct ieee80211com *ic, struct ifnet *ifp) +{ + struct pgt_desc *pd; + struct mbuf *m; + int error; + + if (!pgt_try_enter_data_critical(sc)) + return; + /* + * Management packets should probably be MLME frames + * (i.e. hostap "managed" mode); we don't touch the + * net80211 management queue. + */ + for (; sc->sc_dirtyq_count[PFF_QUEUE_DATA_LOW_TX] < + //PFF_QUEUE_FULL_THRESHOLD && !IFQ_DRV_IS_EMPTY(&ifp->if_snd);) { + PFF_QUEUE_FULL_THRESHOLD;) { + pd = TAILQ_FIRST(&sc->sc_freeq[PFF_QUEUE_DATA_LOW_TX]); + //IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (m->m_pkthdr.len <= PFF_FRAG_SIZE) { + error = pgt_load_tx_desc_frag(sc, + PFF_QUEUE_DATA_LOW_TX, pd); + if (error) { + //IFQ_DRV_PREPEND(&ifp->if_snd, m); + break; + } + m_copydata(m, 0, m->m_pkthdr.len, pd->pd_mem); + pgt_desc_transmit(sc, PFF_QUEUE_DATA_LOW_TX, + pd, m->m_pkthdr.len, 0); + //BPF_MTAP(ifp, m); + ifp->if_opackets++; + sc->sc_critical++; + } else if (m->m_pkthdr.len <= PFF_FRAG_SIZE * 2) { + struct pgt_desc *pd2; + + /* + * Transmit a fragmented frame if there is + * not enough room in one fragment; limit + * to two fragments (802.11 itself couldn't + * even support a full two.) + */ + if (sc->sc_dirtyq_count[PFF_QUEUE_DATA_LOW_TX] + 2 > + PFF_QUEUE_FULL_THRESHOLD) { + //IFQ_DRV_PREPEND(&ifp->if_snd, m); + break; + } + pd2 = TAILQ_NEXT(pd, pd_link); + error = pgt_load_tx_desc_frag(sc, + PFF_QUEUE_DATA_LOW_TX, pd); + if (error == 0) { + error = pgt_load_tx_desc_frag(sc, + PFF_QUEUE_DATA_LOW_TX, pd2); + if (error) { + pgt_unload_tx_desc_frag(sc, pd); + TAILQ_INSERT_HEAD(&sc->sc_freeq[ + PFF_QUEUE_DATA_LOW_TX], pd, + pd_link); + } + } + if (error) { + //IFQ_DRV_PREPEND(&ifp->if_snd, m); + break; + } + m_copydata(m, 0, PFF_FRAG_SIZE, pd->pd_mem); + pgt_desc_transmit(sc, PFF_QUEUE_DATA_LOW_TX, + pd, PFF_FRAG_SIZE, 1); + m_copydata(m, PFF_FRAG_SIZE, + m->m_pkthdr.len - PFF_FRAG_SIZE, pd2->pd_mem); + pgt_desc_transmit(sc, PFF_QUEUE_DATA_LOW_TX, + pd2, m->m_pkthdr.len - PFF_FRAG_SIZE, 0); + //BPF_MTAP(ifp, m); + ifp->if_opackets++; + sc->sc_critical += 2; + } else { + ifp->if_oerrors++; + m_freem(m); + m = NULL; + } + if (m != NULL) { + struct ieee80211_node *ni; + + ifp->if_timer = 1; + //getbinuptime(&sc->sc_data_tx_started); + ni = ieee80211_find_txnode(&sc->sc_ic, + mtod(m, struct ether_header *)->ether_dhost); + if (ni != NULL) { + ni->ni_inact = 0; + if (ni != ic->ic_bss) + ieee80211_release_node(&sc->sc_ic, ni); + } + if (sc->sc_drvbpf != NULL) { + struct pgt_ieee80211_radiotap pir; + struct ether_header eh; + + /* + * Fill out what we can when faking + * up a radiotapified outgoing frame. + */ + bzero(&pir, sizeof(pir)); + pir.pir_header.it_len = htole16(sizeof(pir)); + pir.pir_header.it_present = + htole32(PFF_IEEE80211_RADIOTAP_PRESENT); + if (sc->sc_80211_ioc_wep != IEEE80211_WEP_OFF) + pir.pir_flags |= + IEEE80211_RADIOTAP_F_WEP; + pir.pir_channel = + htole16(ic->ic_bss->ni_chan->ic_freq); + pir.pir_channel_flags = + htole16(ic->ic_bss->ni_chan->ic_flags); + pir.pir_db_antnoise = sc->sc_noise; + memcpy(mtod(m, struct ether_header *), &eh, + sizeof(eh)); + m_adj(m, sizeof(eh)); + m = pgt_ieee80211_encap(sc, &eh, m, NULL); + if (m != NULL) { + //bpf_mtap2(sc->sc_drvbpf, &pir, + // sizeof(pir), m); + m_freem(m); + } + } else { + m_freem(m); + } + } + } + //pgt_try_exit_data_critical(sc); +} + +int +pgt_ioctl(struct ifnet *ifp, u_long cmd, caddr_t req) +{ + struct pgt_softc *sc = ifp->if_softc; + struct ifreq *ifr; + struct wi_req *wreq; + //struct ifprismoidreq *preq; + //struct ieee80211req *ireq; + int error, oldflags; + + ifr = (struct ifreq *)req; + switch (cmd) { +#if 0 + case SIOCGPRISMOID: + case SIOCSPRISMOID: + //error = suser(curthread); + if (error) + return (error); + preq = (struct ifprismoidreq *)req; + if (preq->ifr_oidlen > sizeof(preq->ifr_oiddata)) + return (ENOMEM); + //mtx_lock(&sc->sc_lock); + pgt_enter_critical(sc); + if (cmd == SIOCGPRISMOID) + error = pgt_oid_retrieve(sc, preq->ifr_oid, + preq->ifr_oiddata, preq->ifr_oidlen); + else + error = pgt_oid_set(sc, preq->ifr_oid, + preq->ifr_oiddata, preq->ifr_oidlen); + pgt_exit_critical(sc); + //mtx_unlock(&sc->sc_lock); + break; +#endif + case SIOCGWAVELAN: + case SIOCSWAVELAN: + wreq = malloc(sizeof(*wreq), M_DEVBUF, M_WAITOK); + error = copyin(ifr->ifr_data, wreq, sizeof(*wreq)); + if (error == 0) { + if (cmd == SIOCGWAVELAN) { + error = pgt_wavelan_get(sc, wreq); + if (error == 0) + error = copyout(wreq, ifr->ifr_data, + sizeof(*wreq)); + } else { + error = pgt_wavelan_set(sc, wreq); + } + } + free(wreq, M_DEVBUF); + if (error == EOPNOTSUPP) + goto notours; + break; + case SIOCSIFFLAGS: + error = 0; + //mtx_lock(&sc->sc_lock); + oldflags = sc->sc_if_flags; + sc->sc_if_flags = ifp->if_flags; + if ((oldflags & (IFF_PROMISC | IFF_UP)) != + (ifp->if_flags & (IFF_PROMISC | IFF_UP))) { + if (!(oldflags & IFF_UP) && ifp->if_flags & IFF_UP) { + ieee80211_new_state(&sc->sc_ic, + IEEE80211_S_SCAN, -1); + error = ENETRESET; + } else if (oldflags & IFF_UP && + !(ifp->if_flags & IFF_UP)) { + ieee80211_new_state(&sc->sc_ic, + IEEE80211_S_INIT, -1); + error = ENETRESET; + } + } + //mtx_unlock(&sc->sc_lock); + break; + case SIOCSIFMTU: + if (ifr->ifr_mtu > PFF_FRAG_SIZE) { + uprintf("%s: bad MTU (values > %u non-functional)\n", + ifp->if_xname, PFF_FRAG_SIZE); + error = EINVAL; + } else { + //mtx_lock(&sc->sc_lock); + ifp->if_mtu = ifr->ifr_mtu; + //mtx_unlock(&sc->sc_lock); + error = 0; + } + break; +#ifdef DEVICE_POLLING + case SIOCSIFCAP: + //mtx_lock(&sc->sc_lock); + if (!(ifp->if_capabilities & IFF_RUNNING)) { + error = EIO; + } else { + if ((ifr->ifr_reqcap & IFCAP_POLLING) != + (ifp->if_capenable & IFCAP_POLLING)) + ifp->if_capenable ^= IFCAP_POLLING; + error = 0; + } + //mtx_unlock(&sc->sc_lock); + break; +#endif + default: +notours: + /* + * XXX net80211 does not prevent modification of the + * ieee80211com while it fondles it. + */ + error = ieee80211_ioctl(ifp, cmd, req); + break; + } + if (error == ENETRESET) { + //mtx_lock(&sc->sc_lock); + pgt_update_hw_from_sw(sc, 0, 0); + //mtx_unlock(&sc->sc_lock); + error = 0; + } + return (error); +} + +void +pgt_obj_bss2scanres(struct pgt_softc *sc, struct pgt_obj_bss *pob, + struct wi_scan_res *scanres, uint32_t noise) +{ + struct ieee80211_rateset *rs; + struct wi_scan_res ap; + unsigned int i, n; + + rs = &sc->sc_ic.ic_sup_rates[IEEE80211_MODE_AUTO]; + bzero(&ap, sizeof(ap)); + ap.wi_chan = ieee80211_mhz2ieee(letoh16(pob->pob_channel), 0); + ap.wi_noise = noise; + ap.wi_signal = letoh16(pob->pob_rssi); + IEEE80211_ADDR_COPY(ap.wi_bssid, pob->pob_address); + ap.wi_interval = letoh16(pob->pob_beacon_period); + ap.wi_capinfo = letoh16(pob->pob_capinfo); + ap.wi_ssid_len = min(sizeof(ap.wi_ssid), pob->pob_ssid.pos_length); + memcpy(ap.wi_ssid, pob->pob_ssid.pos_ssid, ap.wi_ssid_len); + n = 0; + for (i = 0; i < 16; i++) { + if (letoh16(pob->pob_rates) & (1 << i)) { + if (i > rs->rs_nrates) + break; + ap.wi_srates[n++] = ap.wi_rate = rs->rs_rates[i]; + if (n >= sizeof(ap.wi_srates) / sizeof(ap.wi_srates[0])) + break; + } + } + memcpy(scanres, &ap, WI_PRISM2_RES_SIZE); +} + +int +pgt_node_set_authorization(struct pgt_softc *sc, + struct pgt_ieee80211_node *pin, enum pin_dot1x_authorization newstate) +{ + int error; + + if (pin->pin_dot1x_auth == newstate) + return (0); + IEEE80211_DPRINTF(("%s: %02x:%02x:%02x:%02x:%02x:%02x " + "changing authorization to %d\n", __func__, + pin->pin_node.ni_macaddr[0], pin->pin_node.ni_macaddr[1], + pin->pin_node.ni_macaddr[2], pin->pin_node.ni_macaddr[3], + pin->pin_node.ni_macaddr[4], pin->pin_node.ni_macaddr[5], + newstate)); + error = pgt_oid_set(sc, + newstate == PIN_DOT1X_AUTHORIZED ? + PFF_OID_EAPAUTHSTA : PFF_OID_EAPUNAUTHSTA, + pin->pin_node.ni_macaddr, sizeof(pin->pin_node.ni_macaddr)); + if (error == 0) + pin->pin_dot1x_auth = pin->pin_dot1x_auth_desired = newstate; + return (error); +} + +#if 0 +int +pgt_do_mlme_sta(struct pgt_softc *sc, struct ieee80211req_mlme *imlme) +{ + struct pgt_obj_mlme pffmlme; + struct ieee80211com *ic; + int error = 0; + + ic = &sc->sc_ic; + switch (imlme->im_op) { + case IEEE80211_MLME_ASSOC: + IEEE80211_ADDR_COPY(pffmlme.pom_address, imlme->im_macaddr); + pffmlme.pom_id = htole16(0); + pffmlme.pom_state = htole16(PFF_MLME_STATE_ASSOC); + pffmlme.pom_code = htole16(imlme->im_reason); + error = pgt_oid_set(sc, PFF_OID_ASSOCIATE, + &pffmlme, sizeof(pffmlme)); + break; + default: + error = EINVAL; + break; + } + return (error); +} +#endif + +#if 0 +int +pgt_do_mlme_hostap(struct pgt_softc *sc, struct ieee80211req_mlme *imlme) +{ + struct pgt_ieee80211_node *pin; + struct ieee80211com *ic; + int error = 0; + + ic = &sc->sc_ic; + switch (imlme->im_op) { + /* Would IEEE80211_MLME_ASSOC/PFF_MLME_STATE_ASSOC be used for WDS? */ + case IEEE80211_MLME_AUTHORIZE: + pin = (struct pgt_ieee80211_node *)ieee80211_find_node(ic, + imlme->im_macaddr); + if (pin == NULL) { + error = ENOENT; + break; + } + error = pgt_node_set_authorization(sc, pin, + PIN_DOT1X_AUTHORIZED); + ieee80211_release_node(ic, (struct ieee80211_node *)pin); + break; + case IEEE80211_MLME_UNAUTHORIZE: + pin = (struct pgt_ieee80211_node *)ieee80211_find_node(ic, + imlme->im_macaddr); + if (pin == NULL) { + error = ENOENT; + break; + } + error = pgt_node_set_authorization(sc, pin, + PIN_DOT1X_UNAUTHORIZED); + ieee80211_release_node(ic, (struct ieee80211_node *)pin); + break; + default: + error = EINVAL; + break; + } + return (error); +} +#endif + +#if 0 +int +pgt_do_mlme_adhoc(struct pgt_softc *sc, struct ieee80211req_mlme *imlme) +{ + struct pgt_ieee80211_node *pin; + struct ieee80211com *ic; + int error = 0; + + ic = &sc->sc_ic; + switch (imlme->im_op) { + case IEEE80211_MLME_AUTHORIZE: + pin = (struct pgt_ieee80211_node *)ieee80211_find_txnode(ic, + imlme->im_macaddr); + if (pin == NULL) { + error = ENOMEM; + break; + } else if ((struct ieee80211_node *)pin == ic->ic_bss) { + error = EINVAL; + break; + } + error = pgt_node_set_authorization(sc, pin, + PIN_DOT1X_AUTHORIZED); + ieee80211_release_node(ic, (struct ieee80211_node *)pin); + break; + case IEEE80211_MLME_UNAUTHORIZE: + pin = (struct pgt_ieee80211_node *)ieee80211_find_txnode(ic, + imlme->im_macaddr); + if (pin == NULL) { + error = ENOMEM; + break; + } else if ((struct ieee80211_node *)pin == ic->ic_bss) { + error = EINVAL; + break; + } + error = pgt_node_set_authorization(sc, pin, + PIN_DOT1X_UNAUTHORIZED); + ieee80211_release_node(ic, (struct ieee80211_node *)pin); + break; + default: + error = EINVAL; + break; + } + return (error); +} +#endif + +#if 0 +int +pgt_80211_set(struct pgt_softc *sc, struct ieee80211req *ireq) +{ + //struct ieee80211req_mlme mlme; + struct ieee80211com *ic; + int error; + + ic = &sc->sc_ic; + switch (ireq->i_type) { + /* + * These are 802.11 requests we want to let fall through to + * net80211 but do not need a reset afterward. + */ + case IEEE80211_POWERSAVE_OFF: + case IEEE80211_POWERSAVE_ON: + error = ieee80211_ioctl(&ic->ic_if, SIOCS80211, + (caddr_t)ireq); + if (error == ENETRESET) + error = 0; + break; + /* + * These are 802.11 requests we want to let fall through to + * net80211 but then use their results without doing a full + * reset afterward. + */ + case IEEE80211_IOC_WEPKEY: + case IEEE80211_IOC_WEPTXKEY: + error = ieee80211_ioctl(&ic->ic_if, SIOCS80211, (caddr_t)ireq); + if (error == ENETRESET) { + //mtx_lock(&sc->sc_lock); + pgt_update_hw_from_sw(sc, + ic->ic_state != IEEE80211_S_INIT, + ic->ic_opmode != IEEE80211_M_MONITOR); + error = 0; + //mtx_unlock(&sc->sc_lock); + } + break; + case IEEE80211_IOC_WEP: + switch (ireq->i_val) { + case IEEE80211_WEP_OFF: + case IEEE80211_WEP_ON: + case IEEE80211_WEP_MIXED: + error = 0; + break; + default: + error = EINVAL; + } + if (error) + break; + //mtx_lock(&sc->sc_lock); + if (sc->sc_80211_ioc_wep != ireq->i_val) { + sc->sc_80211_ioc_wep = ireq->i_val; + pgt_update_hw_from_sw(sc, 0, + ic->ic_opmode != IEEE80211_M_MONITOR); + error = 0; + } else + error = 0; + //mtx_unlock(&sc->sc_lock); + break; + case IEEE80211_IOC_AUTHMODE: + switch (ireq->i_val) { + case IEEE80211_AUTH_NONE: + case IEEE80211_AUTH_OPEN: + case IEEE80211_AUTH_SHARED: + error = 0; + break; + default: + error = EINVAL; + } + if (error) + break; + //mtx_lock(&sc->sc_lock); + if (sc->sc_80211_ioc_auth != ireq->i_val) { + sc->sc_80211_ioc_auth = ireq->i_val; + pgt_update_hw_from_sw(sc, 0, 0); + error = 0; + } else + error = 0; + //mtx_unlock(&sc->sc_lock); + break; + case IEEE80211_IOC_MLME: + if (ireq->i_len != sizeof(mlme)) { + error = EINVAL; + break; + } + error = copyin(ireq->i_data, &mlme, sizeof(mlme)); + if (error) + break; + //mtx_lock(&sc->sc_lock); + pgt_enter_critical(sc); + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + //error = pgt_do_mlme_sta(sc, &mlme); + break; + case IEEE80211_M_HOSTAP: + //error = pgt_do_mlme_hostap(sc, &mlme); + break; + case IEEE80211_M_IBSS: + //error = pgt_do_mlme_adhoc(sc, &mlme); + break; + default: + error = EINVAL; + break; + } + pgt_exit_critical(sc); + //mtx_unlock(&sc->sc_lock); + if (error == 0) + error = copyout(&mlme, ireq->i_data, sizeof(mlme)); + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} +#endif + +int +pgt_wavelan_get(struct pgt_softc *sc, struct wi_req *wreq) +{ + struct ieee80211com *ic; + struct pgt_obj_bsslist *pob; + struct wi_scan_p2_hdr *p2hdr; + struct wi_scan_res *scan; + uint32_t noise; + unsigned int maxscan, i; + int error; + + ic = &sc->sc_ic; + switch (wreq->wi_type) { + case WI_RID_COMMS_QUALITY: + wreq->wi_val[0] = 0; /* don't know correction factor */ + wreq->wi_val[1] = htole16(ic->ic_node_getrssi(ic, ic->ic_bss)); + wreq->wi_val[2] = htole16(sc->sc_noise); + wreq->wi_len = 4; + error = 0; + break; + case WI_RID_SCAN_RES: + maxscan = PFF_OBJ_BSSLIST_NBSS; + pob = malloc(sizeof(*pob) + + sizeof(struct pgt_obj_bss) * maxscan, M_DEVBUF, M_WAITOK); + //mtx_lock(&sc->sc_lock); + pgt_enter_critical(sc); + error = pgt_oid_get(sc, PFF_OID_NOISE_FLOOR, &noise, + sizeof(noise)); + if (error == 0) { + noise = letoh32(noise); + error = pgt_oid_get(sc, PFF_OID_BSS_LIST, pob, + sizeof(*pob) + + sizeof(struct pgt_obj_bss) * maxscan); + } + if (error == 0) { + maxscan = min(PFF_OBJ_BSSLIST_NBSS, + letoh32(pob->pob_count)); + maxscan = min(maxscan, + (sizeof(wreq->wi_val) - sizeof(*p2hdr)) / + WI_PRISM2_RES_SIZE); + p2hdr = (struct wi_scan_p2_hdr *)&wreq->wi_val; + p2hdr->wi_rsvd = 0; + p2hdr->wi_reason = 1; /* what should it be? */ + for (i = 0; i < maxscan; i++) { + scan = (struct wi_scan_res *) + ((char *)&wreq->wi_val + sizeof(*p2hdr) + + i * WI_PRISM2_RES_SIZE); + pgt_obj_bss2scanres(sc, &pob->pob_bsslist[i], + scan, noise); + } + wreq->wi_len = (maxscan * WI_PRISM2_RES_SIZE) / 2 + + sizeof(*p2hdr) / 2; + } + pgt_exit_critical(sc); + //mtx_unlock(&sc->sc_lock); + free(pob, M_DEVBUF); + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +int +pgt_wavelan_set(struct pgt_softc *sc, struct wi_req *wreq) +{ + int error; + + /* + * If we wanted to, we could support the "partial reset" interface + * here, but the Wavelan interface should really not need to be used. + */ + switch (wreq->wi_type) { + case WI_RID_SCAN_REQ: + case WI_RID_SCAN_APS: + /* We're always scanning. */ + error = 0; + break; + case WI_RID_CHANNEL_LIST: + /* The user can just use net80211's interface. */ + error = EPERM; + break; + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +void +node_mark_active_ap(void *arg, struct ieee80211_node *ni) +{ + /* + * HostAP mode lets all nodes stick around unless + * the firmware AP kicks them off. + */ + ni->ni_inact = 0; +} + +void +node_mark_active_adhoc(void *arg, struct ieee80211_node *ni) +{ + struct pgt_ieee80211_node *pin; + + /* + * As there is no association in ad-hoc, we let links just + * time out naturally as long they are not holding any private + * configuration, such as 802.1x authorization. + */ + pin = (struct pgt_ieee80211_node *)ni; + if (pin->pin_dot1x_auth == PIN_DOT1X_AUTHORIZED) + pin->pin_node.ni_inact = 0; +} + +void +pgt_periodic(struct ifnet *ifp) +{ + struct pgt_softc *sc; + + sc = ifp->if_softc; + //mtx_lock(&sc->sc_lock); + /* + * Check for timed out transmissions (and make sure to set + * this watchdog to fire again if there is still data in the + * output device queue). + */ + if (sc->sc_dirtyq_count[PFF_QUEUE_DATA_LOW_TX] != 0) { + struct bintime txtime; + int count; + + ifp->if_timer = 1; + //getbinuptime(&txtime); + bintime_sub(&txtime, &sc->sc_data_tx_started); + if (txtime.sec >= 1) { + count = pgt_drain_tx_queue(sc, PFF_QUEUE_DATA_LOW_TX); + if (sc->sc_flags & SC_DEBUG_UNEXPECTED) + printf("%s: timed out %d data transmissions\n", + sc->sc_dev.dv_xname, count); + } + } + if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET)) { + //mtx_unlock(&sc->sc_lock); + return; + } + /* + * If we're goign to kick the device out of power-save mode + * just to update the BSSID and such, we should not do it + * very often; need to determine in what way to do that. + */ + if (ifp->if_flags & IFF_RUNNING && + sc->sc_ic.ic_state != IEEE80211_S_INIT && + sc->sc_ic.ic_opmode != IEEE80211_M_MONITOR) +#ifdef DEVICE_POLLING + if (!(ifp->if_flags & IFF_POLLING)) +#endif + pgt_async_update(sc); + /* + * As a firmware-based HostAP, we should not time out + * nodes inside the driver additionally to the timeout + * that exists in the firmware. The only things we + * should have to deal with timing out when doing HostAP + * are the privacy-related. + */ + switch (sc->sc_ic.ic_opmode) { + case IEEE80211_M_HOSTAP: + ieee80211_iterate_nodes(&sc->sc_ic, + node_mark_active_ap, NULL); + break; + case IEEE80211_M_IBSS: + ieee80211_iterate_nodes(&sc->sc_ic, + node_mark_active_adhoc, NULL); + break; + default: + break; + } + ieee80211_watchdog(ifp); + ifp->if_timer = 1; + //mtx_unlock(&sc->sc_lock); +} + +int +pgt_init(struct ifnet *ifp) +{ + struct pgt_softc *sc = ifp->if_softc; + struct ieee80211com *ic; + + ic = &sc->sc_ic; + //mtx_lock(&sc->sc_lock); + if (!(sc->sc_flags & (SC_DYING | SC_UNINITIALIZED))) + pgt_update_hw_from_sw(sc, + ic->ic_state != IEEE80211_S_INIT, + ic->ic_opmode != IEEE80211_M_MONITOR); + //mtx_unlock(&sc->sc_lock); + + return (0); +} + +/* + * After most every configuration change, everything needs to be fully + * reinitialized. For some operations (currently, WEP settings + * in ad-hoc+802.1x mode), the change is "soft" and doesn't remove + * "associations," and allows EAP authorization to occur again. + * If keepassoc is specified, the reset operation should try to go + * back to the BSS had before. + */ +void +pgt_update_hw_from_sw(struct pgt_softc *sc, int keepassoc, int keepnodes) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct arpcom *ac = &ic->ic_ac; + struct ifnet *ifp = &ac->ac_if; + struct pgt_obj_key keyobj; + struct pgt_obj_ssid essid; + uint8_t availrates[IEEE80211_RATE_MAXSIZE + 1]; + uint32_t mode, bsstype, config, profile, channel, slot, preamble, + wep, exunencrypted, wepkey, dot1x, auth, mlme; + unsigned int i; + int success, shouldbeup; + + config = PFF_CONFIG_MANUAL_RUN | PFF_CONFIG_RX_ANNEX; + /* + * Promiscuous mode is currently a no-op since packets transmitted, + * while in promiscuous mode, don't ever seem to go anywhere. + */ + shouldbeup = ifp->if_flags & IFF_RUNNING && ifp->if_flags & IFF_UP; + if (shouldbeup) { + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + if (ifp->if_flags & IFF_PROMISC) + mode = PFF_MODE_CLIENT; /* what to do? */ + else + mode = PFF_MODE_CLIENT; + bsstype = PFF_BSS_TYPE_STA; + dot1x = PFF_DOT1X_AUTH_ENABLED; + break; + case IEEE80211_M_IBSS: + if (ifp->if_flags & IFF_PROMISC) + mode = PFF_MODE_CLIENT; /* what to do? */ + else + mode = PFF_MODE_CLIENT; + bsstype = PFF_BSS_TYPE_IBSS; + dot1x = PFF_DOT1X_AUTH_ENABLED; + break; + case IEEE80211_M_HOSTAP: + mode = PFF_MODE_AP; + bsstype = PFF_BSS_TYPE_STA; + /* + * For IEEE 802.1x, we need to authenticate and + * authorize hosts from here on or they remain + * associated but without the ability to send or + * receive normal traffic to us (courtesy the + * firmware AP implementation). + */ + dot1x = PFF_DOT1X_AUTH_ENABLED; + /* + * WDS mode needs several things to work: + * discovery of exactly how creating the WDS + * links is meant to function, an interface + * for this, and ability to encode or decode + * the WDS frames. + */ + if (sc->sc_wds) + config |= PFF_CONFIG_WDS; + break; + case IEEE80211_M_MONITOR: + mode = PFF_MODE_PROMISCUOUS; + bsstype = PFF_BSS_TYPE_ANY; + dot1x = PFF_DOT1X_AUTH_NONE; + break; + default: + goto badopmode; + } + } else { +badopmode: + mode = PFF_MODE_CLIENT; + bsstype = PFF_BSS_TYPE_NONE; + } + switch (ic->ic_curmode) { + case IEEE80211_MODE_11A: + profile = PFF_PROFILE_A_ONLY; + preamble = PFF_OID_PREAMBLE_MODE_DYNAMIC; + break; + case IEEE80211_MODE_11B: + profile = PFF_PROFILE_B_ONLY; + preamble = PFF_OID_PREAMBLE_MODE_LONG; + break; + case IEEE80211_MODE_11G: + profile = PFF_PROFILE_G_ONLY; + preamble = PFF_OID_PREAMBLE_MODE_SHORT; + break; + case IEEE80211_MODE_FH: + case IEEE80211_MODE_TURBO: + /* not handled */ + case IEEE80211_MODE_AUTO: + profile = PFF_PROFILE_MIXED_G_WIFI; + preamble = PFF_OID_PREAMBLE_MODE_DYNAMIC; + break; + default: + panic("unknown mode %d\n", ic->ic_curmode); + } + switch (sc->sc_80211_ioc_auth) { + case IEEE80211_AUTH_NONE: + auth = PFF_AUTH_MODE_NONE; + break; + case IEEE80211_AUTH_OPEN: + auth = PFF_AUTH_MODE_OPEN; + break; + default: + auth = PFF_AUTH_MODE_SHARED; + break; + } + switch (sc->sc_80211_ioc_wep) { + case IEEE80211_WEP_OFF: + wep = 0; + exunencrypted = 0; + break; + case IEEE80211_WEP_MIXED: + wep = 1; + exunencrypted = 0; + break; + case IEEE80211_WEP_ON: + default: + wep = 1; + exunencrypted = 1; + break; + } + mlme = htole32(PFF_MLME_AUTO_LEVEL_AUTO); + wep = htole32(wep); + exunencrypted = htole32(exunencrypted); + profile = htole32(profile); + preamble = htole32(preamble); + bsstype = htole32(bsstype); + config = htole32(config); + mode = htole32(mode); + if (!wep || !sc->sc_dot1x) + dot1x = PFF_DOT1X_AUTH_NONE; + dot1x = htole32(dot1x); + auth = htole32(auth); + if (ic->ic_flags & IEEE80211_F_SHSLOT) + slot = htole32(PFF_OID_SLOT_MODE_SHORT); + else + slot = htole32(PFF_OID_SLOT_MODE_DYNAMIC); + if (ic->ic_des_chan == IEEE80211_CHAN_ANYC) { + if (keepassoc) + channel = htole32(ieee80211_chan2ieee(ic, + ic->ic_bss->ni_chan)); + else + channel = 0; + } else { + channel = htole32(ieee80211_chan2ieee(ic, ic->ic_des_chan)); + } + for (i = 0; i < ic->ic_sup_rates[ic->ic_curmode].rs_nrates; i++) + availrates[i] = ic->ic_sup_rates[ic->ic_curmode].rs_rates[i]; + availrates[i++] = 0; + essid.pos_length = min(ic->ic_des_esslen, sizeof(essid.pos_ssid)); + memcpy(&essid.pos_ssid, ic->ic_des_essid, essid.pos_length); + pgt_enter_critical(sc); + for (success = 0; success == 0; success = 1) { +#define SETOID(oid, var, size) { \ + if (pgt_oid_set(sc, oid, var, size) != 0) \ + break; \ +} + SETOID(PFF_OID_PROFILE, &profile, sizeof(profile)); + SETOID(PFF_OID_CONFIG, &config, sizeof(config)); + SETOID(PFF_OID_MLME_AUTO_LEVEL, &mlme, sizeof(mlme)); + if (!IEEE80211_ADDR_EQ(ic->ic_myaddr, ac->ac_enaddr)) { + SETOID(PFF_OID_MAC_ADDRESS, ac->ac_enaddr, + sizeof(ac->ac_enaddr)); + IEEE80211_ADDR_COPY(ic->ic_myaddr, ac->ac_enaddr); + } + SETOID(PFF_OID_MODE, &mode, sizeof(mode)); + SETOID(PFF_OID_BSS_TYPE, &bsstype, sizeof(bsstype)); + if (channel != 0) + SETOID(PFF_OID_CHANNEL, &channel, sizeof(channel)); + if (ic->ic_flags & IEEE80211_F_DESBSSID) { + SETOID(PFF_OID_BSSID, ic->ic_des_bssid, + sizeof(ic->ic_des_bssid)); + } else if (keepassoc) { + SETOID(PFF_OID_BSSID, ic->ic_bss->ni_bssid, + sizeof(ic->ic_bss->ni_bssid)); + } + SETOID(PFF_OID_SSID, &essid, sizeof(essid)); + if (ic->ic_des_esslen > 0) + SETOID(PFF_OID_SSID_OVERRIDE, &essid, sizeof(essid)); + SETOID(PFF_OID_RATES, &availrates, i); + SETOID(PFF_OID_EXTENDED_RATES, &availrates, i); + SETOID(PFF_OID_PREAMBLE_MODE, &preamble, sizeof(preamble)); + SETOID(PFF_OID_SLOT_MODE, &slot, sizeof(slot)); + SETOID(PFF_OID_AUTH_MODE, &auth, sizeof(auth)); + SETOID(PFF_OID_EXCLUDE_UNENCRYPTED, &exunencrypted, + sizeof(exunencrypted)); + SETOID(PFF_OID_DOT1X, &dot1x, sizeof(dot1x)); + SETOID(PFF_OID_PRIVACY_INVOKED, &wep, sizeof(wep)); + if (letoh32(wep) != 0) { + keyobj.pok_type = PFF_OBJ_KEY_TYPE_WEP; + keyobj.pok_length = min(sizeof(keyobj.pok_key), + IEEE80211_KEYBUF_SIZE); + keyobj.pok_length = min(keyobj.pok_length, + ic->ic_nw_keys[0].wk_len); + bcopy(ic->ic_nw_keys[0].wk_key, keyobj.pok_key, + keyobj.pok_length); + SETOID(PFF_OID_DEFAULT_KEY0, &keyobj, sizeof(keyobj)); + keyobj.pok_length = min(sizeof(keyobj.pok_key), + IEEE80211_KEYBUF_SIZE); + keyobj.pok_length = min(keyobj.pok_length, + ic->ic_nw_keys[1].wk_len); + bcopy(ic->ic_nw_keys[1].wk_key, keyobj.pok_key, + keyobj.pok_length); + SETOID(PFF_OID_DEFAULT_KEY1, &keyobj, sizeof(keyobj)); + keyobj.pok_length = min(sizeof(keyobj.pok_key), + IEEE80211_KEYBUF_SIZE); + keyobj.pok_length = min(keyobj.pok_length, + ic->ic_nw_keys[2].wk_len); + bcopy(ic->ic_nw_keys[2].wk_key, keyobj.pok_key, + keyobj.pok_length); + SETOID(PFF_OID_DEFAULT_KEY2, &keyobj, sizeof(keyobj)); + keyobj.pok_length = min(sizeof(keyobj.pok_key), + IEEE80211_KEYBUF_SIZE); + keyobj.pok_length = min(keyobj.pok_length, + ic->ic_nw_keys[3].wk_len); + bcopy(ic->ic_nw_keys[3].wk_key, keyobj.pok_key, + keyobj.pok_length); + SETOID(PFF_OID_DEFAULT_KEY3, &keyobj, sizeof(keyobj)); + wepkey = htole32(ic->ic_wep_txkey); + SETOID(PFF_OID_DEFAULT_KEYNUM, &wepkey, sizeof(wepkey)); + } + /* set mode again to commit */ + SETOID(PFF_OID_MODE, &mode, sizeof(mode)); +#undef SETOID + } + pgt_exit_critical(sc); + if (success) { + if (shouldbeup && keepnodes) + sc->sc_flags |= SC_NOFREE_ALLNODES; + if (shouldbeup) + ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); + else + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); + } else { + printf("%s: problem setting modes\n", sc->sc_dev.dv_xname); + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); + } +} + +/* + * After doing a soft-reinitialization, we will restore settings from + * our pgt_ieee80211_nodes. As we also lock the node list with our + * softc mutex, unless we were to drop that the node list will remain + * valid (see pgt_periodic()). + */ +void +pgt_update_hw_from_nodes(struct pgt_softc *sc) +{ + struct pgt_ieee80211_node *pin; + //struct ieee80211_node *ni; + struct pgt_ieee80211_node **addresses; + size_t i, n; + + n = 0; + /* + TAILQ_FOREACH(ni, &sc->sc_ic.ic_node, ni_list) { + pin = (struct pgt_ieee80211_node *)ni; + if (pin->pin_dot1x_auth != pin->pin_dot1x_auth_desired) + n++; + } + */ + if (n == 0) + return; + addresses = malloc(sizeof(*addresses) * n, M_DEVBUF, M_NOWAIT); + if (addresses == NULL) + return; + n = 0; + /* + TAILQ_FOREACH(ni, &sc->sc_ic.ic_node, ni_list) { + pin = (struct pgt_ieee80211_node *)ni; + if (pin->pin_dot1x_auth != pin->pin_dot1x_auth_desired) { + addresses[n++] = pin; + ieee80211_ref_node(&pin->pin_node); + } + } + */ + pgt_enter_critical(sc); + for (i = 0; i < n; i++) { + pin = addresses[i]; + if (pgt_oid_set(sc, + pin->pin_dot1x_auth_desired == PIN_DOT1X_AUTHORIZED ? + PFF_OID_EAPAUTHSTA : PFF_OID_EAPUNAUTHSTA, + pin->pin_node.ni_macaddr, sizeof(pin->pin_node.ni_macaddr)) + == 0) { + pin->pin_dot1x_auth = pin->pin_dot1x_auth_desired; + IEEE80211_DPRINTF(("%s: %02x:%02x:%02x:%02x:%02x:%02x " + "reauthorized to %d\n", __func__, + pin->pin_node.ni_macaddr[0], + pin->pin_node.ni_macaddr[1], + pin->pin_node.ni_macaddr[2], + pin->pin_node.ni_macaddr[3], + pin->pin_node.ni_macaddr[4], + pin->pin_node.ni_macaddr[5], + pin->pin_dot1x_auth)); + } + ieee80211_release_node(&sc->sc_ic, &pin->pin_node); + } + pgt_exit_critical(sc); + free(addresses, M_DEVBUF); +} + +void +pgt_hostap_handle_mlme(struct pgt_softc *sc, uint32_t oid, + struct pgt_obj_mlme *mlme) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct pgt_ieee80211_node *pin; + struct ieee80211_node *ni; + + ni = ieee80211_find_node(ic, mlme->pom_address); + pin = (struct pgt_ieee80211_node *)ni; + switch (oid) { + case PFF_OID_DISASSOCIATE: + if (ni != NULL) + ieee80211_release_node(&sc->sc_ic, ni); + break; + case PFF_OID_ASSOCIATE: + if (ni == NULL) { + ni = ieee80211_dup_bss(ic, mlme->pom_address); + if (ni == NULL) + break; + ic->ic_newassoc(ic, ni, 1); + pin = (struct pgt_ieee80211_node *)ni; + } + ni->ni_associd = letoh16(mlme->pom_id); + pin->pin_mlme_state = letoh16(mlme->pom_state); + break; + default: + if (pin != NULL) + pin->pin_mlme_state = letoh16(mlme->pom_state); + break; + } +} + +/* + * Either in response to an event or after a certain amount of time, + * synchronize our idea of the network we're part of from the hardware. + */ +void +pgt_update_sw_from_hw(struct pgt_softc *sc, struct pgt_async_trap *pa, + struct mbuf *args) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct pgt_obj_ssid ssid; + struct pgt_obj_bss bss; + uint32_t channel, noise, ls; + int error; + + if (pa != NULL) { +#if 0 + struct pgt_obj_mlmeex *mlmeex; +#endif + struct pgt_obj_mlme *mlme; + uint32_t oid; + + oid = *mtod(args, uint32_t *); + m_adj(args, sizeof(uint32_t)); + if (sc->sc_debug & SC_DEBUG_TRAP) + printf("%s: trap: oid 0x%x len %u\n", + sc->sc_dev.dv_xname, oid, args->m_len); + switch (oid) { + case PFF_OID_LINK_STATE: + if (args->m_len < sizeof(uint32_t)) + break; + ls = letoh32(*mtod(args, uint32_t *)); + if (sc->sc_debug & (SC_DEBUG_TRAP | SC_DEBUG_LINK)) + printf("%s: link: %u\n", + sc->sc_dev.dv_xname, ls); + if (ls) + ieee80211_new_state(ic, IEEE80211_S_RUN, -1); + else + ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); + goto gotlinkstate; + case PFF_OID_DEAUTHENTICATE: + case PFF_OID_AUTHENTICATE: + case PFF_OID_DISASSOCIATE: + case PFF_OID_ASSOCIATE: + if (args->m_len < sizeof(struct pgt_obj_mlme)) + break; + mlme = mtod(args, struct pgt_obj_mlme *); + if (sc->sc_debug & SC_DEBUG_TRAP) + printf("%s: mlme: address " + "%02x:%02x:%02x:%02x:%02x:%02x " + "id 0x%02x state 0x%02x code 0x%02x\n", + sc->sc_dev.dv_xname, + mlme->pom_address[0], mlme->pom_address[1], + mlme->pom_address[2], mlme->pom_address[3], + mlme->pom_address[4], mlme->pom_address[5], + letoh16(mlme->pom_id), + letoh16(mlme->pom_state), + letoh16(mlme->pom_code)); + if (ic->ic_opmode == IEEE80211_M_HOSTAP) + pgt_hostap_handle_mlme(sc, oid, mlme); + break; + } + return; + } + if (ic->ic_state == IEEE80211_S_SCAN) { + pgt_enter_critical(sc); + error = pgt_oid_get(sc, PFF_OID_LINK_STATE, &ls, sizeof(ls)); + pgt_exit_critical(sc); + if (error) + return; + if (letoh32(ls) != 0) + ieee80211_new_state(ic, IEEE80211_S_RUN, -1); + } +gotlinkstate: + pgt_enter_critical(sc); + if (pgt_oid_get(sc, PFF_OID_NOISE_FLOOR, &noise, sizeof(noise)) != 0) + goto out; + sc->sc_noise = letoh32(noise); + if (ic->ic_state == IEEE80211_S_RUN) { + if (pgt_oid_get(sc, PFF_OID_CHANNEL, &channel, + sizeof(channel)) != 0) + goto out; + channel = min(letoh32(channel), IEEE80211_CHAN_MAX); + ic->ic_bss->ni_chan = &ic->ic_channels[channel]; + if (pgt_oid_get(sc, PFF_OID_BSSID, ic->ic_bss->ni_bssid, + sizeof(ic->ic_bss->ni_bssid)) != 0) + goto out; + IEEE80211_ADDR_COPY(&bss.pob_address, ic->ic_bss->ni_bssid); + error = pgt_oid_retrieve(sc, PFF_OID_BSS_FIND, &bss, + sizeof(bss)); + if (error == 0) + ic->ic_bss->ni_rssi = bss.pob_rssi; + else if (error != EPERM) + goto out; + error = pgt_oid_get(sc, PFF_OID_SSID, &ssid, sizeof(ssid)); + if (error) + goto out; + ic->ic_bss->ni_esslen = min(ssid.pos_length, + sizeof(ic->ic_bss->ni_essid)); + memcpy(ic->ic_bss->ni_essid, ssid.pos_ssid, + ssid.pos_length); + } +out: + pgt_exit_critical(sc); +} + +int +pgt_media_change(struct ifnet *ifp) +{ + struct pgt_softc *sc = ifp->if_softc; + int error; + + error = ieee80211_media_change(ifp); + if (error == ENETRESET) { + //mtx_lock(&sc->sc_lock); + pgt_update_hw_from_sw(sc, 0, 0); + //mtx_unlock(&sc->sc_lock); + error = 0; + } + return (error); +} + +void +pgt_media_status(struct ifnet *ifp, struct ifmediareq *imr) +{ + struct pgt_softc *sc = ifp->if_softc; + struct ieee80211com *ic = &sc->sc_ic; + uint32_t ls; + int alreadylocked; + + imr->ifm_active = IFM_IEEE80211; + if (!(ifp->if_flags & IFF_UP)) + return; + //alreadylocked = mtx_owned(&sc->sc_lock); + alreadylocked = 0; + if (!alreadylocked) + alreadylocked = 0; + //mtx_lock(&sc->sc_lock); + imr->ifm_status = IFM_AVALID; + pgt_enter_critical(sc); + if (pgt_oid_get(sc, PFF_OID_LINK_STATE, &ls, sizeof(ls)) != 0) { + imr->ifm_active |= IFM_NONE; + imr->ifm_status = 0; + goto out; + } + ls = letoh32(ls); + if (sc->sc_debug & SC_DEBUG_LINK) + printf("%s: link: %u\n", sc->sc_dev.dv_xname, ls); + if (ls == 0) { + imr->ifm_active |= IFM_NONE; + imr->ifm_status = 0; + goto out; + } + if (ic->ic_state != IEEE80211_S_INIT) + imr->ifm_status |= IFM_ACTIVE; + /* XXX query the PHY "mode"? */ + imr->ifm_active |= ieee80211_rate2media(ic, ls, IEEE80211_MODE_AUTO); + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + break; + case IEEE80211_M_IBSS: + imr->ifm_active |= IFM_IEEE80211_ADHOC; + break; + case IEEE80211_M_AHDEMO: + imr->ifm_active |= IFM_IEEE80211_ADHOC | IFM_FLAG0; + break; + case IEEE80211_M_HOSTAP: + imr->ifm_active |= IFM_IEEE80211_HOSTAP; + break; + case IEEE80211_M_MONITOR: + imr->ifm_active |= IFM_IEEE80211_MONITOR; + break; + } +out: + pgt_exit_critical(sc); + if (!alreadylocked) + //mtx_unlock(&sc->sc_lock); + alreadylocked = 0; +} + +/* + * Synchronization here is due to the softc lock being held when called. + */ +int +pgt_new_state(struct ieee80211com *ic, enum ieee80211_state nstate, + int mgtdata) +{ + struct pgt_softc *sc; + enum ieee80211_state ostate; + + sc = (struct pgt_softc *)ic->ic_if.if_softc; + ostate = ic->ic_state; + IEEE80211_DPRINTF(("%s: %s -> %s\n", __func__, + ieee80211_state_name[ostate], ieee80211_state_name[nstate])); + switch (nstate) { + case IEEE80211_S_INIT: + if (sc->sc_dirtyq_count[PFF_QUEUE_DATA_LOW_TX] == 0) + ic->ic_if.if_timer = 0; + ic->ic_mgt_timer = 0; + ic->ic_flags &= ~IEEE80211_F_SIBSS; + //IF_DRAIN(&ic->ic_mgtq); + if (ic->ic_wep_ctx != NULL) { + free(ic->ic_wep_ctx, M_DEVBUF); + ic->ic_wep_ctx = NULL; + } + ieee80211_free_allnodes(ic); + ic->ic_state = nstate; + break; + case IEEE80211_S_SCAN: + ic->ic_if.if_timer = 1; + ic->ic_mgt_timer = 0; + if (sc->sc_flags & SC_NOFREE_ALLNODES) { + //struct ieee80211_node *ni; + //struct pgt_ieee80211_node *pin; + + /* Locked already by pff mutex. */ + /* + TAILQ_FOREACH(ni, &ic->ic_node, ni_list) { + pin = (struct pgt_ieee80211_node *)ni; + pin->pin_dot1x_auth = PIN_DOT1X_UNAUTHORIZED; + } + */ + sc->sc_flags &= ~SC_NOFREE_ALLNODES; + } else { + ieee80211_free_allnodes(ic); + } + ic->ic_state = nstate; + /* Just use any old channel; we override it anyway. */ + if (ic->ic_opmode == IEEE80211_M_HOSTAP) + ieee80211_create_ibss(ic, ic->ic_ibss_chan); + break; + case IEEE80211_S_RUN: + ic->ic_if.if_timer = 1; + ic->ic_mgt_timer = 0; + ic->ic_state = nstate; + pgt_update_hw_from_nodes(sc); + //if (!IFQ_DRV_IS_EMPTY(&ic->ic_if.if_snd)) + // pgt_start_body(sc, ic, &ic->ic_if); + break; + default: + break; + } + return (0); +} + +int +pgt_drain_tx_queue(struct pgt_softc *sc, enum pgt_queue pq) +{ + int wokeup = 0; + + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTDMA2CPU | BUS_DMASYNC_PRECPU2DMA); + sc->sc_cb->pcb_device_curfrag[pq] = + sc->sc_cb->pcb_driver_curfrag[pq]; + /* XXX */ + bus_dmamap_sync(sc->sc_cbdmat, sc->sc_cbdmam, 0, 0, + BUS_DMASYNC_POSTCPU2DMA | BUS_DMASYNC_PREDMA2CPU); + while (!TAILQ_EMPTY(&sc->sc_dirtyq[pq])) { + struct pgt_desc *pd; + + pd = TAILQ_FIRST(&sc->sc_dirtyq[pq]); + TAILQ_REMOVE(&sc->sc_dirtyq[pq], pd, pd_link); + sc->sc_dirtyq_count[pq]--; + TAILQ_INSERT_TAIL(&sc->sc_freeq[pq], pd, pd_link); + sc->sc_freeq_count[pq]++; + pgt_unload_tx_desc_frag(sc, pd); + if (sc->sc_debug & SC_DEBUG_QUEUES) + printf("%s: queue: tx %u <- [%u] (drained)\n", + sc->sc_dev.dv_xname, pd->pd_fragnum, pq); + wokeup++; + if (pgt_queue_is_data(pq)) { + sc->sc_ic.ic_if.if_oerrors++; + //pgt_try_exit_data_critical(sc); + } + } + return (wokeup); +} diff --git a/sys/dev/ic/pgtreg.h b/sys/dev/ic/pgtreg.h new file mode 100644 index 00000000000..8c0d2d44c3a --- /dev/null +++ b/sys/dev/ic/pgtreg.h @@ -0,0 +1,387 @@ +/*- + * Copyright (c) 2004 Fujitsu Laboratories of America, Inc. + * Copyright (c) 2004 Brian Fundakowski Feldman + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* Sizes, delays, etc. */ +#define PFF_TX_LIST_CNT 32 +#define PFF_RX_LIST_CNT 8 +#define PFF_FRAG_SIZE 1536 /* overkill for mgmt frames */ +#define PFF_DIRECT_MEMORY_OFFSET 0x1000 +#define PFF_DIRECT_MEMORY_SIZE 0x1000 +#define PFF_FIRMWARE_INTERNAL_OFFSET 0x20000 +#define PFF_WRITEIO_DELAY 10 +#define PFF_RESET_DELAY 50000 + +/* Registers */ +#define PFF_REG_DEV_INT 0x0000 /* device interrupt */ +#define PFF_DEV_INT_RESET 0x00000001 +#define PFF_DEV_INT_UPDATE 0x00000002 +#define PFF_DEV_INT_WAKEUP 0x00000008 +#define PFF_DEV_INT_SLEEP 0x00000010 +#define PFF_REG_INT_STAT 0x0010 /* interrupt status */ +#define PFF_INT_STAT_UPDATE 0x00000002 +#define PFF_INT_STAT_INIT 0x00000004 +#define PFF_INT_STAT_WAKEUP 0x00000008 +#define PFF_INT_STAT_SLEEP 0x00000010 +#define PFF_INT_STAT_UNKNOWN0 0x00004000 +#define PFF_INT_STAT_UNKNOWN1 0x80000000 +#define PFF_INT_STAT_SOURCES 0x8000401e +#define PFF_REG_INT_ACK 0x0014 /* interrupt acknowledgement */ +#define PFF_REG_INT_EN 0x0018 /* interrupt enable */ +#define PFF_REG_CTRL_BLK_BASE 0x0020 /* control block address */ +#define PFF_REG_GEN_PURP_COM 0x0024 +#define PFF_REG_DIR_MEM_BASE 0x0030 +#define PFF_REG_CTRL_STAT 0x0078 +#define PFF_CTRL_STAT_SLEEPMODE 0x00000200 +#define PFF_CTRL_STAT_CLOCKRUN 0x00800000 +#define PFF_CTRL_STAT_RESET 0x10000000 +#define PFF_CTRL_STAT_RAMBOOT 0x20000000 +#define PFF_CTRL_STAT_STARTHALTED 0x40000000 +#define PFF_CTRL_STAT_HOST_OVERRIDE 0x80000000 + +/* + * The control block consists of a set of queues for low- and high-priority + * data, and management, transmission and reception queues. There is a + * set of indices that gives the index (modulo queue size) of the current + * progress in each. Nearly all configuration is done from the management + * queue interface. Almost every structure is little-endian. + */ +enum pgt_queue { + PFF_QUEUE_DATA_LOW_RX = 0, + PFF_QUEUE_DATA_LOW_TX = 1, + PFF_QUEUE_DATA_HIGH_RX = 2, + PFF_QUEUE_DATA_HIGH_TX = 3, + PFF_QUEUE_MGMT_RX = 4, + PFF_QUEUE_MGMT_TX = 5 +}; + +#define PFF_QUEUE_COUNT 6 +#define PFF_QUEUE_DATA_RX_SIZE 8 +#define PFF_QUEUE_DATA_TX_SIZE 32 +#define PFF_QUEUE_MGMT_SIZE 4 +#define PFF_QUEUE_FULL_THRESHOLD 8 + +struct pgt_frag { + uint32_t pf_addr; /* physical host address */ + uint16_t pf_size; + uint16_t pf_flags; +#define PF_FLAG_MF 0x0001 /* more frags (frame continues) */ +}; + +struct pgt_control_block { + uint32_t pcb_driver_curfrag[PFF_QUEUE_COUNT]; + uint32_t pcb_device_curfrag[PFF_QUEUE_COUNT]; + struct pgt_frag pcb_data_low_rx[PFF_QUEUE_DATA_RX_SIZE]; + struct pgt_frag pcb_data_low_tx[PFF_QUEUE_DATA_TX_SIZE]; + struct pgt_frag pcb_data_high_rx[PFF_QUEUE_DATA_RX_SIZE]; + struct pgt_frag pcb_data_high_tx[PFF_QUEUE_DATA_TX_SIZE]; + struct pgt_frag pcb_mgmt_rx[PFF_QUEUE_MGMT_SIZE]; + struct pgt_frag pcb_mgmt_tx[PFF_QUEUE_MGMT_SIZE]; + uint32_t pcb_padding; +}; + +/* + * Unlike the rest of the structures, this is big-endian by default. + * The Linux driver defines a PIMFOR_ETHERTYPE as 0x8828 (why?) + */ +enum pgt_mgmt_operation { + PMF_OP_GET = 0, + PMF_OP_SET = 1, + PMF_OP_RESPONSE = 2, + PMF_OP_ERROR = 3, + PMF_OP_TRAP = 4 + /* may be more */ +}; + +struct pgt_mgmt_frame { + uint8_t pmf_version; +#define PMF_VER 0x01 + uint8_t pmf_operation; + uint32_t pmf_oid; + uint8_t pmf_device; +#define PMF_DEV 0x00 + uint8_t pmf_flags; +#define PMF_FLAG_APP 0x01 /* application origin (?) */ +#define PMF_FLAG_LE 0x02 /* little-endian */ +#define PMF_FLAG_VALID (PMF_FLAG_APP | PMF_FLAG_LE) + uint32_t pmf_size; + /* data[]; */ +} __attribute__((packed)); + +struct pgt_data_frame { + uint16_t pdf_unknown; /* seems to give status incl. crypto? */ + /* data[]; */ +}; + +struct pgt_rx_annex { + uint8_t pra_ether_dhost[ETHER_ADDR_LEN]; + uint8_t pra_ether_shost[ETHER_ADDR_LEN]; + uint16_t pra_unknown0; /* always 0x0000 */ + uint16_t pra_length; /* always 0x1400 */ + uint32_t pra_clock; /* 1MHz timestamp */ + uint8_t pra_flags; +#define PRA_FLAG_BAD 0x01 + uint8_t pra_unknown1; + uint8_t pra_rate; + uint8_t pra_unknown2; + uint16_t pra_frequency; + uint16_t pra_unknown3; + uint8_t pra_rssi; + uint8_t pra_pad[3]; + uint16_t pra_ether_type; +} __attribute__((packed)); + +/* + * OIDs used to communicate management information. + */ +enum pgt_oid { + PFF_OID_MAC_ADDRESS = 0x00000000, + /* uint8_t ether[6]; */ + PFF_OID_LINK_STATE = 0x00000001, + /* uint32_t rate; (500kbps units) */ + PFF_OID_BSS_TYPE = 0x10000000, + /* uint32_t network; */ +#define PFF_BSS_TYPE_NONE 0 +#define PFF_BSS_TYPE_STA 1 +#define PFF_BSS_TYPE_IBSS 2 +#define PFF_BSS_TYPE_ANY 3 + PFF_OID_BSSID = 0x10000001, + /* uint8_t bssid[6]; */ + PFF_OID_SSID = 0x10000002, + /* struct pgt_obj_ssid; */ + PFF_OID_COUNTRY = 0x10000005, + /* uint32_t country; guessing until I see some foreign hardware... */ +#define PFF_COUNTRY_USA 0 + PFF_OID_SSID_OVERRIDE = 0x10000006, + /* struct pgt_obj_ssid; */ + PFF_OID_AUTH_MODE = 0x12000000, + /* uint32_t auth; */ +#define PFF_AUTH_MODE_NONE 0 +#define PFF_AUTH_MODE_OPEN 1 +#define PFF_AUTH_MODE_SHARED 2 +#define PFF_AUTH_MODE_BOTH 3 + PFF_OID_PRIVACY_INVOKED = 0x12000001, + /* uint32_t privacy; */ + PFF_OID_EXCLUDE_UNENCRYPTED = 0x12000002, + /* uint32_t exunencrypted; */ + PFF_OID_DEFAULT_KEYNUM = 0x12000003, + /* uint32_t defkey; */ + PFF_OID_DEFAULT_KEY0 = 0x12000004, + /* struct pgt_obj_key; */ + PFF_OID_DEFAULT_KEY1 = 0x12000005, + /* struct pgt_obj_key; */ + PFF_OID_DEFAULT_KEY2 = 0x12000006, + /* struct pgt_obj_key; */ + PFF_OID_DEFAULT_KEY3 = 0x12000007, + /* struct pgt_obj_key; */ + PFF_OID_STA_KEY = 0x12000008, + PFF_OID_PSM = 0x14000000, + /* uint32_t powersave; */ + PFF_OID_EAPAUTHSTA = 0x150007de, + /* uint8_t sta[6]; */ + PFF_OID_EAPUNAUTHSTA = 0x150007df, + /* uint8_t sta[6]; */ + PFF_OID_DOT1X = 0x150007e0, + /* uint32_t dot1x; */ +#define PFF_DOT1X_AUTH_NONE 0 +#define PFF_DOT1X_AUTH_ENABLED 1 +#define PFF_DOT1X_KEYTX_ENABLED 2 + PFF_OID_SLOT_TIME = 0x17000000, + /* uint32_t slottime; */ + PFF_OID_CHANNEL = 0x17000007, + /* uint32_t channel; */ + PFF_OID_PREAMBLE_MODE = 0x17000009, + /* uint32_t preamble; */ +#define PFF_OID_PREAMBLE_MODE_LONG 0 +#define PFF_OID_PREAMBLE_MODE_SHORT 1 +#define PFF_OID_PREAMBLE_MODE_DYNAMIC 2 + PFF_OID_RATES = 0x1700000a, + /* uint8_t rates[]; nul terminated */ + PFF_OID_RSSI_VECTOR = 0x1700000d, + PFF_OID_OUTPUT_POWER_TABLE = 0x1700000e, + PFF_OID_OUTPUT_POWER = 0x1700000f, + PFF_OID_SUPPORTED_RATES = 0x17000010, + /* uint8_t rates[]; nul terminated */ + PFF_OID_NOISE_FLOOR = 0x17000013, + /* uint32_t noise; */ + PFF_OID_SLOT_MODE = 0x17000017, + /* uint32_t slot; */ +#define PFF_OID_SLOT_MODE_LONG 0 +#define PFF_OID_SLOT_MODE_SHORT 1 +#define PFF_OID_SLOT_MODE_DYNAMIC 2 + PFF_OID_EXTENDED_RATES = 0x17000020, + /* uint8_t rates[]; nul terminated */ + PFF_OID_FREQUENCY = 0x17000011, + /* uint32_t frequency; */ + PFF_OID_SUPPORTED_FREQUENCIES = 0x17000012, + /* struct pgt_obj_freq; */ + PFF_OID_PROFILE = 0x17000019, + /* uint32_t profile; */ +#define PFF_PROFILE_B_ONLY 0 +#define PFF_PROFILE_MIXED_G_WIFI 1 +#define PFF_PROFILE_MIXED_LONG 2 +#define PFF_PROFILE_G_ONLY 3 +#define PFF_PROFILE_TEST 4 +#define PFF_PROFILE_B_WIFI 5 +#define PFF_PROFILE_A_ONLY 6 +#define PFF_PROFILE_MIXED_SHORT 7 + PFF_OID_DEAUTHENTICATE = 0x18000000, + /* struct pgt_obj_mlme; */ + PFF_OID_AUTHENTICATE = 0x18000001, + /* struct pgt_obj_mlme; */ + PFF_OID_DISASSOCIATE = 0x18000002, + /* struct pgt_obj_mlme; */ + PFF_OID_ASSOCIATE = 0x18000003, + /* struct pgt_obj_mlme; */ + PFF_OID_SCAN = 0x18000004, + PFF_OID_BEACON = 0x18000005, + /* struct pgt_obj_mlmeex; */ + PFF_OID_PROBE = 0x18000006, + /* struct pgt_obj_mlmeex; */ + PFF_OID_DEAUTHENTICATEEX = 0x18000007, + /* struct pgt_obj_mlmeex; */ + PFF_OID_AUTHENTICATEEX = 0x18000008, + /* struct pgt_obj_mlmeex; */ + PFF_OID_DISASSOCIATEEX = 0x18000009, + /* struct pgt_obj_mlmeex; */ + PFF_OID_ASSOCIATEEX = 0x1800000a, + /* struct pgt_obj_mlmeex; */ + PFF_OID_REASSOCIATE = 0x1800000b, + /* struct pgt_obj_mlmeex; */ + PFF_OID_REASSOCIATEEX = 0x1800000c, + /* struct pgt_obj_mlmeex; */ + PFF_OID_MLME_AUTO_LEVEL = 0x19000001, + /* uint32_t mlme; */ +#define PFF_MLME_AUTO_LEVEL_AUTO 0 +#define PFF_MLME_AUTO_LEVEL_INTERMEDIATE 1 +#define PFF_MLME_AUTO_LEVEL_EXTENDED 2 + /* struct pgt_obj_buffer;*/ + PFF_OID_PSM_BUFFER = 0x19000004, + /* struct pgt_obj_buffer;*/ +#define PFF_PSM_BUFFER_FRAME_COUNT 64 + PFF_OID_MAX_FRAME_BURST = 0x1b000008, + /* uint32_t max_usec_grouped; */ + PFF_OID_BSS_FIND = 0x1c000042, + /* struct pgt_obj_bss; */ + PFF_OID_BSS_LIST = 0x1c000043, + /* struct pgt_obj_bsslist; */ + /* Initial settings. */ + PFF_OID_MODE = 0xff020003, + /* uint32_t mode; */ +#define PFF_MODE_PROMISCUOUS 0 +#define PFF_MODE_CLIENT 1 +#define PFF_MODE_AP 2 +#define PFF_MODE_SNIFFER 3 + PFF_OID_CONFIG = 0xff020008, + /* uint32_t flags; */ +#define PFF_CONFIG_MANUAL_RUN 0x00000001 +#define PFF_CONFIG_FRAME_TRAP 0x00000002 +#define PFF_CONFIG_RX_ANNEX 0x00000004 +#define PFF_CONFIG_TX_ANNEX 0x00000008 +#define PFF_CONFIG_WDS 0x00000010 + PFF_OID_PHY = 0xff02000d, +#define PFF_OID_PHY_2400MHZ 0x00000001 +#define PFF_OID_PHY_5000MHZ 0x00000002 +#define PFF_OID_PHY_FAA 0x80000000 +}; + +/* + * Structures used to communicate via the OIDs. + */ +struct pgt_obj_ssid { + uint8_t pos_length; + char pos_ssid[33]; +} __attribute__((packed)); + +struct pgt_obj_key { + uint8_t pok_type; +#define PFF_OBJ_KEY_TYPE_WEP 0 +#define PFF_OBJ_KEY_TYPE_TKIP 1 + uint8_t pok_length; + uint8_t pok_key[32]; +} __attribute__((packed)); + +/* + * Each mlme received includes the current 802.11 state. + */ +#define PFF_MLME_STATE_NONE 0 +#define PFF_MLME_STATE_AUTHING 1 +#define PFF_MLME_STATE_AUTH 2 +#define PFF_MLME_STATE_ASSOCING 3 + +#define PFF_MLME_STATE_ASSOC 5 +#define PFF_MLME_STATE_IBSS 6 +#define PFF_MLME_STATE_WDS 7 + +struct pgt_obj_mlme { + uint8_t pom_address[6]; + uint16_t pom_id; /* 802.11 association number */ + uint16_t pom_state; + uint16_t pom_code; /* 802.11 reason code */ +} __attribute__((packed)); + +struct pgt_obj_mlmeex { + uint8_t pom_address[6]; + uint16_t pom_id; + uint16_t pom_state; + uint16_t pom_code; + uint16_t pom_size; + uint8_t pom_data[0]; +} __attribute__((packed)); + +struct pgt_obj_buffer { + uint32_t pob_size; + uint32_t pob_addr; +} __attribute__((packed)); + +struct pgt_obj_bss { + uint8_t pob_address[6]; + uint16_t pob_padding0; + uint8_t pob_state; + uint8_t pob_reserved; + uint16_t pob_age; + uint8_t pob_quality; + uint8_t pob_rssi; + struct pgt_obj_ssid pob_ssid; + uint16_t pob_channel; + uint8_t pob_beacon_period; + uint8_t pob_dtim_period; + uint16_t pob_capinfo; + uint16_t pob_rates; + uint16_t pob_basic_rates; + uint16_t pob_padding1; +} __attribute__((packed)); + +struct pgt_obj_bsslist { + uint32_t pob_count; + struct pgt_obj_bss pob_bsslist[0]; +#define PFF_OBJ_BSSLIST_NBSS 24 +} __attribute__((packed)); + +struct pgt_obj_frequencies { + uint16_t pof_count; + uint16_t pof_freqlist_mhz[0]; +} __attribute__((packed)); diff --git a/sys/dev/ic/pgtvar.h b/sys/dev/ic/pgtvar.h new file mode 100644 index 00000000000..50a7c06066f --- /dev/null +++ b/sys/dev/ic/pgtvar.h @@ -0,0 +1,205 @@ +/*- + * Copyright (c) 2004 Fujitsu Laboratories of America, Inc. + * Copyright (c) 2004 Brian Fundakowski Feldman + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * The struct pgt_desc is used to either enqueue or dequeue pgt_frags + * (packets) when either free or in flight. + */ +struct pgt_desc { + TAILQ_ENTRY(pgt_desc) pd_link; + void *pd_mem; + bus_addr_t pd_dmaaddr; + bus_dmamap_t pd_dmam; + struct pgt_frag *pd_fragp; + unsigned int pd_fragnum; +}; +TAILQ_HEAD(pgt_descq, pgt_desc); + +/* + * The struct pgt_mgmt_desc is used to enqueue a management request + * and await response. + */ +struct pgt_mgmt_desc { + TAILQ_ENTRY(pgt_mgmt_desc) pmd_link; + const void *pmd_sendbuf; /* NULL = get op */ + void *pmd_recvbuf; /* NULL = set op */ + size_t pmd_len; + uint32_t pmd_oid; + int pmd_error; +}; +TAILQ_HEAD(pgt_mgmt_descq, pgt_mgmt_desc); + +/* + * These events are put on the per-device kthread to be + * able to trigger actions from inside the interrupt; as most + * operations require waiting for another interrupt for response + * (that is, management packets), this is common. + */ +struct pgt_async_trap { + TAILQ_ENTRY(pgt_async_trap) pa_link; + struct mbuf *pa_mbuf; + /* followed by the rest of the mbuf data */ +}; + +struct pgt_ieee80211_node { + struct ieee80211_node pin_node; + enum pin_dot1x_authorization { + PIN_DOT1X_UNAUTHORIZED, + PIN_DOT1X_AUTHORIZED + } pin_dot1x_auth_desired, pin_dot1x_auth; + uint16_t pin_mlme_state; +}; + +struct pgt_ieee80211_radiotap { + struct ieee80211_radiotap_header pir_header; + uint8_t pir_flags; + uint8_t pir_rate; + uint16_t pir_channel; + uint16_t pir_channel_flags; + uint8_t pir_db_antsignal; + uint8_t pir_db_antnoise; +}; +#define PFF_IEEE80211_RADIOTAP_PRESENT \ + (1 << IEEE80211_RADIOTAP_FLAGS | \ + 1 << IEEE80211_RADIOTAP_RATE | \ + 1 << IEEE80211_RADIOTAP_CHANNEL | \ + 1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL | \ + 1 << IEEE80211_RADIOTAP_DB_ANTNOISE) + +struct bpf_if; + +struct pgt_softc { + struct device sc_dev; + struct ieee80211com sc_ic; + struct bpf_if *sc_drvbpf; +// struct mtx sc_lock; + unsigned int sc_flags; +#define SC_NEEDS_FIRMWARE 0x00000001 /* do firmware upload on reset */ +#define SC_UNINITIALIZED 0x00000002 /* still awaiting initial intr */ +#define SC_DYING 0x00000004 /* going away */ +#define SC_NEEDS_RESET 0x00000008 /* going to reset when refcnt = 1 */ +#define SC_INTR_RESET 0x00000020 /* interrupt resets at end */ +#define SC_POWERSAVE 0x00000040 /* device is asleep */ +#define SC_GONE 0x00000080 /* device did not come back */ +#define SC_NOFREE_ALLNODES 0x00000100 /* do not free assoc w/reinit */ +#define SC_START_DESIRED 0x00000200 /* tried to start during mgmt-crit */ +#define SC_KTHREAD 0x00000400 /* has a kthread around */ + /* configuration sysctls */ + int sc_dot1x; + int sc_wds; + /* cached values */ + int sc_if_flags; + int16_t sc_80211_ioc_wep; + int16_t sc_80211_ioc_auth; + uint32_t sc_noise; + unsigned int sc_refcnt; /* # sleeping with sc */ +// struct cv sc_critical_cv; + struct thread *sc_critical_thread; /* allow mgmt recursion */ + int sc_critical; /* -1- = mgmt < 0 < 1+ data */ + struct thread *sc_drainer; /* who's doing removal/reset? */ + unsigned int sc_debug; +#define SC_DEBUG_QUEUES 0x00000001 +#define SC_DEBUG_MGMT 0x00000002 +#define SC_DEBUG_UNEXPECTED 0x00000004 +#define SC_DEBUG_TRIGGER 0x00000008 +#define SC_DEBUG_EVENTS 0x00000010 +#define SC_DEBUG_POWER 0x00000020 +#define SC_DEBUG_TRAP 0x00000040 +#define SC_DEBUG_LINK 0x00000080 +#define SC_DEBUG_RXANNEX 0x00000100 +#define SC_DEBUG_RXFRAG 0x00000200 +#define SC_DEBUG_RXETHER 0x00000400 + enum pgt_dev_type { + PFF_DEV_ISL3877, + PFF_DEV_ISL3890, + PFF_DEV_3COM6001 + } sc_dev_type; + struct resource *sc_intres; /* interrupt resource */ + void *sc_intcookie; + struct resource *sc_iores; /* IO memory resource */ + bus_space_tag_t sc_iotag; + bus_space_handle_t sc_iohandle; + bus_dma_tag_t sc_cbdmat; /* control block DMA */ + bus_dmamap_t sc_cbdmam; + bus_addr_t sc_cbdmabusaddr; + struct pgt_control_block *sc_cb; /* DMA-mapped control block */ + bus_dma_tag_t sc_psmdmat; /* power save buffer DMA */ + bus_dmamap_t sc_psmdmam; + bus_addr_t sc_psmdmabusaddr; + void *sc_psmbuf; /* DMA-mapped psm frame area */ + bus_dma_tag_t sc_fragdmat; /* tags for all queues */ + struct pgt_mgmt_descq sc_mgmtinprog; + struct pgt_descq sc_freeq[PFF_QUEUE_COUNT]; + size_t sc_freeq_count[PFF_QUEUE_COUNT]; + struct pgt_descq sc_dirtyq[PFF_QUEUE_COUNT]; + size_t sc_dirtyq_count[PFF_QUEUE_COUNT]; + struct bintime sc_data_tx_started; + struct pgt_softc_kthread { + struct proc *sck_proc; +// struct cv sck_needed; + int sck_exit, sck_reset, sck_update; + TAILQ_HEAD(, pgt_async_trap) sck_traps; + } sc_kthread; +}; + +int pgt_attach(struct pgt_softc *); +int pgt_detach(struct pgt_softc *sc); +void pgt_reboot(struct pgt_softc *); +/* Load one seg into the bus_addr_t * arg. */ +void pgt_load_busaddr(void *, bus_dma_segment_t *, int, int); + +static __inline int +pgt_queue_is_rx(enum pgt_queue pq) +{ + return (pq == PFF_QUEUE_DATA_LOW_RX || + pq == PFF_QUEUE_DATA_HIGH_RX || + pq == PFF_QUEUE_MGMT_RX); +} + +static __inline int +pgt_queue_is_tx(enum pgt_queue pq) +{ + return (pq == PFF_QUEUE_DATA_LOW_TX || + pq == PFF_QUEUE_DATA_HIGH_TX || + pq == PFF_QUEUE_MGMT_TX); +} + +static __inline int +pgt_queue_is_data(enum pgt_queue pq) +{ + return (pq == PFF_QUEUE_DATA_LOW_RX || + pq == PFF_QUEUE_DATA_HIGH_RX || + pq == PFF_QUEUE_DATA_LOW_TX || + pq == PFF_QUEUE_DATA_HIGH_TX); +} + +static __inline int +pgt_queue_is_mgmt(enum pgt_queue pq) +{ + return (pq == PFF_QUEUE_MGMT_RX || + pq == PFF_QUEUE_MGMT_TX); +} diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci index 117371e4f6b..f65e4e704d4 100644 --- a/sys/dev/pci/files.pci +++ b/sys/dev/pci/files.pci @@ -1,4 +1,4 @@ -# $OpenBSD: files.pci,v 1.215 2006/08/16 21:06:23 kettenis Exp $ +# $OpenBSD: files.pci,v 1.216 2006/08/22 18:12:12 mglocker Exp $ # $NetBSD: files.pci,v 1.20 1996/09/24 17:47:15 christos Exp $ # # Config file and device description for machine-independent PCI code. @@ -579,6 +579,12 @@ file dev/pci/if_ral_pci.c ral_pci attach acx at pci with acx_pci file dev/pci/if_acx_pci.c acx_pci +# +# Conexant PrismGT +# +attach pgt at pci with pgt_pci +file dev/pci/if_pgt_pci.c pgt_pci + # Sangoma PCI card device san: ifnet, ifmedia, sppp attach san at pci diff --git a/sys/dev/pci/if_pgt_pci.c b/sys/dev/pci/if_pgt_pci.c new file mode 100644 index 00000000000..46a4b1205be --- /dev/null +++ b/sys/dev/pci/if_pgt_pci.c @@ -0,0 +1,607 @@ +/* $OpenBSD: if_pgt_pci.c,v 1.1 2006/08/22 18:12:12 mglocker Exp $ */ + +/*- + * Copyright (c) 2006 Theo de Raadt <deraadt@openbsd.org> + * Copyright (c) 2005, 2006 + * Damien Bergamini <damien.bergamini@free.fr> + * + * 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. + */ + +/* + * PCI front-end for the PrismGT + */ + +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/timeout.h> +#include <sys/device.h> + +#include <machine/bus.h> +#include <machine/intr.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_amrr.h> +#include <net80211/ieee80211_rssadapt.h> +#include <net80211/ieee80211_radiotap.h> + +#include <dev/ic/pgtreg.h> +#include <dev/ic/pgtvar.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> + +int pgt_pci_match(struct device *, void *, void *); +void pgt_pci_attach(struct device *, struct device *, void *); +int pgt_pci_detach(struct device *, int); + +struct pgt_pci_softc { + struct pgt_softc sc_sc; + + pci_chipset_tag_t sc_pc; + pcitag_t sc_pcitag; + + void *sc_ih; +}; + +struct cfattach pgt_pci_ca = { + sizeof(struct pgt_pci_softc), pgt_pci_match, pgt_pci_attach, + pgt_pci_detach +}; + +const struct pci_matchid pgt_pci_devices[] = { + /* 3COM 3CRWE154G72 Wireless LAN adapter */ + //{ PCI_VENDOR_3COM, PCI_PRODUCT_3COM_3CRWE154G72 }, + /* D-Link Air Plus Xtreme G A1 - DWL-g650 A1" */ + //{ PCI_VENDOR_DLINK, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* I-O Data WN-G54/CB - WN-G54/CB" */ + //{ PCI_VENDOR_IODATA, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* NETGEAR WG511" */ + { PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890 } + //{ PCI_VENDOR_NETGEAR, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* PLANEX GW-DS54G" */ + //{ PCI_VENDOR_I4, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* EZ Connect g 2.4GHz 54 Mbps Wireless PCI Card - SMC2802W" */ + //{ PCI_VENDOR_SMC, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* EZ Connect g 2.4GHz 54 Mbps Wireless Cardbus Adapter - SMC2835W" */ + //{ PCI_VENDOR_SMC, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* I4 Z-Com XG-600" */ + //{ PCI_VENDOR_I4, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* I4 Z-Com XG-900/PLANEX GW-DS54G" */ + //{ PCI_VENDOR_I4, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* SMC 2802Wv2" */ + //{ PCI_VENDOR_ACCTON, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* SMC 2835Wv2" */ + //{ PCI_VENDOR_SMC, PCI_PRODUCT_INTERSIL_ISL3890 }, + /* Intersil PRISM Indigo Wireless LAN adapter" */ + //{ PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3877 }, + /* Intersil PRISM Duette/Prism GT Wireless LAN adapter" */ + //{ PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890 } +}; + +int +pgt_pci_match(struct device *parent, void *match, void *aux) +{ + return (pci_matchbyid((struct pci_attach_args *)aux, pgt_pci_devices, + sizeof (pgt_pci_devices) / sizeof (pgt_pci_devices[0]))); +} + +void +pgt_pci_attach(struct device *parent, struct device *self, void *aux) +{ + return; +} + +int +pgt_pci_detach(struct device *self, int flags) +{ + return (0); +} + +#if 0 +/*- + * Copyright (c) 2004 Fujitsu Laboratories of America, Inc. + * Copyright (c) 2004 Brian Fundakowski Feldman + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: if_pgt_pci.c,v 1.1 2006/08/22 18:12:12 mglocker Exp $ + */ + +#include <sys/cdefs.h> +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/proc.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/ioctl.h> +#include <sys/types.h> + +#include <machine/bus.h> +#include <machine/endian.h> +#include <machine/intr.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#endif + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/if_ether.h> +#include <netinet/ip.h> +#endif + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_radiotap.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> + +#include <dev/ic/if_pffreg.h> +#include <dev/ic/if_pffvar.h> + +int pgt_pci_match(struct device *, void *, void *); +void pgt_pci_attach(struct device *, struct device *, void *); +int pgt_pci_detach(struct device *, int); + +struct cfattach pgt_pci_ca = { + sizeof (struct pgt_softc), pgt_pci_match, pgt_pci_attach, + pgt_pci_detach +}; + +static const struct pgt_ident { + enum pgt_dev_type dev_type; + uint16_t vendor; + uint16_t device; + uint16_t subvendor; + uint16_t subdevice; +} pgt_ident_table[] = { + { /* 3COM 3CRWE154G72 Wireless LAN adapter */ + PFF_DEV_3COM6001, + PCI_VENDOR_3COM, PCI_PRODUCT_3COM_3CRWE154G72, + PCI_VENDOR_3COM, PCI_PRODUCT_3COM_3CRWE154G72 + }, + { /* D-Link Air Plus Xtreme G A1 - DWL-g650 A1" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_DLINK, 0x3202 + }, + { /* I-O Data WN-G54/CB - WN-G54/CB" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_IODATA, 0xd019 + }, + { /* NETGEAR WG511" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_NETGEAR, 0x4800 + }, + { /* PLANEX GW-DS54G" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_I4, 0x0020 + }, + { /* EZ Connect g 2.4GHz 54 Mbps Wireless PCI Card - SMC2802W" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_SMC, 0x2802 + }, + { /* EZ Connect g 2.4GHz 54 Mbps Wireless Cardbus Adapter - SMC2835W" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_SMC, 0x2835 + }, + { /* I4 Z-Com XG-600" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_I4, 0x0014 + }, + { /* I4 Z-Com XG-900/PLANEX GW-DS54G" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_I4, 0x0020 + }, + { /* SMC 2802Wv2" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_ACCTON, 0xee03 + }, + { /* SMC 2835Wv2" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + PCI_VENDOR_SMC, 0xa835 + }, + { /* Intersil PRISM Indigo Wireless LAN adapter" */ + PFF_DEV_ISL3877, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3877, + 0, 0 + }, + { /* Intersil PRISM Duette/Prism GT Wireless LAN adapter" */ + PFF_DEV_ISL3890, + PCI_VENDOR_INTERSIL, PCI_PRODUCT_INTERSIL_ISL3890, + 0, 0 + }, + { 0, 0, 0, 0, 0 } +}; + +static const struct pgt_ident * +pgt_match(struct pci_attach_args *pa) +{ + u_int32_t subsysid; + int i; + + subsysid = pci_conf_read(pa->pa_pc, pa->pa_tag, + PCI_SUBSYS_ID_REG); + for (i = 0; pgt_ident_table[i].dev_type; i++) { + if (PCI_VENDOR(pa->pa_id) == pgt_ident_table[i].vendor && + PCI_PRODUCT(pa->pa_id) == pgt_ident_table[i].device && + (pgt_ident_table[i].subvendor == 0x0000 || + PCI_VENDOR(subsysid) == pgt_ident_table[i].subvendor) && + (pgt_ident_table[i].subdevice == 0x0000 || + PCI_PRODUCT(subsysid) == pgt_ident_table[i].subdevice)) + return &pgt_ident_table[i]; + } + return (NULL); +} + +int +pgt_pci_match(struct device *parent, void *match, void *aux) +{ + struct pci_attach_args *pa = aux; + const struct pgt_ident *id; + + id = pgt_match(pa); + if (id == NULL) + return (0); + return (1); +} + +void +pgt_load_busaddr(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + if (error == 0) + *(bus_addr_t *)arg = segs->ds_addr; +} + +void +pgt_pci_attach(struct device *parent, struct device *self, void *aux) +{ + struct pci_attach_args *pa = aux; + const struct pgt_ident *id; + struct pgt_softc *sc = (void *)self; + void *vaddr; + size_t size; + int error, i, rid; + + id = pgt_match(pa); + sc->sc_dev_type = id->dev_type; + for (i = 0; i < PFF_QUEUE_COUNT; i++) { + TAILQ_INIT(&sc->sc_freeq[i]); + TAILQ_INIT(&sc->sc_dirtyq[i]); + } + pci_enable_busmaster(dev); + rid = 0; + sc->sc_intres = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE | RF_SHAREABLE); + if (sc->sc_intres == NULL) { + printf("%s: cannot allocate IRQ", sc->sc_dev.dv_xname); + error = ENXIO; + goto out; + } + rid = PCIR_BAR(0); + sc->sc_iores = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->sc_iores == NULL) { + printf("%s: cannot map IO\n", sc->sc_dev.dv_xname); + error = ENXIO; + goto out; + } + sc->sc_iotag = rman_get_bustag(sc->sc_iores); + sc->sc_iohandle = rman_get_bushandle(sc->sc_iores); + if (rman_get_size(sc->sc_iores) < PFF_DIRECT_MEMORY_OFFSET + + PFF_DIRECT_MEMORY_SIZE) { + printf("%s: IO range too small (%lu)\n", + sc->sc_dev.dv_xname, + rman_get_size(sc->sc_iores)); + error = ENXIO; + goto out; + } + size = sizeof(struct pgt_control_block); + error = bus_dma_tag_create(NULL, 4, 0, BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, NULL, NULL, size, 1, size, + BUS_DMA_ALLOCNOW, NULL, NULL, &sc->sc_cbdmat); + if (error != 0) { + printf("%s: could not set up control block tag: %d\n", + sc->sc_dev.dv_xname, error); + goto out; + } + error = bus_dmamem_alloc(sc->sc_cbdmat, &vaddr, + BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sc_cbdmam); + if (error) { + printf("%s: could not set up control block map: %d\n", + sc->sc_dev.dv_xname, error); + goto out; + } + sc->sc_cb = vaddr; + error = bus_dmamap_load(sc->sc_cbdmat, sc->sc_cbdmam, sc->sc_cb, + size, pgt_load_busaddr, &sc->sc_cbdmabusaddr, 0); + if (error) { + printf("%s: could not load control block: %d\n", + sc->sc_dev.dv_xname, error); + goto out; + } + /* + * Allocate the power-saving mode frame buffering area. + */ + size = PFF_FRAG_SIZE * PFF_PSM_BUFFER_FRAME_COUNT; + error = bus_dma_tag_create(NULL, 4, 0, BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, NULL, NULL, size, 1, size, + BUS_DMA_ALLOCNOW, NULL, NULL, &sc->sc_psmdmat); + if (error != 0) { + printf("%s: could not set up psm tag: %d\n", + sc->sc_dev.dv_xname, error); + goto out; + } + error = bus_dmamem_alloc(sc->sc_psmdmat, &vaddr, + BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->sc_psmdmam); + if (error) { + printf("%s: could not set up psm buffer: %d\n", + sc->sc_dev.dv_xname, error); + goto out; + } + sc->sc_psmbuf = vaddr; + error = bus_dmamap_load(sc->sc_psmdmat, sc->sc_psmdmam, sc->sc_psmbuf, + size, pgt_load_busaddr, &sc->sc_psmdmabusaddr, 0); + if (error) { + printf("%s: could not load psm buffer: %d\n", + sc->sc_dev.dv_xname, error); + goto out; + } + /* + * Allocate an mbuf-cluster-sized DMA tag for every pgt_frag in + * the pgt_control_block (mirrored in the pgt_descqs). + */ + i = PFF_QUEUE_DATA_RX_SIZE + PFF_QUEUE_DATA_TX_SIZE + + PFF_QUEUE_DATA_RX_SIZE + PFF_QUEUE_DATA_TX_SIZE + + PFF_QUEUE_MGMT_SIZE + PFF_QUEUE_MGMT_SIZE; + error = bus_dma_tag_create(NULL, 4, 0, BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, NULL, NULL, PFF_FRAG_SIZE, i, PFF_FRAG_SIZE, + BUS_DMA_ALLOCNOW, NULL, NULL, &sc->sc_fragdmat); + if (error != 0) { + printf("%s: could not set up fragment tags: %d\n", + sc->sc_dev.dv_xname, error); + goto out; + } + error = pgt_pci_attach_queue(sc, PFF_QUEUE_DATA_LOW_RX); + if (error == 0) + error = pgt_pci_attach_queue(sc, PFF_QUEUE_DATA_LOW_TX); + if (error == 0) + error = pgt_pci_attach_queue(sc, PFF_QUEUE_DATA_HIGH_RX); + if (error == 0) + error = pgt_pci_attach_queue(sc, PFF_QUEUE_DATA_HIGH_TX); + if (error == 0) + error = pgt_pci_attach_queue(sc, PFF_QUEUE_MGMT_RX); + if (error == 0) + error = pgt_pci_attach_queue(sc, PFF_QUEUE_MGMT_TX); + if (error != 0) + goto out; + error = pgt_attach(dev); +out: + if (error) + pgt_pci_release(dev, sc); + return (error); +} + +static void +pgt_pci_release(device_t dev, struct pgt_softc *sc) +{ + if (sc->sc_fragdmat != NULL) { + pgt_pci_detach_queue(sc, PFF_QUEUE_DATA_LOW_RX); + pgt_pci_detach_queue(sc, PFF_QUEUE_DATA_LOW_TX); + pgt_pci_detach_queue(sc, PFF_QUEUE_DATA_HIGH_RX); + pgt_pci_detach_queue(sc, PFF_QUEUE_DATA_HIGH_TX); + pgt_pci_detach_queue(sc, PFF_QUEUE_MGMT_RX); + pgt_pci_detach_queue(sc, PFF_QUEUE_MGMT_TX); + bus_dma_tag_destroy(sc->sc_fragdmat); + sc->sc_fragdmat = NULL; + } + if (sc->sc_psmdmabusaddr != 0) { + bus_dmamap_unload(sc->sc_psmdmat, sc->sc_psmdmam); + sc->sc_psmdmabusaddr = 0; + } + if (sc->sc_psmbuf != NULL) { + bus_dmamem_free(sc->sc_psmdmat, sc->sc_psmbuf, sc->sc_psmdmam); + sc->sc_psmbuf = NULL; + sc->sc_psmdmam = NULL; + } + if (sc->sc_psmdmat != NULL) { + bus_dma_tag_destroy(sc->sc_psmdmat); + sc->sc_psmdmat = NULL; + } + if (sc->sc_cbdmabusaddr != 0) { + bus_dmamap_unload(sc->sc_psmdmat, sc->sc_psmdmam); + sc->sc_cbdmabusaddr = 0; + } + if (sc->sc_cb != NULL) { + bus_dmamem_free(sc->sc_cbdmat, sc->sc_cb, sc->sc_cbdmam); + sc->sc_cb = NULL; + sc->sc_cbdmam = NULL; + } + if (sc->sc_cbdmat != NULL) { + bus_dma_tag_destroy(sc->sc_cbdmat); + sc->sc_cbdmat = NULL; + } + if (sc->sc_iores != NULL) { + bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), + sc->sc_iores); + sc->sc_iores = NULL; + } + if (sc->sc_intres != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_intres); + sc->sc_intres = NULL; + } +} + +static int +pgt_pci_detach(device_t dev) +{ + int error; + + error = pgt_detach(dev); + if (error) + return (error); + pgt_pci_release(dev, device_get_softc(dev)); + return (error); +} + +static int +pgt_pci_shutdown(device_t dev) +{ + struct pgt_softc *sc; + + sc = device_get_softc(dev); + pgt_reboot(sc); + return (0); +} + +static int +pgt_pci_attach_queue(struct pgt_softc *sc, enum pgt_queue pq) +{ + struct pgt_desc *pd; + struct pgt_frag *pcbqueue; + size_t i, qsize; + int error; + + switch (pq) { + case PFF_QUEUE_DATA_LOW_RX: + pcbqueue = sc->sc_cb->pcb_data_low_rx; + qsize = PFF_QUEUE_DATA_RX_SIZE; + break; + case PFF_QUEUE_DATA_LOW_TX: + pcbqueue = sc->sc_cb->pcb_data_low_tx; + qsize = PFF_QUEUE_DATA_TX_SIZE; + break; + case PFF_QUEUE_DATA_HIGH_RX: + pcbqueue = sc->sc_cb->pcb_data_high_rx; + qsize = PFF_QUEUE_DATA_RX_SIZE; + break; + case PFF_QUEUE_DATA_HIGH_TX: + pcbqueue = sc->sc_cb->pcb_data_high_tx; + qsize = PFF_QUEUE_DATA_TX_SIZE; + break; + case PFF_QUEUE_MGMT_RX: + pcbqueue = sc->sc_cb->pcb_mgmt_rx; + qsize = PFF_QUEUE_MGMT_SIZE; + break; + case PFF_QUEUE_MGMT_TX: + pcbqueue = sc->sc_cb->pcb_mgmt_tx; + qsize = PFF_QUEUE_MGMT_SIZE; + break; + } + for (i = 0; i < qsize; i++) { + pd = malloc(sizeof(*pd), M_PFF, M_WAITOK); + bzero(pd, sizeof *pd); + error = bus_dmamem_alloc(sc->sc_fragdmat, + &pd->pd_mem, BUS_DMA_WAITOK | BUS_DMA_ZERO, + &pd->pd_dmam); + if (error) { + printf("%s: error allocating fragment " + "%u on queue %u: %d\n", + sc->sc_dev.dv_xname, i, pq, error); + free(pd, M_PFF); + break; + } + if (pgt_queue_is_rx(pq)) { + error = bus_dmamap_load(sc->sc_fragdmat, pd->pd_dmam, + pd->pd_mem, PFF_FRAG_SIZE, pgt_load_busaddr, + &pd->pd_dmaaddr, 0); + if (error) { + printf("%s: error loading " + "fragment %u on queue %u: %d\n", + sc->sc_dev.dv_xname, i, pq, error); + bus_dmamem_free(sc->sc_fragdmat, pd->pd_mem, + pd->pd_dmam); + free(pd, M_PFF); + break; + } + } + TAILQ_INSERT_TAIL(&sc->sc_freeq[pq], pd, pd_link); + } + return (error); +} + +static void +pgt_pci_detach_queue(struct pgt_softc *sc, enum pgt_queue pq) +{ + struct pgt_desc *pd; + while (!TAILQ_EMPTY(&sc->sc_freeq[pq])) { + pd = TAILQ_FIRST(&sc->sc_freeq[pq]); + TAILQ_REMOVE_HEAD(&sc->sc_freeq[pq], pd_link); + if (pd->pd_dmaaddr != 0) { + bus_dmamap_unload(sc->sc_fragdmat, pd->pd_dmam); + pd->pd_dmaaddr = 0; + } + bus_dmamem_free(sc->sc_fragdmat, pd->pd_mem, pd->pd_dmam); + free(pd, M_PFF); + } +} +#endif |