On Sun, Feb 04, 2018 at 11:15:01AM +0100, Stefan Sperling wrote:
> On Sun, Feb 04, 2018 at 12:01:58AM +0100, Stefan Sperling wrote:
> > I have also briefly tested hostap with both AR9271 and AR7010 and it
> > now seems to work well enough to justify removing the BUGS section from
> > the athn(4) page.
> 
> I have to retract the above statement.
> I've done some more testing and hostap mode still has serious problems.
> But it can be fixed later. I've already got some ideas about what's wrong.

Here is a new version with fixed hostap mode.

There were two major problems for hostap with the previous code:

1) The previous code tried to add each new node to the firmware's table
when an ASSOC request is about to be confirmed. But that is too late.
We need to add the new client to the firmware's node table already
when net80211 tries to acknowledge the AUTH request.
Otherwise our AUTH response will be dropped by the device because it
has no destination node in the firmware. The client will now keep
trying to AUTH, and until it has done so it won't try to ASSOC.
The fix requires a new function hook in net80211 which drivers can set if
they need to do something before an incoming AUTH request is acknowledged.
Some client capabilities are not available this early, so this driver
still needs to update the node's rate set when an ASSOC request is
acknowledged (otherwise we'd break 11n support).

2) There is only room for 8 nodes in the firmware's node table.
The previous code used a counter to keep track of nodes added/removed,
but for hostap a counter is insufficient as nodes will show up and leave
in no particular order.
What is needed instead is a bitmask which keeps track of free node slots.
Since there are only 8 slots, we need to be a bit aggressive about removing
inactive clients when the table is full and a new client wants to connect.
However, it is still hard for new clients to steal slots which are being
legitimately used. We rely on net80211's inactivity timer to detect inactive
clients. It takes about a minute or so for a new client to associate to
an AP with a full table and no active clients. I think this is acceptable.

This diff keeps growing.... Can I commit?

