[802.11] Revamped probe, fully asynchronous association, better error handling, much...
authorJoshua Oreman <oremanj@xenon.get-linux.org>
Fri, 19 Jun 2009 09:13:00 +0000 (02:13 -0700)
committerJoshua Oreman <oremanj@xenon.get-linux.org>
Fri, 19 Jun 2009 09:13:00 +0000 (02:13 -0700)
The probe process has been made asynchronous and split into three stages:
start (returning an opaque net80211_probe_ctx *), step (taking that context
pointer, processing new arrivals, and returning an indication of error or
completeness), and finish (extracting the desired network information and
freeing everything else). It is now also possible to use probe in a generalized
network-scanning mode.

Association has been cleaned up; the kludgy state of using half the net80211_wlan
structure for returning probe information and half for association scratch space
has been replaced by an association context structure similar to the probe context
one. All direct accesses to netdev->state have been replaced by calls to
netdev_link_*, including use of the new netdev link-up error mechanism.

gPXE is now a better 802.11 citizen: it sends a disassociation packet to the AP
just before closing its network interface. This is not required by the standard,
but it helps avoid stale associations on the AP.

Also fixed some things like a comment that referred to "scanning for all entworks".

src/include/gpxe/net80211.h
src/net/net80211.c

index eeeb3af..85d8a6d 100644 (file)
@@ -173,27 +173,33 @@ enum net80211_crypto_alg {
 /** Whether the error code provided is a "reason" code, not a "status" code */
 #define NET80211_IS_REASON     0x80
 
+/** Whether we have found the network we will be associating with */
+#define NET80211_PROBED                (1 << 8)
+
 /** Whether we have successfully authenticated with the network
  *
  * This usually has nothing to do with actual security; it is a
  * holdover from older 802.11 implementation ideas.
  */
-#define NET80211_AUTHENTICATED  (1 << 8)
+#define NET80211_AUTHENTICATED  (1 << 9)
 
 /** Whether we have successfully associated with the network */
-#define NET80211_ASSOCIATED     (1 << 9)
+#define NET80211_ASSOCIATED     (1 << 10)
 
 /** Whether we have completed security handshaking with the network
  *
  * Once this is set, we can send data packets.
  */
-#define NET80211_CRYPTO_SYNCED  (1 << 10)
+#define NET80211_CRYPTO_SYNCED  (1 << 11)
 
 /** Whether the auto-association task is running */
-#define NET80211_WORKING        (1 << 11)
+#define NET80211_WORKING        (1 << 12)
 
 /** Whether the auto-association task is waiting for a reply from the AP */
-#define NET80211_WAITING        (1 << 12)
+#define NET80211_WAITING        (1 << 13)
+
+/** Whether the auto-association task should be suppressed */
+#define NET80211_NO_ASSOC      (1 << 14)
 
 /** @} */
 
@@ -600,6 +606,11 @@ struct net80211_crypto
        void *priv;
 };
 
+
+struct net80211_probe_ctx;
+struct net80211_assoc_ctx;
+
+
 /** Structure encapsulating the complete state of an 802.11 device
  *
  * An 802.11 device is always wrapped by a network device, and this
@@ -628,20 +639,7 @@ struct net80211_device
        /** Information about the hardware, provided to net80211_register() */
        struct net80211_hw_info *hw;
 
-       /** The asynchronous association process.
-        *
-        * When an 802.11 netdev is opened, or when the user changes
-        * the SSID setting on an open 802.11 device, an
-        * autoassociation task is started by net80211_autoassocate()
-        * to associate with the new best network. The association is
-        * generally asynchronous but may block for a few seconds
-        * during network probing. If it is successful, the wrapping
-        * net_device is set as "link up".
-        */
-       struct process proc_assoc;
-
-       /** Data structure for the association process. */
-       struct net80211_wlan *associating;
+       /* ---------- Channel and rate fields ---------- */
 
        /** A list of all possible channels we might use */
        struct net80211_channel channels[NET80211_MAX_CHANNELS];
@@ -683,19 +681,53 @@ struct net80211_device
         */
        u32 basic_rates;
 
+       /* ---------- Association fields ---------- */
+
+       /** The asynchronous association process.
+        *
+        * When an 802.11 netdev is opened, or when the user changes
+        * the SSID setting on an open 802.11 device, an
+        * autoassociation task is started by net80211_autoassocate()
+        * to associate with the new best network. The association is
+        * asynchronous, but no packets can be transmitted until it is
+        * complete. If it is successful, the wrapping net_device is
+        * set as "link up". If it fails, @c assoc_rc will be set with
+        * an error indication.
+        */
+       struct process proc_assoc;
+
+       /** Network with which we are associating
+        *
+        * This will be NULL when we are not associating or we haven't
+        * yet probed to determine the parameters of the network we
+        * are associating with.
+        */
+       struct net80211_wlan *associating;
+
+       /** Context for the association process
+        *
+        * This is a probe_ctx if the @c PROBED flag is not set in @c
+        * state, and an assoc_ctx otherwise.
+        */
+       union {
+               struct net80211_probe_ctx *probe;
+               struct net80211_assoc_ctx *assoc;
+       } ctx;
+
        /** State of our association to the network
         *
         * Since the association process happens asynchronously, it's
         * necessary to have some channel of communication so the
         * driver can say "I got an association reply and we're OK" or
         * similar. This variable provides that link. It is a bitmask
-        * of any of NET80211_AUTHENTICATED, NET80211_ASSOCIATED,
-        * NET80211_CRYPTO_SYNCED to indicate how far along in
-        * associating we are; NET80211_WORKING if the association
-        * task is running; and NET80211_WAITING if a packet has been
-        * sent that we're waiting for a reply to. We can only be
-        * crypto-synced if we're associated, and we can only be
-        * associated if we're authenticated.
+        * of any of NET80211_PROBED, NET80211_AUTHENTICATED,
+        * NET80211_ASSOCIATED, NET80211_CRYPTO_SYNCED to indicate how
+        * far along in associating we are; NET80211_WORKING if the
+        * association task is running; and NET80211_WAITING if a
+        * packet has been sent that we're waiting for a reply to. We
+        * can only be crypto-synced if we're associated, we can
+        * only be associated if we're authenticated, we can only be
+        * authenticated if we've probed.
         *
         * If an association process fails (that is, we receive a
         * packet with an error indication), the error code is copied
@@ -706,16 +738,25 @@ struct net80211_device
         * defined) explaining why it canceled an association after it
         * had originally OK'ed it. Status and reason codes serve
         * similar functions, but they use separate error message
-        * tables.
+        * tables. A gPXE-formatted return code (negative) is placed
+        * in @c assoc_rc.
         *
         * If the failure to associate is indicated by a status code,
         * the NET80211_IS_REASON bit will be clear; if it is
         * indicated by a reason code, the bit will be set. If we were
         * successful, both zero status and zero reason mean success,
         * so there is no ambiguity.
+        *
+        * To prevent association when opening the device, user code
+        * can set the NET80211_NO_ASSOC bit.
         */
        short state;
 
