Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 19 Jun 2015 01:44:18 +0000 (UTC)
From:      Adrian Chadd <adrian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r284588 - head/sys/dev/iwn
Message-ID:  <201506190144.t5J1iIbH083926@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: adrian
Date: Fri Jun 19 01:44:17 2015
New Revision: 284588
URL: https://svnweb.freebsd.org/changeset/base/284588

Log:
  First cut at attempting to buffer frames until we see a beacon.
  
  The iwn(4) firmware forgets most of its channel state after an RXON
  command.  This means that any beacons its seen on passive 5GHz channels
  are forgotten upon an association/authorisation request.
  This unfortuantely means that 5GHz association almost always fails -
  the assoc and/or auth frames are dropped with a status of "passive
  channel, haven't seen a beacon yet." (0x90.)
  
  So:
  
  * add an xmit queue, global, to buffer frames
  * modify the xmit path to use the mbuf tag from net80211
    to specify raw frame details
  * buffer xmit frames from both raw and non-raw paths
  * if a beacon is seen in the RX path, schedule a taskqueue to
    send said frames and un-buffer things.
  * flush frames during state change back to INIT, or NIC
    down/up/detach.
  
  This isn't the final shape I'd like this to be in but it certainly
  is better than 5GHz "not working at all".
  
  Tested:
  
  * Intel 5100, STA mode (before spilling coffee)
  * Intel 5300, STA mode (after spilling coffee)
  
  Story:
  
  * This has been bugging me at work for months, which I just
    worked around by throwing an ath(4) into my Lenovo T400 cardbus
    slot.
  
  * Our ops director discovered indeed FreeBSD runs well on the
    Lenovo T420p, except for that pesky 5GHz thing.  So now developers
    also can have a T420p running FreeBSD to do work with.
    Their #1 feedback to me - "boy it'd be nice if 5GHz wifi worked."
  
  * .. then, I was at NANOG but stuck with 5GHz only wifi and no ath(4)
    NIC to put in a laptop - and I snapped.
  
  Thus, the reason this is actually work related.
  
  MFC after:	2 weeks
  Sponsored by:	Norse Corp, Inc.

Modified:
  head/sys/dev/iwn/if_iwn.c
  head/sys/dev/iwn/if_iwnvar.h

Modified: head/sys/dev/iwn/if_iwn.c
==============================================================================
--- head/sys/dev/iwn/if_iwn.c	Fri Jun 19 01:21:10 2015	(r284587)
+++ head/sys/dev/iwn/if_iwn.c	Fri Jun 19 01:44:17 2015	(r284588)
@@ -232,6 +232,7 @@ static int	iwn_tx_data(struct iwn_softc 
 static int	iwn_tx_data_raw(struct iwn_softc *, struct mbuf *,
 		    struct ieee80211_node *,
 		    const struct ieee80211_bpf_params *params);
+static void	iwn_xmit_task(void *arg0, int pending);
 static int	iwn_raw_xmit(struct ieee80211_node *, struct mbuf *,
 		    const struct ieee80211_bpf_params *);
 static void	iwn_start(struct ifnet *);
@@ -682,6 +683,9 @@ iwn_attach(device_t dev)
 	TASK_INIT(&sc->sc_radioon_task, 0, iwn_radio_on, sc);
 	TASK_INIT(&sc->sc_radiooff_task, 0, iwn_radio_off, sc);
 	TASK_INIT(&sc->sc_panic_task, 0, iwn_panicked, sc);
+	TASK_INIT(&sc->sc_xmit_task, 0, iwn_xmit_task, sc);
+
+	mbufq_init(&sc->sc_xmit_queue, 1024);
 
 	sc->sc_tq = taskqueue_create("iwn_taskq", M_WAITOK,
 	    taskqueue_thread_enqueue, &sc->sc_tq);
@@ -1360,6 +1364,28 @@ iwn_vap_delete(struct ieee80211vap *vap)
 	free(ivp, M_80211_VAP);
 }
 
