[802.11] The 802.11 MAC layer, first big commit
authorJoshua Oreman <oremanj@xenon.get-linux.org>
Fri, 5 Jun 2009 04:06:16 +0000 (21:06 -0700)
committerJoshua Oreman <oremanj@xenon.get-linux.org>
Fri, 5 Jun 2009 04:06:16 +0000 (21:06 -0700)
Implemented much of the basic functionality required of the 802.11 MAC
layer. gPXE with rtl8180 compiled in will now link, but should not be
tested yet because the rather fundamental "associate with a network"
function is one of the ones still stubbed.

src/include/gpxe/errfile.h
src/include/gpxe/ieee80211.h
src/include/gpxe/net80211.h
src/net/net80211.c [new file with mode: 0644]

index 3a77113..888b859 100644 (file)
@@ -144,6 +144,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_ib_sma                 ( ERRFILE_NET | 0x00170000 )
 #define ERRFILE_ib_packet              ( ERRFILE_NET | 0x00180000 )
 #define ERRFILE_icmp                   ( ERRFILE_NET | 0x00190000 )
+#define ERRFILE_net80211               ( ERRFILE_NET | 0x001a0000 )
 
 #define ERRFILE_image                ( ERRFILE_IMAGE | 0x00000000 )
 #define ERRFILE_elf                  ( ERRFILE_IMAGE | 0x00010000 )
index 0e6a0eb..b67c471 100644 (file)
 FILE_LICENCE(GPL2_OR_LATER);
 
 /* Maximum lengths of things: */
-#define IEEE80211_MAX_DATA_LEN          2304 /* plus any crypto overhead */
-#define IEEE80211_MAX_FRAME_LEN         2352
+
+/* Maximum length of `data' field in frame: doesn't include any crypto
+   overhead, but DOES include the 802.3 LLC + SNAP headers in data frames! */
+#define IEEE80211_MAX_DATA_LEN          2304
+/* Length of said 802.3 LLC + SNAP headers. */
+#define IEEE80211_LLC_HEADER_LEN       8
+/* Amount of crypto overhead there might be. */
+#define IEEE80211_MAX_CRYPTO_HEADER    8    /* TKIP and CCMP */
+#define IEEE80211_MAX_CRYPTO_TRAILER    12   /* TKIP: 8-byte MIC, 4-byte ICV */
+#define IEEE80211_MAX_CRYPTO_OVERHEAD  20   /* HEADER + TRAILER */
+/* Number of bytes of real data we can put in one non-fragmented frame. */
+#define IEEE80211_MAX_FRAME_DATA       2296 /* DATA_LEN - LLC_HEADER_LEN */
+
+/* Frame header lengths - the LLC header is separate. We should never see
+   a frame with the maximum header length; it's included for completeness. */
+#define IEEE80211_TYP_FRAME_HEADER_LEN 24   /* for non-QoS, not-between-APs */
+#define IEEE80211_MAX_FRAME_HEADER_LEN 32   /* 4-address QoS format */
+
+#define IEEE80211_MAX_FRAME_LEN         2356 /* data + crypto + header */
+
 #define IEEE80211_MAX_SSID_LEN          32
 
 /* Flags for Frame Control field, ieee80211_frame.fc: */
 #define IEEE80211_FC_VERSION   0x0003
+#define  IEEE80211_THIS_VERSION  0x0000
 #define IEEE80211_FC_TYPE      0x000C
 #define  IEEE80211_TYPE_MGMT     0x0000
 #define  IEEE80211_TYPE_CTRL     0x0004
@@ -55,7 +74,7 @@ FILE_LICENCE(GPL2_OR_LATER);
 #define IEEE80211_SEQNR(seq)    ((seq) & 0x0FFF)
 #define IEEE80211_FRAG(seq)     (((seq) >> 12) & 0xF)
 
-/* Generic data or management frame. */
+/* Generic data or management frame - non-QoS, not between APs. */
 struct ieee80211_frame
 {
        u16 fc;                 /**< Frame Control field */
@@ -67,6 +86,23 @@ struct ieee80211_frame
        u8 data[0];
 } __attribute__((packed));
 
+/* The 802.3 link-layer header that begins the data, plus SNAP fields
+   for encapsulating Ethernet protocol type */
+struct ieee80211_llc_snap_header
+{
+       /* LLC part: */
+       u8 dsap;                /**< destination SAP id */
+       u8 ssap;                /**< source SAP id */
+       u8 ctrl;                /**< control info */
+#define IEEE80211_LLC_DSAP 0xAA        /* for SNAP */
+#define IEEE80211_LLC_SSAP 0xAA        /* for SNAP */
+#define IEEE80211_LLC_CTRL 0x03        /* Unnumbered Information */
+
+       /* SNAP part: */
+       u8 oui[3];              /**< Organization code, usually 0 */
+       u16 ethertype;          /**< Ethernet Type field */
+} __attribute__((packed));
+
 
 /* Control frame formats, with abbreviated frame header. */
 struct ieee80211_rts