+       /** gPXE error code associated with @c state */
+       int assoc_rc;
+
+       /* ---------- Parameters of currently associated network ---------- */
+
        /** 802.11 cryptographic algorithm for our current network
         *
         * For an open network, this will be set to NULL.
@@ -736,6 +777,8 @@ struct net80211_device
        /** Association ID given to us by the AP */
        u16 aid;
 
+       /* ---------- Physical layer information ---------- */
+
        /** Physical layer options
         *
         * These control the use of CTS protection, short preambles,
@@ -746,6 +789,8 @@ struct net80211_device
        /** Signal strength of last received packet */
        int last_signal;
 
+       /* ---------- Packet handling state ---------- */
+
        /** Fragment reassembly state */
        struct net80211_frag_cache frags[NET80211_NR_CONCURRENT_FRAGS];
 
@@ -808,10 +853,6 @@ struct net80211_device
  * association functions. At least essid, bssid, channel, beacon, and
  * security must be filled in if you want to build this structure
  * manually.
- *
- * This structure is also used by the MAC layer in coordinating the
- * association state machine for associating with the network it
- * represents.
  */
 struct net80211_wlan
 {
@@ -840,18 +881,11 @@ struct net80211_wlan
        /** The complete beacon or probe-response frame received */
        struct io_buffer *beacon;
 
-       /** One of the NET80211_CRYPT_* constants indicating the
-           security mode of the network. */
-       int security;
-
-       /** Next authentication method to try using */
-       int method;
+       /** Security handshaking method used on the network */
+       enum net80211_security_proto handshaking;
 
-       /** Time (in ticks) of the last sent association-related packet */
-       int last_packet;
-
-       /** Number of times we have tried sending it */
-       int times_tried;
+       /** Cryptographic algorithm used on the network */
+       enum net80211_crypto_alg crypto;
 
        /** Link to allow chaining multiple structures into a list to
            be returned from net80211_scan(). */
@@ -863,18 +897,24 @@ struct net80211_wlan
 void net80211_autoassociate ( struct net80211_device *dev );
 
 /* Find networks: */
-struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
-                                       const char *essid, int active );
+struct net80211_probe_ctx * net80211_probe_start ( struct net80211_device *dev,
+                                                  const char *essid,
+                                                  int active );
+int net80211_probe_step ( struct net80211_probe_ctx *ctx );
+struct net80211_wlan *net80211_probe_finish_best ( struct net80211_probe_ctx *ctx );
+struct list_head *net80211_probe_finish_all ( struct net80211_probe_ctx *ctx );
+
 void net80211_free_wlan ( struct net80211_wlan *wlan );
+void net80211_free_wlanlist ( struct list_head *list );
 
 /* Get the 802.11 device from a wrapping net_device: */
 struct net80211_device * net80211_get ( struct net_device *netdev );
 
 /* Association steps: */