Index: dev/usb/if_athn_usb.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/if_athn_usb.c,v
retrieving revision 1.48
diff -u -p -r1.48 if_athn_usb.c
--- dev/usb/if_athn_usb.c       26 Oct 2017 15:00:28 -0000      1.48
+++ dev/usb/if_athn_usb.c       4 Feb 2018 20:27:26 -0000
@@ -130,17 +130,27 @@ void              athn_usb_newstate_cb(struct athn_u
 void           athn_usb_newassoc(struct ieee80211com *,
                    struct ieee80211_node *, int);
 void           athn_usb_newassoc_cb(struct athn_usb_softc *, void *);
-void           athn_usb_node_leave(struct ieee80211com *,
+struct ieee80211_node *athn_usb_node_alloc(struct ieee80211com *);
+void           athn_usb_count_active_sta(void *, struct ieee80211_node *);
+void           athn_usb_newauth_cb(struct athn_usb_softc *, void *);
+int            athn_usb_newauth(struct ieee80211com *,
+                   struct ieee80211_node *, int, uint16_t);
+void           athn_usb_node_free(struct ieee80211com *,
                    struct ieee80211_node *);
-void           athn_usb_node_leave_cb(struct athn_usb_softc *, void *);
+void           athn_usb_node_free_cb(struct athn_usb_softc *, void *);
 int            athn_usb_ampdu_tx_start(struct ieee80211com *,
                    struct ieee80211_node *, uint8_t);
 void           athn_usb_ampdu_tx_start_cb(struct athn_usb_softc *, void *);
 void           athn_usb_ampdu_tx_stop(struct ieee80211com *,
                    struct ieee80211_node *, uint8_t);
 void           athn_usb_ampdu_tx_stop_cb(struct athn_usb_softc *, void *);
+void           athn_usb_clean_nodes(void *, struct ieee80211_node *);
 int            athn_usb_create_node(struct athn_usb_softc *,
                    struct ieee80211_node *);
+int            athn_usb_node_set_rates(struct athn_usb_softc *,
+                   struct ieee80211_node *);
+int            athn_usb_remove_node(struct athn_usb_softc *,
+                   struct ieee80211_node *);
 void           athn_usb_rx_enable(struct athn_softc *);
 int            athn_set_chan(struct athn_softc *, struct ieee80211_channel *,
                    struct ieee80211_channel *);
@@ -159,6 +169,7 @@ void                athn_usb_delete_key_cb(struct athn
 void           athn_usb_bcneof(struct usbd_xfer *, void *,
                    usbd_status);
 void           athn_usb_swba(struct athn_usb_softc *);
+void           athn_usb_tx_status(void *, struct ieee80211_node *);
 void           athn_usb_rx_wmi_ctrl(struct athn_usb_softc *, uint8_t *, int);
 void           athn_usb_intr(struct usbd_xfer *, void *,
                    usbd_status);
@@ -319,8 +330,13 @@ athn_usb_attachhook(struct device *self)
        ifp->if_ioctl = athn_usb_ioctl;
        ifp->if_start = athn_usb_start;
        ifp->if_watchdog = athn_usb_watchdog;
+       ic->ic_node_alloc = athn_usb_node_alloc;
+       ic->ic_newauth = athn_usb_newauth;
        ic->ic_newassoc = athn_usb_newassoc;
-       ic->ic_node_leave = athn_usb_node_leave;
+#ifndef IEEE80211_STA_ONLY
+       usc->sc_node_free = ic->ic_node_free;
+       ic->ic_node_free = athn_usb_node_free;
+#endif
        ic->ic_updateslot = athn_usb_updateslot;
        ic->ic_updateedca = athn_usb_updateedca;
 #ifdef notyet
@@ -629,12 +645,9 @@ athn_usb_load_firmware(struct athn_usb_s
        /* Determine which firmware image to load. */
        if (usc->flags & ATHN_USB_FLAG_AR7010) {
                dd = usbd_get_device_descriptor(usc->sc_udev);
-               if (UGETW(dd->bcdDevice) == 0x0202)
-                       name = "athn-ar7010-11";
-               else
-                       name = "athn-ar7010";
+               name = "athn-open-ar7010";
        } else
-               name = "athn-ar9271";
+               name = "athn-open-ar9271";
        /* Read firmware image from the filesystem. */
        if ((error = loadfirmware(name, &fw, &fwsize)) != 0) {
                printf("%s: failed loadfirmware of file %s (error %d)\n",
@@ -1026,20 +1039,15 @@ athn_usb_newstate_cb(struct athn_usb_sof
        struct ieee80211com *ic = &sc->sc_ic;
        enum ieee80211_state ostate;
        uint32_t reg, imask;
-       uint8_t sta_index;
        int s, error;
 
        timeout_del(&sc->calib_to);
 
        s = splnet();
        ostate = ic->ic_state;
-       DPRINTF(("newstate %d -> %d\n", ostate, cmd->state));
 
-       if (ostate == IEEE80211_S_RUN) {
-               sta_index = ((struct athn_node *)ic->ic_bss)->sta_index;
-               (void)athn_usb_wmi_xcmd(usc, AR_WMI_CMD_NODE_REMOVE,
-                   &sta_index, sizeof(sta_index), NULL);
-               usc->nnodes--;
+       if (ostate == IEEE80211_S_RUN && ic->ic_opmode == IEEE80211_M_STA) {
+               athn_usb_remove_node(usc, ic->ic_bss);
                reg = AR_READ(sc, AR_RX_FILTER);
                reg = (reg & ~AR_RX_FILTER_MYBEACON) |
                    AR_RX_FILTER_BEACON;
@@ -1053,13 +1061,21 @@ athn_usb_newstate_cb(struct athn_usb_sof
        case IEEE80211_S_SCAN:
                /* Make the LED blink while scanning. */
                athn_set_led(sc, !sc->led_state);
-               (void)athn_usb_switch_chan(sc, ic->ic_bss->ni_chan, NULL);
+               error = athn_usb_switch_chan(sc, ic->ic_bss->ni_chan, NULL);
+               if (error)
+                       printf("%s: could not switch to channel %d\n",
+                           usc->usb_dev.dv_xname,
+                           ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan));
                if (!usbd_is_dying(usc->sc_udev))
                        timeout_add_msec(&sc->scan_to, 200);
                break;
        case IEEE80211_S_AUTH:
                athn_set_led(sc, 0);
                error = athn_usb_switch_chan(sc, ic->ic_bss->ni_chan, NULL);
+               if (error)
+                       printf("%s: could not switch to channel %d\n",
+                           usc->usb_dev.dv_xname,
+                           ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan));
                break;
        case IEEE80211_S_ASSOC:
                break;
@@ -1069,13 +1085,18 @@ athn_usb_newstate_cb(struct athn_usb_sof
                if (ic->ic_opmode == IEEE80211_M_MONITOR)
                        break;
 
-               /* Create node entry for our BSS */
-               error = athn_usb_create_node(usc, ic->ic_bss);
-
+               if (ic->ic_opmode == IEEE80211_M_STA) {
+                       /* Create node entry for our BSS */
+                       error = athn_usb_create_node(usc, ic->ic_bss);
+                       if (error)
+                               printf("%s: could not update firmware station "
+                                   "table\n", usc->usb_dev.dv_xname);
+               }
                athn_set_bss(sc, ic->ic_bss);
                athn_usb_wmi_cmd(usc, AR_WMI_CMD_DISABLE_INTR);
 #ifndef IEEE80211_STA_ONLY
                if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
+                       athn_usb_switch_chan(sc, ic->ic_bss->ni_chan, NULL);
                        athn_set_hostap_timers(sc);
                        /* Enable software beacon alert interrupts. */
                        imask = htobe32(AR_IMR_SWBA);
@@ -1108,50 +1129,180 @@ athn_usb_newassoc(struct ieee80211com *i
 #ifndef IEEE80211_STA_ONLY
        struct athn_usb_softc *usc = ic->ic_softc;
 
-       if (ic->ic_opmode != IEEE80211_M_HOSTAP || !isnew)
+       if (ic->ic_opmode != IEEE80211_M_HOSTAP &&
+           ic->ic_state != IEEE80211_S_RUN)
                return;
-       /* Do it in a process context. */
+
+       /* Update the node's supported rates in a process context. */
        ieee80211_ref_node(ni);
        athn_usb_do_async(usc, athn_usb_newassoc_cb, &ni, sizeof(ni));
+#endif
 }
 
+#ifndef IEEE80211_STA_ONLY
 void
 athn_usb_newassoc_cb(struct athn_usb_softc *usc, void *arg)
 {
-       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
+       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
        struct ieee80211_node *ni = *(void **)arg;
+       struct athn_node *an = (struct athn_node *)ni;
        int s;
 
-       s = splnet();
+       if (ic->ic_state != IEEE80211_S_RUN)
+               return;
+
+       s = splnet();
        /* NB: Node may have left before we got scheduled. */
-       if (ni->ni_associd != 0 && ni->ni_state == IEEE80211_STA_ASSOC)
-               (void)athn_usb_create_node(usc, ni);
+       if (an->sta_index != 0)
+               (void)athn_usb_node_set_rates(usc, ni);
        ieee80211_release_node(ic, ni);
        splx(s);
+}
 #endif
+
+struct ieee80211_node *
+athn_usb_node_alloc(struct ieee80211com *ic)
+{
+       struct athn_node *an;
+
+       an = malloc(sizeof(struct athn_node), M_DEVBUF, M_NOWAIT | M_ZERO);
+       return (struct ieee80211_node *)an;
+}
+
+
+#ifndef IEEE80211_STA_ONLY
+void
+athn_usb_count_active_sta(void *arg, struct ieee80211_node *ni)
+{
+       int *nsta = arg;
+       struct athn_node *an = (struct athn_node *)ni;
+
+       if (an->sta_index == 0)
+               return;
+
+       if ((ni->ni_state == IEEE80211_STA_AUTH ||
+           ni->ni_state == IEEE80211_STA_ASSOC) &&
+           ni->ni_inact < IEEE80211_INACT_MAX)
+               (*nsta)++;
 }
 
+struct athn_usb_newauth_cb_arg {
+       struct ieee80211_node *ni;
+       uint16_t seq;
+};
+
 void
-athn_usb_node_leave(struct ieee80211com *ic, struct ieee80211_node *ni)
+athn_usb_newauth_cb(struct athn_usb_softc *usc, void *arg)
 {
+       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
+       struct athn_usb_newauth_cb_arg *a = arg;
+       struct ieee80211_node *ni = a->ni;
+       uint16_t seq = a->seq;
+       struct athn_node *an = (struct athn_node *)ni;
+       int s, error = 0;
+
+       free(arg, M_DEVBUF, sizeof(*arg));
+
+       if (ic->ic_state != IEEE80211_S_RUN)
+               return;
+
+       s = splnet();
+       if (an->sta_index == 0) {
+               error = athn_usb_create_node(usc, ni);
+               if (error)
+                       printf("%s: could not add station %s to firmware "
+                           "table\n", usc->usb_dev.dv_xname,
+                           ether_sprintf(ni->ni_macaddr));
+       }
+       if (error == 0)
+               ieee80211_auth_open_confirm(ic, ni, seq);
+       ieee80211_unref_node(&ni);
+       splx(s);
+}
+#endif
+
+int
+athn_usb_newauth(struct ieee80211com *ic, struct ieee80211_node *ni,
+    int isnew, uint16_t seq)
+{
+#ifndef IEEE80211_STA_ONLY
        struct athn_usb_softc *usc = ic->ic_softc;
-       uint8_t sta_index;
+       struct ifnet *ifp = &ic->ic_if;
+       struct athn_node *an = (struct athn_node *)ni;
+       int nsta;
+       struct athn_usb_newauth_cb_arg *arg;
 
-       /* Do it in a process context. */
-       sta_index = ((struct athn_node *)ni)->sta_index;
-       athn_usb_do_async(usc, athn_usb_node_leave_cb,
-           &sta_index, sizeof(sta_index));
+       if (ic->ic_opmode != IEEE80211_M_HOSTAP)
+               return 0;
+
+       if (!isnew && an->sta_index != 0) /* already in firmware table */
+               return 0;
+
+       /* Check if we have room in the firmware table. */
+       nsta = 1; /* Account for default node. */
+       ieee80211_iterate_nodes(ic, athn_usb_count_active_sta, &nsta);
+       if (nsta >= AR_USB_MAX_STA) {
+               if (ifp->if_flags & IFF_DEBUG)
+                       printf("%s: cannot authenticate station %s: firmware "
+                           "table is full\n", usc->usb_dev.dv_xname,
+                           ether_sprintf(ni->ni_macaddr));
+               return ENOSPC;
+       }
+
+       /* 
+        * In a process context, try to add this node to the
+        * firmware table and confirm the AUTH request.
+        */
+       arg = malloc(sizeof(*arg), M_DEVBUF, M_NOWAIT);
+       if (arg == NULL)
+               return ENOMEM;
+       arg->ni = ieee80211_ref_node(ni);
+       arg->seq = seq;
+       athn_usb_do_async(usc, athn_usb_newauth_cb, arg, sizeof(*arg));
+       return EBUSY;
+#else
+       return 0;
+#endif /* IEEE80211_STA_ONLY */
 }
 
+#ifndef IEEE80211_STA_ONLY
 void
-athn_usb_node_leave_cb(struct athn_usb_softc *usc, void *arg)
+athn_usb_node_free(struct ieee80211com *ic, struct ieee80211_node *ni)
 {
+       struct athn_usb_softc *usc = ic->ic_softc;
+       struct athn_node *an = (struct athn_node *)ni;
+
+       /* 
+        * Remove the node from the firmware table in a process context.
+        * Pass an index rather than the pointer which we will free.
+        */
+       if (an->sta_index != 0)
+               athn_usb_do_async(usc, athn_usb_node_free_cb,
+                   &an->sta_index, sizeof(an->sta_index));
+       usc->sc_node_free(ic, ni);
+}
+
+void
+athn_usb_node_free_cb(struct athn_usb_softc *usc, void *arg)
+{
+       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
+       struct ifnet *ifp = &ic->ic_if;
        uint8_t sta_index = *(uint8_t *)arg;
+       int error;
 
-       (void)athn_usb_wmi_xcmd(usc, AR_WMI_CMD_NODE_REMOVE,
+       error = athn_usb_wmi_xcmd(usc, AR_WMI_CMD_NODE_REMOVE,
            &sta_index, sizeof(sta_index), NULL);
-       usc->nnodes--;
+       if (error) {
+               printf("%s: could not remove station %u from firmware table\n",
+                   usc->usb_dev.dv_xname, sta_index);
+               return;
+       }
+       usc->free_node_slots |= (1 << sta_index);
+       if (ifp->if_flags & IFF_DEBUG)
+               printf("%s: station %u removed from firmware table\n",
+                   usc->usb_dev.dv_xname, sta_index);
 }
+#endif /* IEEE80211_STA_ONLY */
 
 int
 athn_usb_ampdu_tx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
@@ -1210,27 +1361,67 @@ athn_usb_ampdu_tx_stop_cb(struct athn_us
            &aggr, sizeof(aggr), NULL);
 }
 
+#ifndef IEEE80211_STA_ONLY
+/* Try to find a node we can evict to make room in the firmware table. */
+void
+athn_usb_clean_nodes(void *arg, struct ieee80211_node *ni)
+{
+       struct athn_usb_softc *usc = arg;
+       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
+       struct athn_node *an = (struct athn_node *)ni;
+
+       /* 
+        * Don't remove the default node (used for management frames).
+        * Nodes which are not in the firmware table also have index zero.
+        */
+       if (an->sta_index == 0)
+               return;
+
+       /* Remove non-associated nodes. */
+       if (ni->ni_state != IEEE80211_STA_AUTH &&
+           ni->ni_state != IEEE80211_STA_ASSOC) {
+               athn_usb_remove_node(usc, ni);
+               return;
+       }
+
+       /* 
+        * Kick off inactive associated nodes. This won't help
+        * immediately but will help if the new STA retries later.
+        */
+       if (ni->ni_inact >= IEEE80211_INACT_MAX) {
+               IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
+                   IEEE80211_REASON_AUTH_EXPIRE);
+               ieee80211_node_leave(ic, ni);
+       }
+}
+#endif
+
 int
 athn_usb_create_node(struct athn_usb_softc *usc, struct ieee80211_node *ni)
 {
        struct athn_node *an = (struct athn_node *)ni;
        struct ar_htc_target_sta sta;
-       struct ar_htc_target_rate rate;
-       int error, i, j;
+       int error, sta_index;
+#ifndef IEEE80211_STA_ONLY
+       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
+       struct ifnet *ifp = &ic->ic_if;
 
-       /* Firmware cannot handle more than 8 STAs. */
-       if (usc->nnodes > AR_USB_MAX_STA)
+       /* Firmware cannot handle more than 8 STAs. Try to make room first. */
+       if (ic->ic_opmode == IEEE80211_M_HOSTAP)
+               ieee80211_iterate_nodes(ic, athn_usb_clean_nodes, usc);
+#endif
+       if (usc->free_node_slots == 0x00)
                return ENOBUFS;
 
-       an->sta_index = IEEE80211_AID(ni->ni_associd);
+       sta_index = ffs(usc->free_node_slots) - 1;
+       if (sta_index < 0 || sta_index >= AR_USB_MAX_STA)
+               return ENOSPC;
 
        /* Create node entry on target. */
        memset(&sta, 0, sizeof(sta));
        IEEE80211_ADDR_COPY(sta.macaddr, ni->ni_macaddr);
        IEEE80211_ADDR_COPY(sta.bssid, ni->ni_bssid);
-       sta.associd = htobe16(ni->ni_associd);
-       sta.valid = 1;
-       sta.sta_index = an->sta_index;
+       sta.sta_index = sta_index;
        sta.maxampdu = 0xffff;
        if (ni->ni_flags & IEEE80211_NODE_HT)
                sta.flags |= htobe16(AR_HTC_STA_HT);
@@ -1238,11 +1429,28 @@ athn_usb_create_node(struct athn_usb_sof
            &sta, sizeof(sta), NULL);
        if (error != 0)
                return (error);
-       usc->nnodes++;
+       an->sta_index = sta_index;
+       usc->free_node_slots &= ~(1 << an->sta_index);
+
+#ifndef IEEE80211_STA_ONLY
+       if (ifp->if_flags & IFF_DEBUG)
+               printf("%s: station %u (%s) added to firmware table\n",
+                   usc->usb_dev.dv_xname, sta_index,
+                   ether_sprintf(ni->ni_macaddr));
+#endif
+       return athn_usb_node_set_rates(usc, ni);
+}
+
+int
+athn_usb_node_set_rates(struct athn_usb_softc *usc, struct ieee80211_node *ni)
+{
+       struct athn_node *an = (struct athn_node *)ni;
+       struct ar_htc_target_rate rate;
+       int i, j;
 
        /* Setup supported rates. */
        memset(&rate, 0, sizeof(rate));
-       rate.sta_index = sta.sta_index;
+       rate.sta_index = an->sta_index;
        rate.isnew = 1;
        rate.lg_rates.rs_nrates = ni->ni_rates.rs_nrates;
        memcpy(rate.lg_rates.rs_rates, ni->ni_rates.rs_rates,
@@ -1270,9 +1478,40 @@ athn_usb_create_node(struct athn_usb_sof
                        rate.capflags |= htobe32(AR_RC_SGI_FLAG);
 #endif
        }
-       error = athn_usb_wmi_xcmd(usc, AR_WMI_CMD_RC_RATE_UPDATE,
+
+       return athn_usb_wmi_xcmd(usc, AR_WMI_CMD_RC_RATE_UPDATE,
            &rate, sizeof(rate), NULL);
-       return (error);
+}
+
+int
+athn_usb_remove_node(struct athn_usb_softc *usc, struct ieee80211_node *ni)
+{
+       struct athn_node *an = (struct athn_node *)ni;
+       int error;
+#ifndef IEEE80211_STA_ONLY
+       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
+       struct ifnet *ifp = &ic->ic_if;
+#endif
+
+       error = athn_usb_wmi_xcmd(usc, AR_WMI_CMD_NODE_REMOVE,
+           &an->sta_index, sizeof(an->sta_index), NULL);
+       if (error) {
+               printf("%s: could not remove station %u (%s) from "
+                   "firmware table\n", usc->usb_dev.dv_xname, an->sta_index,
+                   ether_sprintf(ni->ni_macaddr));
+               return error;
+       }
+
+#ifndef IEEE80211_STA_ONLY
+       if (ifp->if_flags & IFF_DEBUG)
+               printf("%s: station %u (%s) removed from firmware table\n",
+                   usc->usb_dev.dv_xname, an->sta_index,
+                   ether_sprintf(ni->ni_macaddr));
+#endif
+
+       usc->free_node_slots |= (1 << an->sta_index);
+       an->sta_index = 0;
+       return 0;
 }
 
 void
@@ -1319,8 +1558,16 @@ athn_usb_switch_chan(struct athn_softc *
                error = athn_hw_reset(sc, c, extc, 0);
                if (error != 0) /* Hopeless case. */
                        return (error);
+
+               error = athn_set_chan(sc, c, extc);
+               if (AR_SREV_9271(sc) && error == 0)
+                       ar9271_load_ani(sc);
+               if (error != 0)
+                       return (error);
        }
 
+       sc->ops.set_txpower(sc, c, extc);
+
        error = athn_usb_wmi_cmd(usc, AR_WMI_CMD_START_RECV);
        if (error != 0)
                return (error);
@@ -1518,11 +1765,28 @@ athn_usb_swba(struct athn_usb_softc *usc
 }
 #endif
 
+/* Update current transmit rate for a node based on firmware Tx status. */
+void
+athn_usb_tx_status(void *arg, struct ieee80211_node *ni)
+{
+       struct ar_wmi_evt_txstatus *ts = arg;
+       struct athn_node *an = (struct athn_node *)ni;
+       uint8_t rate_index = (ts->rate & AR_HTC_TXSTAT_RATE);
+
+       if (an->sta_index != ts->cookie) /* Tx report for a different node */
+               return;
+
+       if (ts->flags & AR_HTC_TXSTAT_MCS) {
+               if (isset(ni->ni_rxmcs, rate_index))
+                       ni->ni_txmcs = rate_index;
+       } else if (rate_index < ni->ni_rates.rs_nrates)
+               ni->ni_txrate = rate_index;
+}
+
 void
 athn_usb_rx_wmi_ctrl(struct athn_usb_softc *usc, uint8_t *buf, int len)
 {
        struct ar_wmi_cmd_hdr *wmi;
-       struct ar_wmi_evt_txrate *txrate;
        uint16_t cmd_id;
 
        if (__predict_false(len < sizeof(*wmi)))
@@ -1547,10 +1811,38 @@ athn_usb_rx_wmi_ctrl(struct athn_usb_sof
                athn_usb_swba(usc);
                break;
 #endif
-       case AR_WMI_EVT_TXRATE:
-               txrate = (struct ar_wmi_evt_txrate *)&wmi[1];
-               DPRINTF(("txrate=%d\n", betoh32(txrate->txrate)));
+       case AR_WMI_EVT_TXSTATUS: {
+               struct ar_wmi_evt_txstatus_list *tsl;
+               int i;
+
+               tsl = (struct ar_wmi_evt_txstatus_list *)&wmi[1];
+               for (i = 0; i < tsl->count && i < nitems(tsl->ts); i++) {
+                       struct ieee80211com *ic = &usc->sc_sc.sc_ic;
+                       struct athn_node *an = (struct athn_node *)ic->ic_bss;
+                       struct ar_wmi_evt_txstatus *ts = &tsl->ts[i];
+                       uint8_t qid;
+
+                       /* Skip the node we use to send management frames. */
+                       if (ts->cookie == 0)
+                               continue;
+
+                       /* Skip Tx reports for non-data frame endpoints. */
+                       qid = (ts->rate & AR_HTC_TXSTAT_EPID) >>
+                               AR_HTC_TXSTAT_EPID_SHIFT;
+                       if (qid != usc->ep_data[EDCA_AC_BE] &&
+                           qid != usc->ep_data[EDCA_AC_BK] &&
+                           qid != usc->ep_data[EDCA_AC_VI] &&
+                           qid != usc->ep_data[EDCA_AC_VO])
+                               continue;
+
+                       if (ts->cookie == an->sta_index)
+                               athn_usb_tx_status(ts, ic->ic_bss);
+                       else
+                               ieee80211_iterate_nodes(ic, athn_usb_tx_status,
+                                   ts);
+               }
                break;
+       }
        case AR_WMI_EVT_FATAL:
                printf("%s: fatal firmware error\n", usc->usb_dev.dv_xname);
                break;
@@ -1944,7 +2236,7 @@ athn_usb_tx(struct athn_softc *sc, struc
        struct ar_tx_mgmt *txm;
        uint8_t *frm;
        uint16_t qos;
-       uint8_t sta_index, qid, tid = 0;
+       uint8_t qid, tid = 0;
        int hasqos, xferlen, error;
 
        wh = mtod(m, struct ieee80211_frame *);
@@ -1983,7 +2275,6 @@ athn_usb_tx(struct athn_softc *sc, struc
                bpf_mtap(sc->sc_drvbpf, &mb, BPF_DIRECTION_OUT);
        }
 #endif
-       sta_index = an->sta_index;
 
        /* NB: We don't take advantage of USB Tx stream mode for now. */
        hdr = (struct ar_stream_hdr *)data->buf;
@@ -1998,7 +2289,7 @@ athn_usb_tx(struct athn_softc *sc, struc
                txf = (struct ar_tx_frame *)&htc[1];
                memset(txf, 0, sizeof(*txf));
                txf->data_type = AR_HTC_NORMAL;
-               txf->node_idx = sta_index;
+               txf->node_idx = an->sta_index;
                txf->vif_idx = 0;
                txf->tid = tid;
                if (m->m_pkthdr.len + IEEE80211_CRC_LEN > ic->ic_rtsthreshold)
@@ -2010,15 +2301,17 @@ athn_usb_tx(struct athn_softc *sc, struc
                                txf->flags |= htobe32(AR_HTC_TX_RTSCTS);
                }
                txf->key_idx = 0xff;
+               txf->cookie = an->sta_index;
                frm = (uint8_t *)&txf[1];
        } else {
                htc->endpoint_id = usc->ep_mgmt;
 
                txm = (struct ar_tx_mgmt *)&htc[1];
                memset(txm, 0, sizeof(*txm));
-               txm->node_idx = sta_index;
+               txm->node_idx = an->sta_index;
                txm->vif_idx = 0;
                txm->key_idx = 0xff;
+               txm->cookie = an->sta_index;
                frm = (uint8_t *)&txm[1];
        }
        /* Copy payload. */
@@ -2282,17 +2575,13 @@ athn_usb_init(struct ifnet *ifp)
            &sta, sizeof(sta), NULL);
        if (error != 0)
                goto fail;
-       usc->nnodes++;
+       usc->free_node_slots = ~(1 << sta.sta_index);
 
        /* Update target capabilities. */
        memset(&hic, 0, sizeof(hic));
-       hic.flags = htobe32(0x400c2400);
-       hic.flags_ext = htobe32(0x00106080);
        hic.ampdu_limit = htobe32(0x0000ffff);
        hic.ampdu_subframes = 20;
-       hic.protmode = 1;       /* XXX */
-       hic.lg_txchainmask = sc->txchainmask;
-       hic.ht_txchainmask = sc->txchainmask;
+       hic.txchainmask = sc->txchainmask;
        DPRINTF(("updating target configuration\n"));
        error = athn_usb_wmi_xcmd(usc, AR_WMI_CMD_TARGET_IC_UPDATE,
            &hic, sizeof(hic), NULL);
@@ -2356,17 +2645,22 @@ athn_usb_stop(struct ifnet *ifp)
        timeout_del(&sc->scan_to);
        timeout_del(&sc->calib_to);
 
-       /* Remove main interface. */
+       /* Remove all non-default nodes. */
+       for (sta_index = 1; sta_index < AR_USB_MAX_STA; sta_index++) {
+               if (usc->free_node_slots & (1 << sta_index))
+                       continue;
+               (void)athn_usb_wmi_xcmd(usc, AR_WMI_CMD_NODE_REMOVE,
+                   &sta_index, sizeof(sta_index), NULL);
+       }
+
+       /* Remove main interface. This also invalides our default node. */
        memset(&hvif, 0, sizeof(hvif));
        hvif.index = 0;
        IEEE80211_ADDR_COPY(hvif.myaddr, ic->ic_myaddr);
        (void)athn_usb_wmi_xcmd(usc, AR_WMI_CMD_VAP_REMOVE,
            &hvif, sizeof(hvif), NULL);
-       /* Remove default node. */
-       sta_index = 0;
-       (void)athn_usb_wmi_xcmd(usc, AR_WMI_CMD_NODE_REMOVE,
-           &sta_index, sizeof(sta_index), NULL);
-       usc->nnodes--;
+
+       usc->free_node_slots = 0xff;
 
        (void)athn_usb_wmi_cmd(usc, AR_WMI_CMD_DISABLE_INTR);
        (void)athn_usb_wmi_cmd(usc, AR_WMI_CMD_DRAIN_TXQ_ALL);
Index: dev/usb/if_athn_usb.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/if_athn_usb.h,v
retrieving revision 1.8
diff -u -p -r1.8 if_athn_usb.h
--- dev/usb/if_athn_usb.h       8 Apr 2017 02:57:25 -0000       1.8
+++ dev/usb/if_athn_usb.h       4 Feb 2018 20:21:21 -0000
@@ -34,47 +34,45 @@
 /* Wireless module interface commands. */
 #define AR_WMI_CMD_ECHO                        0x001
 #define AR_WMI_CMD_ACCESS_MEMORY       0x002
-#define AR_WMI_CMD_DISABLE_INTR                0x003
-#define AR_WMI_CMD_ENABLE_INTR         0x004
-#define AR_WMI_CMD_RX_LINK             0x005
+#define AR_WMI_GET_FW_VERSION          0x003
+#define AR_WMI_CMD_DISABLE_INTR                0x004
+#define AR_WMI_CMD_ENABLE_INTR         0x005
 #define AR_WMI_CMD_ATH_INIT            0x006
 #define AR_WMI_CMD_ABORT_TXQ           0x007
 #define AR_WMI_CMD_STOP_TX_DMA         0x008
-#define AR_WMI_CMD_STOP_DMA_RECV       0x009
-#define AR_WMI_CMD_ABORT_TX_DMA                0x00a
-#define AR_WMI_CMD_DRAIN_TXQ           0x00b
-#define AR_WMI_CMD_DRAIN_TXQ_ALL       0x00c
-#define AR_WMI_CMD_START_RECV          0x00d
-#define AR_WMI_CMD_STOP_RECV           0x00e
-#define AR_WMI_CMD_FLUSH_RECV          0x00f
-#define AR_WMI_CMD_SET_MODE            0x010
-#define AR_WMI_CMD_RESET               0x011
-#define AR_WMI_CMD_NODE_CREATE         0x012
-#define AR_WMI_CMD_NODE_REMOVE         0x013
-#define AR_WMI_CMD_VAP_REMOVE          0x014
-#define AR_WMI_CMD_VAP_CREATE          0x015
-#define AR_WMI_CMD_BEACON_UPDATE       0x016
-#define AR_WMI_CMD_REG_READ            0x017
-#define AR_WMI_CMD_REG_WRITE           0x018
-#define AR_WMI_CMD_RC_STATE_CHANGE     0x019
-#define AR_WMI_CMD_RC_RATE_UPDATE      0x01a
-#define AR_WMI_CMD_DEBUG_INFO          0x01b
-#define AR_WMI_CMD_HOST_ATTACH         0x01c
-#define AR_WMI_CMD_TARGET_IC_UPDATE    0x01d
-#define AR_WMI_CMD_TGT_STATS           0x01e
-#define AR_WMI_CMD_TX_AGGR_ENABLE      0x01f
+#define AR_WMI_CMD_ABORT_TX_DMA                0x009
+#define AR_WMI_CMD_DRAIN_TXQ           0x00a
+#define AR_WMI_CMD_DRAIN_TXQ_ALL       0x00b
+#define AR_WMI_CMD_START_RECV          0x00c
+#define AR_WMI_CMD_STOP_RECV           0x00d
+#define AR_WMI_CMD_FLUSH_RECV          0x00e
+#define AR_WMI_CMD_SET_MODE            0x00f
+#define AR_WMI_CMD_NODE_CREATE         0x010
+#define AR_WMI_CMD_NODE_REMOVE         0x011
+#define AR_WMI_CMD_VAP_REMOVE          0x012
+#define AR_WMI_CMD_VAP_CREATE          0x013
+#define AR_WMI_CMD_REG_READ            0x014
+#define AR_WMI_CMD_REG_WRITE           0x015
+#define AR_WMI_CMD_RC_STATE_CHANGE     0x016
+#define AR_WMI_CMD_RC_RATE_UPDATE      0x017
+#define AR_WMI_CMD_TARGET_IC_UPDATE    0x018
+#define AR_WMI_CMD_TX_AGGR_ENABLE      0x019
 #define AR_WMI_CMD_TGT_DETACH          0x020
-#define AR_WMI_CMD_TGT_TXQ_ENABLE      0x021
-#define AR_WMI_CMD_AGGR_LIMIT          0x026
+#define AR_WMI_CMD_NODE_UPDATE         0x021
+#define AR_WMI_CMD_INT_STATS           0x022
+#define AR_WMI_CMD_TX_STATS            0x023
+#define AR_WMI_CMD_RX_STATS            0x024
+#define AR_WMI_CMD_BITRATE_MASK                0x025
+#define AR_WMI_CMD_REG_RMW             0x026
+
 /* Wireless module interface events. */
 #define AR_WMI_EVT_TGT_RDY             0x001
 #define AR_WMI_EVT_SWBA                        0x002
 #define AR_WMI_EVT_FATAL               0x003
 #define AR_WMI_EVT_TXTO                        0x004
 #define AR_WMI_EVT_BMISS               0x005
-#define AR_WMI_EVT_WLAN_TXCOMP         0x006
-#define AR_WMI_EVT_DELBA               0x007
-#define AR_WMI_EVT_TXRATE              0x008
+#define AR_WMI_EVT_DELBA               0x006
+#define AR_WMI_EVT_TXSTATUS            0x007
 
 /* Structure for service AR_SVC_WMI_CONTROL. */
 struct ar_wmi_cmd_hdr {
@@ -85,15 +83,8 @@ struct ar_wmi_cmd_hdr {
 } __packed;
 
 /* Values for AR_WMI_CMD_SET_MODE. */
-#define AR_HTC_MODE_AUTO       0
-#define AR_HTC_MODE_11A                1
-#define AR_HTC_MODE_11B                2
-#define AR_HTC_MODE_11G                3
-#define AR_HTC_MODE_FH         4
-#define AR_HTC_MODE_TURBO_A    5
-#define AR_HTC_MODE_TURBO_G    6
-#define AR_HTC_MODE_11NA       7
-#define AR_HTC_MODE_11NG       8
+#define AR_HTC_MODE_11NA       0
+#define AR_HTC_MODE_11NG       1
 
 #define AR_MAX_WRITE_COUNT     32
 /* Structure for command AR_WMI_CMD_REG_WRITE. */
@@ -104,14 +95,11 @@ struct ar_wmi_cmd_reg_write {
 
 /* Structure for command AR_WMI_CMD_NODE_{CREATE,REMOVE}. */
 struct ar_htc_target_sta {
-       uint16_t        associd;
-       uint16_t        txpower;
-       uint32_t        pariwisekey;
        uint8_t         macaddr[IEEE80211_ADDR_LEN];
        uint8_t         bssid[IEEE80211_ADDR_LEN];
        uint8_t         sta_index;
        uint8_t         vif_index;
-       uint8_t         vif_sta;
+       uint8_t         is_vif_sta;
        uint16_t        flags;
 #define AR_HTC_STA_AUTH        0x0001
 #define AR_HTC_STA_QOS 0x0002
@@ -119,14 +107,14 @@ struct ar_htc_target_sta {
 #define AR_HTC_STA_HT  0x0008
 
        uint16_t        htcap;
-       uint8_t         valid;
-       uint16_t        capinfo;
-       uint32_t        reserved[2];
-       uint16_t        txseqmgmt;
-       uint8_t         is_vif_sta;
        uint16_t        maxampdu;
+       uint8_t         pad;
+
+       /* Internal state. */
+       uint16_t        txseqmgmt;
        uint16_t        iv16;
        uint32_t        iv32;
+       void            *ni_vap;
 } __packed;
 
 /* Structures for command AR_WMI_CMD_RC_RATE_UPDATE. */
@@ -139,12 +127,14 @@ struct ar_htc_rateset {
 struct ar_htc_target_rate {
        uint8_t                 sta_index;
        uint8_t                 isnew;
+       uint8_t                 pad[2];
        uint32_t                capflags;
-#define AR_RC_DS_FLAG  0x00000001
-#define AR_RC_40_FLAG  0x00000002
-#define AR_RC_SGI_FLAG 0x00000004
-#define AR_RC_HT_FLAG  0x00000008
-#define AR_RC_STBC_FLAG        0x00000020
+#define AR_RC_DS_FLAG          0x00000001
+#define AR_RC_40_FLAG          0x00000002
+#define AR_RC_SGI_FLAG         0x00000004
+#define AR_RC_HT_FLAG          0x00000008
+#define AR_RC_STBC_FLAG                0x00000030 /* 2 bits */
+#define AR_RC_WEP_TKIP_FLAG    0x00000100 
 
        struct ar_htc_rateset   lg_rates;
        struct ar_htc_rateset   ht_rates;
@@ -161,7 +151,6 @@ struct ar_htc_target_aggr {
 /* Structure for command AR_WMI_CMD_VAP_CREATE. */
 struct ar_htc_target_vif {
        uint8_t         index;
-       uint8_t         des_bssid[IEEE80211_ADDR_LEN];
        uint32_t        opmode;
 #define AR_HTC_M_IBSS          0
 #define AR_HTC_M_STA           1
@@ -169,41 +158,60 @@ struct ar_htc_target_vif {
 #define AR_HTC_M_AHDEMO                3
 #define AR_HTC_M_HOSTAP                6
 #define AR_HTC_M_MONITOR       8
-
        uint8_t         myaddr[IEEE80211_ADDR_LEN];
-       uint8_t         bssid[IEEE80211_ADDR_LEN];
-       uint32_t        flags;
-       uint32_t        flags_ext;
-       uint16_t        ps_sta;
-       uint16_t        rtsthreshold;
        uint8_t         ath_cap;
-       int8_t          mcast_rate;
+       uint16_t        rtsthreshold;
+       uint8_t         pad;
+
+       /* Internal state. */
+       int8_t          nodeindex;
+       void            *iv_bss;
 } __packed;
 
 /* Structure for command AM_WMI_CMD_TARGET_IC_UPDATE. */
 struct ar_htc_cap_target {
-       uint32_t        flags;
-       uint32_t        flags_ext;
        uint32_t        ampdu_limit;
        uint8_t         ampdu_subframes;
-       uint8_t         ht_txchainmask;
-       uint8_t         lg_txchainmask;
-       uint8_t         rtscts_ratecode;
-       uint8_t         protmode;
+       uint8_t         enable_coex;
+       uint8_t         txchainmask;
+       uint8_t         pad;
+} __packed;
+
+struct ar_wmi_evt_txstatus {
+       uint8_t         cookie;
+
+       /* 
+        * Legacy rates are indicated as rate array indices.
+        * HT rates are indicated as MCS indices.
+        */
+       uint8_t         rate;
+#define AR_HTC_TXSTAT_RATE             0x0f
+#define AR_HTC_TXSTAT_EPID             0xf0
+#define AR_HTC_TXSTAT_EPID_SHIFT       4
+
+       uint8_t         flags;
+#define AR_HTC_TXSTAT_ACK      0x01
+#define AR_HTC_TXSTAT_FILT     0x02
+#define AR_HTC_TXSTAT_RTC_CTS  0x04
+#define AR_HTC_TXSTAT_MCS      0x08
+#define AR_HTC_TXSTAT_CW40     0x10
+#define AR_HTC_TXSTAT_SGI      0x20
 } __packed;
 
-/* Structure for event AR_WMI_EVT_TXRATE. */
-struct ar_wmi_evt_txrate {
-       uint32_t        txrate;
-       uint8_t         rssi_thresh;
-       uint8_t         per;
+/* Structure for event AR_WMI_EVT_TXSTATUS. */
+#define AR_HTC_MAX_TX_STATUS 12
+struct ar_wmi_evt_txstatus_list {
+       uint8_t                 count;
+       struct ar_wmi_evt_txstatus ts[AR_HTC_MAX_TX_STATUS];
 } __packed;
 
 /* HTC header. */
 struct ar_htc_frame_hdr {
        uint8_t         endpoint_id;
        uint8_t         flags;
-#define AR_HTC_FLAG_TRAILER    0x02
+#define AR_HTC_FLAG_NEED_CREDIT_UPDATE         0x01
+#define AR_HTC_FLAG_TRAILER                    0x02
+#define AR_HTC_FLAG_CREDIT_REDISTRIBUTION      0x03
 
        uint16_t        payload_len;
        uint8_t         control[4];
@@ -236,7 +244,8 @@ struct ar_tx_frame {
 
        uint8_t         key_type;
        uint8_t         key_idx;
-       uint8_t         reserved[26];
+       uint8_t         cookie;
+       uint8_t         pad;
 } __packed;
 
 /* Structure for service AR_SVC_WMI_MGMT. */
@@ -247,7 +256,8 @@ struct ar_tx_mgmt {
        uint8_t         flags;
        uint8_t         key_type;
        uint8_t         key_idx;
-       uint16_t        reserved;
+       uint8_t         cookie;
+       uint8_t         pad;
 } __packed;
 
 /* Structure for service AR_SVC_WMI_BEACON. */
@@ -459,6 +469,20 @@ struct athn_usb_softc {
        uint8_t                         ep_mgmt;
        uint8_t                         ep_data[EDCA_NUM_AC];
 
-       /* Firmware cannot handle more than 8 STAs. */
-       uint8_t                         nnodes;
+       /* 
+        * Firmware cannot handle more than 8 STAs.
+        * We use a bitmask to keep track of available slots in the firmware's
+        * node array. A 1 bit at index N, as determined by ffs(3), means the
+        * slot at this index is available.
+        */
+       uint8_t                         free_node_slots;
+
+       void                            (*sc_node_free)(struct ieee80211com *,
+                                           struct ieee80211_node *);
+
+       /* Arguments for the newauth callback. */
+       struct {
+               struct ieee80211_node *ni;
+               uint16_t seq;
+       } newauth_cb_arg;
 };
Index: net80211/ieee80211_proto.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_proto.c,v
retrieving revision 1.81
diff -u -p -r1.81 ieee80211_proto.c
--- net80211/ieee80211_proto.c  8 Dec 2017 21:16:01 -0000       1.81
+++ net80211/ieee80211_proto.c  4 Feb 2018 19:02:11 -0000
@@ -715,6 +715,24 @@ ieee80211_delba_request(struct ieee80211
        }
 }
 
+#ifndef IEEE80211_STA_ONLY
+void
+ieee80211_auth_open_confirm(struct ieee80211com *ic,
+    struct ieee80211_node *ni, uint16_t seq)
+{
+       struct ifnet *ifp = &ic->ic_if;
+
+       IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1);
+       if (ifp->if_flags & IFF_DEBUG)
+               printf("%s: station %s %s authenticated (open)\n",
+                   ifp->if_xname,
+                   ether_sprintf((u_int8_t *)ni->ni_macaddr),
+                   ni->ni_state != IEEE80211_STA_CACHE ?
+                   "newly" : "already");
+       ieee80211_node_newstate(ni, IEEE80211_STA_AUTH);
+}
+#endif
+
 void
 ieee80211_auth_open(struct ieee80211com *ic, const struct ieee80211_frame *wh,
     struct ieee80211_node *ni, struct ieee80211_rxinfo *rxi, u_int16_t seq,
@@ -765,15 +783,16 @@ ieee80211_auth_open(struct ieee80211com 
                        ni->ni_rstamp = rxi->rxi_tstamp;
                        ni->ni_chan = ic->ic_bss->ni_chan;
                }
-               IEEE80211_SEND_MGMT(ic, ni,
-                       IEEE80211_FC0_SUBTYPE_AUTH, seq + 1);
-               if (ifp->if_flags & IFF_DEBUG)
-                       printf("%s: station %s %s authenticated (open)\n",
-                           ifp->if_xname,
-                           ether_sprintf((u_int8_t *)ni->ni_macaddr),
-                           ni->ni_state != IEEE80211_STA_CACHE ?
-                           "newly" : "already");
-               ieee80211_node_newstate(ni, IEEE80211_STA_AUTH);
+
+               /* 
+                * Drivers may want to set up state before confirming.
+                * In which case this returns EBUSY and the driver will
+                * later call ieee80211_auth_open_confirm() by itself.
+                */
+               if (ic->ic_newauth && ic->ic_newauth(ic, ni,
+                   ni->ni_state != IEEE80211_STA_CACHE, seq) != 0)
+                       break;
+               ieee80211_auth_open_confirm(ic, ni, seq);
                break;
 #endif /* IEEE80211_STA_ONLY */
 
Index: net80211/ieee80211_proto.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_proto.h,v
retrieving revision 1.43
diff -u -p -r1.43 ieee80211_proto.h
--- net80211/ieee80211_proto.h  21 Oct 2017 20:15:17 -0000      1.43
+++ net80211/ieee80211_proto.h  4 Feb 2018 16:26:24 -0000
@@ -144,6 +144,8 @@ extern      int ieee80211_ibss_merge(struct i
                struct ieee80211_node *, u_int64_t);
 extern void ieee80211_reset_erp(struct ieee80211com *);
 extern void ieee80211_set_shortslottime(struct ieee80211com *, int);
+extern void ieee80211_auth_open_confirm(struct ieee80211com *,
+           struct ieee80211_node *, uint16_t);
 extern void ieee80211_auth_open(struct ieee80211com *,
            const struct ieee80211_frame *, struct ieee80211_node *,
            struct ieee80211_rxinfo *rs, u_int16_t, u_int16_t);
Index: net80211/ieee80211_var.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_var.h,v
retrieving revision 1.83
diff -u -p -r1.83 ieee80211_var.h
--- net80211/ieee80211_var.h    12 Dec 2017 15:50:39 -0000      1.83
+++ net80211/ieee80211_var.h    4 Feb 2018 16:18:12 -0000
@@ -204,6 +204,8 @@ struct ieee80211com {
                                    struct ieee80211_node *, int, int, int);
        int                     (*ic_newstate)(struct ieee80211com *,
                                    enum ieee80211_state, int);
+       int                     (*ic_newauth)(struct ieee80211com *,
+                                   struct ieee80211_node *, int, uint16_t);
        void                    (*ic_newassoc)(struct ieee80211com *,
                                    struct ieee80211_node *, int);
        void                    (*ic_node_leave)(struct ieee80211com *,

Reply via email to