index 673157e..613df62 100644 (file)
@@ -1,8 +1,10 @@
 #ifndef _GPXE_NET80211_H
 #define _GPXE_NET80211_H
 
+#include <gpxe/process.h>
 #include <gpxe/ieee80211.h>
 #include <gpxe/iobuf.h>
+#include <gpxe/netdevice.h>
 
 /** @file
  * The gPXE 802.11 MAC layer.
@@ -66,14 +68,13 @@ struct net80211_device_operations {
 /** An 802.11 RF channel. */
 struct net80211_channel
 {
-       unsigned band;          /* one of NET80211_BAND_* */
+       u8 band;                /* one of NET80211_BAND_* */
 
-       unsigned channel_nr;    /* a traditional channel number,
+       u8 channel_nr;          /* a traditional channel number,
                                   e.g. 1-11 for the 2.4GHz band */
 
-       unsigned center_freq;   /* MHz of channel center */
-       unsigned maxpower;      /* maximum allowable EIRP in dBm */
-       unsigned txpower;       /* current requested txpower, dBm */
+       u16 center_freq;        /* MHz of channel center */
+       u8 maxpower;            /* maximum allowable EIRP in dBm */
 
        /* There are more regulatory issues than this - bandwidth and
           antenna gain are the main ones - but we can't do anything
@@ -105,16 +106,13 @@ struct net80211_hw_info
                /** Received frames include a frame check sequence. */
                NET80211_HW_RX_HAS_FCS = (1 << 1),
 
-               /** We need to verify the received frame check sequence. */
-               NET80211_HW_RX_UNCHECKED_FCS = (1 << 2),
-
                /** Hardware doesn't support short preambles on the
                    2.4GHz band (all 802.11g devices do). */
-               NET80211_HW_NO_SHORT_PREAMBLE = (1 << 3),
+               NET80211_HW_NO_SHORT_PREAMBLE = (1 << 2),
 
                /** Hardware doesn't support short slot operation on
                    the 2.4GHz band (only relevant for 802.11g). */
-               NET80211_HW_NO_SHORT_SLOT = (1 << 4),
+               NET80211_HW_NO_SHORT_SLOT = (1 << 3),
        } flags;
 
        /** Set according to the type of signal strength information
@@ -142,9 +140,9 @@ struct net80211_hw_info
        unsigned channel_change_time;
 };
 
-/** Milliseconds we'll wait to get all fragments of a packet. If this
+/** Seconds we'll wait to get all fragments of a packet. If this
     timer expires, we drop the fragments we've received so far. */
-#define NET80211_FRAG_TIMEOUT  2000
+#define NET80211_FRAG_TIMEOUT  2
 
 /** Keeps track of the fragments for a particular packet as we receive
     them. We set up the frag_cache entry when we receive a fragment 0
@@ -155,8 +153,14 @@ struct net80211_hw_info
     like this, as long as we have >= 3 cache entries. */
 struct net80211_frag_cache
 {
-       /** Is this fragment cache entry being used (0) or not (1)? */
-       u16 avail;
+       /** State of this frag cache entry. */
+       u8 state;
+#define NET80211_FRAG_AVAIL    0 /* not in use */
+#define NET80211_FRAG_WAITING  1 /* waiting for the final fragment */
+#define NET80211_FRAG_FINISHING        2 /* waiting for holes to fill */
+
+       /** Number of fragments to expect, if state is FINISHING. */
+       u8 nfrags;
 
        /** Sequence number of this MSDU (packet). */
        u16 seqnr;
@@ -168,6 +172,42 @@ struct net80211_frag_cache
        struct io_buffer *iob[16];
 };
 