-int net80211_prepare_default ( struct net80211_device *dev, int band,
-                              int active );
-int net80211_prepare ( struct net80211_device *dev,
-                      struct net80211_wlan *wlan );
+int net80211_prepare_probe ( struct net80211_device *dev, int band,
+                            int active );
+int net80211_prepare_assoc ( struct net80211_device *dev,
+                            struct net80211_wlan *wlan );
 int net80211_send_auth ( struct net80211_device *dev,
                         struct net80211_wlan *wlan, int method );
 int net80211_send_assoc ( struct net80211_device *dev,
index 20e9936..b3a5bc8 100644 (file)
@@ -75,7 +75,7 @@ static struct list_head net80211_devices = LIST_HEAD_INIT ( net80211_devices );
 
 /** The network name to associate with
  *
- * If this is blank, we scan for all entworks and use the one with the
+ * If this is blank, we scan for all networks and use the one with the
  * greatest signal strength.
  */
 struct setting net80211_ssid_setting __setting = {
@@ -109,6 +109,51 @@ struct net80211_rx_info {
        struct list_head list;
 };
 
+/** Context for a probe operation */
+struct net80211_probe_ctx {
+       /** 802.11 device to probe on */
+       struct net80211_device *dev;
+
+       /** Value of keep_mgmt before probe was started */
+       int old_keep_mgmt;
+
+       /** If scanning actively, pointer to probe packet to send */
+       struct io_buffer *probe;
+
+       /** If non-"", the ESSID to limit ourselves to */
+       const char *essid;
+
+       /** Time probe was started */
+       u32 ticks_start;
+
+       /** Time last useful beacon was received */
+       u32 ticks_beacon;
+
+       /** Time channel was last changed */
+       u32 ticks_channel;
+
+       /** Time to stay on each channel */
+       u32 hop_time;
+
+       /** Channels to hop by when changing channel */
+       int hop_step;
+
+       /** List of best beacons for each network found so far */
+       struct list_head *beacons;
+};
+
+/** Context for the association task */
+struct net80211_assoc_ctx {
+       /** Next authentication method to try using */
+       int method;
+
+       /** Time (in ticks) of the last sent association-related packet */
+       int last_packet;
+
+       /** Number of times we have tried sending it */
+       int times_tried;
+};
+
 static u16 net80211_duration ( struct net80211_device *dev, int bytes );
 static int net80211_ll_push ( struct net_device *netdev,
                              struct io_buffer *iobuf, const void *ll_dest,
@@ -137,6 +182,7 @@ static void net80211_handle_auth ( struct net80211_device *dev,
                                   struct io_buffer *iob );
 static void net80211_handle_assoc_reply ( struct net80211_device *dev,
                                          struct io_buffer *iob );
+static int net80211_send_disassoc ( struct net80211_device *dev, int reason );
 static void net80211_handle_mgmt ( struct net80211_device *dev,
                                   struct io_buffer *iob, int signal );
 
@@ -166,9 +212,11 @@ struct settings_applicator net80211_ssid_applicator __settings_applicator = {
  * @ret rc     Return status code
  *
  * This sets up a default conservative set of channels for probing,
- * and starts the auto-association task.
+ * and starts the auto-association task unless the @c
+ * NET80211_NO_ASSOC flag is set in the wrapped 802.11 device's @c
+ * state field.
  */
-int net80211_netdev_open ( struct net_device *netdev )
+static int net80211_netdev_open ( struct net_device *netdev )
 {
        struct net80211_device *dev = netdev->priv;
        int rc = 0;
@@ -176,12 +224,17 @@ int net80211_netdev_open ( struct net_device *netdev )
        /* In case someone tries to transmit before we set link-up, we
           need to at least be in a consistent enough state not to
           crash. */
-       net80211_prepare_default ( dev, dev->hw->bands, 0 );
+       net80211_prepare_probe ( dev, dev->hw->bands, 0 );
 
        if ( dev->op->open )
                rc = dev->op->open ( dev );
 
-       net80211_autoassociate ( dev );
+       if ( rc < 0 )
+               return rc;
+
+       if ( ! ( dev->state & NET80211_NO_ASSOC ) )
+               net80211_autoassociate ( dev );
+
        return 0;
 }
 
@@ -192,15 +245,19 @@ int net80211_netdev_open ( struct net_device *netdev )
  *
  * If the association task is running, this will stop it.
  */
-void net80211_netdev_close ( struct net_device *netdev )
+static void net80211_netdev_close ( struct net_device *netdev )
 {
        struct net80211_device *dev = netdev->priv;
 
        if ( dev->state & NET80211_WORKING )
                process_del ( &dev->proc_assoc );
 
+       /* Send disassociation frame to AP, to be polite */
+       if ( dev->state & NET80211_ASSOCIATED )
+               net80211_send_disassoc ( dev, IEEE80211_REASON_LEAVING );
+
+       netdev_link_down ( netdev );
        dev->state = 0;
-       dev->netdev->state &= ~NETDEV_LINK_UP;
 
        if ( dev->op->close )
                dev->op->close ( dev );
@@ -216,8 +273,8 @@ void net80211_netdev_close ( struct net_device *netdev )
  * If encryption is enabled for the currently associated network, the
  * packet will be encrypted prior to transmission.
  */
-int net80211_netdev_transmit ( struct net_device *netdev,
-                              struct io_buffer *iobuf )
+static int net80211_netdev_transmit ( struct net_device *netdev,
+                                     struct io_buffer *iobuf )
 {
        struct net80211_device *dev = netdev->priv;
        int rc = -ENOSYS;
@@ -243,7 +300,7 @@ int net80211_netdev_transmit ( struct net_device *netdev,
  *
  * @v netdev   Wrapping network device
  */
-void net80211_netdev_poll ( struct net_device *netdev )
+static void net80211_netdev_poll ( struct net_device *netdev )
 {
        struct net80211_device *dev = netdev->priv;
 
@@ -257,7 +314,7 @@ void net80211_netdev_poll ( struct net_device *netdev )
  * @v netdev   Wrapping network device
  * @v enable   Whether to enable interrupts
  */
-void net80211_netdev_irq ( struct net_device *netdev, int enable )
+static void net80211_netdev_irq ( struct net_device *netdev, int enable )
 {
        struct net80211_device *dev = netdev->priv;
 
@@ -266,7 +323,7 @@ void net80211_netdev_irq ( struct net_device *netdev, int enable )
 }
 
 /** Network device operations for a wrapped 802.11 device */
-struct net_device_operations net80211_netdev_ops = {
+static struct net_device_operations net80211_netdev_ops = {
        .open = net80211_netdev_open,
        .close = net80211_netdev_close,
        .transmit = net80211_netdev_transmit,
@@ -385,12 +442,9 @@ static int net80211_ll_push ( struct net_device *netdev,
                ( void * ) hdr + IEEE80211_TYP_FRAME_HEADER_LEN;
 
        /* We can't send data packets if we're not associated. */
-       if ( ! ( dev->state & NET80211_ASSOCIATED ) ) {
-               if ( dev->state & NET80211_STATUS_MASK ) {
-                       if ( dev->state & NET80211_IS_REASON )
-                               return -E80211_REASON ( dev->state );
-                       return -E80211_STATUS ( dev->state );
-               }
+       if ( ! netdev_link_ok ( netdev ) ) {
+               if ( dev->assoc_rc )
+                       return dev->assoc_rc;
                return -ENETUNREACH;
        }
 
@@ -508,7 +562,7 @@ static struct ll_protocol net80211_ll_protocol __ll_protocol = {
        .pull = net80211_ll_pull,
        .ntoa = eth_ntoa,
        .mc_hash = net80211_ll_mc_hash,
-       .ll_proto = htons ( ARPHRD_ETHER ),     /* it's "encapsulated Ethernet" */
+       .ll_proto = htons ( ARPHRD_ETHER ),     /* "encapsulated Ethernet" */
        .ll_addr_len = ETH_ALEN,
        .ll_header_len = IEEE80211_TYP_FRAME_HEADER_LEN +
                                IEEE80211_LLC_HEADER_LEN,
@@ -753,9 +807,12 @@ static inline void net80211_set_state ( struct net80211_device *dev,
                                        u16 status )
 {
        /* The conditions in this function are deliberately formulated
-          to be decidable at compile-time. */
+          to be decidable at compile-time in most cases. */
        const int statmsk = NET80211_STATUS_MASK | NET80211_IS_REASON;
 
+       if ( clear & NET80211_PROBED )
+               clear |= NET80211_AUTHENTICATED;
+
        if ( clear & NET80211_AUTHENTICATED )
                clear |= NET80211_ASSOCIATED;
 
@@ -766,10 +823,18 @@ static inline void net80211_set_state ( struct net80211_device *dev,
        dev->state = ( dev->state & ~statmsk ) | ( status & statmsk );
 
        if ( clear & NET80211_ASSOCIATED )
-               dev->netdev->state &= ~NETDEV_LINK_UP;
+               netdev_link_down ( dev->netdev );
 
        if ( ( clear | set ) & NET80211_ASSOCIATED )
                dev->op->config ( dev, NET80211_CFG_ASSOC );
+
+       if ( status != 0 ) {
+               if ( status & NET80211_IS_REASON )
+                       dev->assoc_rc = -E80211_REASON ( status );
+               else
+                       dev->assoc_rc = -E80211_STATUS ( status );
+               netdev_link_err ( dev->netdev, dev->assoc_rc );
+       }
 }
 
 /**
@@ -1038,62 +1103,64 @@ net80211_marshal_request_info ( struct net80211_device *dev,
        return ie;
 }
 
-/** Seconds to always take when probing, to gather better signal strengths */
-#define NET80211_PROBE_GATHER    2
+/** Seconds to wait after finding a network, to possibly find better APs
+ *
+ * This is used when a specific SSID to scan for is specified.
+ */
+#define NET80211_PROBE_GATHER    1
 
-/** Seconds to allow a probe to take, if no usable AP has yet been found */
+/** Seconds to wait after finding a network, to possibly find others
+ *
+ * This is used when an empty SSID is specified, to scan for all
+ * networks.
+ */
+#define NET80211_PROBE_GATHER_ALL 2
+
+/** Seconds to allow a probe to take if no network has been found */
 #define NET80211_PROBE_TIMEOUT   6
 
 /**
- * Probe 802.11 networks
+ * Begin probe of 802.11 networks
  *
  * @v dev      802.11 device
  * @v essid    SSID to probe for, or "" to accept any (may not be NULL)
  * @v active   Whether to use active scanning
- * @ret wlan   WLAN structure for best detected network
+ * @ret ctx    Probe context
  *
  * Active scanning may only be used on channels 1-11 in the 2.4GHz
  * band, due to gPXE's lack of a complete regulatory database. If
  * active scanning is used, probe packets will be sent on each
  * channel; this can allow association with hidden-SSID networks if
  * the SSID is properly specified.
+ *
+ * A @c NULL return indicates an out-of-memory condition.
+ *
+ * The returned context must be periodically passed to
+ * net80211_probe_step() until that function returns zero.
  */
-struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
-                                       const char *essid, int active )
+struct net80211_probe_ctx * net80211_probe_start ( struct net80211_device *dev,
+                                                  const char *essid,
+                                                  int active ) 
 {
-       int old_keep = net80211_keep_mgmt ( dev, 1 );
-       u32 start_ticks = currticks(); /* started scanning */
-       u32 change_ticks = currticks(); /* changed channel */
-       u32 start_timeout = NET80211_PROBE_TIMEOUT * ticks_per_sec();
-       u32 gather_timeout = NET80211_PROBE_GATHER * ticks_per_sec();
-       u32 change_timeout = ticks_per_sec() / ( active ? 2 : 6 );
-       int hop = 5;
-       struct net80211_wlan *wlan = NULL;
-       void *probe = NULL;
-       int probe_len = 0;
-       struct io_buffer *iob;
-       union ieee80211_ie *ie;
-       int rc;
+       struct net80211_probe_ctx *ctx = zalloc ( sizeof ( *ctx ) );
 
-       /* Channels on 2.4GHz overlap, and the most commonly used
-          are 1, 6, and 11. We'll get a result faster if we check
-          every 5 channels, but in order to hit all of them the
-          number of channels must be relatively prime to 5. If it's
-          not, tweak the hop. */
-       while ( dev->nr_channels % hop == 0 && hop > 1 )
-               hop--;  
+       if ( ! ctx )
+               return NULL;
 
-       dev->channel = 0;
+       ctx->dev = dev;
+       ctx->old_keep_mgmt = net80211_keep_mgmt ( dev, 1 );
+       ctx->essid = essid;
+       if ( dev->essid != ctx->essid )
+               strcpy ( dev->essid, ctx->essid );
 
-       /* If we're scanning actively, make a probe packet. */
        if ( active ) {
                struct ieee80211_probe_req *probe_req;
+               union ieee80211_ie *ie;
 
-               probe = malloc ( 128 );
-               probe_req = probe;
+               ctx->probe = alloc_iob ( 128 );
+               iob_reserve ( ctx->probe, IEEE80211_TYP_FRAME_HEADER_LEN );
+               probe_req = ctx->probe->data;
 
-               /* Put the standard SSID/rate info in the probe, and
-                  then add our requests: country, ERP, RSN info */
                ie = net80211_marshal_request_info ( dev,
                                                     probe_req->info_element );
                ie->id = IEEE80211_IE_REQUEST;
@@ -1102,53 +1169,115 @@ struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
                ie->request[1] = IEEE80211_IE_ERP_INFO;
                ie->request[2] = IEEE80211_IE_RSN;
 
-               probe_len = ( void * ) ie + ie->len + 2 - probe;
+               ie = ieee80211_next_ie ( ie, NULL );
+
+               iob_put ( ctx->probe, ( void * ) ie - ctx->probe->data );
        }
 
-       while ( currticks() < start_ticks + start_timeout ) {
-               struct ieee80211_frame *hdr;
-               struct ieee80211_beacon *beacon; /* == iee80211_probe_resp */
-               union ieee80211_ie *ie;
-               int signal;
-               u16 type;
+       ctx->ticks_start = currticks();
+       ctx->ticks_beacon = 0;
+       ctx->ticks_channel = currticks();
+       ctx->hop_time = ticks_per_sec() / ( active ? 2 : 6 );
+
+       /*
+        * Channels on 2.4GHz overlap, and the most commonly used
+        * are 1, 6, and 11. We'll get a result faster if we check
+        * every 5 channels, but in order to hit all of them the
+        * number of channels must be relatively prime to 5. If it's
+        * not, tweak the hop.
+        */
+       ctx->hop_step = 5;
+       while ( dev->nr_channels % ctx->hop_step == 0 && ctx->hop_step > 1 )
+               ctx->hop_step--;
+
+       ctx->beacons = malloc ( sizeof ( *ctx->beacons ) );
+       INIT_LIST_HEAD ( ctx->beacons );
 
-               if ( currticks() < change_ticks + change_timeout ) {
-                       dev->channel = ( dev->channel + hop ) % dev->nr_channels;
-                       dev->op->config ( dev, NET80211_CFG_CHANNEL );
-                       udelay ( dev->hw->channel_change_time );
-
-                       change_ticks = currticks();
-
-                       if ( active ) {
-                               int hdr_len = IEEE80211_TYP_FRAME_HEADER_LEN;
-                               struct io_buffer *piob =
-                                       alloc_iob ( probe_len + hdr_len );
-                               iob_reserve ( piob, hdr_len );
-                               memcpy ( iob_put ( piob, probe_len ), probe,
-                                        probe_len );
-
-                               rc = net80211_tx_mgmt ( dev,
-                                                       IEEE80211_STYPE_PROBE_REQ,
-                                                       net80211_ll_broadcast,
-                                                       iob_disown ( piob ) );
-                               if ( rc ) {
-                                       DBGC ( dev, "802.11 %p send probe "
-                                              "failed: %s\n", dev,
-                                              strerror ( rc ) );
-                               }
+       dev->channel = 0;
+       dev->op->config ( dev, NET80211_CFG_CHANNEL );
+
+       return ctx;
+}
+
+/**
+ * Continue probe of 802.11 networks
+ *
+ * @v ctx      Probe context returned by net80211_probe_start()
+ * @ret rc     Probe status
+ *
+ * The return code will be 0 if the probe is still going on (and this
+ * function should be called again), a positive number if the probe
+ * completed successfully, or a negative error code if the probe
+ * failed for that reason.
+ *
+ * Whether the probe succeeded or failed, you must call
+ * net80211_probe_finish_all() or net80211_probe_finish_best()
+ * (depending on whether you want information on all networks or just
+ * the best-signal one) in order to release the probe context. A
+ * failed probe may still have acquired some valid data.
+ */
+int net80211_probe_step ( struct net80211_probe_ctx *ctx ) 
+{
+       struct net80211_device *dev = ctx->dev;
+       u32 start_timeout = NET80211_PROBE_TIMEOUT * ticks_per_sec();
+       u32 gather_timeout = ticks_per_sec();
+       u32 now = currticks();
+       struct io_buffer *iob;
+       int signal;
+       int rc;
+       char ssid[IEEE80211_MAX_SSID_LEN + 1];
+
+       gather_timeout *= ( ctx->essid[0] ? NET80211_PROBE_GATHER :
+                           NET80211_PROBE_GATHER_ALL );
+
+       /* Time out if necessary */
+       if ( now >= ctx->ticks_start + start_timeout )
+               return list_empty ( ctx->beacons ) ? -ETIMEDOUT : +1;
+
+       if ( ctx->ticks_beacon > 0 && now >= ctx->ticks_start + gather_timeout )
+               return +1;
+
+       /* Change channels if necessary */
+       if ( now >= ctx->ticks_channel + ctx->hop_time ) {
+               dev->channel = ( dev->channel + ctx->hop_step )
+                       % dev->nr_channels;
+               dev->op->config ( dev, NET80211_CFG_CHANNEL );
+               udelay ( dev->hw->channel_change_time );
+
+               ctx->ticks_channel = now;
+
+               if ( ctx->probe ) {
+                       struct io_buffer *siob = ctx->probe; /* to send */
+
+                       /* make a copy for future use */
+                       iob = alloc_iob ( siob->tail - siob->head );
+                       iob_reserve ( iob, iob_headroom ( siob ) );
+                       memcpy ( iob_put ( iob, iob_len ( siob ) ),
+                                siob->data, iob_len ( siob ) );
+
+                       ctx->probe = iob;
+                       rc = net80211_tx_mgmt ( dev, IEEE80211_STYPE_PROBE_REQ,
+                                               net80211_ll_broadcast,
+                                               iob_disown ( siob ) );
+                       if ( rc ) {
+                               DBGC ( dev, "802.11 %p send probe failed: "
+                                      "%s\n", dev, strerror ( rc ) );
+                               return rc;
                        }
                }
+       }
 
-               dev->op->poll ( dev );
-               iob = net80211_mgmt_dequeue ( dev, &signal );
-               if ( ! iob ) {
-                       cpu_nap();
-                       continue;
-               }
+       /* Check for new management packets */
+       while ( ( iob = net80211_mgmt_dequeue ( dev, &signal ) ) != NULL ) {
+               struct ieee80211_frame *hdr;
+               struct ieee80211_beacon *beacon;
+               union ieee80211_ie *ie;
+               struct net80211_wlan *wlan;
+               u16 type;
 
                hdr = iob->data;
                type = hdr->fc & IEEE80211_FC_SUBTYPE;
-               beacon = (struct ieee80211_beacon *)hdr->data;
+               beacon = ( struct ieee80211_beacon * ) hdr->data;
 
                if ( type != IEEE80211_STYPE_BEACON &&
                     type != IEEE80211_STYPE_PROBE_RESP ) {
@@ -1157,8 +1286,8 @@ struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
                }
 
                if ( ( void * ) beacon->info_element >= iob->tail ) {
-                       DBGC2 ( dev, "802.11 %p probe: beacon with no IEs\n",
-                               dev );
+                       DBGC ( dev, "802.11 %p probe: beacon with no IEs\n",
+                              dev );
                        goto drop;
                }
 
@@ -1167,71 +1296,137 @@ struct net80211_wlan * net80211_probe ( struct net80211_device *dev,
                        ie = ieee80211_next_ie ( ie, iob->tail );
 
                if ( ! ie ) {
-                       /* didn't find an SSID */
-                       DBGC2 ( dev, "802.11 %p probe: beacon with no SSID\n",
-                               dev );
+                       DBGC ( dev, "802.11 %p probe: beacon with no SSID\n",
+                              dev );
                        goto drop;
                }
-               if ( essid[0] && memcmp ( essid, ie->ssid, ie->len ) != 0 ) {
-                       ie->ssid[ie->len] = 0;
+
+               memcpy ( ssid, ie->ssid, ie->len );
+               ssid[ie->len] = 0;
+
+               if ( ctx->essid[0] && strcmp ( ctx->essid, ssid ) != 0 ) {
                        DBGC2 ( dev, "802.11 %p probe: beacon with wrong SSID "
-                               "(%s)\n", dev, ie->ssid );
+                               "(%s)\n", dev, ssid );
                        goto drop;
                }
 
-               if ( ! wlan ) {
-                       wlan = malloc ( sizeof ( *wlan ) );
-                       if ( ! wlan ) {
-                               DBGC ( dev, "802.11 %p probe: out of memory\n",
-                                      dev );
-                               goto fail;
+               /* See if we've got an entry for this network */
+               list_for_each_entry ( wlan, ctx->beacons, list ) {
+                       if ( strcmp ( wlan->essid, ssid ) != 0 )
+                               continue;
+
+                       if ( signal < wlan->signal ) {
+                               DBGC2 ( dev, "802.11 %p probe: beacon for %s "
+                                       "(%s) with weaker signal %d\n", dev,
+                                       ssid, eth_ntoa ( hdr->addr3 ), signal );
+                               goto drop;
                        }
 
-                       DBGP ( "802.11 %p first good beacon:\n", dev );
-                       DBGP_HD ( iob->data, iob_len ( iob ) );
-               } else if ( signal < wlan->signal ) {
-                       ie->ssid[ie->len] = 0;
-                       DBGC2 ( dev, "802.11 %p probe: beacon for %s (%s) with "
-                               "weaker signal %d\n", dev, ie->ssid,
-                               eth_ntoa ( hdr->addr3 ), signal );
-                       goto drop;
-               } else {
-                       free_iob ( iob_disown ( wlan->beacon ) );
+                       goto fill;
                }
 
-               wlan->essid[ie->len] = 0;
-               memcpy ( wlan->essid, ie->ssid, ie->len );
-               memcpy ( wlan->bssid, hdr->addr3, ETH_ALEN);
+               /* No entry yet - make one */
+               wlan = zalloc ( sizeof ( *wlan ) );
+               strcpy ( wlan->essid, ssid );
+               list_add_tail ( &wlan->list, ctx->beacons );
+
+               /* Whether we're using an old entry or a new one, fill
+                  it with new data. */
+       fill:
+               memcpy ( wlan->bssid, hdr->addr3, ETH_ALEN );
                wlan->signal = signal;
-               wlan->beacon = iob;
-               wlan->security = 0; /* XXX implement */
                wlan->channel = dev->channels[dev->channel].channel_nr;
-               DBGC2 ( dev, "802.11 %p probe: beacon for %s (%s) with "
-                       "new best signal %d\n", dev, wlan->essid,
-                       eth_ntoa ( wlan->bssid ), wlan->signal );
 
-               if ( currticks() > start_ticks + gather_timeout )
-                       break;
+               /* Copy this I/O buffer into a new wlan->beacon; the
+                * iob we've got probably came from the device driver
+                * and may have the full 2.4k allocation, which we
+                * don't want to keep around wasting memory.
+                */
+               free_iob ( wlan->beacon );
+               wlan->beacon = alloc_iob ( iob_len ( iob ) );
+               memcpy ( iob_put ( wlan->beacon, iob_len ( iob ) ),
+                        iob->data, iob_len ( iob ) );
+
+               /* XXX actually check capab and RSN ie to
+                  figure this out */
+               wlan->handshaking = NET80211_SECPROT_NONE;
+               wlan->crypto = NET80211_CRYPT_NONE;
 
-               continue;
+               ctx->ticks_beacon = now;
+
+               DBGC2 ( dev, "802.11 %p probe: good beacon for %s (%s)\n",
+                       dev, wlan->essid, eth_ntoa ( wlan->bssid ) );
 
        drop:
                free_iob ( iob );
        }
 
-       if ( ! wlan )
-               DBGC ( dev, "802.11 %p probe: found no response for '%s'\n",
-                      dev, essid );
+       return 0;
+}
 
-       free ( probe );
-       net80211_keep_mgmt ( dev, old_keep );
-       return wlan;
 
- fail:
-       free ( probe );
-       net80211_free_wlan ( wlan );
-       net80211_keep_mgmt ( dev, old_keep );
-       return NULL;
+/**
+ * Finish probe of 802.11 networks, returning best-signal network found
+ *
+ * @v ctx      Probe context
+ * @ret wlan   Best-signal network found, or @c NULL if none were found
+ *
+ * If net80211_probe_start() was called with a particular SSID
+ * parameter as filter, only a network with that SSID (matching
+ * case-sensitively) can be returned from this function.
+ */
+struct net80211_wlan *
+net80211_probe_finish_best ( struct net80211_probe_ctx *ctx )
+{
+       struct net80211_wlan *best = NULL, *wlan;
+
+       list_for_each_entry ( wlan, ctx->beacons, list ) {
+               if ( ! best || best->signal < wlan->signal )
+                       best = wlan;
+       }
+
+       if ( best )
+               list_del ( &best->list );
+       else
+               DBGC ( ctx->dev, "802.11 %p probe: found nothing for '%s'\n",
+                      ctx->dev, ctx->essid );
+
+       if ( ! list_empty ( ctx->beacons ) )
+               net80211_free_wlanlist ( ctx->beacons );
+
+       net80211_keep_mgmt ( ctx->dev, ctx->old_keep_mgmt );
+
+       if ( ctx->probe )
+               free_iob ( ctx->probe );
+
+       free ( ctx );
+
+       return best;
+}
+
+
+/**
+ * Finish probe of 802.11 networks, returning all networks found
+ *
+ * @v ctx      Probe context
+ * @ret list   List of net80211_wlan detailing networks found
+ *
+ * If net80211_probe_start() was called with a particular SSID
+ * parameter as filter, this will always return either an empty or a
+ * one-element list.
+ */
+struct list_head *net80211_probe_finish_all ( struct net80211_probe_ctx *ctx )
+{
+       struct list_head *beacons = ctx->beacons;
+
+       net80211_keep_mgmt ( ctx->dev, ctx->old_keep_mgmt );
+
+       if ( ctx->probe )
+               free_iob ( ctx->probe );
+
+       free ( ctx );
+
+       return beacons;
 }
 
 
@@ -1248,6 +1443,29 @@ void net80211_free_wlan ( struct net80211_wlan *wlan )
        }
 }
 
+
+/**
+ * Free list of WLAN structures
+ *
+ * @v list     List of WLAN structures to free
+ */
+void net80211_free_wlanlist ( struct list_head *list ) 
+{
+       struct net80211_wlan *wlan, *tmp;
+
+       if ( ! list )
+               return;
+
+       list_for_each_entry_safe ( wlan, tmp, list, list ) {
+               list_del ( &wlan->list );
+               net80211_free_wlan ( wlan );
+       }
+
+       free ( list );
+}
+
+
+
 #define ASSOC_TIMEOUT  TICKS_PER_SEC
 #define ASSOC_RETRIES  2
 
@@ -1267,9 +1485,9 @@ static void net80211_step_associate ( struct process *proc )
                if ( ! dev->associating )
                        return;
 
-               if ( currticks() - dev->associating->last_packet > ASSOC_TIMEOUT ) {
-                       dev->associating->times_tried++;
-                       if ( ++dev->associating->times_tried > ASSOC_RETRIES ) {
+               if ( currticks() - dev->ctx.assoc->last_packet > ASSOC_TIMEOUT ) {
+                       dev->ctx.assoc->times_tried++;
+                       if ( ++dev->ctx.assoc->times_tried > ASSOC_RETRIES ) {
                                rc = -ETIMEDOUT;
                                goto fail;
                        }
@@ -1277,26 +1495,44 @@ static void net80211_step_associate ( struct process *proc )
                        return;
                }
        } else {
-               if ( dev->associating )
-                       dev->associating->times_tried = 0;
+               if ( dev->state & NET80211_PROBED )
+                       dev->ctx.assoc->times_tried = 0;
        }
 
-       if ( ! dev->associating ) {
+       if ( ! ( dev->state & NET80211_PROBED ) ) {
                /* state: scan */
-               int active = fetch_intz_setting ( NULL,
-                                                 &net80211_active_setting );
-               int band = dev->hw->bands;
 
-               if ( active )
-                       band &= ~NET80211_BAND_5GHZ;
+               if ( ! dev->ctx.probe ) {
+                       /* start probe */
+                       int active = fetch_intz_setting ( NULL,
+                                               &net80211_active_setting );
+                       int band = dev->hw->bands;
 
-               rc = net80211_prepare_default ( dev, band, active );
-               if ( rc )
-                       goto fail;
+                       if ( active )
+                               band &= ~NET80211_BAND_5GHZ;
+
+                       rc = net80211_prepare_probe ( dev, band, active );
+                       if ( rc )
+                               goto fail;
 
-               dev->associating = net80211_probe ( dev, dev->essid, active );
+                       dev->ctx.probe = net80211_probe_start ( dev, dev->essid,
+                                                               active );
+                       if ( ! dev->ctx.probe ) {
+                               dev->assoc_rc = -ENOMEM;
+                               goto fail;
+                       }
+               }
+               
+               rc = net80211_probe_step ( dev->ctx.probe );
+               if ( ! rc ) {
+                       return; /* still going */
+               }
+
+               dev->associating = net80211_probe_finish_best ( dev->ctx.probe );
+               dev->ctx.probe = NULL;
                if ( ! dev->associating ) {
-                       rc = -ETIMEDOUT;
+                       if ( rc > 0 ) /* "successful" probe found nothing */
+                               rc = -ETIMEDOUT;
                        goto fail;
                }
 
@@ -1304,39 +1540,47 @@ static void net80211_step_associate ( struct process *proc )
                       dev->associating->essid,
                       eth_ntoa ( dev->associating->bssid ) );
 
-               dev->associating->method = IEEE80211_AUTH_OPEN_SYSTEM;
+               dev->ctx.assoc = zalloc ( sizeof ( *dev->ctx.assoc ) );
+               if ( ! dev->ctx.assoc ) {
+                       rc = -ENOMEM;
+                       goto fail;
+               }
+
+               dev->state |= NET80211_PROBED;
+               dev->ctx.assoc->method = IEEE80211_AUTH_OPEN_SYSTEM;
+
                return;
        }
 
+       dev->ctx.assoc->last_packet = currticks();
+
        if ( ! ( dev->state & NET80211_AUTHENTICATED ) ) {
                /* state: prepare and authenticate */
-               int method = dev->associating->method;
 
                if ( status != IEEE80211_STATUS_SUCCESS ) {
                        /* we tried authenticating already, but failed */
+                       int method = dev->ctx.assoc->method;
 
                        if ( method == IEEE80211_AUTH_OPEN_SYSTEM &&
                             ( status == IEEE80211_STATUS_AUTH_CHALL_INVALID ||
                               status == IEEE80211_STATUS_AUTH_ALGO_UNSUPP ) ) {
                                /* Maybe this network uses Shared Key? */
-                               method = dev->associating->method =
+                               dev->ctx.assoc->method =
                                        IEEE80211_AUTH_SHARED_KEY;
                        } else {
-                               rc = E80211_STATUS ( status );
                                goto fail;
                        }
                }
 
                DBGC ( dev, "802.11 %p authenticating with method %d\n", dev,
-                      method );
+                      dev->ctx.assoc->method );
 
-               dev->associating->last_packet = currticks();
-
-               rc = net80211_prepare ( dev, dev->associating );
+               rc = net80211_prepare_assoc ( dev, dev->associating );
                if ( rc )
                        goto fail;
 
-               rc = net80211_send_auth ( dev, dev->associating, method );
+               rc = net80211_send_auth ( dev, dev->associating,
+                                         dev->ctx.assoc->method );
                if ( rc )
                        goto fail;
 
@@ -1347,12 +1591,8 @@ static void net80211_step_associate ( struct process *proc )
                /* state: associate */
                DBGC ( dev, "802.11 %p associating\n", dev );
 
-               dev->associating->last_packet = currticks();
-
-               if ( status != IEEE80211_STATUS_SUCCESS ) {
-                       rc = E80211_STATUS ( status );
+               if ( status != IEEE80211_STATUS_SUCCESS )
                        goto fail;
-               }
 
                rc = net80211_send_assoc ( dev, dev->associating );
                if ( rc )
@@ -1365,8 +1605,6 @@ static void net80211_step_associate ( struct process *proc )
                /* state: crypto sync */
                DBGC ( dev, "802.11 %p security handshaking\n", dev );
 
-               dev->associating->last_packet = currticks();
-
                dev->state |= NET80211_CRYPTO_SYNCED;
                /* XXX need to actually do something here once we
                   support WPA */
@@ -1374,19 +1612,35 @@ static void net80211_step_associate ( struct process *proc )
        }
 
        /* state: done! */
+       netdev_link_up ( dev->netdev );
+       dev->assoc_rc = 0;
+       dev->state &= ~NET80211_WORKING;
+
+       free ( dev->ctx.assoc );
+       net80211_free_wlan ( dev->associating );
+       dev->associating = NULL;
+
+       process_del ( proc );
+
        DBGC ( dev, "802.11 %p associated with %s (%s)\n", dev,
               dev->essid, eth_ntoa ( dev->bssid ) );
 
-       dev->netdev->state |= NETDEV_LINK_UP;
-       dev->state &= ~NET80211_WORKING;
-       process_del ( proc );
        return;
 
  fail:
        dev->state &= ~( NET80211_WORKING | NET80211_WAITING );
-       DBGC ( dev, "802.11 %p association failed (state=%04x): "
-              "%s\n", dev, dev->state, strerror ( rc ) );
+       if ( rc )
+               dev->assoc_rc = rc;
+
+       netdev_link_err ( dev->netdev, dev->assoc_rc );
+
+       net80211_free_wlan ( dev->associating );
+       dev->associating = NULL;
+
        process_del ( proc );
+
+       DBGC ( dev, "802.11 %p association failed (state=%04x): "
+              "%s\n", dev, dev->state, strerror ( dev->assoc_rc ) );
 }
 
 /**
@@ -1414,7 +1668,7 @@ int net80211_check_ssid_update ( void )
                if ( strcmp ( ssid, dev->essid ) != 0 ) {
                        DBGC ( dev, "802.11 %p updating association: "
                               "%s -> %s\n", dev, dev->essid, ssid );
-                       net80211_set_state ( dev, NET80211_AUTHENTICATED, 0, 0 );
+                       net80211_set_state ( dev, NET80211_PROBED, 0, 0 );
                        net80211_autoassociate ( dev );
                }
        }
@@ -1445,6 +1699,7 @@ void net80211_autoassociate ( struct net80211_device *dev )
                              &net80211_ssid_setting, dev->essid,
                              IEEE80211_MAX_SSID_LEN );
        dev->essid[len] = 0;
+       dev->ctx.probe = NULL;
        dev->associating = NULL;
        net80211_set_state ( dev, NET80211_ASSOCIATED, NET80211_WORKING, 0 );
 }
@@ -1557,8 +1812,8 @@ int net80211_set_channel_nr ( struct net80211_device *dev, int channel )
  * @v active   Whether the scanning will be active
  * @ret rc     Return status code
  */
-int net80211_prepare_default ( struct net80211_device *dev, int band,
-                              int active )
+int net80211_prepare_probe ( struct net80211_device *dev, int band,
+                            int active )
 {
        if ( active && band != NET80211_BAND_2GHZ ) {
                DBGC ( dev, "802.11 %p cannot perform active scanning on "
@@ -1603,8 +1858,8 @@ int net80211_prepare_default ( struct net80211_device *dev, int band,
  * @v wlan     WLAN to prepare for communication with
  * @ret rc     Return status code
  */
-int net80211_prepare ( struct net80211_device *dev,
-                      struct net80211_wlan *wlan )
+int net80211_prepare_assoc ( struct net80211_device *dev,
+                            struct net80211_wlan *wlan )
 {
        struct ieee80211_frame *hdr = wlan->beacon->data;
        struct ieee80211_beacon *beacon =
@@ -1753,7 +2008,7 @@ int net80211_send_assoc ( struct net80211_device *dev,
                assoc->capability |= IEEE80211_CAPAB_SHORT_PMBL;
        if ( ! ( dev->hw->flags & NET80211_HW_NO_SHORT_SLOT ) )
                assoc->capability |= IEEE80211_CAPAB_SHORT_SLOT;
-       if ( wlan->security )
+       if ( wlan->crypto )
                assoc->capability |= IEEE80211_CAPAB_PRIVACY;
 
        assoc->listen_interval = 1;
@@ -1803,6 +2058,32 @@ static void net80211_handle_assoc_reply ( struct net80211_device *dev,
                             IEEE80211_STATUS_SUCCESS );
 }
 
+
+/**
+ * Send 802.11 disassociation frame
+ *
+ * @v dev      802.11 device
+ * @v reason   Reason for disassociation
+ * @ret rc     Return status code
+ */
+static int net80211_send_disassoc ( struct net80211_device *dev, int reason )
+{
+       struct io_buffer *iob = alloc_iob ( 64 );
+       struct ieee80211_disassoc *disassoc;
+
+       if ( ! ( dev->state & NET80211_ASSOCIATED ) )
+               return -EINVAL;
+
+       net80211_set_state ( dev, NET80211_ASSOCIATED, 0, 0 );
+       iob_reserve ( iob, IEEE80211_TYP_FRAME_HEADER_LEN );
+       disassoc = iob_put ( iob, sizeof ( *disassoc ) );
+       disassoc->reason = reason;
+
+       return net80211_tx_mgmt ( dev, IEEE80211_STYPE_DISASSOC, dev->bssid,
+                                 iob );
+}
+
+
 /**
  * Handle receipt of 802.11 management frame
  *
@@ -1817,6 +2098,7 @@ static void net80211_handle_mgmt ( struct net80211_device *dev,
        struct ieee80211_disassoc *disassoc;
        u16 stype = hdr->fc & IEEE80211_FC_SUBTYPE;
        int keep = 0;
+       int is_deauth = ( stype == IEEE80211_STYPE_DEAUTH );
 
        if ( ( hdr->fc & IEEE80211_FC_TYPE ) != IEEE80211_TYPE_MGMT ) {
                free_iob ( iob );
@@ -1827,18 +2109,14 @@ static void net80211_handle_mgmt ( struct net80211_device *dev,
                /* These are usually indicative of a deeper problem,
                   so don't just reassociate right away. */
        case IEEE80211_STYPE_DEAUTH:
-               disassoc = ( struct ieee80211_disassoc * ) hdr->data;
-               net80211_set_state ( dev, NET80211_AUTHENTICATED, 0,
-                                    NET80211_IS_REASON | disassoc->reason );
-               DBGC ( dev, "802.11 %p deauthenticated: reason %d\n",
-                      dev, disassoc->reason );
-               break;
        case IEEE80211_STYPE_DISASSOC:
                disassoc = ( struct ieee80211_disassoc * ) hdr->data;
-               net80211_set_state ( dev, NET80211_ASSOCIATED, 0,
+               net80211_set_state ( dev, is_deauth ? NET80211_ASSOCIATED :
+                                    NET80211_AUTHENTICATED, 0,
                                     NET80211_IS_REASON | disassoc->reason );
-               DBGC ( dev, "802.11 %p disassociated: reason %d\n",
-                     dev, disassoc->reason );
+               DBGC ( dev, "802.11 %p %s: reason %d\n",
+                      dev, is_deauth ? "deauthenticated" : "disassociated",
+                      disassoc->reason );
                break;
 
                /* We handle authentication and association. */
@@ -2117,7 +2395,7 @@ void net80211_rx ( struct net80211_device *dev, struct io_buffer *iob,
                        dev, hdr->seq );
        }
 
-       if ( dev->netdev->state & NETDEV_LINK_UP ) {
+       if ( netdev_link_ok ( dev->netdev ) ) {
                netdev_rx ( dev->netdev, iob );
                return;
        }