File: [local] / sys / dev / ic / pgt.c (download)
Revision 1.1.1.1 (vendor branch), Tue Mar 4 16:10:44 2008 UTC (16 years, 6 months ago) by nbrk
Branch: OPENBSD_4_2_BASE, MAIN
CVS Tags: jornada-partial-support-wip, HEAD Changes since 1.1: +0 -0 lines
Import of OpenBSD 4.2 release kernel tree with initial code to support
Jornada 720/728, StrongARM 1110-based handheld PC.
At this point kernel roots on NFS and boots into vfs_mountroot() and traps.
What is supported:
- glass console, Jornada framebuffer (jfb) works in 16bpp direct color mode
(needs some palette tweaks for non black/white/blue colors, i think)
- saic, SA11x0 interrupt controller (needs cleanup)
- sacom, SA11x0 UART (supported only as boot console for now)
- SA11x0 GPIO controller fully supported (but can't handle multiple interrupt
handlers on one gpio pin)
- sassp, SSP port on SA11x0 that attaches spibus
- Jornada microcontroller (jmcu) to control kbd, battery, etc throught
the SPI bus (wskbd attaches on jmcu, but not tested)
- tod functions seem work
- initial code for SA-1111 (chip companion) : this is TODO
Next important steps, i think:
- gpio and intc on sa1111
- pcmcia support for sa11x0 (and sa1111 help logic)
- REAL root on nfs when we have PCMCIA support (we may use any of supported pccard NICs)
- root on wd0! (using already supported PCMCIA-ATA)
|
/* $OpenBSD: pgt.c,v 1.43 2007/07/18 18:10:31 damien Exp $ */
/*
* Copyright (c) 2006 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2006 Marcus Glocker <mglocker@openbsd.org>
*
* 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.
*/
/*
* 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 <sys/device.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>
#ifdef PGT_DEBUG
#define DPRINTF(x) do { printf x; } while (0)
#else
#define DPRINTF(x)
#endif
#define SETOID(oid, var, size) { \
if (pgt_oid_set(sc, oid, var, size) != 0) \
break; \
}
/*
* 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/.
*/
#define SCAN_TIMEOUT 5 /* 5 seconds */
struct cfdriver pgt_cd = {
NULL, "pgt", DV_IFNET
};
void pgt_media_status(struct ifnet *ifp, struct ifmediareq *imr);
int pgt_media_change(struct ifnet *ifp);
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 *);
int pgt_load_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_stop(struct pgt_softc *, unsigned int);
void pgt_reboot(struct pgt_softc *);
void pgt_init_intr(struct pgt_softc *);
void pgt_update_intr(struct pgt_softc *, 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 *);
void pgt_async_reset(struct pgt_softc *);
void pgt_async_update(struct pgt_softc *);
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 *);
struct mbuf
*pgt_datarx_completion(struct pgt_softc *, enum pgt_queue);
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_start(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);
void node_mark_active_ap(void *, struct ieee80211_node *);
void node_mark_active_adhoc(void *, struct ieee80211_node *);
void pgt_watchdog(struct ifnet *);
int pgt_init(struct ifnet *);
void pgt_update_hw_from_sw(struct pgt_softc *, int, int);
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_newstate(struct ieee80211com *, enum ieee80211_state, int);
int pgt_drain_tx_queue(struct pgt_softc *, enum pgt_queue);
int pgt_dma_alloc(struct pgt_softc *);
int pgt_dma_alloc_queue(struct pgt_softc *sc, enum pgt_queue pq);
void pgt_dma_free(struct pgt_softc *);
void pgt_dma_free_queue(struct pgt_softc *sc, enum pgt_queue pq);
void pgt_shutdown(void *);
void pgt_power(int, void *);
void
pgt_write_memory_barrier(struct pgt_softc *sc)
{
bus_space_barrier(sc->sc_iotag, sc->sc_iohandle, 0, 0,
BUS_SPACE_BARRIER_WRITE);
}
u_int32_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, PGT_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)
DPRINTF(("%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(PGT_FRAG_SIZE);
pd->pd_fragp->pf_flags = 0;
bus_dmamap_sync(sc->sc_dmat, pd->pd_dmam, 0, pd->pd_dmam->dm_mapsize,
BUS_DMASYNC_POSTWRITE);
}
int
pgt_load_tx_desc_frag(struct pgt_softc *sc, enum pgt_queue pq,
struct pgt_desc *pd)
{
int error;
error = bus_dmamap_load(sc->sc_dmat, pd->pd_dmam, pd->pd_mem,
PGT_FRAG_SIZE, NULL, BUS_DMA_NOWAIT);
if (error) {
DPRINTF(("%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_dmaaddr = pd->pd_dmam->dm_segs[0].ds_addr;
pd->pd_fragp->pf_addr = htole32((uint32_t)pd->pd_dmaaddr);
pd->pd_fragp->pf_size = htole16(PGT_FRAG_SIZE);
pd->pd_fragp->pf_flags = htole16(0);
bus_dmamap_sync(sc->sc_dmat, pd->pd_dmam, 0, pd->pd_dmam->dm_mapsize,
BUS_DMASYNC_POSTWRITE);
return (0);
}
void
pgt_unload_tx_desc_frag(struct pgt_softc *sc, struct pgt_desc *pd)
{
bus_dmamap_unload(sc->sc_dmat, pd->pd_dmam);
pd->pd_dmaaddr = 0;
}
int
pgt_load_firmware(struct pgt_softc *sc)
{
int error, reg, dirreg, fwoff, ucodeoff, fwlen;
uint8_t *ucode;
uint32_t *uc;
size_t size;
char *name;
if (sc->sc_flags & SC_ISL3877)
name = "pgt-isl3877";
else
name = "pgt-isl3890"; /* includes isl3880 */
error = loadfirmware(name, &ucode, &size);
if (error != 0) {
DPRINTF(("%s: error %d, could not read microcode %s!\n",
sc->sc_dev.dv_xname, error, name));
return (EIO);
}
if (size & 3) {
DPRINTF(("%s: bad firmware size %u\n",
sc->sc_dev.dv_xname, size));
free(ucode, M_DEVBUF);
return (EINVAL);
}
pgt_reboot(sc);
fwoff = 0;
ucodeoff = 0;
uc = (uint32_t *)ucode;
reg = PGT_FIRMWARE_INTERNAL_OFFSET;
while (fwoff < size) {
pgt_write_4_flush(sc, PGT_REG_DIR_MEM_BASE, reg);
if ((size - fwoff) >= PGT_DIRECT_MEMORY_SIZE)
fwlen = PGT_DIRECT_MEMORY_SIZE;
else
fwlen = size - fwoff;
dirreg = PGT_DIRECT_MEMORY_OFFSET;
while (fwlen > 4) {
pgt_write_4(sc, dirreg, uc[ucodeoff]);
fwoff += 4;
dirreg += 4;
reg += 4;
fwlen -= 4;
ucodeoff++;
}
pgt_write_4_flush(sc, dirreg, uc[ucodeoff]);
fwoff += 4;
dirreg += 4;
reg += 4;
fwlen -= 4;
ucodeoff++;
}
DPRINTF(("%s: %d bytes microcode loaded from %s\n",
sc->sc_dev.dv_xname, fwoff, name));
reg = pgt_read_4(sc, PGT_REG_CTRL_STAT);
reg &= ~(PGT_CTRL_STAT_RESET | PGT_CTRL_STAT_CLOCKRUN);
reg |= PGT_CTRL_STAT_RAMBOOT;
pgt_write_4_flush(sc, PGT_REG_CTRL_STAT, reg);
pgt_write_memory_barrier(sc);
DELAY(PGT_WRITEIO_DELAY);
reg |= PGT_CTRL_STAT_RESET;
pgt_write_4(sc, PGT_REG_CTRL_STAT, reg);
pgt_write_memory_barrier(sc);
DELAY(PGT_WRITEIO_DELAY);
reg &= ~PGT_CTRL_STAT_RESET;
pgt_write_4(sc, PGT_REG_CTRL_STAT, reg);
pgt_write_memory_barrier(sc);
DELAY(PGT_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] = 0;
i = 0;
/* XXX why only freeq ??? */
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] = 0;
}
/*
* Turn off interrupts, reset the device (possibly loading 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, PGT_REG_INT_EN, 0);
DELAY(PGT_WRITEIO_DELAY);
/*
* Set up the management receive queue, assuming there are no
* requests in progress.
*/
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_PREWRITE);
pgt_cleanup_queue(sc, PGT_QUEUE_DATA_LOW_RX,
&sc->sc_cb->pcb_data_low_rx[0]);
pgt_cleanup_queue(sc, PGT_QUEUE_DATA_LOW_TX,
&sc->sc_cb->pcb_data_low_tx[0]);
pgt_cleanup_queue(sc, PGT_QUEUE_DATA_HIGH_RX,
&sc->sc_cb->pcb_data_high_rx[0]);
pgt_cleanup_queue(sc, PGT_QUEUE_DATA_HIGH_TX,
&sc->sc_cb->pcb_data_high_tx[0]);
pgt_cleanup_queue(sc, PGT_QUEUE_MGMT_RX,
&sc->sc_cb->pcb_mgmt_rx[0]);
pgt_cleanup_queue(sc, PGT_QUEUE_MGMT_TX,
&sc->sc_cb->pcb_mgmt_tx[0]);
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_PREREAD);
/* load firmware */
if (sc->sc_flags & SC_NEEDS_FIRMWARE) {
error = pgt_load_firmware(sc);
if (error) {
printf("%s: firmware load failed\n",
sc->sc_dev.dv_xname);
return (error);
}
sc->sc_flags &= ~SC_NEEDS_FIRMWARE;
DPRINTF(("%s: firmware loaded\n", sc->sc_dev.dv_xname));
}
/* upload the control block's DMA address */
pgt_write_4_flush(sc, PGT_REG_CTRL_BLK_BASE,
htole32((uint32_t)sc->sc_cbdmam->dm_segs[0].ds_addr));
DELAY(PGT_WRITEIO_DELAY);
/* send a reset event */
pgt_write_4_flush(sc, PGT_REG_DEV_INT, PGT_DEV_INT_RESET);
DELAY(PGT_WRITEIO_DELAY);
/* await only the initialization interrupt */
pgt_write_4_flush(sc, PGT_REG_INT_EN, PGT_INT_STAT_INIT);
DELAY(PGT_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_stop(struct pgt_softc *sc, unsigned int flag)
{
struct ieee80211com *ic;
unsigned int wokeup;
int tryagain = 0;
ic = &sc->sc_ic;
ic->ic_if.if_flags &= ~IFF_RUNNING;
sc->sc_flags |= SC_UNINITIALIZED;
sc->sc_flags |= flag;
pgt_drain_tx_queue(sc, PGT_QUEUE_DATA_LOW_TX);
pgt_drain_tx_queue(sc, PGT_QUEUE_DATA_HIGH_TX);
pgt_drain_tx_queue(sc, PGT_QUEUE_MGMT_TX);
trying_again:
/* disable all interrupts */
pgt_write_4_flush(sc, PGT_REG_INT_EN, 0);
DELAY(PGT_WRITEIO_DELAY);
/* reboot card */
pgt_reboot(sc);
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)
DPRINTF(("%s: queue: mgmt %p <- %#x "
"(drained)\n", sc->sc_dev.dv_xname,
pmd, pmd->pmd_oid));
wokeup++;
}
if (wokeup > 0) {
if (flag == SC_NEEDS_RESET && sc->sc_flags & SC_DYING) {
sc->sc_flags &= ~flag;
return;
}
}
} while (wokeup > 0);
if (flag == SC_NEEDS_RESET) {
int error;
DPRINTF(("%s: resetting\n", sc->sc_dev.dv_xname));
sc->sc_flags &= ~SC_POWERSAVE;
sc->sc_flags |= SC_NEEDS_FIRMWARE;
error = pgt_reset(sc);
if (error == 0) {
tsleep(&sc->sc_flags, 0, "pgtres", hz);
if (sc->sc_flags & SC_UNINITIALIZED) {
printf("%s: not responding\n",
sc->sc_dev.dv_xname);
/* Thud. It was probably removed. */
if (tryagain)
panic("pgt went for lunch"); /* XXX */
tryagain = 1;
} else {
/* await all interrupts */
pgt_write_4_flush(sc, PGT_REG_INT_EN,
PGT_INT_STAT_SOURCES);
DELAY(PGT_WRITEIO_DELAY);
ic->ic_if.if_flags |= IFF_RUNNING;
}
}
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);
}
ic->ic_if.if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
ieee80211_new_state(&sc->sc_ic, IEEE80211_S_INIT, -1);
}
void
pgt_attach(void *xsc)
{
struct pgt_softc *sc = xsc;
int error;
/* debug flags */
//sc->sc_debug |= SC_DEBUG_QUEUES; /* super verbose */
//sc->sc_debug |= SC_DEBUG_MGMT;
sc->sc_debug |= SC_DEBUG_UNEXPECTED;
//sc->sc_debug |= SC_DEBUG_TRIGGER; /* verbose */
//sc->sc_debug |= SC_DEBUG_EVENTS; /* super verbose */
//sc->sc_debug |= SC_DEBUG_POWER;
sc->sc_debug |= SC_DEBUG_TRAP;
sc->sc_debug |= SC_DEBUG_LINK;
//sc->sc_debug |= SC_DEBUG_RXANNEX;
//sc->sc_debug |= SC_DEBUG_RXFRAG;
//sc->sc_debug |= SC_DEBUG_RXETHER;
/* enable card if possible */
if (sc->sc_enable != NULL)
(*sc->sc_enable)(sc);
error = pgt_dma_alloc(sc);
if (error)
return;
sc->sc_ic.ic_if.if_softc = sc;
TAILQ_INIT(&sc->sc_mgmtinprog);
TAILQ_INIT(&sc->sc_kthread.sck_traps);
sc->sc_flags |= SC_NEEDS_FIRMWARE | SC_UNINITIALIZED;
sc->sc_80211_ioc_auth = IEEE80211_AUTH_OPEN;
error = pgt_reset(sc);
if (error)
return;
tsleep(&sc->sc_flags, 0, "pgtres", hz);
if (sc->sc_flags & SC_UNINITIALIZED) {
printf("%s: not responding\n", sc->sc_dev.dv_xname);
return;
} else {
/* await all interrupts */
pgt_write_4_flush(sc, PGT_REG_INT_EN, PGT_INT_STAT_SOURCES);
DELAY(PGT_WRITEIO_DELAY);
}
error = pgt_net_attach(sc);
if (error)
return;
if (kthread_create(pgt_per_device_kthread, sc, NULL,
sc->sc_dev.dv_xname) != 0)
return;
ieee80211_new_state(&sc->sc_ic, IEEE80211_S_INIT, -1);
}
int
pgt_detach(struct pgt_softc *sc)
{
if (sc->sc_flags & SC_NEEDS_FIRMWARE || sc->sc_flags & SC_UNINITIALIZED)
/* device was not initialized correctly, so leave early */
goto out;
/* stop card */
pgt_stop(sc, SC_DYING);
pgt_reboot(sc);
/*
* Disable shutdown and power hooks
*/
if (sc->sc_shutdown_hook != NULL)
shutdownhook_disestablish(sc->sc_shutdown_hook);
if (sc->sc_power_hook != NULL)
powerhook_disestablish(sc->sc_power_hook);
ieee80211_ifdetach(&sc->sc_ic.ic_if);
if_detach(&sc->sc_ic.ic_if);
out:
/* disable card if possible */
if (sc->sc_disable != NULL)
(*sc->sc_disable)(sc);
pgt_dma_free(sc);
return (0);
}
void
pgt_reboot(struct pgt_softc *sc)
{
uint32_t reg;
reg = pgt_read_4(sc, PGT_REG_CTRL_STAT);
reg &= ~(PGT_CTRL_STAT_RESET | PGT_CTRL_STAT_RAMBOOT);
pgt_write_4(sc, PGT_REG_CTRL_STAT, reg);
pgt_write_memory_barrier(sc);
DELAY(PGT_WRITEIO_DELAY);
reg |= PGT_CTRL_STAT_RESET;
pgt_write_4(sc, PGT_REG_CTRL_STAT, reg);
pgt_write_memory_barrier(sc);
DELAY(PGT_WRITEIO_DELAY);
reg &= ~PGT_CTRL_STAT_RESET;
pgt_write_4(sc, PGT_REG_CTRL_STAT, reg);
pgt_write_memory_barrier(sc);
DELAY(PGT_RESET_DELAY);
}
void
pgt_init_intr(struct pgt_softc *sc)
{
if ((sc->sc_flags & SC_UNINITIALIZED) == 0) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%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, int hack)
{
/* priority order */
enum pgt_queue pqs[PGT_QUEUE_COUNT] = {
PGT_QUEUE_MGMT_TX, PGT_QUEUE_MGMT_RX,
PGT_QUEUE_DATA_HIGH_TX, PGT_QUEUE_DATA_HIGH_RX,
PGT_QUEUE_DATA_LOW_TX, PGT_QUEUE_DATA_LOW_RX
};
struct mbuf *m;
uint32_t npend;
unsigned int dirtycount;
int i;
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_PREWRITE);
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 < PGT_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 PGT_BUGGY_INTERRUPT_RECOVERY
if (hack && 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_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%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)
pgt_rxdone(sc, pqs[i]);
} else {
npend = pgt_queue_frags_pending(sc, pqs[i]);
if (npend > qdirty) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%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 (pgt_queue_is_data(pqs[i])) {
sc->sc_ic.ic_if.if_timer = 0;
sc->sc_ic.ic_if.if_flags &=
~IFF_OACTIVE;
}
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[PGT_QUEUE_MGMT_RX];
while (!TAILQ_EMPTY(&sc->sc_dirtyq[PGT_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[PGT_QUEUE_MGMT_RX] =
htole32(dirtycount +
letoh32(sc->sc_cb->pcb_driver_curfrag[PGT_QUEUE_MGMT_RX]));
dirtycount = sc->sc_dirtyq_count[PGT_QUEUE_DATA_HIGH_RX];
while (!TAILQ_EMPTY(&sc->sc_dirtyq[PGT_QUEUE_DATA_HIGH_RX])) {
if ((m = pgt_datarx_completion(sc, PGT_QUEUE_DATA_HIGH_RX)))
pgt_input_frames(sc, m);
}
sc->sc_cb->pcb_driver_curfrag[PGT_QUEUE_DATA_HIGH_RX] =
htole32(dirtycount +
letoh32(sc->sc_cb->pcb_driver_curfrag[PGT_QUEUE_DATA_HIGH_RX]));
dirtycount = sc->sc_dirtyq_count[PGT_QUEUE_DATA_LOW_RX];
while (!TAILQ_EMPTY(&sc->sc_dirtyq[PGT_QUEUE_DATA_LOW_RX])) {
if ((m = pgt_datarx_completion(sc, PGT_QUEUE_DATA_LOW_RX)))
pgt_input_frames(sc, m);
}
sc->sc_cb->pcb_driver_curfrag[PGT_QUEUE_DATA_LOW_RX] =
htole32(dirtycount +
letoh32(sc->sc_cb->pcb_driver_curfrag[PGT_QUEUE_DATA_LOW_RX]));
/*
* Write out what we've finished with.
*/
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_PREREAD);
}
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;
if (ni != NULL && ic->ic_opmode == IEEE80211_M_MONITOR) {
*ni = ieee80211_ref_node(ic->ic_bss);
(*ni)->ni_inact = 0;
return (m);
}
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) {
*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:
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_rx_annex *pra;
struct pgt_rx_header *pha;
struct mbuf *next;
unsigned int n;
uint32_t rstamp;
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 (ic->ic_opmode == IEEE80211_M_MONITOR) {
if (m->m_len < sizeof(*pha)) {
m = m_pullup(m, sizeof(*pha));
if (m == NULL) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%s: m_pullup "
"failure\n",
sc->sc_dev.dv_xname));
ifp->if_ierrors++;
continue;
}
}
pha = mtod(m, struct pgt_rx_header *);
pra = NULL;
goto input;
}
if (m->m_len < sizeof(*pra)) {
m = m_pullup(m, sizeof(*pra));
if (m == NULL) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%s: m_pullup failure\n",
sc->sc_dev.dv_xname));
ifp->if_ierrors++;
continue;
}
}
pra = mtod(m, struct pgt_rx_annex *);
pha = &pra->pra_header;
if (sc->sc_debug & SC_DEBUG_RXANNEX)
DPRINTF(("%s: rx annex: ? %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(pha->pra_unknown0),
letoh16(pha->pra_length),
letoh32(pha->pra_clock), pha->pra_flags,
pha->pra_unknown1, pha->pra_rate,
pha->pra_unknown2, letoh32(pha->pra_frequency),
pha->pra_unknown3, pha->pra_rssi,
pha->pra_pad[0], pha->pra_pad[1], pha->pra_pad[2]));
if (sc->sc_debug & SC_DEBUG_RXETHER)
DPRINTF(("%s: rx ether: %s < %s 0x%04x\n",
sc->sc_dev.dv_xname,
ether_sprintf(pra->pra_ether_dhost),
ether_sprintf(pra->pra_ether_shost),
ntohs(pra->pra_ether_type)));
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;
input:
/*
* This flag is set if e.g. packet could not be decrypted.
*/
if (pha->pra_flags & PRA_FLAG_BAD) {
ifp->if_ierrors++;
m_freem(m);
continue;
}
/*
* After getting what we want, chop off the annex, then
* turn into something that looks like it really was
* 802.11.
*/
rssi = pha->pra_rssi;
rstamp = letoh32(pha->pra_clock);
rate = pha->pra_rate;
n = ieee80211_mhz2ieee(letoh32(pha->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. */
if (pra) {
m_adj(m, sizeof(*pra));
} else
m_adj(m, sizeof(*pha));
m = pgt_ieee80211_encap(sc, &eh, m, &ni);
if (m != NULL) {
#if NBPFILTER > 0
if (sc->sc_drvbpf != NULL) {
struct mbuf mb;
struct pgt_rx_radiotap_hdr *tap = &sc->sc_rxtap;
tap->wr_flags = 0;
tap->wr_chan_freq = htole16(chan->ic_freq);
tap->wr_chan_flags = htole16(chan->ic_flags);
tap->wr_rssi = rssi;
tap->wr_max_rssi = ic->ic_max_rssi;
mb.m_data = (caddr_t)tap;
mb.m_len = sc->sc_rxtap_len;
mb.m_next = m;
mb.m_nextpkt = NULL;
mb.m_type = 0;
mb.m_flags = 0;
bpf_mtap(sc->sc_drvbpf, &mb, BPF_DIRECTION_IN);
}
#endif
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. */
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTREAD);
for (i = 0; !shouldupdate && i < PGT_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)
DPRINTF(("%s: wakeup interrupt (update = %d)\n",
sc->sc_dev.dv_xname, shouldupdate));
sc->sc_flags &= ~SC_POWERSAVE;
if (shouldupdate) {
pgt_write_4_flush(sc, PGT_REG_DEV_INT, PGT_DEV_INT_UPDATE);
DELAY(PGT_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. */
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTREAD);
for (i = 0; allowed && i < PGT_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)
DPRINTF(("%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, PGT_REG_DEV_INT, PGT_DEV_INT_SLEEP);
DELAY(PGT_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;
int s;
sc = argp;
sck = &sc->sc_kthread;
while (!sck->sck_exit) {
if (!sck->sck_update && !sck->sck_reset &&
TAILQ_EMPTY(&sck->sck_traps))
tsleep(&sc->sc_kthread, 0, "pgtkth", 0);
if (sck->sck_reset) {
DPRINTF(("%s: [thread] async reset\n",
sc->sc_dev.dv_xname));
sck->sck_reset = 0;
sck->sck_update = 0;
pgt_empty_traps(sck);
s = splnet();
pgt_stop(sc, SC_NEEDS_RESET);
splx(s);
} else if (!TAILQ_EMPTY(&sck->sck_traps)) {
DPRINTF(("%s: [thread] got a trap\n",
sc->sc_dev.dv_xname));
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 if (sck->sck_update) {
sck->sck_update = 0;
pgt_update_sw_from_hw(sc, NULL, NULL);
}
}
pgt_empty_traps(sck);
kthread_exit(0);
}
void
pgt_async_reset(struct pgt_softc *sc)
{
if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET))
return;
sc->sc_kthread.sck_reset = 1;
wakeup(&sc->sc_kthread);
}
void
pgt_async_update(struct pgt_softc *sc)
{
if (sc->sc_flags & SC_DYING)
return;
sc->sc_kthread.sck_update = 1;
wakeup(&sc->sc_kthread);
}
int
pgt_intr(void *arg)
{
struct pgt_softc *sc;
struct ifnet *ifp;
u_int32_t reg;
sc = arg;
ifp = &sc->sc_ic.ic_if;
/*
* 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, PGT_REG_CTRL_STAT);
if (reg & PGT_CTRL_STAT_SLEEPMODE)
return (0);
}
reg = pgt_read_4(sc, PGT_REG_INT_STAT);
if (reg == 0)
return (0); /* This interrupt is not from us */
pgt_write_4_flush(sc, PGT_REG_INT_ACK, reg);
if (reg & PGT_INT_STAT_INIT)
pgt_init_intr(sc);
if (reg & PGT_INT_STAT_UPDATE) {
pgt_update_intr(sc, 0);
/*
* 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, PGT_REG_DEV_INT, PGT_DEV_INT_UPDATE);
DELAY(PGT_WRITEIO_DELAY);
}
if (reg & PGT_INT_STAT_SLEEP && !(reg & PGT_INT_STAT_WAKEUP))
pgt_sleep_intr(sc);
if (reg & PGT_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 & ~PGT_INT_STAT_SOURCES && sc->sc_debug & SC_DEBUG_UNEXPECTED) {
DPRINTF(("%s: unknown interrupt bits %#x (stat %#x)\n",
sc->sc_dev.dv_xname,
reg & ~PGT_INT_STAT_SOURCES,
pgt_read_4(sc, PGT_REG_CTRL_STAT)));
}
if (!IFQ_IS_EMPTY(&ifp->if_snd))
pgt_start(ifp);
return (1);
}
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]++;
bus_dmamap_sync(sc->sc_dmat, pd->pd_dmam, 0,
pd->pd_dmam->dm_mapsize,
BUS_DMASYNC_POSTREAD);
/* Management frames want completion information. */
if (sc->sc_debug & SC_DEBUG_QUEUES) {
DPRINTF(("%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;
DPRINTF(("%s: queue: txmgmt %p <- "
"(ver %u, op %u, flags %#x)\n",
sc->sc_dev.dv_xname,
pd, pmf->pmf_version, pmf->pmf_operation,
pmf->pmf_flags));
}
}
pgt_unload_tx_desc_frag(sc, pd);
}
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]++;
bus_dmamap_sync(sc->sc_dmat, pd->pd_dmam, 0,
pd->pd_dmam->dm_mapsize,
BUS_DMASYNC_POSTREAD);
if (sc->sc_debug & SC_DEBUG_QUEUES)
DPRINTF(("%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))
DPRINTF(("%s: unknown flags on rx [%u]: %#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)
{
struct pgt_async_trap *pa;
struct mbuf *m;
char *p;
size_t total;
if (sc->sc_flags & SC_DYING)
return;
total = sizeof(oid) + size + sizeof(struct pgt_async_trap);
if (total >= MINCLSIZE) {
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
return;
MCLGET(m, M_DONTWAIT);
if (!(m->m_flags & M_EXT)) {
m_freem(m);
m = NULL;
}
} else
m = m_get(M_DONTWAIT, MT_DATA);
if (m == NULL)
return;
else
m->m_len = total;
pa = mtod(m, struct pgt_async_trap *);
p = mtod(m, char *) + sizeof(*pa);
*(uint32_t *)p = oid;
p += sizeof(uint32_t);
memcpy(p, trapdata, size);
pa->pa_mbuf = m;
TAILQ_INSERT_TAIL(&sc->sc_kthread.sck_traps, pa, pa_link);
wakeup(&sc->sc_kthread);
}
/*
* 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[PGT_QUEUE_MGMT_RX]);
TAILQ_REMOVE(&sc->sc_dirtyq[PGT_QUEUE_MGMT_RX], pd, pd_link);
sc->sc_dirtyq_count[PGT_QUEUE_MGMT_RX]--;
TAILQ_INSERT_TAIL(&sc->sc_freeq[PGT_QUEUE_MGMT_RX],
pd, pd_link);
sc->sc_freeq_count[PGT_QUEUE_MGMT_RX]++;
if (letoh16(pd->pd_fragp->pf_size) < sizeof(*pmf)) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%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)
DPRINTF(("%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)
DPRINTF(("%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)
DPRINTF(("%s: unknown mgmt flags %x\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 */
DPRINTF(("%s: mgmt trap received (op %u, oid %#x, len %u)\n",
sc->sc_dev.dv_xname,
pmf->pmf_operation, oid, size));
pgt_trap_received(sc, oid, (char *)pmf + sizeof(*pmf),
min(size, PGT_FRAG_SIZE - sizeof(*pmf)));
goto out_nopmd;
}
if (pmd == NULL) {
if (sc->sc_debug & (SC_DEBUG_UNEXPECTED | SC_DEBUG_MGMT))
DPRINTF(("%s: spurious mgmt received "
"(op %u, oid %#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)
DPRINTF(("%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)
DPRINTF(("%s: mgmt oid changed from %#x -> %#x\n",
sc->sc_dev.dv_xname, pmd->pmd_oid, oid));
pmd->pmd_oid = oid;
}
if (pmd->pmd_recvbuf != NULL) {
if (size > PGT_FRAG_SIZE) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%s: mgmt oid %#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);
wakeup_one(pmd);
if (sc->sc_debug & SC_DEBUG_MGMT)
DPRINTF(("%s: queue: mgmt %p <- (op %u, oid %#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.
*/
struct mbuf *
pgt_datarx_completion(struct pgt_softc *sc, enum pgt_queue pq)
{
struct ifnet *ifp;
struct pgt_desc *pd;
struct mbuf *top, **mp, *m;
size_t datalen;
uint16_t morefrags, dataoff;
int tlen = 0;
ifp = &sc->sc_ic.ic_if;
m = NULL;
top = NULL;
mp = ⊤
while ((pd = TAILQ_FIRST(&sc->sc_dirtyq[pq])) != NULL) {
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)
DPRINTF(("%s: rx frag: len %u memoff %u flags %x\n",
sc->sc_dev.dv_xname, datalen, dataoff,
pd->pd_fragp->pf_flags));
/* Add the (two+?) bytes for the header. */
if (datalen + dataoff > PGT_FRAG_SIZE) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%s data rx too big: %u\n",
sc->sc_dev.dv_xname, datalen));
goto fail;
}
if (m == NULL)
MGETHDR(m, M_DONTWAIT, MT_DATA);
else
m = m_get(M_DONTWAIT, MT_DATA);
if (m == NULL)
goto fail;
if (datalen >= MINCLSIZE) {
MCLGET(m, M_DONTWAIT);
if (!(m->m_flags & M_EXT)) {
m_free(m);
goto fail;
}
}
bcopy(pd->pd_mem + dataoff, mtod(m, char *), datalen);
m->m_len = datalen;
tlen += datalen;
*mp = m;
mp = &m->m_next;
TAILQ_INSERT_TAIL(&sc->sc_freeq[pq], pd, pd_link);
sc->sc_freeq_count[pq]++;
pgt_reinit_rx_desc_frag(sc, pd);
if (!morefrags)
break;
}
if (top) {
ifp->if_ipackets++;
top->m_pkthdr.len = tlen;
top->m_pkthdr.rcvif = ifp;
}
return (top);
fail:
TAILQ_INSERT_TAIL(&sc->sc_freeq[pq], pd, pd_link);
sc->sc_freeq_count[pq]++;
pgt_reinit_rx_desc_frag(sc, pd);
ifp->if_ierrors++;
if (top)
m_freem(top);
return (NULL);
}
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)
DPRINTF(("%s: failure getting oid %#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)
DPRINTF(("%s: failure retrieving oid %#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)
DPRINTF(("%s: failure setting oid %#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, PGT_REG_CTRL_STAT),
pgt_read_4(sc, PGT_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 > PGT_FRAG_SIZE - sizeof(*pmf))
return (ENOMEM);
pd = TAILQ_FIRST(&sc->sc_freeq[PGT_QUEUE_MGMT_TX]);
if (pd == NULL)
return (ENOMEM);
error = pgt_load_tx_desc_frag(sc, PGT_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)
DPRINTF(("%s: queue: mgmt %p -> (op %u, oid %#x, len %u)\n",
sc->sc_dev.dv_xname,
pmd, pmf->pmf_operation,
pmd->pmd_oid, pmd->pmd_len));
pgt_desc_transmit(sc, PGT_QUEUE_MGMT_TX, pd,
sizeof(*pmf) + pmd->pmd_len, 0);
/*
* 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 (tsleep(pmd, 0, "pgtmgm", 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, PGT_QUEUE_MGMT_RX);
#ifdef PGT_BUGGY_INTERRUPT_RECOVERY
pgt_update_intr(sc, 0);
#endif
} while (i++ < 10);
if (pmd->pmd_error == EINPROGRESS) {
printf("%s: timeout waiting for management "
"packet response to %#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;
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)
DPRINTF(("%s: queue: tx %u -> [%u]\n", sc->sc_dev.dv_xname,
pd->pd_fragnum, pq));
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_PREWRITE);
if (morecoming)
pd->pd_fragp->pf_flags |= htole16(PF_FLAG_MF);
pd->pd_fragp->pf_size = htole16(len);
bus_dmamap_sync(sc->sc_dmat, pd->pd_dmam, 0,
pd->pd_dmam->dm_mapsize,
BUS_DMASYNC_POSTWRITE);
sc->sc_cb->pcb_driver_curfrag[pq] =
htole32(letoh32(sc->sc_cb->pcb_driver_curfrag[pq]) + 1);
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_PREREAD);
if (!morecoming)
pgt_maybe_trigger(sc, pq);
}
void
pgt_maybe_trigger(struct pgt_softc *sc, enum pgt_queue pq)
{
unsigned int tries = 1000000 / PGT_WRITEIO_DELAY; /* one second */
uint32_t reg;
if (sc->sc_debug & SC_DEBUG_TRIGGER)
DPRINTF(("%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, PGT_REG_INT_STAT) == 0xabadface) {
do {
reg = pgt_read_4(sc, PGT_REG_CTRL_STAT);
if (!(reg & PGT_CTRL_STAT_SLEEPMODE))
DELAY(PGT_WRITEIO_DELAY);
} while (tries-- != 0);
if (!(reg & PGT_CTRL_STAT_SLEEPMODE)) {
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%s: timeout triggering from "
"sleep mode\n",
sc->sc_dev.dv_xname));
pgt_async_reset(sc);
return;
}
}
pgt_write_4_flush(sc, PGT_REG_DEV_INT,
PGT_DEV_INT_WAKEUP);
DELAY(PGT_WRITEIO_DELAY);
/* read the status back in */
(void)pgt_read_4(sc, PGT_REG_CTRL_STAT);
DELAY(PGT_WRITEIO_DELAY);
} else {
pgt_write_4_flush(sc, PGT_REG_DEV_INT, PGT_DEV_INT_UPDATE);
DELAY(PGT_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);
if (pin != NULL) {
bzero(pin, sizeof *pin);
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 ifnet *ifp = &ic->ic_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;
psbuffer.pob_size = htole32(PGT_FRAG_SIZE * PGT_PSM_BUFFER_FRAME_COUNT);
psbuffer.pob_addr = htole32(sc->sc_psmdmam->dm_segs[0].ds_addr);
error = pgt_oid_set(sc, PGT_OID_PSM_BUFFER, &psbuffer, sizeof(country));
if (error)
return (error);
error = pgt_oid_get(sc, PGT_OID_PHY, &phymode, sizeof(phymode));
if (error)
return (error);
error = pgt_oid_get(sc, PGT_OID_MAC_ADDRESS, ic->ic_myaddr,
sizeof(ic->ic_myaddr));
if (error)
return (error);
error = pgt_oid_get(sc, PGT_OID_COUNTRY, &country, sizeof(country));
if (error)
return (error);
ifp->if_softc = sc;
ifp->if_init = pgt_init;
ifp->if_ioctl = pgt_ioctl;
ifp->if_start = pgt_start;
ifp->if_watchdog = pgt_watchdog;
ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST;
strlcpy(ifp->if_xname, sc->sc_dev.dv_xname, IFNAMSIZ);
IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
IFQ_SET_READY(&ifp->if_snd);
/*
* Set channels
*
* Prism hardware likes to report supported frequencies that are
* not actually available for the country of origin.
*/
j = sizeof(*freqs) + (IEEE80211_CHAN_MAX + 1) * sizeof(uint16_t);
freqs = malloc(j, M_DEVBUF, M_WAITOK);
error = pgt_oid_get(sc, PGT_OID_SUPPORTED_FREQUENCIES, freqs, j);
if (error) {
free(freqs, M_DEVBUF);
return (error);
}
for (i = 0, j = letoh16(freqs->pof_count); 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(PGT_OID_PHY_2400MHZ)))
continue;
if (country == letoh32(PGT_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(PGT_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;
DPRINTF(("%s: set channel %d to freq %uMHz\n",
sc->sc_dev.dv_xname, chan,
letoh16(freqs->pof_freqlist_mhz[i])));
}
free(freqs, M_DEVBUF);
if (firstchan == -1) {
printf("%s: no channels found\n", sc->sc_dev.dv_xname);
return (EIO);
}
/*
* Set rates
*/
bzero(rates, sizeof(rates));
error = pgt_oid_get(sc, PGT_OID_SUPPORTED_RATES, rates, sizeof(rates));
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(PGT_OID_PHY_2400MHZ)) {
rs = &ic->ic_sup_rates[IEEE80211_MODE_11B];
rs->rs_rates[rs->rs_nrates++] = rates[i];
}
default:
if (phymode & htole32(PGT_OID_PHY_2400MHZ)) {
rs = &ic->ic_sup_rates[IEEE80211_MODE_11G];
rs->rs_rates[rs->rs_nrates++] = rates[i];
}
if (phymode & htole32(PGT_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_opmode = IEEE80211_M_STA;
ic->ic_state = IEEE80211_S_INIT;
if_attach(ifp);
ieee80211_ifattach(ifp);
/* setup post-attach/pre-lateattach vector functions */
sc->sc_newstate = ic->ic_newstate;
ic->ic_newstate = pgt_newstate;
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;
ic->ic_max_rssi = 255; /* rssi is a u_int8_t */
/* let net80211 handle switching around the media + resetting */
ieee80211_media_init(ifp, pgt_media_change, pgt_media_status);
#if NBPFILTER > 0
bpfattach(&sc->sc_drvbpf, ifp, DLT_IEEE802_11_RADIO,
sizeof(struct ieee80211_frame) + 64);
sc->sc_rxtap_len = sizeof(sc->sc_rxtapu);
sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len);
sc->sc_rxtap.wr_ihdr.it_present = htole32(PGT_RX_RADIOTAP_PRESENT);
sc->sc_txtap_len = sizeof(sc->sc_txtapu);
sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len);
sc->sc_txtap.wt_ihdr.it_present = htole32(PGT_TX_RADIOTAP_PRESENT);
#endif
/*
* Enable shutdown and power hooks
*/
sc->sc_shutdown_hook = shutdownhook_establish(pgt_shutdown, sc);
if (sc->sc_shutdown_hook == NULL)
printf("%s: WARNING: unable to establish shutdown hook\n",
sc->sc_dev.dv_xname);
sc->sc_power_hook = powerhook_establish(pgt_power, sc);
if (sc->sc_power_hook == NULL)
printf("%s: WARNING: unable to establish power hook\n",
sc->sc_dev.dv_xname);
return (0);
}
int
pgt_media_change(struct ifnet *ifp)
{
struct pgt_softc *sc = ifp->if_softc;
int error;
error = ieee80211_media_change(ifp);
if (error == ENETRESET) {
pgt_update_hw_from_sw(sc, 0, 0);
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 rate;
int s;
imr->ifm_status = 0;
imr->ifm_active = IFM_IEEE80211 | IFM_NONE;
if (!(ifp->if_flags & IFF_UP))
return;
s = splnet();
if (ic->ic_fixed_rate != -1) {
rate = ic->ic_sup_rates[ic->ic_curmode].
rs_rates[ic->ic_fixed_rate] & IEEE80211_RATE_VAL;
} else {
if (pgt_oid_get(sc, PGT_OID_LINK_STATE, &rate, sizeof(rate)))
return;
rate = letoh32(rate);
if (sc->sc_debug & SC_DEBUG_LINK) {
DPRINTF(("%s: %s: link rate %u\n",
sc->sc_dev.dv_xname, __func__, rate));
}
if (rate == 0)
return;
}
imr->ifm_status = IFM_AVALID;
imr->ifm_active = IFM_IEEE80211;
if (ic->ic_state == IEEE80211_S_RUN)
imr->ifm_status |= IFM_ACTIVE;
imr->ifm_active |= ieee80211_rate2media(ic, rate, ic->ic_curmode);
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;
default:
break;
}
splx(s);
}
/*
* Start data frames. Critical sections surround the boundary of
* management frame transmission / transmission acknowledgement / response
* and data frame transmission / transmission acknowledgement.
*/
void
pgt_start(struct ifnet *ifp)
{
struct pgt_softc *sc;
struct ieee80211com *ic;
struct pgt_desc *pd;
struct mbuf *m;
int error;
sc = ifp->if_softc;
ic = &sc->sc_ic;
if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET) ||
!(ifp->if_flags & IFF_RUNNING) ||
ic->ic_state != IEEE80211_S_RUN) {
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[PGT_QUEUE_DATA_LOW_TX] <
PGT_QUEUE_FULL_THRESHOLD && !IFQ_IS_EMPTY(&ifp->if_snd);) {
pd = TAILQ_FIRST(&sc->sc_freeq[PGT_QUEUE_DATA_LOW_TX]);
IFQ_POLL(&ifp->if_snd, m);
if (m == NULL)
break;
if (m->m_pkthdr.len <= PGT_FRAG_SIZE) {
error = pgt_load_tx_desc_frag(sc,
PGT_QUEUE_DATA_LOW_TX, pd);
if (error)
break;
IFQ_DEQUEUE(&ifp->if_snd, m);
m_copydata(m, 0, m->m_pkthdr.len, pd->pd_mem);
pgt_desc_transmit(sc, PGT_QUEUE_DATA_LOW_TX,
pd, m->m_pkthdr.len, 0);
} else if (m->m_pkthdr.len <= PGT_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[PGT_QUEUE_DATA_LOW_TX] + 2 >
PGT_QUEUE_FULL_THRESHOLD)
break;
pd2 = TAILQ_NEXT(pd, pd_link);
error = pgt_load_tx_desc_frag(sc,
PGT_QUEUE_DATA_LOW_TX, pd);
if (error == 0) {
error = pgt_load_tx_desc_frag(sc,
PGT_QUEUE_DATA_LOW_TX, pd2);
if (error) {
pgt_unload_tx_desc_frag(sc, pd);
TAILQ_INSERT_HEAD(&sc->sc_freeq[
PGT_QUEUE_DATA_LOW_TX], pd,
pd_link);
}
}
if (error)
break;
IFQ_DEQUEUE(&ifp->if_snd, m);
m_copydata(m, 0, PGT_FRAG_SIZE, pd->pd_mem);
pgt_desc_transmit(sc, PGT_QUEUE_DATA_LOW_TX,
pd, PGT_FRAG_SIZE, 1);
m_copydata(m, PGT_FRAG_SIZE,
m->m_pkthdr.len - PGT_FRAG_SIZE, pd2->pd_mem);
pgt_desc_transmit(sc, PGT_QUEUE_DATA_LOW_TX,
pd2, m->m_pkthdr.len - PGT_FRAG_SIZE, 0);
} else {
IFQ_DEQUEUE(&ifp->if_snd, m);
ifp->if_oerrors++;
m_freem(m);
m = NULL;
}
if (m != NULL) {
struct ieee80211_node *ni;
#if NBPFILTER > 0
if (ifp->if_bpf != NULL)
bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
#endif
ifp->if_opackets++;
ifp->if_timer = 1;
sc->sc_txtimer = 5;
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 NBPFILTER > 0
if (sc->sc_drvbpf != NULL) {
struct mbuf mb;
struct ether_header eh;
struct pgt_tx_radiotap_hdr *tap = &sc->sc_txtap;
bcopy(mtod(m, struct ether_header *), &eh,
sizeof(eh));
m_adj(m, sizeof(eh));
m = pgt_ieee80211_encap(sc, &eh, m, NULL);
tap->wt_flags = 0;
//tap->wt_rate = rate;
tap->wt_rate = 0;
tap->wt_chan_freq =
htole16(ic->ic_bss->ni_chan->ic_freq);
tap->wt_chan_flags =
htole16(ic->ic_bss->ni_chan->ic_flags);
if (m != NULL) {
mb.m_data = (caddr_t)tap;
mb.m_len = sc->sc_txtap_len;
mb.m_next = m;
mb.m_nextpkt = NULL;
mb.m_type = 0;
mb.m_flags = 0;
bpf_mtap(sc->sc_drvbpf, &mb,
BPF_DIRECTION_OUT);
}
}
#endif
if (m != NULL)
m_freem(m);
}
}
}
int
pgt_ioctl(struct ifnet *ifp, u_long cmd, caddr_t req)
{
struct pgt_softc *sc = ifp->if_softc;
struct ifaddr *ifa;
struct ifreq *ifr;
struct wi_req *wreq;
struct ieee80211_nodereq_all *na;
struct ieee80211com *ic;
struct pgt_obj_bsslist *pob;
struct wi_scan_p2_hdr *p2hdr;
struct wi_scan_res *res;
uint32_t noise;
int maxscan, i, j, s, error = 0;
ic = &sc->sc_ic;
ifr = (struct ifreq *)req;
s = splnet();
switch (cmd) {
case SIOCS80211SCAN:
/*
* This chip scans always as soon as it gets initialized.
*/
/*
* Give us a bit time to scan in case we were not
* initialized before and let the userland process wait.
*/
tsleep(&sc->sc_flags, 0, "pgtsca", hz * SCAN_TIMEOUT);
break;
case SIOCG80211ALLNODES: {
struct ieee80211_nodereq *nr = NULL;
na = (struct ieee80211_nodereq_all *)req;
wreq = malloc(sizeof(*wreq), M_DEVBUF, M_WAITOK);
bzero(wreq, sizeof(*wreq));
maxscan = PGT_OBJ_BSSLIST_NBSS;
pob = malloc(sizeof(*pob) +
sizeof(struct pgt_obj_bss) * maxscan, M_DEVBUF, M_WAITOK);
error = pgt_oid_get(sc, PGT_OID_NOISE_FLOOR, &noise,
sizeof(noise));
if (error == 0) {
noise = letoh32(noise);
error = pgt_oid_get(sc, PGT_OID_BSS_LIST, pob,
sizeof(*pob) +
sizeof(struct pgt_obj_bss) * maxscan);
}
if (error == 0) {
maxscan = min(PGT_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;
wreq->wi_len = (maxscan * WI_PRISM2_RES_SIZE) / 2 +
sizeof(*p2hdr) / 2;
wreq->wi_type = WI_RID_SCAN_RES;
}
for (na->na_nodes = j = i = 0; i < maxscan &&
(na->na_size >= j + sizeof(struct ieee80211_nodereq));
i++) {
/* allocate node space */
if (nr == NULL)
nr = malloc(sizeof(*nr), M_DEVBUF, M_WAITOK);
/* get next BSS scan result */
res = (struct wi_scan_res *)
((char *)&wreq->wi_val + sizeof(*p2hdr) +
i * WI_PRISM2_RES_SIZE);
pgt_obj_bss2scanres(sc, &pob->pob_bsslist[i],
res, noise);
/* copy it to node structure for ifconfig to read */
bzero(nr, sizeof(*nr));
IEEE80211_ADDR_COPY(nr->nr_macaddr, res->wi_bssid);
IEEE80211_ADDR_COPY(nr->nr_bssid, res->wi_bssid);
nr->nr_channel = letoh16(res->wi_chan);
nr->nr_chan_flags = IEEE80211_CHAN_B;
nr->nr_rssi = letoh16(res->wi_signal);
nr->nr_max_rssi = 0; /* XXX */
nr->nr_nwid_len = letoh16(res->wi_ssid_len);
bcopy(res->wi_ssid, nr->nr_nwid, nr->nr_nwid_len);
nr->nr_intval = letoh16(res->wi_interval);
nr->nr_capinfo = letoh16(res->wi_capinfo);
nr->nr_txrate = res->wi_rate == WI_WAVELAN_RES_1M ? 2 :
(res->wi_rate == WI_WAVELAN_RES_2M ? 4 :
(res->wi_rate == WI_WAVELAN_RES_5M ? 11 :
(res->wi_rate == WI_WAVELAN_RES_11M ? 22 : 0)));
nr->nr_nrates = 0;
while (res->wi_srates[nr->nr_nrates] != 0) {
nr->nr_rates[nr->nr_nrates] =
res->wi_srates[nr->nr_nrates] &
WI_VAR_SRATES_MASK;
nr->nr_nrates++;
}
nr->nr_flags = 0;
if (bcmp(nr->nr_macaddr, nr->nr_bssid,
IEEE80211_ADDR_LEN) == 0)
nr->nr_flags |= IEEE80211_NODEREQ_AP;
error = copyout(nr, (caddr_t)na->na_node + j,
sizeof(struct ieee80211_nodereq));
if (error)
break;
/* point to next node entry */
j += sizeof(struct ieee80211_nodereq);
na->na_nodes++;
}
if (nr)
free(nr, M_DEVBUF);
free(pob, M_DEVBUF);
break;
}
case SIOCSIFADDR:
ifa = (struct ifaddr *)req;
ifp->if_flags |= IFF_UP;
#ifdef INET
if (ifa->ifa_addr->sa_family == AF_INET)
arp_ifinit(&sc->sc_ic.ic_ac, ifa);
#endif
/* FALLTHROUGH */
case SIOCSIFFLAGS:
if (ifp->if_flags & IFF_UP) {
if ((ifp->if_flags & IFF_RUNNING) == 0) {
pgt_init(ifp);
error = ENETRESET;
}
} else {
if (ifp->if_flags & IFF_RUNNING) {
pgt_stop(sc, SC_NEEDS_RESET);
error = ENETRESET;
}
}
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
error = (cmd == SIOCADDMULTI) ?
ether_addmulti(ifr, &ic->ic_ac) :
ether_delmulti(ifr, &ic->ic_ac);
if (error == ENETRESET)
error = 0;
break;
case SIOCSIFMTU:
if (ifr->ifr_mtu > PGT_FRAG_SIZE) {
error = EINVAL;
break;
}
/* FALLTHROUGH */
default:
error = ieee80211_ioctl(ifp, cmd, req);
break;
}
if (error == ENETRESET) {
pgt_update_hw_from_sw(sc, 0, 0);
error = 0;
}
splx(s);
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);
}
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_watchdog(struct ifnet *ifp)
{
struct pgt_softc *sc;
sc = ifp->if_softc;
/*
* 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[PGT_QUEUE_DATA_LOW_TX] != 0) {
int count;
ifp->if_timer = 1;
if (sc->sc_txtimer && --sc->sc_txtimer == 0) {
count = pgt_drain_tx_queue(sc, PGT_QUEUE_DATA_LOW_TX);
if (sc->sc_debug & SC_DEBUG_UNEXPECTED)
DPRINTF(("%s: timeout %d data transmissions\n",
sc->sc_dev.dv_xname, count));
}
}
if (sc->sc_flags & (SC_DYING | SC_NEEDS_RESET))
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)
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;
}
int
pgt_init(struct ifnet *ifp)
{
struct pgt_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
/* set default channel */
ic->ic_bss->ni_chan = ic->ic_ibss_chan;
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);
ifp->if_flags |= IFF_RUNNING;
ifp->if_flags &= ~IFF_OACTIVE;
/* Begin background scanning */
ieee80211_new_state(&sc->sc_ic, IEEE80211_S_SCAN, -1);
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;
uint32_t wep, exunencrypted, wepkey, dot1x, auth, mlme;
unsigned int i;
int success, shouldbeup, s;
config = PGT_CONFIG_MANUAL_RUN | PGT_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 = PGT_MODE_CLIENT; /* what to do? */
else
mode = PGT_MODE_CLIENT;
bsstype = PGT_BSS_TYPE_STA;
dot1x = PGT_DOT1X_AUTH_ENABLED;
break;
case IEEE80211_M_IBSS:
if (ifp->if_flags & IFF_PROMISC)
mode = PGT_MODE_CLIENT; /* what to do? */
else
mode = PGT_MODE_CLIENT;
bsstype = PGT_BSS_TYPE_IBSS;
dot1x = PGT_DOT1X_AUTH_ENABLED;
break;
case IEEE80211_M_HOSTAP:
mode = PGT_MODE_AP;
bsstype = PGT_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 = PGT_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 |= PGT_CONFIG_WDS;
break;
case IEEE80211_M_MONITOR:
mode = PGT_MODE_PROMISCUOUS;
bsstype = PGT_BSS_TYPE_ANY;
dot1x = PGT_DOT1X_AUTH_NONE;
break;
default:
goto badopmode;
}
} else {
badopmode:
mode = PGT_MODE_CLIENT;
bsstype = PGT_BSS_TYPE_NONE;
}
DPRINTF(("%s: current mode is ", sc->sc_dev.dv_xname));
switch (ic->ic_curmode) {
case IEEE80211_MODE_11A:
profile = PGT_PROFILE_A_ONLY;
preamble = PGT_OID_PREAMBLE_MODE_DYNAMIC;
DPRINTF(("IEEE80211_MODE_11A\n"));
break;
case IEEE80211_MODE_11B:
profile = PGT_PROFILE_B_ONLY;
preamble = PGT_OID_PREAMBLE_MODE_LONG;
DPRINTF(("IEEE80211_MODE_11B\n"));
break;
case IEEE80211_MODE_11G:
profile = PGT_PROFILE_G_ONLY;
preamble = PGT_OID_PREAMBLE_MODE_SHORT;
DPRINTF(("IEEE80211_MODE_11G\n"));
break;
case IEEE80211_MODE_FH:
/* FALLTHROUGH */
case IEEE80211_MODE_TURBO: /* not handled */
/* FALLTHROUGH */
case IEEE80211_MODE_AUTO:
profile = PGT_PROFILE_MIXED_G_WIFI;
preamble = PGT_OID_PREAMBLE_MODE_DYNAMIC;
DPRINTF(("IEEE80211_MODE_AUTO\n"));
break;
default:
panic("unknown mode %d\n", ic->ic_curmode);
}
switch (sc->sc_80211_ioc_auth) {
case IEEE80211_AUTH_NONE:
auth = PGT_AUTH_MODE_NONE;
break;
case IEEE80211_AUTH_OPEN:
auth = PGT_AUTH_MODE_OPEN;
break;
default:
auth = PGT_AUTH_MODE_SHARED;
break;
}
if (sc->sc_ic.ic_flags & IEEE80211_F_WEPON) {
wep = 1;
exunencrypted = 1;
} else {
wep = 0;
exunencrypted = 0;
}
mlme = htole32(PGT_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 = PGT_DOT1X_AUTH_NONE;
dot1x = htole32(dot1x);
auth = htole32(auth);
if (ic->ic_flags & IEEE80211_F_SHSLOT)
slot = htole32(PGT_OID_SLOT_MODE_SHORT);
else
slot = htole32(PGT_OID_SLOT_MODE_DYNAMIC);
if (ic->ic_des_chan == IEEE80211_CHAN_ANYC) {
if (keepassoc)
channel = 0;
else
channel = ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan);
} else
channel = ieee80211_chan2ieee(ic, ic->ic_des_chan);
DPRINTF(("%s: set rates", sc->sc_dev.dv_xname));
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];
DPRINTF((" %d", availrates[i]));
}
DPRINTF(("\n"));
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);
s = splnet();
for (success = 0; success == 0; success = 1) {
SETOID(PGT_OID_PROFILE, &profile, sizeof(profile));
SETOID(PGT_OID_CONFIG, &config, sizeof(config));
SETOID(PGT_OID_MLME_AUTO_LEVEL, &mlme, sizeof(mlme));
if (!IEEE80211_ADDR_EQ(ic->ic_myaddr, ac->ac_enaddr)) {
SETOID(PGT_OID_MAC_ADDRESS, ac->ac_enaddr,
sizeof(ac->ac_enaddr));
IEEE80211_ADDR_COPY(ic->ic_myaddr, ac->ac_enaddr);
}
SETOID(PGT_OID_MODE, &mode, sizeof(mode));
SETOID(PGT_OID_BSS_TYPE, &bsstype, sizeof(bsstype));
if (channel != 0 && channel != IEEE80211_CHAN_ANY)
SETOID(PGT_OID_CHANNEL, &channel, sizeof(channel));
if (ic->ic_flags & IEEE80211_F_DESBSSID) {
SETOID(PGT_OID_BSSID, ic->ic_des_bssid,
sizeof(ic->ic_des_bssid));
} else if (keepassoc) {
SETOID(PGT_OID_BSSID, ic->ic_bss->ni_bssid,
sizeof(ic->ic_bss->ni_bssid));
}
SETOID(PGT_OID_SSID, &essid, sizeof(essid));
if (ic->ic_des_esslen > 0)
SETOID(PGT_OID_SSID_OVERRIDE, &essid, sizeof(essid));
SETOID(PGT_OID_RATES, &availrates, i);
SETOID(PGT_OID_EXTENDED_RATES, &availrates, i);
SETOID(PGT_OID_PREAMBLE_MODE, &preamble, sizeof(preamble));
SETOID(PGT_OID_SLOT_MODE, &slot, sizeof(slot));
SETOID(PGT_OID_AUTH_MODE, &auth, sizeof(auth));
SETOID(PGT_OID_EXCLUDE_UNENCRYPTED, &exunencrypted,
sizeof(exunencrypted));
SETOID(PGT_OID_DOT1X, &dot1x, sizeof(dot1x));
SETOID(PGT_OID_PRIVACY_INVOKED, &wep, sizeof(wep));
/*
* Setting WEP key(s)
*/
if (letoh32(wep) != 0) {
keyobj.pok_type = PGT_OBJ_KEY_TYPE_WEP;
/* key 1 */
keyobj.pok_length = min(sizeof(keyobj.pok_key),
IEEE80211_KEYBUF_SIZE);
keyobj.pok_length = min(keyobj.pok_length,
ic->ic_nw_keys[0].k_len);
bcopy(ic->ic_nw_keys[0].k_key, keyobj.pok_key,
keyobj.pok_length);
SETOID(PGT_OID_DEFAULT_KEY0, &keyobj, sizeof(keyobj));
/* key 2 */
keyobj.pok_length = min(sizeof(keyobj.pok_key),
IEEE80211_KEYBUF_SIZE);
keyobj.pok_length = min(keyobj.pok_length,
ic->ic_nw_keys[1].k_len);
bcopy(ic->ic_nw_keys[1].k_key, keyobj.pok_key,
keyobj.pok_length);
SETOID(PGT_OID_DEFAULT_KEY1, &keyobj, sizeof(keyobj));
/* key 3 */
keyobj.pok_length = min(sizeof(keyobj.pok_key),
IEEE80211_KEYBUF_SIZE);
keyobj.pok_length = min(keyobj.pok_length,
ic->ic_nw_keys[2].k_len);
bcopy(ic->ic_nw_keys[2].k_key, keyobj.pok_key,
keyobj.pok_length);
SETOID(PGT_OID_DEFAULT_KEY2, &keyobj, sizeof(keyobj));
/* key 4 */
keyobj.pok_length = min(sizeof(keyobj.pok_key),
IEEE80211_KEYBUF_SIZE);
keyobj.pok_length = min(keyobj.pok_length,
ic->ic_nw_keys[3].k_len);
bcopy(ic->ic_nw_keys[3].k_key, keyobj.pok_key,
keyobj.pok_length);
SETOID(PGT_OID_DEFAULT_KEY3, &keyobj, sizeof(keyobj));
wepkey = htole32(ic->ic_wep_txkey);
SETOID(PGT_OID_DEFAULT_KEYNUM, &wepkey, sizeof(wepkey));
}
/* set mode again to commit */
SETOID(PGT_OID_MODE, &mode, sizeof(mode));
}
splx(s);
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);
}
}
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 PGT_OID_DISASSOCIATE:
if (ni != NULL)
ieee80211_release_node(&sc->sc_ic, ni);
break;
case PGT_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, s;
if (pa != NULL) {
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)
DPRINTF(("%s: trap: oid %#x len %u\n",
sc->sc_dev.dv_xname, oid, args->m_len));
switch (oid) {
case PGT_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))
DPRINTF(("%s: %s: link rate %u\n",
sc->sc_dev.dv_xname, __func__, ls));
if (ls)
ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
else
ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
goto gotlinkstate;
case PGT_OID_DEAUTHENTICATE:
case PGT_OID_AUTHENTICATE:
case PGT_OID_DISASSOCIATE:
case PGT_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)
DPRINTF(("%s: mlme: address "
"%s id 0x%02x state 0x%02x code 0x%02x\n",
sc->sc_dev.dv_xname,
ether_sprintf(mlme->pom_address),
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) {
s = splnet();
error = pgt_oid_get(sc, PGT_OID_LINK_STATE, &ls, sizeof(ls));
splx(s);
if (error)
return;
DPRINTF(("%s: up_sw_from_hw: link %u\n", sc->sc_dev.dv_xname,
htole32(ls)));
if (ls != 0)
ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
}
gotlinkstate:
s = splnet();
if (pgt_oid_get(sc, PGT_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, PGT_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, PGT_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, PGT_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, PGT_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:
splx(s);
}
int
pgt_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
struct pgt_softc *sc = ic->ic_if.if_softc;
enum ieee80211_state ostate;
ostate = ic->ic_state;
DPRINTF(("%s: newstate %s -> %s\n", sc->sc_dev.dv_xname,
ieee80211_state_name[ostate], ieee80211_state_name[nstate]));
switch (nstate) {
case IEEE80211_S_INIT:
if (sc->sc_dirtyq_count[PGT_QUEUE_DATA_LOW_TX] == 0)
ic->ic_if.if_timer = 0;
ic->ic_mgt_timer = 0;
ic->ic_flags &= ~IEEE80211_F_SIBSS;
if (ic->ic_wep_ctx != NULL) {
free(ic->ic_wep_ctx, M_DEVBUF);
ic->ic_wep_ctx = NULL;
}
ieee80211_free_allnodes(ic);
break;
case IEEE80211_S_SCAN:
ic->ic_if.if_timer = 1;
ic->ic_mgt_timer = 0;
if (sc->sc_flags & SC_NOFREE_ALLNODES)
sc->sc_flags &= ~SC_NOFREE_ALLNODES;
else
ieee80211_free_allnodes(ic);
/* 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;
break;
default:
break;
}
return (sc->sc_newstate(ic, nstate, arg));
}
int
pgt_drain_tx_queue(struct pgt_softc *sc, enum pgt_queue pq)
{
int wokeup = 0;
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_PREWRITE);
sc->sc_cb->pcb_device_curfrag[pq] =
sc->sc_cb->pcb_driver_curfrag[pq];
bus_dmamap_sync(sc->sc_dmat, sc->sc_cbdmam, 0,
sc->sc_cbdmam->dm_mapsize,
BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_PREREAD);
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)
DPRINTF(("%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++;
}
return (wokeup);
}
int
pgt_dma_alloc(struct pgt_softc *sc)
{
size_t size;
int i, error, nsegs;
for (i = 0; i < PGT_QUEUE_COUNT; i++) {
TAILQ_INIT(&sc->sc_freeq[i]);
TAILQ_INIT(&sc->sc_dirtyq[i]);
}
/*
* control block
*/
size = sizeof(struct pgt_control_block);
error = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
BUS_DMA_NOWAIT, &sc->sc_cbdmam);
if (error != 0) {
printf("%s: can not create DMA tag for control block\n",
sc->sc_dev.dv_xname);
goto out;
}
error = bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE,
0, &sc->sc_cbdmas, 1, &nsegs, BUS_DMA_NOWAIT);
if (error != 0) {
printf("%s: can not allocate DMA memory for control block\n",
sc->sc_dev.dv_xname);
goto out;
}
error = bus_dmamem_map(sc->sc_dmat, &sc->sc_cbdmas, nsegs,
size, (caddr_t *)&sc->sc_cb, BUS_DMA_NOWAIT);
if (error != 0) {
printf("%s: can not map DMA memory for control block\n",
sc->sc_dev.dv_xname);
goto out;
}
bzero(sc->sc_cb, size);
error = bus_dmamap_load(sc->sc_dmat, sc->sc_cbdmam,
sc->sc_cb, size, NULL, BUS_DMA_NOWAIT);
if (error != 0) {
printf("%s: can not load DMA map for control block\n",
sc->sc_dev.dv_xname);
goto out;
}
/*
* powersave
*/
size = PGT_FRAG_SIZE * PGT_PSM_BUFFER_FRAME_COUNT;
error = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
BUS_DMA_ALLOCNOW, &sc->sc_psmdmam);
if (error != 0) {
printf("%s: can not create DMA tag for powersave\n",
sc->sc_dev.dv_xname);
goto out;
}
error = bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE,
0, &sc->sc_psmdmas, 1, &nsegs, BUS_DMA_NOWAIT);
if (error != 0) {
printf("%s: can not allocate DMA memory for powersave\n",
sc->sc_dev.dv_xname);
goto out;
}
error = bus_dmamem_map(sc->sc_dmat, &sc->sc_psmdmas, nsegs,
size, (caddr_t *)&sc->sc_psmbuf, BUS_DMA_NOWAIT);
if (error != 0) {
printf("%s: can not map DMA memory for powersave\n",
sc->sc_dev.dv_xname);
goto out;
}
bzero(sc->sc_psmbuf, size);
error = bus_dmamap_load(sc->sc_dmat, sc->sc_psmdmam,
sc->sc_psmbuf, size, NULL, BUS_DMA_WAITOK);
if (error != 0) {
printf("%s: can not load DMA map for powersave\n",
sc->sc_dev.dv_xname);
goto out;
}
/*
* fragments
*/
error = pgt_dma_alloc_queue(sc, PGT_QUEUE_DATA_LOW_RX);
if (error != 0)
goto out;
error = pgt_dma_alloc_queue(sc, PGT_QUEUE_DATA_LOW_TX);
if (error != 0)
goto out;
error = pgt_dma_alloc_queue(sc, PGT_QUEUE_DATA_HIGH_RX);
if (error != 0)
goto out;
error = pgt_dma_alloc_queue(sc, PGT_QUEUE_DATA_HIGH_TX);
if (error != 0)
goto out;
error = pgt_dma_alloc_queue(sc, PGT_QUEUE_MGMT_RX);
if (error != 0)
goto out;
error = pgt_dma_alloc_queue(sc, PGT_QUEUE_MGMT_TX);
if (error != 0)
goto out;
out:
if (error) {
printf("%s: error in DMA allocation\n", sc->sc_dev.dv_xname);
pgt_dma_free(sc);
}
return (error);
}
int
pgt_dma_alloc_queue(struct pgt_softc *sc, enum pgt_queue pq)
{
struct pgt_desc *pd;
struct pgt_frag *pcbqueue;
size_t i, qsize;
int error, nsegs;
switch (pq) {
case PGT_QUEUE_DATA_LOW_RX:
pcbqueue = sc->sc_cb->pcb_data_low_rx;
qsize = PGT_QUEUE_DATA_RX_SIZE;
break;
case PGT_QUEUE_DATA_LOW_TX:
pcbqueue = sc->sc_cb->pcb_data_low_tx;
qsize = PGT_QUEUE_DATA_TX_SIZE;
break;
case PGT_QUEUE_DATA_HIGH_RX:
pcbqueue = sc->sc_cb->pcb_data_high_rx;
qsize = PGT_QUEUE_DATA_RX_SIZE;
break;
case PGT_QUEUE_DATA_HIGH_TX:
pcbqueue = sc->sc_cb->pcb_data_high_tx;
qsize = PGT_QUEUE_DATA_TX_SIZE;
break;
case PGT_QUEUE_MGMT_RX:
pcbqueue = sc->sc_cb->pcb_mgmt_rx;
qsize = PGT_QUEUE_MGMT_SIZE;
break;
case PGT_QUEUE_MGMT_TX:
pcbqueue = sc->sc_cb->pcb_mgmt_tx;
qsize = PGT_QUEUE_MGMT_SIZE;
break;
}
for (i = 0; i < qsize; i++) {
pd = malloc(sizeof(*pd), M_DEVBUF, M_WAITOK);
error = bus_dmamap_create(sc->sc_dmat, PGT_FRAG_SIZE, 1,
PGT_FRAG_SIZE, 0, BUS_DMA_ALLOCNOW, &pd->pd_dmam);
if (error != 0) {
printf("%s: can not create DMA tag for fragment\n",
sc->sc_dev.dv_xname);
free(pd, M_DEVBUF);
break;
}
error = bus_dmamem_alloc(sc->sc_dmat, PGT_FRAG_SIZE, PAGE_SIZE,
0, &pd->pd_dmas, 1, &nsegs, BUS_DMA_WAITOK);
if (error != 0) {
printf("%s: error alloc frag %u on queue %u\n",
sc->sc_dev.dv_xname, i, pq);
free(pd, M_DEVBUF);
break;
}
error = bus_dmamem_map(sc->sc_dmat, &pd->pd_dmas, nsegs,
PGT_FRAG_SIZE, (caddr_t *)&pd->pd_mem, BUS_DMA_WAITOK);
if (error != 0) {
printf("%s: error map frag %u on queue %u\n",
sc->sc_dev.dv_xname, i, pq);
free(pd, M_DEVBUF);
break;
}
if (pgt_queue_is_rx(pq)) {
error = bus_dmamap_load(sc->sc_dmat, pd->pd_dmam,
pd->pd_mem, PGT_FRAG_SIZE, NULL, BUS_DMA_NOWAIT);
if (error != 0) {
printf("%s: error load frag %u on queue %u\n",
sc->sc_dev.dv_xname, i, pq);
bus_dmamem_free(sc->sc_dmat, &pd->pd_dmas,
nsegs);
free(pd, M_DEVBUF);
break;
}
pd->pd_dmaaddr = pd->pd_dmam->dm_segs[0].ds_addr;
}
TAILQ_INSERT_TAIL(&sc->sc_freeq[pq], pd, pd_link);
}
return (error);
}
void
pgt_dma_free(struct pgt_softc *sc)
{
/*
* fragments
*/
if (sc->sc_dmat != NULL) {
pgt_dma_free_queue(sc, PGT_QUEUE_DATA_LOW_RX);
pgt_dma_free_queue(sc, PGT_QUEUE_DATA_LOW_TX);
pgt_dma_free_queue(sc, PGT_QUEUE_DATA_HIGH_RX);
pgt_dma_free_queue(sc, PGT_QUEUE_DATA_HIGH_TX);
pgt_dma_free_queue(sc, PGT_QUEUE_MGMT_RX);
pgt_dma_free_queue(sc, PGT_QUEUE_MGMT_TX);
}
/*
* powersave
*/
if (sc->sc_psmbuf != NULL) {
bus_dmamap_unload(sc->sc_dmat, sc->sc_psmdmam);
bus_dmamem_free(sc->sc_dmat, &sc->sc_psmdmas, 1);
sc->sc_psmbuf = NULL;
sc->sc_psmdmam = NULL;
}
/*
* control block
*/
if (sc->sc_cb != NULL) {
bus_dmamap_unload(sc->sc_dmat, sc->sc_cbdmam);
bus_dmamem_free(sc->sc_dmat, &sc->sc_cbdmas, 1);
sc->sc_cb = NULL;
sc->sc_cbdmam = NULL;
}
}
void
pgt_dma_free_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(&sc->sc_freeq[pq], pd, pd_link);
if (pd->pd_dmam != NULL) {
bus_dmamap_unload(sc->sc_dmat, pd->pd_dmam);
pd->pd_dmam = NULL;
}
bus_dmamem_free(sc->sc_dmat, &pd->pd_dmas, 1);
free(pd, M_DEVBUF);
}
}
void
pgt_shutdown(void *arg)
{
struct pgt_softc *sc = arg;
DPRINTF(("%s: %s\n", sc->sc_dev.dv_xname, __func__));
pgt_stop(sc, SC_DYING);
}
void
pgt_power(int why, void *arg)
{
struct pgt_softc *sc = arg;
struct ifnet *ifp = &sc->sc_ic.ic_if;
int s;
DPRINTF(("%s: %s(%d)\n", sc->sc_dev.dv_xname, __func__, why));
s = splnet();
switch (why) {
case PWR_STANDBY:
case PWR_SUSPEND:
pgt_stop(sc, SC_NEEDS_RESET);
pgt_update_hw_from_sw(sc, 0, 0);
if (sc->sc_power != NULL)
(*sc->sc_power)(sc, why);
break;
case PWR_RESUME:
if (sc->sc_power != NULL)
(*sc->sc_power)(sc, why);
pgt_stop(sc, SC_NEEDS_RESET);
pgt_update_hw_from_sw(sc, 0, 0);
if ((ifp->if_flags & IFF_UP) &&
!(ifp->if_flags & IFF_RUNNING)) {
pgt_init(ifp);
pgt_update_hw_from_sw(sc, 0, 0);
}
break;
}
splx(s);
}