+/* 802.11 data security protocols: */
+#define NET80211_CRYPT_NONE   0        /* "Open" */
+#define NET80211_CRYPT_WEP40  2        /* "WEP 64-bit" (5-character key) */
+#define NET80211_CRYPT_WEP104 3 /* "WEP 128-bit" (13-character key) */
+#define NET80211_CRYPT_TKIP   4        /* "WPA Personal" */
+#define NET80211_CRYPT_CCMP   5        /* "WPA2 Personal" */
+#define NET80211_CRYPT_EAP    8        /* "WPA2 Enterprise" */
+
+/** Interface to a cryptographic algorithm. */
+struct net80211_crypto
+{
+       /** Type, as one of the constants defined above. */
+       short type;
+
+       /** Initialize crypto algorithm using the given key. */
+       void (*initialize)(struct net80211_device *dev, u8 *key, int keylen);
+
+       /** Encrypt the given frame into a newly allocated io_buffer
+           with all frame headers intact. Returns NULL if out of
+           memory. */
+       struct io_buffer *(*encrypt)(struct net80211_device *dev,
+                                    struct io_buffer *iob);
+       
+       /** Decrypt the given frame into a newly allocated io_buffer
+           with all frame headers intact. Returns NULL if decryption
+           fails. */
+       struct io_buffer *(*decrypt)(struct net80211_device *dev,
+                                    struct io_buffer *iob);
+
+       /** Private data for the crypto algorithm to store key and
+           state information. */
+       void *priv;
+
+       /* More to come... */
+};
+
 /** Structure encapsulating the complete state of an 802.11
     device. Stored in the `priv' member of some net_device. */
 struct net80211_device
@@ -175,6 +215,9 @@ struct net80211_device
        /** The net_device that wraps us. */
        struct net_device *netdev;
 
+       /** List of 802.11 devices. */
+       struct list_head list;
+
        /** Network device operations */
        struct net80211_device_operations *op;
 
@@ -182,31 +225,41 @@ struct net80211_device
        void *priv;
 
        /** Information about the hardware, which the driver is
-           responsible for setting. */
+           responsible for providing. */
        struct net80211_hw_info *hw;
 
+       /** The asynchronous association process. (Successful
+           association is represented as "link up".) */
+       struct process proc_assoc;
+
        /** A list of all possible channels, as computed from
            regulatory information in the beacon frame. `channel' is
            an index into this list. */
        struct net80211_channel *channels;
        /** The number of channels in the `channels' list. */
-       int nr_channels;
+       u8 nr_channels;
        /** The channel currently in use, as an index into the
            `channels' list. */
-       int channel;
+       u8 channel;
 
        /** A list of all transmission rates supported by the AP we're
-           associated with, in units of 100 kbps. */
+           associated with, in units of 100 kbps. Max given flags is
+           16384 = 1.6 Gbps. */
        u16 rates[NET80211_MAX_RATES];
+#define NET80211_RATE_VALUE(r) ((r) & 0x3fff)
+#define NET80211_RATE_ERP      0x8000 /* an 802.11g extended rate (>11Mbps) */
        /** The number of transmission rates in the `rates' array. */
-       int nr_rates;   
+       u8 nr_rates;    
+       /** The rate currently in use, as an index into the `rates' array. */
+       u8 rate;
        /** If basic_rates & (1 << i), then rates[i] is a basic rate. */
        u32 basic_rates;
-       /** The rate currently in use, as an index into the `rates' array. */
-       int rate;
 
        /** Are we associated to a network? */
-       int assoc;
+       short assoc;
+       /** Encryption method for the network we're associated to.
+           NULL for open. */
+       struct net80211_crypto *crypto;
        /** Network to which we're associated, if `assoc' is set. */
        u8 bssid[ETH_ALEN];
        /** Name of the SSID we're connected to. */
@@ -214,7 +267,8 @@ struct net80211_device
        /** Association ID given to us by the AP. */
        u16 aid;
        /** ERP-related options, set dynamically based on the assoc
-           reply packet. */
+           reply packet. Set even for non-ERP networks based on the
+           capabilities field. */
        int erp_flags;
 #define NET80211_ERP_USE_CTS_PROTECTION  (1 << 1)
 #define NET80211_ERP_USE_SHORT_PREAMBLE  (1 << 2)
@@ -230,18 +284,13 @@ struct net80211_device
 #define NET80211_NR_CONCURRENT_FRAGS  3
        struct net80211_frag_cache frags[NET80211_NR_CONCURRENT_FRAGS];
 
+       /** The sequence number of the last packet we sent. */
+       u16 last_tx_seqnr;
+
        /** Packet deduping state. We only are required to handle
            immediate duplicates for each direct sender, and as we can
            only have one direct sender (the AP), our job is easy. */