+static void
+iwn_xmit_queue_drain(struct iwn_softc *sc)
+{
+	struct mbuf *m;
+	struct ieee80211_node *ni;
+
+	IWN_LOCK_ASSERT(sc);
+	while ((m = mbufq_dequeue(&sc->sc_xmit_queue)) != NULL) {
+		ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+		ieee80211_free_node(ni);
+		m_freem(m);
+	}
+}
+
+static int
+iwn_xmit_queue_enqueue(struct iwn_softc *sc, struct mbuf *m)
+{
+
+	IWN_LOCK_ASSERT(sc);
+	return (mbufq_enqueue(&sc->sc_xmit_queue, m));
+}
+
 static int
 iwn_detach(device_t dev)
 {
@@ -1373,6 +1399,11 @@ iwn_detach(device_t dev)
 	if (ifp != NULL) {
 		ic = ifp->if_l2com;
 
+		/* Free the mbuf queue and node references */
+		IWN_LOCK(sc);
+		iwn_xmit_queue_drain(sc);
+		IWN_UNLOCK(sc);
+
 		ieee80211_draintask(ic, &sc->sc_reinit_task);
 		ieee80211_draintask(ic, &sc->sc_radioon_task);
 		ieee80211_draintask(ic, &sc->sc_radiooff_task);
@@ -2831,6 +2862,9 @@ iwn_newstate(struct ieee80211vap *vap, e
 		sc->rxon->filter &= ~htole32(IWN_FILTER_BSS);
 		sc->calib.state = IWN_CALIB_STATE_INIT;
 
+		/* Wait until we hear a beacon before we transmit */
+		sc->sc_beacon_wait = 1;
+
 		if ((error = iwn_auth(sc, vap)) != 0) {
 			device_printf(sc->sc_dev,
 			    "%s: could not move to auth state\n", __func__);
@@ -2846,6 +2880,9 @@ iwn_newstate(struct ieee80211vap *vap, e
 			break;
 		}
 
+		/* Wait until we hear a beacon before we transmit */
+		sc->sc_beacon_wait = 1;
+
 		/*
 		 * !RUN -> RUN requires setting the association id
 		 * which is done with a firmware cmd.  We also defer
@@ -2859,6 +2896,12 @@ iwn_newstate(struct ieee80211vap *vap, e
 
 	case IEEE80211_S_INIT:
 		sc->calib.state = IWN_CALIB_STATE_INIT;
+		/*
+		 * Purge the xmit queue so we don't have old frames
+		 * during a new association attempt.
+		 */
+		sc->sc_beacon_wait = 0;
+		iwn_xmit_queue_drain(sc);
 		break;
 
 	default:
@@ -3066,6 +3109,32 @@ iwn_rx_done(struct iwn_softc *sc, struct
 		}
 	}
 
+	/*
+	 * If it's a beacon and we're waiting, then do the
+	 * wakeup.  This should unblock raw_xmit/start.
+	 */
+	if (sc->sc_beacon_wait) {
+		uint8_t type, subtype;
+		/* NB: Re-assign wh */
+		wh = mtod(m, struct ieee80211_frame *);
+		type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+		subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
+		/*
+		 * This assumes at this point we've received our own
+		 * beacon.
+		 */
+		DPRINTF(sc, IWN_DEBUG_TRACE,
+		    "%s: beacon_wait, type=%d, subtype=%d\n",
+		    __func__, type, subtype);
+		if (type == IEEE80211_FC0_TYPE_MGT &&
+		    subtype == IEEE80211_FC0_SUBTYPE_BEACON) {
+			DPRINTF(sc, IWN_DEBUG_TRACE | IWN_DEBUG_XMIT,
+			    "%s: waking things up\n", __func__);
+			/* queue taskqueue to transmit! */
+			taskqueue_enqueue(sc->sc_tq, &sc->sc_xmit_task);
+		}
+	}
+
 	IWN_UNLOCK(sc);
 
 	/* Send the frame to the 802.11 layer. */
@@ -4802,6 +4871,51 @@ iwn_tx_data_raw(struct iwn_softc *sc, st
 	return 0;
 }
 
+static void
+iwn_xmit_task(void *arg0, int pending)
+{
+	struct iwn_softc *sc = arg0;
+	struct ifnet *ifp = sc->sc_ifp;
+	struct ieee80211_node *ni;
+	struct mbuf *m;
+	int error;
+	struct ieee80211_bpf_params p;
+	int have_p;
+
+	DPRINTF(sc, IWN_DEBUG_XMIT, "%s: called\n", __func__);
+
+	IWN_LOCK(sc);
+	/*
+	 * Dequeue frames, attempt to transmit,
+	 * then disable beaconwait when we're done.
+	 */
+	while ((m = mbufq_dequeue(&sc->sc_xmit_queue)) != NULL) {
+		have_p = 0;
+		ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+
+		/* Get xmit params if appropriate */
+		if (ieee80211_get_xmit_params(m, &p) == 0)
+			have_p = 1;
+
+		DPRINTF(sc, IWN_DEBUG_XMIT, "%s: m=%p, have_p=%d\n",
+		    __func__, m, have_p);
+
+		/* If we have xmit params, use them */
+		if (have_p)
+			error = iwn_tx_data_raw(sc, m, ni, &p);
+		else
+			error = iwn_tx_data(sc, m, ni);
+
+		if (error != 0) {
+			ieee80211_free_node(ni);
+			if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+		}
+	}
+
+	sc->sc_beacon_wait = 0;
+	IWN_UNLOCK(sc);
+}
+
 static int
 iwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
     const struct ieee80211_bpf_params *params)
@@ -4819,7 +4933,25 @@ iwn_raw_xmit(struct ieee80211_node *ni, 
 		return ENETDOWN;
 	}
 
+	/* XXX? net80211 doesn't set this on xmit'ed raw frames? */
+	m->m_pkthdr.rcvif = (void *) ni;
+
 	IWN_LOCK(sc);
+
+	/* queue frame if we have to */
+	if (sc->sc_beacon_wait) {
+		if (iwn_xmit_queue_enqueue(sc, m) != 0) {
+			m_freem(m);
+			ieee80211_free_node(ni);
+			if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+			IWN_UNLOCK(sc);
+			return (ENOBUFS);
+		}
+		/* Queued, so just return OK */
+		IWN_UNLOCK(sc);
+		return (0);
+	}
+
 	if (params == NULL) {
 		/*
 		 * Legacy path; interpret frame contents to decide
@@ -4866,6 +4998,14 @@ iwn_start_locked(struct ifnet *ifp)
 
 	IWN_LOCK_ASSERT(sc);
 
+	/*
+	 * If we're waiting for a beacon, we can just exit out here
+	 * and wait for the taskqueue to be kicked.
+	 */
+	if (sc->sc_beacon_wait) {
+		return;
+	}
+
 	DPRINTF(sc, IWN_DEBUG_XMIT, "%s: called\n", __func__);
 
 	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 ||

Modified: head/sys/dev/iwn/if_iwnvar.h
==============================================================================
--- head/sys/dev/iwn/if_iwnvar.h	Fri Jun 19 01:21:10 2015	(r284587)
+++ head/sys/dev/iwn/if_iwnvar.h	Fri Jun 19 01:44:17 2015	(r284588)
@@ -309,6 +309,7 @@ struct iwn_softc {
 	struct task		sc_radioon_task;
 	struct task		sc_radiooff_task;
 	struct task		sc_panic_task;
+	struct task		sc_xmit_task;
 
 	/* Taskqueue */
 	struct taskqueue	*sc_tq;
@@ -385,6 +386,9 @@ struct iwn_softc {
 	/* Are we doing a scan? */
 	int			sc_is_scanning;
 
+	/* Are we waiting for a beacon before xmit? */
+	int			sc_beacon_wait;
+
 	struct ieee80211_tx_ampdu *qid2tap[IWN5000_NTXQUEUES];
 
 	int			(*sc_ampdu_rx_start)(struct ieee80211_node *,
@@ -417,6 +421,13 @@ struct iwn_softc {
 
 #define	IWN_UCODE_API(ver)	(((ver) & 0x0000FF00) >> 8)
 	uint32_t		ucode_rev;
+
+	/*
+	 * Global queue for queuing xmit frames
+	 * when we can't yet transmit (eg raw
+	 * frames whilst waiting for beacons.)
+	 */
+	struct mbufq		sc_xmit_queue;
 };
 
 #define IWN_LOCK_INIT(_sc) \



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201506190144.t5J1iIbH083926>