-       int last_seqnr;
-       int last_frag;
-
-       /** TX packet queue. Packets here go onto the air. */
-       struct list_head tx_queue;
-
-       /** RX packet queue. Packets here go into netdev_rx(),
-           mgmt_queue, or get dropped. */
-       struct list_head rx_queue;
+       s16 last_rx_seq;        /* (frag << 12) | seqnr, as hdr->seq */
 
        /** Sometimes we want to keep management packets we receive,
            e.g. when we're scanning for networks. They will be stored
@@ -252,14 +301,10 @@ struct net80211_device
        int keep_mgmt;
 };
 
-/* 802.11 data security protocols: */
-#define NET80211_CRYPT_NONE   0        /* "Open" */
-#define NET80211_CRYPT_WEP40  2        /* "WEP 64-bit" (5-character key) */
-#define NET80211_CRYPT_WEP104 3 /* "WEP 128-bit" (13-character key) */
-#define NET80211_CRYPT_TKIP   4        /* "WPA Personal" */
-#define NET80211_CRYPT_CCMP   5        /* "WPA2 Personal" */
-#define NET80211_CRYPT_EAP    8        /* "WPA2 Enterprise" */
-
+/** Structure returned from the scanning functions and passed to
+    net80211_associate(). At least essid, bssid, channel, and security
+    must be filled in. channel should point to something in the
+    device's channels[] array. */
 struct net80211_wlan
 {
        /** The human-readable ESSID (network name). */
@@ -291,7 +336,7 @@ struct net80211_wlan
 /* 2.4GHz channels 1-11 are safe to transmit on everywhere. */
 
 
-/* -- gPXE API: -- */
+/* -- network scanning API: -- */
 
 /** Scans the area, returning a list of net80211_wlan structures
     corresponding to each of the reachable wireless networks. */
@@ -302,9 +347,26 @@ struct net80211_wlan *net80211_scan(struct net80211_device *dev);
 struct net80211_wlan *net80211_probe(struct net80211_device *dev,
                                     const char *essid);
 
+/** Does scan/probe as appropriate given configured settings, and
+    associates. Operates as a background process; this function
+    returns immediately, before the association has been performed. */
+void net80211_autoassociate(struct net80211_device *dev);
+
 /** Frees the list returned from a scan or probe call. */
 void net80211_free_netlist(struct net80211_wlan *nets);
 
+/* -- general-use net80211 API: -- */
+
+/** If the passed net_device wraps a net80211_device, returns the
+    net80211_device. If not, returns NULL. */
+struct net80211_device *net80211_get(struct net_device *netdev);
+
+/** Sets up the device's regulatory parameters as the provided beacon
+    or probe response frame tells us to. Must be called before
+    associating. */
+int net80211_prepare(struct net80211_device *netdev,
+                    struct io_buffer *iobuf);
+
 /** Requests that we associate with the network identified by
     `wlan'. If authentication credentials are required, they must
     have been set up using a previous call. This function blocks until
@@ -347,18 +409,24 @@ int net80211_register(struct net80211_device *dev,
 
 /** Indicate receipt of a raw packet from the network interface. Takes
     ownership of the iob. */
-int net80211_rx(struct net80211_device *dev, struct io_buffer *iob);
+void net80211_rx(struct net80211_device *dev, struct io_buffer *iob);
 
 /** Indicate an error in receiving a packet. Pass iob if you want it
     freed. */
-int net80211_rx_err(struct net80211_device *dev, struct io_buffer *iob,
-                   int rc);
+static inline void net80211_rx_err(struct net80211_device *dev,
+                                  struct io_buffer *iob, int rc) 
+{
+       netdev_rx_err(dev->netdev, iob, rc);
+}
 
 /** Indicate the completed transmission of a packet passed to
     (*transmit)(), successfully if rc == 0 and unsuccessfully if
     not. In either case this function takes ownership of the iob. */
-int net80211_tx_complete(struct net80211_device *dev, struct io_buffer *iob,
-                        int rc);
+static inline void net80211_tx_complete(struct net80211_device *dev,
+                                       struct io_buffer *iob, int rc) 
+{
+       netdev_tx_complete_err(dev->netdev, iob, rc);
+}
 
 /** Removes the registration of an 802.11 device. */
 void net80211_unregister(struct net80211_device *dev);
diff --git a/src/net/net80211.c b/src/net/net80211.c
new file mode 100644 (file)
index 0000000..4db5a91
--- /dev/null
@@ -0,0 +1,510 @@
+/*
+ * The gPXE 802.11 MAC layer.
+ *
+ * Copyright (c) 2009 Joshua Oreman <oremanj@rwcr.net>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <string.h>
+#include <byteswap.h>
+#include <stdlib.h>
+#include <gpxe/if_arp.h>
+#include <gpxe/ethernet.h>
+#include <gpxe/ieee80211.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/net80211.h>
+#include <gpxe/timer.h>
+#include <errno.h>
+
+/** List of 802.11 devices */
+static struct list_head net80211_devices = LIST_HEAD_INIT(net80211_devices);
+
+/** Set of device operations that does nothing */
+static struct net80211_device_operations net80211_null_ops;
+
+/* ---------- still needs implementing... ---------- */
+void net80211_handle_mgmt(struct net80211_device *dev, struct io_buffer *iob)
+{
+       (void)dev; (void)iob;
+       /* ... */
+}
+
+void net80211_autoassociate(struct net80211_device *dev) 
+{
+       (void)dev;
+       /* ... */
+}
+
+/* ---------- net_device wrapper ---------- */
+
+int net80211_netdev_open(struct net_device *netdev) 
+{
+       struct net80211_device *dev = netdev->priv;
+
+       if (dev->op->open)
+               return dev->op->open(dev);
+
+       net80211_autoassociate(dev);
+       return 0;
+}
+
+void net80211_netdev_close(struct net_device *netdev)
+{
+       struct net80211_device *dev = netdev->priv;
+
+       if (dev->op->close)
+               dev->op->close(dev);
+}
+
+int net80211_netdev_transmit(struct net_device *netdev, struct io_buffer *iobuf)
+{
+       struct net80211_device *dev = netdev->priv;
+       
+       if (dev->crypto) {
+               struct io_buffer *niob = dev->crypto->encrypt(dev, iobuf);
+               if (!niob)
+                       return -ENOMEM; /* only reason encryption could fail */
+
+               free_iob(iobuf);
+               iobuf = niob;
+       }
+       
+       if (dev->op->transmit)
+               return dev->op->transmit(dev, iobuf);
+       return -ENOSYS;
+}
+
+void net80211_netdev_poll(struct net_device *netdev)
+{
+       struct net80211_device *dev = netdev->priv;
+       
+       if (dev->op->poll)
+               dev->op->poll(dev);
+}
+
+void net80211_netdev_irq(struct net_device *netdev, int enable)
+{
+       struct net80211_device *dev = netdev->priv;
+       
+       if (dev->op->irq)
+               dev->op->irq(dev, enable);
+}
+
+struct net_device_operations net80211_netdev_ops = {
+       .open = net80211_netdev_open,
+       .close = net80211_netdev_close,
+       .transmit = net80211_netdev_transmit,
+       .poll = net80211_netdev_poll,
+       .irq = net80211_netdev_irq,
+};
+
+/* ---------- 802.11 link-layer protocol ---------- */
+
+static u8 net80211_ll_broadcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+/* Returns the time taken to transmit a packet of length `bytes' using
+   the current PHY parameters of `dev', measured in microseconds
+   starting immediately after reception of the previous packet is
+   complete. */
+static u16 net80211_duration(struct net80211_device *dev, int bytes) 
+{
+       struct net80211_channel *chan = &dev->channels[dev->channel];
+       u16 ratecode = dev->rates[dev->rate];
+       u32 kbps = NET80211_RATE_VALUE(ratecode) * 100;
+       
+       if (chan->band == NET80211_BAND_5GHZ || ratecode & NET80211_RATE_ERP) {
+               /* OFDM encoding (802.11a/g) */
+               int bits_per_symbol = (kbps * 4) / 1000; /* 4us/symbol */
+               int bits = 22 + (bytes << 3); /* 22-bit PLCP */
+               int symbols = (bits + bits_per_symbol - 1) / bits_per_symbol;
+               return 16 + 20 + (symbols * 4); /* 16us SIFS, 20us preamble */
+       } else {
+               /* CCK encoding (802.11b) */
+               int phy_time = 144 + 48; /* preamble + PLCP */
+               if (dev->erp_flags & NET80211_ERP_USE_SHORT_PREAMBLE)
+                       phy_time >>= 1;
+               int bits = bytes << 3;
+               int data_time = (bits * 1000) / kbps;
+               return 10 + phy_time + data_time; /* 10us SIFS */
+       }
+}
+
+static int net80211_ll_push(struct net_device *netdev, struct io_buffer *iobuf,
+                           const void *ll_dest, const void *ll_source,
+                           uint16_t net_proto)
+{
+       struct net80211_device *dev = netdev->priv;
+       struct ieee80211_frame *hdr = iob_push(iobuf, IEEE80211_LLC_HEADER_LEN +
+                                              IEEE80211_TYP_FRAME_HEADER_LEN);
+       struct ieee80211_llc_snap_header *lhdr = (struct ieee80211_llc_snap_header *)
+               ((u8 *)hdr + IEEE80211_TYP_FRAME_HEADER_LEN);
+
+       hdr->fc = IEEE80211_THIS_VERSION | IEEE80211_TYPE_DATA |
+                 IEEE80211_STYPE_DATA | IEEE80211_FC_TODS;
+
+       /* We don't send fragmented frames, so duration is the time
+          for an SIFS + 10-byte ACK. */
+       hdr->duration = net80211_duration(dev, 10);
+
+       memcpy(hdr->addr1, dev->bssid, ETH_ALEN);
+       memcpy(hdr->addr2, ll_source, ETH_ALEN);
+       memcpy(hdr->addr3, ll_dest, ETH_ALEN);
+
+       hdr->seq = ++dev->last_tx_seqnr & 0x0FFF; /* fragment 0 */
+
+       lhdr->dsap = IEEE80211_LLC_DSAP;
+       lhdr->ssap = IEEE80211_LLC_SSAP;
+       lhdr->ctrl = IEEE80211_LLC_CTRL;
+       memset(lhdr->oui, 0x00, 3);
+       lhdr->ethertype = net_proto;
+
+       return 0;
+}
+
+static int net80211_ll_pull(struct net_device *netdev __unused,
+                           struct io_buffer *iobuf, const void **ll_dest,
+                           const void **ll_source, uint16_t *net_proto)
+{
+       struct ieee80211_frame *hdr = iobuf->data;
+       struct ieee80211_llc_snap_header *lhdr = (struct ieee80211_llc_snap_header *)
+               ((u8 *)hdr + IEEE80211_TYP_FRAME_HEADER_LEN);
+
+       /* Bunch of sanity checks */
+       if (iob_len(iobuf) < IEEE80211_TYP_FRAME_HEADER_LEN +
+                               IEEE80211_LLC_HEADER_LEN) {
+               DBG("802.11 packet too short (%zd bytes)\n", iob_len(iobuf));
+               return -EINVAL;
+       }
+
+       if ((hdr->fc & IEEE80211_FC_VERSION) != IEEE80211_THIS_VERSION) {
+               DBG("802.11 packet invalid version %04x\n",
+                   hdr->fc & IEEE80211_FC_VERSION);
+               return -EINVAL;
+       }
+
+       if ((hdr->fc & IEEE80211_FC_TYPE) != IEEE80211_TYPE_DATA ||
+           (hdr->fc & IEEE80211_FC_SUBTYPE) != IEEE80211_STYPE_DATA) {
+               DBG("802.11 packet not data/data (fc=%04x)\n", hdr->fc);
+               return -EINVAL;
+       }
+
+       if ((hdr->fc & (IEEE80211_FC_TODS|IEEE80211_FC_FROMDS)) !=
+           IEEE80211_FC_FROMDS) {
+               DBG("802.11 packet not from DS (fc=%04x)\n", hdr->fc);
+               return -EINVAL;
+       }
+
+       if (lhdr->dsap != IEEE80211_LLC_DSAP || lhdr->ssap != IEEE80211_LLC_SSAP ||
+           lhdr->ctrl != IEEE80211_LLC_CTRL || lhdr->oui[0] || lhdr->oui[1] ||
+           lhdr->oui[2]) {
+               DBG("802.11 LLC header is not plain EtherType encapsulator: "
+                   "%02x->%02x [%02x] %02x:%02x:%02x %04x\n",
+                   lhdr->dsap, lhdr->ssap, lhdr->ctrl, lhdr->oui[0],
+                   lhdr->oui[1], lhdr->oui[2], lhdr->ethertype);
+               return -EINVAL;
+       }
+
+       iob_pull(iobuf, sizeof(*hdr) + sizeof(*lhdr));
+
+       *ll_dest = hdr->addr1;
+       *ll_source = hdr->addr3;
+       *net_proto = lhdr->ethertype;
+       return 0;
+}
+
+static int net80211_ll_mc_hash(unsigned int af __unused,
+                              const void *net_addr __unused,
+                              void *ll_addr __unused) 
+{
+       return -ENOTSUP;
+}
+
+static struct ll_protocol net80211_ll_protocol __ll_protocol = {
+       .name           = "802.11",
+       .push           = net80211_ll_push,
+       .pull           = net80211_ll_pull,
+       .ntoa           = eth_ntoa,
+       .mc_hash        = net80211_ll_mc_hash,
+       .ll_proto       = htons(ARPHRD_ETHER), /* it's "encapsulated Ethernet" */
+       .ll_addr_len    = ETH_ALEN,
+       .ll_header_len  = IEEE80211_TYP_FRAME_HEADER_LEN +
+                         IEEE80211_LLC_HEADER_LEN,
+       .ll_broadcast   = net80211_ll_broadcast,
+};
+
+/* ---------- rest-of-gPXE 802.11 API ---------- */
+
+struct net80211_device *net80211_get(struct net_device *netdev) 
+{
+       struct net80211_device *dev;
+       
+       list_for_each_entry(dev, &net80211_devices, list) {
+               if (netdev->priv == dev)
+                       return netdev->priv;
+       }
+
+       return NULL;
+}
+
+int net80211_keep_mgmt(struct net80211_device *dev, int enable) 
+{
+       int oldenab = dev->keep_mgmt;
+       dev->keep_mgmt = enable;
+       return oldenab;
+}
+
+struct io_buffer *net80211_mgmt_dequeue(struct net80211_device *dev) 
+{
+       struct io_buffer *iobuf;
+
+       list_for_each_entry(iobuf, &dev->mgmt_queue, list) {
+               list_del(&iobuf->list);
+               return iobuf;
+       }
+       return NULL;
+}
+
+/* ---------- driver API ---------- */
+
+struct net80211_device *net80211_alloc(size_t priv_size) 
+{
+       struct net80211_device *dev;
+       struct net_device *netdev = alloc_netdev(sizeof(*dev) + priv_size);
+
+       if (!netdev)
+               return NULL;
+
+       netdev->ll_protocol = &net80211_ll_protocol;
+       netdev->max_pkt_len = IEEE80211_MAX_DATA_LEN;
+       netdev_init(netdev, &net80211_netdev_ops);
+
+       dev = netdev->priv;
+       dev->netdev = netdev;
+       dev->priv = (u8 *)dev + sizeof(*dev);
+       dev->op = &net80211_null_ops;
+       dev->last_rx_seq = -1;
+
+       INIT_LIST_HEAD(&dev->mgmt_queue);
+
+       return dev;
+}
+
+int net80211_register(struct net80211_device *dev,
+                     struct net80211_device_operations *ops,
+                     struct net80211_hw_info *hw) 
+{
+       dev->op = ops;
+       dev->hw = malloc(sizeof(*hw));
+       if (!dev->hw)
+               return -ENOMEM;
+
+       memcpy(dev->hw, hw, sizeof(*hw));
+       memcpy(dev->netdev->ll_addr, hw->hwaddr, ETH_ALEN);
+       
+       list_add_tail(&dev->list, &net80211_devices);
+       return register_netdev(dev->netdev);
+}
+
+void net80211_unregister(struct net80211_device *dev) 
+{
+       unregister_netdev(dev->netdev);
+       list_del(&dev->list);
+       dev->op = &net80211_null_ops;
+}
+
+void net80211_free(struct net80211_device *dev) 
+{
+       free(dev->hw);
+       netdev_nullify(dev->netdev);
+       netdev_put(dev->netdev);
+}
+
+/* ---------- packet handling ---------- */
+
+/* Free the iob's for frag cache entry fcid and mark it unused. */
+static void net80211_free_frags(struct net80211_device *dev, int fcid) 
+{
+       int j;
+       struct net80211_frag_cache *frag = &dev->frags[fcid];
+
+       for (j = 0; j < 16; j++) {
+               if (frag->iob[j]) {
+                       free_iob(frag->iob[j]);
+                       frag->iob[j] = NULL;
+               }
+       }
+
+       frag->seqnr = 0;
+       frag->nfrags = 0;
+       frag->start_ticks = 0;
+       frag->state = NET80211_FRAG_AVAIL;
+}
+
+/* Accumulate fragments from frag cache entry fcid, of total size
+   `size' (headers included), into one iob and return it. */
+static struct io_buffer *net80211_accum_frags(struct net80211_device *dev,
+                                             int fcid, int size)
+{
+       struct net80211_frag_cache *frag = &dev->frags[fcid];
+       int hdrsize = IEEE80211_TYP_FRAME_HEADER_LEN;
+       int nfrags = frag->nfrags;
+       int nsize = size - hdrsize * (nfrags - 1);
+       int i;
+
+       struct io_buffer *niob = alloc_iob(nsize);
+       struct ieee80211_frame *hdr;
+
+       /* Add the header from the first one... */
+       memcpy(iob_put(niob, hdrsize), frag->iob[0]->data, hdrsize);
+
+       /* ... and all the data from all of them. */
+       for (i = 0; i < frag->nfrags; i++) {
+               int len = iob_len(frag->iob[i]) - hdrsize;
+               memcpy(iob_put(niob, len), frag->iob[i]->data + hdrsize, len);
+       }
+
+       /* Turn off the fragment bit. */
+       hdr = niob->data;
+       hdr->fc &= ~IEEE80211_FC_MORE_FRAG;
+
+       return niob;
+}
+
+static void net80211_rx_frag(struct net80211_device *dev, struct io_buffer *iob)
+{
+       struct ieee80211_frame *hdr = iob->data;
+
+       if (IEEE80211_FRAG(hdr->seq) == 0 && (hdr->fc & IEEE80211_FC_MORE_FRAG)) {
+               /* start a frag cache entry */
+               int i, newest = -1;
+               u32 curr_ticks = currticks(), newest_ticks = 0;
+               u32 timeout = ticks_per_sec() * NET80211_FRAG_TIMEOUT;
+
+               for (i = 0; i < NET80211_NR_CONCURRENT_FRAGS; i++) {
+                       if (dev->frags[i].state == NET80211_FRAG_AVAIL)
+                               break;
+
+                       if (dev->frags[i].start_ticks + timeout >= curr_ticks) {
+                               net80211_free_frags(dev, i);
+                               break;
+                       }
+
+                       if (dev->frags[i].start_ticks > newest_ticks) {
+                               newest = i;
+                               newest_ticks = dev->frags[i].start_ticks;
+                       }
+               }
+
+               /* If we're being sent more concurrent fragmented
+                  packets than we can handle, drop the newest so the
+                  oldest has time to complete. */
+               if (i == NET80211_NR_CONCURRENT_FRAGS) {
+                       i = newest;
+                       net80211_free_frags(dev, i);
+               }
+
+               dev->frags[i].state = NET80211_FRAG_WAITING;
+               dev->frags[i].seqnr = IEEE80211_SEQNR(hdr->seq);
+               dev->frags[i].start_ticks = currticks();
+               dev->frags[i].iob[0] = iob;
+               return;
+       } else {
+               int i;
+               for (i = 0; i < NET80211_NR_CONCURRENT_FRAGS; i++) {
+                       if (dev->frags[i].state != NET80211_FRAG_AVAIL &&
+                           dev->frags[i].seqnr == IEEE80211_SEQNR(hdr->seq))
+                               break;
+               }
+               if (i == NET80211_NR_CONCURRENT_FRAGS)
+                       return; /* drop non-first not-in-cache fragments */
+
+               dev->frags[i].iob[IEEE80211_FRAG(hdr->seq)] = iob;
+
+               if (dev->frags[i].state == NET80211_FRAG_WAITING &&
+                   !(hdr->fc & IEEE80211_FC_MORE_FRAG)) {
+                       dev->frags[i].state = NET80211_FRAG_FINISHING;
+                       dev->frags[i].nfrags = IEEE80211_FRAG(hdr->seq) + 1;
+               }
+               
+               if (dev->frags[i].state == NET80211_FRAG_FINISHING) {
+                       int j, size = 0;
+                       for (j = 0; j < dev->frags[i].nfrags; j++) {
+                               size += iob_len(dev->frags[i].iob[j]);
+                               if (dev->frags[i].iob[j] == NULL)
+                                       break;
+                       }
+                       if (j == dev->frags[i].nfrags) {
+                               /* we've got everything! */
+                               struct io_buffer *niob =
+                                       net80211_accum_frags(dev, i, size);
+                               net80211_free_frags(dev, i);
+                               net80211_rx(dev, niob);
+                       }
+               }
+       }
+}
+
+void net80211_rx(struct net80211_device *dev, struct io_buffer *iob) 
+{
+       struct ieee80211_frame *hdr = iob->data;
+       u16 type = hdr->fc & IEEE80211_FC_TYPE;
+       if ((hdr->fc & IEEE80211_FC_VERSION) != IEEE80211_THIS_VERSION)
+               return;         /* drop invalid-version packets */
+
+       if (type == IEEE80211_TYPE_CTRL)
+               return;         /* we don't handle control packets,
+                                  the hardware does */
+
+       if (dev->last_rx_seq == hdr->seq)
+               return;         /* avoid duplicate packet */
+       dev->last_rx_seq = hdr->seq;
+
+       if (dev->hw->flags & NET80211_HW_RX_HAS_FCS) {
+               /* discard the FCS */
+               iob_unput(iob, 4);
+       }
+
+       if (hdr->fc & IEEE80211_FC_PROTECTED) {
+               struct io_buffer *niob;
+               if (!dev->crypto)
+                       return; /* can't decrypt packets on an open network */
+
+               niob = dev->crypto->decrypt(dev, iob);
+               if (!niob)
+                       return; /* drop failed decryption */
+               free_iob(iob);
+               iob = niob;
+       }
+
+       if (IEEE80211_FRAG(hdr->seq) != 0 || (hdr->fc & IEEE80211_FC_MORE_FRAG)) {
+               net80211_rx_frag(dev, iob);
+               return;
+       }
+
+       if (type == IEEE80211_TYPE_MGMT) {
+               net80211_handle_mgmt(dev, iob);
+               return;
+       } else {
+               if ((hdr->fc & IEEE80211_FC_SUBTYPE) != IEEE80211_STYPE_DATA)
+                       return; /* drop QoS, CFP, or null data packets */
+
+               if (dev->netdev->state & NETDEV_LINK_UP)
+                       netdev_rx(dev->netdev, iob);
+               return;
+       }
+}