aboutsummaryrefslogtreecommitdiffstats
path: root/gpxe/src/net/80211/net80211.c
blob: 1c54597f90d57468e2676357ec0e365eb7c82053 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
/*
 * 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/settings.h>
#include <gpxe/if_arp.h>
#include <gpxe/ethernet.h>
#include <gpxe/ieee80211.h>
#include <gpxe/netdevice.h>
#include <gpxe/net80211.h>
#include <gpxe/sec80211.h>
#include <gpxe/timer.h>
#include <gpxe/nap.h>
#include <unistd.h>
#include <errno.h>

/** @file
 *
 * 802.11 device management
 */

/* Disambiguate the EINVAL's a bit */
#define EINVAL_PKT_TOO_SHORT	( EINVAL | EUNIQ_01 )
#define EINVAL_PKT_VERSION	( EINVAL | EUNIQ_02 )
#define EINVAL_PKT_NOT_DATA	( EINVAL | EUNIQ_03 )
#define EINVAL_PKT_NOT_FROMDS	( EINVAL | EUNIQ_04 )
#define EINVAL_PKT_LLC_HEADER	( EINVAL | EUNIQ_05 )
#define EINVAL_CRYPTO_REQUEST	( EINVAL | EUNIQ_06 )
#define EINVAL_ACTIVE_SCAN	( EINVAL | EUNIQ_07 )

/*
 * 802.11 error codes: The AP can give us a status code explaining why
 * authentication failed, or a reason code explaining why we were
 * deauthenticated/disassociated. These codes range from 0-63 (the
 * field is 16 bits wide, but only up to 45 or so are defined yet; we
 * allow up to 63 for extensibility). This is encoded into an error
 * code as such:
 *
 *                                      status & 0x1f goes here --vv--
 *   Status code 0-31:  ECONNREFUSED | EUNIQ_(status & 0x1f) (0e1a6038)
 *   Status code 32-63: EHOSTUNREACH | EUNIQ_(status & 0x1f) (171a6011)
 *   Reason code 0-31:  ECONNRESET | EUNIQ_(reason & 0x1f)   (0f1a6039)
 *   Reason code 32-63: ENETRESET | EUNIQ_(reason & 0x1f)    (271a6001)
 *
 * The POSIX error codes more or less convey the appropriate message
 * (status codes occur when we can't associate at all, reason codes
 * when we lose association unexpectedly) and let us extract the
 * complete 802.11 error code from the rc value.
 */

/** Make return status code from 802.11 status code */
#define E80211_STATUS( stat )  ( ((stat & 0x20)? EHOSTUNREACH : ECONNREFUSED) \
					| ((stat & 0x1f) << 8) )

/** Make return status code from 802.11 reason code */
#define E80211_REASON( reas )  ( ((reas & 0x20)? ENETRESET : ECONNRESET) \
					| ((reas & 0x1f) << 8) )


/** 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;

/** Information associated with a received management packet
 *
 * This is used to keep beacon signal strengths in a parallel queue to
 * the beacons themselves.
 */
struct net80211_rx_info {
	int signal;
	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;
};

/**
 * @defgroup net80211_netdev Network device interface functions
 * @{
 */
static int net80211_netdev_open ( struct net_device *netdev );
static void net80211_netdev_close ( struct net_device *netdev );
static int net80211_netdev_transmit ( struct net_device *netdev,
				      struct io_buffer *iobuf );
static void net80211_netdev_poll ( struct net_device *netdev );
static void net80211_netdev_irq ( struct net_device *netdev, int enable );
/** @} */

/**
 * @defgroup net80211_linklayer 802.11 link-layer protocol functions
 * @{
 */
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 );
static int net80211_ll_pull ( struct net_device *netdev,
			      struct io_buffer *iobuf, const void **ll_dest,
			      const void **ll_source, uint16_t * net_proto );
/** @} */

/**
 * @defgroup net80211_help 802.11 helper functions
 * @{
 */
static void net80211_add_channels ( struct net80211_device *dev, int start,
				    int len, int txpower );
static void net80211_filter_hw_channels ( struct net80211_device *dev );
static void net80211_set_rtscts_rate ( struct net80211_device *dev );
static int net80211_process_capab ( struct net80211_device *dev,
				    u16 capab );
static int net80211_process_ie ( struct net80211_device *dev,
				 union ieee80211_ie *ie, void *ie_end );
static union ieee80211_ie *
net80211_marshal_request_info ( struct net80211_device *dev,
				union ieee80211_ie *ie );
/** @} */

/**
 * @defgroup net80211_assoc_ll 802.11 association handling functions
 * @{
 */
static void net80211_step_associate ( struct process *proc );
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,
				    int deauth );
static void net80211_handle_mgmt ( struct net80211_device *dev,
				   struct io_buffer *iob, int signal );
/** @} */

/**
 * @defgroup net80211_frag 802.11 fragment handling functions
 * @{
 */
static void net80211_free_frags ( struct net80211_device *dev, int fcid );
static struct io_buffer *net80211_accum_frags ( struct net80211_device *dev,
						int fcid, int nfrags, int size );
static void net80211_rx_frag ( struct net80211_device *dev,
			       struct io_buffer *iob, int signal );
/** @} */

/**
 * @defgroup net80211_settings 802.11 settings handlers
 * @{
 */
static int net80211_check_settings_update ( void );

/** 802.11 settings applicator
 *
 * When the SSID is changed, this will cause any open devices to
 * re-associate; when the encryption key is changed, we similarly
 * update their state.
 */
struct settings_applicator net80211_applicator __settings_applicator = {
	.apply = net80211_check_settings_update,
};

/** The network name to associate with
 *
 * If this is blank, we scan for all networks and use the one with the
 * greatest signal strength.
 */
struct setting net80211_ssid_setting __setting = {
	.name = "ssid",
	.description = "802.11 SSID (network name)",
	.type = &setting_type_string,
};

/** Whether to use active scanning
 *
 * In order to associate with a hidden SSID, it's necessary to use an
 * active scan (send probe packets). If this setting is nonzero, an
 * active scan on the 2.4GHz band will be used to associate.
 */
struct setting net80211_active_setting __setting = {
	.name = "active-scan",
	.description = "Use an active scan during 802.11 association",
	.type = &setting_type_int8,
};

/** The cryptographic key to use
 *
 * For hex WEP keys, as is common, this must be entered using the
 * normal gPXE method for entering hex settings; an ASCII string of
 * hex characters will not behave as expected.
 */
struct setting net80211_key_setting __setting = {
	.name = "key",
	.description = "Encryption key for protected 802.11 networks",
	.type = &setting_type_string,
};

/** @} */


/* ---------- net_device wrapper ---------- */

/**
 * Open 802.11 device and start association
 *
 * @v netdev	Wrapping network device
 * @ret rc	Return status code
 *
 * This sets up a default conservative set of channels for probing,
 * 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.
 */
static int net80211_netdev_open ( struct net_device *netdev )
{
	struct net80211_device *dev = netdev->priv;
	int rc = 0;

	if ( dev->op == &net80211_null_ops )
		return -EFAULT;

	if ( dev->op->open )
		rc = dev->op->open ( dev );

	if ( rc < 0 )
		return rc;

	if ( ! ( dev->state & NET80211_NO_ASSOC ) )
		net80211_autoassociate ( dev );

	return 0;
}

/**
 * Close 802.11 device
 *
 * @v netdev	Wrapping network device.
 *
 * If the association task is running, this will stop it.
 */
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, 0 );

	if ( dev->handshaker && dev->handshaker->stop &&
	     dev->handshaker->started )
		dev->handshaker->stop ( dev );

	free ( dev->crypto );
	free ( dev->handshaker );
	dev->crypto = NULL;
	dev->handshaker = NULL;

	netdev_link_down ( netdev );
	dev->state = 0;

	if ( dev->op->close )
		dev->op->close ( dev );
}

/**
 * Transmit packet on 802.11 device
 *
 * @v netdev	Wrapping network device
 * @v iobuf	I/O buffer
 * @ret rc	Return status code
 *
 * If encryption is enabled for the currently associated network, the
 * packet will be encrypted prior to transmission.
 */
static int net80211_netdev_transmit ( struct net_device *netdev,
				      struct io_buffer *iobuf )
{
	struct net80211_device *dev = netdev->priv;
	struct ieee80211_frame *hdr = iobuf->data;
	int rc = -ENOSYS;

	if ( dev->crypto && ! ( hdr->fc & IEEE80211_FC_PROTECTED ) &&
	     ( ( hdr->fc & IEEE80211_FC_TYPE ) == IEEE80211_TYPE_DATA ) ) {
		struct io_buffer *niob = dev->crypto->encrypt ( dev->crypto,
								iobuf );
		if ( ! niob )
			return -ENOMEM;	/* only reason encryption could fail */

		/* Free the non-encrypted iob */
		netdev_tx_complete ( netdev, iobuf );

		/* Transmit the encrypted iob; the Protected flag is
		   set, so we won't recurse into here again */
		netdev_tx ( netdev, niob );

		/* Don't transmit the freed packet */
		return 0;
	}

	if ( dev->op->transmit )
		rc = dev->op->transmit ( dev, iobuf );

	return rc;
}

/**
 * Poll 802.11 device for received packets and completed transmissions
 *
 * @v netdev	Wrapping network device
 */
static void net80211_netdev_poll ( struct net_device *netdev )
{
	struct net80211_device *dev = netdev->priv;

	if ( dev->op->poll )
		dev->op->poll ( dev );
}

/**
 * Enable or disable interrupts for 802.11 device
 *
 * @v netdev	Wrapping network device
 * @v enable	Whether to enable interrupts
 */
static 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 );
}

/** Network device operations for a wrapped 802.11 device */
static 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 ---------- */

/** 802.11 broadcast MAC address */
static u8 net80211_ll_broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

/**
 * Determine whether a transmission rate uses ERP/OFDM
 *
 * @v rate	Rate in 100 kbps units
 * @ret is_erp	TRUE if the rate is an ERP/OFDM rate
 *
 * 802.11b supports rates of 1.0, 2.0, 5.5, and 11.0 Mbps; any other
 * rate than these on the 2.4GHz spectrum is an ERP (802.11g) rate.
 */
static inline int net80211_rate_is_erp ( u16 rate )
{
	if ( rate == 10 || rate == 20 || rate == 55 || rate == 110 )
		return 0;
	return 1;
}


/**
 * Calculate one frame's contribution to 802.11 duration field
 *
 * @v dev	802.11 device
 * @v bytes	Amount of data to calculate duration for
 * @ret dur	Duration field in microseconds
 *
 * To avoid multiple stations attempting to transmit at once, 802.11
 * provides that every packet shall include a duration field
 * specifying a length of time for which the wireless medium will be
 * reserved after it is transmitted. The duration is measured in
 * microseconds and is calculated with respect to the current
 * physical-layer parameters of the 802.11 device.
 *
 * For an unfragmented data or management frame, or the last fragment
 * of a fragmented frame, the duration captures only the 10 data bytes
 * of one ACK; call once with bytes = 10.
 *
 * For a fragment of a data or management rame that will be followed
 * by more fragments, the duration captures an ACK, the following
 * fragment, and its ACK; add the results of three calls, two with
 * bytes = 10 and one with bytes set to the next fragment's size.
 *
 * For an RTS control frame, the duration captures the responding CTS,
 * the frame being sent, and its ACK; add the results of three calls,
 * two with bytes = 10 and one with bytes set to the next frame's size
 * (assuming unfragmented).
 *
 * For a CTS-to-self control frame, the duration captures the frame
 * being protected and its ACK; add the results of two calls, one with
 * bytes = 10 and one with bytes set to the next frame's size.
 *
 * No other frame types are currently supported by gPXE.
 */
u16 net80211_duration ( struct net80211_device *dev, int bytes, u16 rate )
{
	struct net80211_channel *chan = &dev->channels[dev->channel];
	u32 kbps = rate * 100;

	if ( chan->band == NET80211_BAND_5GHZ || net80211_rate_is_erp ( rate ) ) {
		/* 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 */
		int bits = bytes << 3;
		int data_time = ( bits * 1000 + kbps - 1 ) / kbps;

		if ( dev->phy_flags & NET80211_PHY_USE_SHORT_PREAMBLE )
			phy_time >>= 1;

		return 10 + phy_time + data_time; /* 10us SIFS */
	}
}

/**
 * Add 802.11 link-layer header
 *
 * @v netdev		Wrapping network device
 * @v iobuf		I/O buffer
 * @v ll_dest		Link-layer destination address
 * @v ll_source		Link-layer source address
 * @v net_proto		Network-layer protocol, in network byte order
 * @ret rc		Return status code
 *
 * This adds both the 802.11 frame header and the 802.2 LLC/SNAP
 * header used on data packets.
 *
 * We also check here for state of the link that would make it invalid
 * to send a data packet; every data packet must pass through here,
 * and no non-data packet (e.g. management frame) should.
 */
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 =
		( 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->assoc_rc )
			return dev->assoc_rc;
		return -ENETUNREACH;
	}

	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, dev->rates[dev->rate] );

	memcpy ( hdr->addr1, dev->bssid, ETH_ALEN );
	memcpy ( hdr->addr2, ll_source, ETH_ALEN );
	memcpy ( hdr->addr3, ll_dest, ETH_ALEN );

	hdr->seq = IEEE80211_MAKESEQ ( ++dev->last_tx_seqnr, 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;
}

/**
 * Remove 802.11 link-layer header
 *
 * @v netdev		Wrapping network device
 * @v iobuf		I/O buffer
 * @ret ll_dest		Link-layer destination address
 * @ret ll_source	Link-layer source
 * @ret net_proto	Network-layer protocol, in network byte order
 * @ret rc		Return status code
 *
 * This expects and removes both the 802.11 frame header and the 802.2
 * LLC/SNAP header that are used on data packets.
 */
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 =
		( void * ) hdr + IEEE80211_TYP_FRAME_HEADER_LEN;

	/* Bunch of sanity checks */
	if ( iob_len ( iobuf ) < IEEE80211_TYP_FRAME_HEADER_LEN +
	     IEEE80211_LLC_HEADER_LEN ) {
		DBGC ( netdev->priv, "802.11 %p packet too short (%zd bytes)\n",
		       netdev->priv, iob_len ( iobuf ) );
		return -EINVAL_PKT_TOO_SHORT;
	}

	if ( ( hdr->fc & IEEE80211_FC_VERSION ) != IEEE80211_THIS_VERSION ) {
		DBGC ( netdev->priv, "802.11 %p packet invalid version %04x\n",
		       netdev->priv, hdr->fc & IEEE80211_FC_VERSION );
		return -EINVAL_PKT_VERSION;
	}

	if ( ( hdr->fc & IEEE80211_FC_TYPE ) != IEEE80211_TYPE_DATA ||
	     ( hdr->fc & IEEE80211_FC_SUBTYPE ) != IEEE80211_STYPE_DATA ) {
		DBGC ( netdev->priv, "802.11 %p packet not data/data (fc=%04x)\n",
		       netdev->priv, hdr->fc );
		return -EINVAL_PKT_NOT_DATA;
	}

	if ( ( hdr->fc & ( IEEE80211_FC_TODS | IEEE80211_FC_FROMDS ) ) !=
	     IEEE80211_FC_FROMDS ) {
		DBGC ( netdev->priv, "802.11 %p packet not from DS (fc=%04x)\n",
		       netdev->priv, hdr->fc );
		return -EINVAL_PKT_NOT_FROMDS;
	}

	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] ) {
		DBGC ( netdev->priv, "802.11 %p LLC header is not plain EtherType "
		       "encapsulator: %02x->%02x [%02x] %02x:%02x:%02x %04x\n",
		       netdev->priv, lhdr->dsap, lhdr->ssap, lhdr->ctrl,
		       lhdr->oui[0], lhdr->oui[1], lhdr->oui[2], lhdr->ethertype );
		return -EINVAL_PKT_LLC_HEADER;
	}

	iob_pull ( iobuf, sizeof ( *hdr ) + sizeof ( *lhdr ) );

	*ll_dest = hdr->addr1;
	*ll_source = hdr->addr3;
	*net_proto = lhdr->ethertype;
	return 0;
}

/** 802.11 link-layer protocol */
static struct ll_protocol net80211_ll_protocol __ll_protocol = {
	.name = "802.11",
	.push = net80211_ll_push,
	.pull = net80211_ll_pull,
	.init_addr = eth_init_addr,
	.ntoa = eth_ntoa,
	.mc_hash = eth_mc_hash,
	.eth_addr = eth_eth_addr,
	.ll_proto = htons ( ARPHRD_ETHER ),	/* "encapsulated Ethernet" */
	.hw_addr_len = ETH_ALEN,
	.ll_addr_len = ETH_ALEN,
	.ll_header_len = IEEE80211_TYP_FRAME_HEADER_LEN +
				IEEE80211_LLC_HEADER_LEN,
};


/* ---------- 802.11 network management API ---------- */

/**
 * Get 802.11 device from wrapping network device
 *
 * @v netdev	Wrapping network device
 * @ret dev	802.11 device wrapped by network device, or NULL
 *
 * Returns NULL if the network device does not wrap an 802.11 device.
 */
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;
}

/**
 * Set state of 802.11 device keeping management frames
 *
 * @v dev	802.11 device
 * @v enable	Whether to keep management frames
 * @ret oldenab	Whether management frames were enabled before this call
 *
 * If enable is TRUE, beacon, probe, and action frames will be kept
 * and may be retrieved by calling net80211_mgmt_dequeue().
 */
int net80211_keep_mgmt ( struct net80211_device *dev, int enable )
{
	int oldenab = dev->keep_mgmt;

	dev->keep_mgmt = enable;
	return oldenab;
}

/**
 * Get 802.11 management frame
 *
 * @v dev	802.11 device
 * @ret signal	Signal strength of returned management frame
 * @ret iob	I/O buffer, or NULL if no management frame is queued
 *
 * Frames will only be returned by this function if
 * net80211_keep_mgmt() has been previously called with enable set to
 * TRUE.
 *
 * The calling function takes ownership of the returned I/O buffer.
 */
struct io_buffer * net80211_mgmt_dequeue ( struct net80211_device *dev,
					   int *signal )
{
	struct io_buffer *iobuf;
	struct net80211_rx_info *rxi;

	list_for_each_entry ( rxi, &dev->mgmt_info_queue, list ) {
		list_del ( &rxi->list );
		if ( signal )
			*signal = rxi->signal;
		free ( rxi );

		list_for_each_entry ( iobuf, &dev->mgmt_queue, list ) {
			list_del ( &iobuf->list );
			return iobuf;
		}
		assert ( 0 );
	}

	return NULL;
}

/**
 * Transmit 802.11 management frame
 *
 * @v dev	802.11 device
 * @v fc	Frame Control flags for management frame
 * @v dest	Destination access point
 * @v iob	I/O buffer
 * @ret rc	Return status code
 *
 * The @a fc argument must contain at least an IEEE 802.11 management
 * subtype number (e.g. IEEE80211_STYPE_PROBE_REQ). If it contains
 * IEEE80211_FC_PROTECTED, the frame will be encrypted prior to
 * transmission.
 *
 * It is required that @a iob have at least 24 bytes of headroom
 * reserved before its data start.
 */
int net80211_tx_mgmt ( struct net80211_device *dev, u16 fc, u8 dest[6],
		       struct io_buffer *iob )
{
	struct ieee80211_frame *hdr = iob_push ( iob,
						 IEEE80211_TYP_FRAME_HEADER_LEN );

	hdr->fc = IEEE80211_THIS_VERSION | IEEE80211_TYPE_MGMT |
	    ( fc & ~IEEE80211_FC_PROTECTED );
	hdr->duration = net80211_duration ( dev, 10, dev->rates[dev->rate] );
	hdr->seq = IEEE80211_MAKESEQ ( ++dev->last_tx_seqnr, 0 );

	memcpy ( hdr->addr1, dest, ETH_ALEN );	/* DA = RA */
	memcpy ( hdr->addr2, dev->netdev->ll_addr, ETH_ALEN );	/* SA = TA */
	memcpy ( hdr->addr3, dest, ETH_ALEN );	/* BSSID */

	if ( fc & IEEE80211_FC_PROTECTED ) {
		if ( ! dev->crypto )
			return -EINVAL_CRYPTO_REQUEST;

		struct io_buffer *eiob = dev->crypto->encrypt ( dev->crypto,
								iob );
		free_iob ( iob );
		iob = eiob;
	}

	return netdev_tx ( dev->netdev, iob );
}


/* ---------- Driver API ---------- */

/**
 * Allocate 802.11 device
 *
 * @v priv_size		Size of driver-private allocation area
 * @ret dev		Newly allocated 802.11 device
 *
 * This function allocates a net_device with space in its private area
 * for both the net80211_device it will wrap and the driver-private
 * data space requested. It initializes the link-layer-specific parts
 * of the net_device, and links the net80211_device to the net_device
 * appropriately.
 */
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->ll_broadcast = net80211_ll_broadcast;
	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;

	process_init_stopped ( &dev->proc_assoc, net80211_step_associate,
			       &netdev->refcnt );
	INIT_LIST_HEAD ( &dev->mgmt_queue );
	INIT_LIST_HEAD ( &dev->mgmt_info_queue );

	return dev;
}

/**
 * Register 802.11 device with network stack
 *
 * @v dev	802.11 device
 * @v ops	802.11 device operations
 * @v hw	802.11 hardware information
 *
 * This also registers the wrapping net_device with the higher network
 * layers.
 */
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->hw_addr, hw->hwaddr, ETH_ALEN );

	/* Set some sensible channel defaults for driver's open() function */
	memcpy ( dev->channels, dev->hw->channels,
		 NET80211_MAX_CHANNELS * sizeof ( dev->channels[0] ) );
	dev->channel = 0;

	list_add_tail ( &dev->list, &net80211_devices );
	return register_netdev ( dev->netdev );
}

/**
 * Unregister 802.11 device from network stack
 *
 * @v dev	802.11 device
 *
 * After this call, the device operations are cleared so that they
 * will not be called.
 */
void net80211_unregister ( struct net80211_device *dev )
{
	unregister_netdev ( dev->netdev );
	list_del ( &dev->list );
	dev->op = &net80211_null_ops;
}

/**
 * Free 802.11 device
 *
 * @v dev	802.11 device
 *
 * The device should be unregistered before this function is called.
 */
void net80211_free ( struct net80211_device *dev )
{
	free ( dev->hw );
	rc80211_free ( dev->rctl );
	netdev_nullify ( dev->netdev );
	netdev_put ( dev->netdev );
}


/* ---------- 802.11 network management workhorse code ---------- */

/**
 * Set state of 802.11 device
 *
 * @v dev	802.11 device
 * @v clear	Bitmask of flags to clear
 * @v set	Bitmask of flags to set
 * @v status	Status or reason code for most recent operation
 *
 * If @a status represents a reason code, it should be OR'ed with
 * NET80211_IS_REASON.
 *
 * Clearing authentication also clears association; clearing
 * association also clears security handshaking state. Clearing
 * association removes the link-up flag from the wrapping net_device,
 * but setting it does not automatically set the flag; that is left to
 * the judgment of higher-level code.
 */
static inline void net80211_set_state ( struct net80211_device *dev,
					short clear, short set,
					u16 status )
{
	/* The conditions in this function are deliberately formulated
	   to be decidable at compile-time in most cases. Since clear
	   and set are generally passed as constants, the body of this
	   function can be reduced down to a few statements by the
	   compiler. */

	const int statmsk = NET80211_STATUS_MASK | NET80211_IS_REASON;

	if ( clear & NET80211_PROBED )
		clear |= NET80211_AUTHENTICATED;

	if ( clear & NET80211_AUTHENTICATED )
		clear |= NET80211_ASSOCIATED;

	if ( clear & NET80211_ASSOCIATED )
		clear |= NET80211_CRYPTO_SYNCED;

	dev->state = ( dev->state & ~clear ) | set;
	dev->state = ( dev->state & ~statmsk ) | ( status & statmsk );

	if ( clear & NET80211_ASSOCIATED )
		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 );
	}
}

/**
 * Add channels to 802.11 device
 *
 * @v dev	802.11 device
 * @v start	First channel number to add
 * @v len	Number of channels to add
 * @v txpower	TX power (dBm) to allow on added channels
 *
 * To replace the current list of channels instead of adding to it,
 * set the nr_channels field of the 802.11 device to 0 before calling
 * this function.
 */
static void net80211_add_channels ( struct net80211_device *dev, int start,
				    int len, int txpower )
{
	int i, chan = start;

	for ( i = dev->nr_channels; len-- && i < NET80211_MAX_CHANNELS; i++ ) {
		dev->channels[i].channel_nr = chan;
		dev->channels[i].maxpower = txpower;
		dev->channels[i].hw_value = 0;

		if ( chan >= 1 && chan <= 14 ) {
			dev->channels[i].band = NET80211_BAND_2GHZ;
			if ( chan == 14 )
				dev->channels[i].center_freq = 2484;
			else
				dev->channels[i].center_freq = 2407 + 5 * chan;
			chan++;
		} else {
			dev->channels[i].band = NET80211_BAND_5GHZ;
			dev->channels[i].center_freq = 5000 + 5 * chan;
			chan += 4;
		}
	}

	dev->nr_channels = i;
}

/**
 * Filter 802.11 device channels for hardware capabilities
 *
 * @v dev	802.11 device
 *
 * Hardware may support fewer channels than regulatory restrictions
 * allow; this function filters out channels in dev->channels that are
 * not supported by the hardware list in dev->hwinfo. It also copies
 * over the net80211_channel::hw_value and limits maximum TX power
 * appropriately.
 *
 * Channels are matched based on center frequency, ignoring band and
 * channel number.
 *
 * If the driver specifies no supported channels, the effect will be
 * as though all were supported.
 */
static void net80211_filter_hw_channels ( struct net80211_device *dev )
{
	int delta = 0, i = 0;
	int old_freq = dev->channels[dev->channel].center_freq;
	struct net80211_channel *chan, *hwchan;

	if ( ! dev->hw->nr_channels )
		return;

	dev->channel = 0;
	for ( chan = dev->channels; chan < dev->channels + dev->nr_channels;
	      chan++, i++ ) {
		int ok = 0;
		for ( hwchan = dev->hw->channels;
		      hwchan < dev->hw->channels + dev->hw->nr_channels;
		      hwchan++ ) {
			if ( hwchan->center_freq == chan->center_freq ) {
				ok = 1;
				break;
			}
		}

		if ( ! ok )
			delta++;
		else {
			chan->hw_value = hwchan->hw_value;
			if ( hwchan->maxpower != 0 &&
			     chan->maxpower > hwchan->maxpower )
				chan->maxpower = hwchan->maxpower;
			if ( old_freq == chan->center_freq )
				dev->channel = i - delta;
			if ( delta )
				chan[-delta] = *chan;
		}
	}

	dev->nr_channels -= delta;

	if ( dev->channels[dev->channel].center_freq != old_freq )
		dev->op->config ( dev, NET80211_CFG_CHANNEL );
}

/**
 * Update 802.11 device state to reflect received capabilities field
 *
 * @v dev	802.11 device
 * @v capab	Capabilities field in beacon, probe, or association frame
 * @ret rc	Return status code
 */
static int net80211_process_capab ( struct net80211_device *dev,
				    u16 capab )
{
	u16 old_phy = dev->phy_flags;

	if ( ( capab & ( IEEE80211_CAPAB_MANAGED | IEEE80211_CAPAB_ADHOC ) ) !=
	     IEEE80211_CAPAB_MANAGED ) {
		DBGC ( dev, "802.11 %p cannot handle IBSS network\n", dev );
		return -ENOSYS;
	}

	dev->phy_flags &= ~( NET80211_PHY_USE_SHORT_PREAMBLE |
			     NET80211_PHY_USE_SHORT_SLOT );

	if ( capab & IEEE80211_CAPAB_SHORT_PMBL )
		dev->phy_flags |= NET80211_PHY_USE_SHORT_PREAMBLE;

	if ( capab & IEEE80211_CAPAB_SHORT_SLOT )
		dev->phy_flags |= NET80211_PHY_USE_SHORT_SLOT;

	if ( old_phy != dev->phy_flags )
		dev->op->config ( dev, NET80211_CFG_PHY_PARAMS );

	return 0;
}

/**
 * Update 802.11 device state to reflect received information elements
 *
 * @v dev	802.11 device
 * @v ie	Pointer to first information element
 * @v ie_end	Pointer to tail of packet I/O buffer
 * @ret rc	Return status code
 */
static int net80211_process_ie ( struct net80211_device *dev,
				 union ieee80211_ie *ie, void *ie_end )
{
	u16 old_rate = dev->rates[dev->rate];
	u16 old_phy = dev->phy_flags;
	int have_rates = 0, i;
	int ds_channel = 0;
	int changed = 0;
	int band = dev->channels[dev->channel].band;

	if ( ! ieee80211_ie_bound ( ie, ie_end ) )
		return 0;

	for ( ; ie; ie = ieee80211_next_ie ( ie, ie_end ) ) {
		switch ( ie->id ) {
		case IEEE80211_IE_SSID:
			if ( ie->len <= 32 ) {
				memcpy ( dev->essid, ie->ssid, ie->len );
				dev->essid[ie->len] = 0;
			}
			break;

		case IEEE80211_IE_RATES:
		case IEEE80211_IE_EXT_RATES:
			if ( ! have_rates ) {
				dev->nr_rates = 0;
				dev->basic_rates = 0;
				have_rates = 1;
			}
			for ( i = 0; i < ie->len &&
			      dev->nr_rates < NET80211_MAX_RATES; i++ ) {
				u8 rid = ie->rates[i];
				u16 rate = ( rid & 0x7f ) * 5;

				if ( rid & 0x80 )
					dev->basic_rates |=
						( 1 << dev->nr_rates );

				dev->rates[dev->nr_rates++] = rate;
			}

			break;

		case IEEE80211_IE_DS_PARAM:
			if ( dev->channel < dev->nr_channels && ds_channel ==
			     dev->channels[dev->channel].channel_nr )
				break;
			ds_channel = ie->ds_param.current_channel;
			net80211_change_channel ( dev, ds_channel );
			break;

		case IEEE80211_IE_COUNTRY:
			dev->nr_channels = 0;

			DBGC ( dev, "802.11 %p setting country regulations "
			       "for %c%c\n", dev, ie->country.name[0],
			       ie->country.name[1] );
			for ( i = 0; i < ( ie->len - 3 ) / 3; i++ ) {
				union ieee80211_ie_country_triplet *t =
					&ie->country.triplet[i];
				if ( t->first > 200 ) {
					DBGC ( dev, "802.11 %p ignoring regulatory "
					       "extension information\n", dev );
				} else {
					net80211_add_channels ( dev,
							t->band.first_channel,
							t->band.nr_channels,
							t->band.max_txpower );
				}
			}
			net80211_filter_hw_channels ( dev );
			break;

		case IEEE80211_IE_ERP_INFO:
			dev->phy_flags &= ~( NET80211_PHY_USE_PROTECTION |
					     NET80211_PHY_USE_SHORT_PREAMBLE );
			if ( ie->erp_info & IEEE80211_ERP_USE_PROTECTION )
				dev->phy_flags |= NET80211_PHY_USE_PROTECTION;
			if ( ! ( ie->erp_info & IEEE80211_ERP_BARKER_LONG ) )
				dev->phy_flags |= NET80211_PHY_USE_SHORT_PREAMBLE;
			break;
		}
	}

	if ( have_rates ) {
		/* Allow only those rates that are also supported by
		   the hardware. */
		int delta = 0, j;

		dev->rate = 0;
		for ( i = 0; i < dev->nr_rates; i++ ) {
			int ok = 0;
			for ( j = 0; j < dev->hw->nr_rates[band]; j++ ) {
				if ( dev->hw->rates[band][j] == dev->rates[i] ){
					ok = 1;
					break;
				}
			}

			if ( ! ok )
				delta++;
			else {
				dev->rates[i - delta] = dev->rates[i];
				if ( old_rate == dev->rates[i] )
					dev->rate = i - delta;
			}
		}

		dev->nr_rates -= delta;

		/* Sort available rates - sorted subclumps tend to already
		   exist, so insertion sort works well. */
		for ( i = 1; i < dev->nr_rates; i++ ) {
			u16 rate = dev->rates[i];
			u32 tmp, br, mask;

			for ( j = i - 1; j >= 0 && dev->rates[j] >= rate; j-- )
				dev->rates[j + 1] = dev->rates[j];
			dev->rates[j + 1] = rate;

			/* Adjust basic_rates to match by rotating the
			   bits from bit j+1 to bit i left one position. */
			mask = ( ( 1 << i ) - 1 ) & ~( ( 1 << ( j + 1 ) ) - 1 );
			br = dev->basic_rates;
			tmp = br & ( 1 << i );
			br = ( br & ~( mask | tmp ) ) | ( ( br & mask ) << 1 );
			br |= ( tmp >> ( i - j - 1 ) );
			dev->basic_rates = br;
		}

		net80211_set_rtscts_rate ( dev );

		if ( dev->rates[dev->rate] != old_rate )
			changed |= NET80211_CFG_RATE;
	}

	if ( dev->hw->flags & NET80211_HW_NO_SHORT_PREAMBLE )
		dev->phy_flags &= ~NET80211_PHY_USE_SHORT_PREAMBLE;
	if ( dev->hw->flags & NET80211_HW_NO_SHORT_SLOT )
		dev->phy_flags &= ~NET80211_PHY_USE_SHORT_SLOT;

	if ( old_phy != dev->phy_flags )
		changed |= NET80211_CFG_PHY_PARAMS;

	if ( changed )
		dev->op->config ( dev, changed );

	return 0;
}

/**
 * Create information elements for outgoing probe or association packet
 *
 * @v dev		802.11 device
 * @v ie		Pointer to start of information element area
 * @ret next_ie		Pointer to first byte after added information elements
 */
static union ieee80211_ie *
net80211_marshal_request_info ( struct net80211_device *dev,
				union ieee80211_ie *ie )
{
	int i;

	ie->id = IEEE80211_IE_SSID;
	ie->len = strlen ( dev->essid );
	memcpy ( ie->ssid, dev->essid, ie->len );

	ie = ieee80211_next_ie ( ie, NULL );

	ie->id = IEEE80211_IE_RATES;
	ie->len = dev->nr_rates;
	if ( ie->len > 8 )
		ie->len = 8;

	for ( i = 0; i < ie->len; i++ ) {
		ie->rates[i] = dev->rates[i] / 5;
		if ( dev->basic_rates & ( 1 << i ) )
			ie->rates[i] |= 0x80;
	}

	ie = ieee80211_next_ie ( ie, NULL );

	if ( dev->rsn_ie && dev->rsn_ie->id == IEEE80211_IE_RSN ) {
		memcpy ( ie, dev->rsn_ie, dev->rsn_ie->len + 2 );
		ie = ieee80211_next_ie ( ie, NULL );
	}

	if ( dev->nr_rates > 8 ) {
		/* 802.11 requires we use an Extended Basic Rates IE
		   for the rates beyond the eighth. */

		ie->id = IEEE80211_IE_EXT_RATES;
		ie->len = dev->nr_rates - 8;

		for ( ; i < dev->nr_rates; i++ ) {
			ie->rates[i - 8] = dev->rates[i] / 5;
			if ( dev->basic_rates & ( 1 << i ) )
				ie->rates[i - 8] |= 0x80;
		}

		ie = ieee80211_next_ie ( ie, NULL );
	}

	if ( dev->rsn_ie && dev->rsn_ie->id == IEEE80211_IE_VENDOR ) {
		memcpy ( ie, dev->rsn_ie, dev->rsn_ie->len + 2 );
		ie = ieee80211_next_ie ( ie, NULL );
	}

	return ie;
}

/** Seconds to wait after finding a network, to possibly find better APs for it
 *
 * This is used when a specific SSID to scan for is specified.
 */
#define NET80211_PROBE_GATHER    1

/** Seconds to wait after finding a network, to possibly find other networks
 *
 * 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

/**
 * 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 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_probe_ctx * net80211_probe_start ( struct net80211_device *dev,
						   const char *essid,
						   int active )
{
	struct net80211_probe_ctx *ctx = zalloc ( sizeof ( *ctx ) );

	if ( ! ctx )
		return NULL;

	assert ( dev->netdev->state & NETDEV_OPEN );

	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 ( active ) {
		struct ieee80211_probe_req *probe_req;
		union ieee80211_ie *ie;

		ctx->probe = alloc_iob ( 128 );
		iob_reserve ( ctx->probe, IEEE80211_TYP_FRAME_HEADER_LEN );
		probe_req = ctx->probe->data;

		ie = net80211_marshal_request_info ( dev,
						     probe_req->info_element );

		iob_put ( ctx->probe, ( void * ) ie - ctx->probe->data );
	}

	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 );

	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;
			}
		}
	}

	/* 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;

		if ( type != IEEE80211_STYPE_BEACON &&
		     type != IEEE80211_STYPE_PROBE_RESP ) {
			DBGC2 ( dev, "802.11 %p probe: non-beacon\n", dev );
			goto drop;
		}

		if ( ( void * ) beacon->info_element >= iob->tail ) {
			DBGC ( dev, "802.11 %p probe: beacon with no IEs\n",
			       dev );
			goto drop;
		}

		ie = beacon->info_element;

		if ( ! ieee80211_ie_bound ( ie, iob->tail ) )
			ie = NULL;

		while ( ie && ie->id != IEEE80211_IE_SSID )
			ie = ieee80211_next_ie ( ie, iob->tail );

		if ( ! ie ) {
			DBGC ( dev, "802.11 %p probe: beacon with no SSID\n",
			       dev );
			goto drop;
		}

		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, ssid );
			goto drop;
		}

		/* 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;
			}

			goto fill;
		}

		/* 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->channel = dev->channels[dev->channel].channel_nr;

		/* 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 ) );

		if ( ( rc = sec80211_detect ( wlan->beacon, &wlan->handshaking,
					      &wlan->crypto ) ) == -ENOTSUP ) {
			struct ieee80211_beacon *beacon =
				( struct ieee80211_beacon * ) hdr->data;

			if ( beacon->capability & IEEE80211_CAPAB_PRIVACY ) {
				DBG ( "802.11 %p probe: secured network %s but "
				      "encryption support not compiled in\n",
				      dev, wlan->essid );
				wlan->handshaking = NET80211_SECPROT_UNKNOWN;
				wlan->crypto = NET80211_CRYPT_UNKNOWN;
			} else {
				wlan->handshaking = NET80211_SECPROT_NONE;
				wlan->crypto = NET80211_CRYPT_NONE;
			}
		} else if ( rc != 0 ) {
			DBGC ( dev, "802.11 %p probe warning: network "
			       "%s with unidentifiable security "
			       "settings: %s\n", dev, wlan->essid,
			       strerror ( rc ) );
		}

		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 );
	}

	return 0;
}


/**
 * 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;

	if ( ! ctx )
		return NULL;

	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 );

	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;

	if ( ! ctx )
		return NULL;

	net80211_keep_mgmt ( ctx->dev, ctx->old_keep_mgmt );

	if ( ctx->probe )
		free_iob ( ctx->probe );

	free ( ctx );

	return beacons;
}


/**
 * Free WLAN structure
 *
 * @v wlan	WLAN structure to free
 */
void net80211_free_wlan ( struct net80211_wlan *wlan )
{
	if ( wlan ) {
		free_iob ( wlan->beacon );
		free ( 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 );
}


/** Number of ticks to wait for replies to association management frames */
#define ASSOC_TIMEOUT	TICKS_PER_SEC

/** Number of times to try sending a particular association management frame */
#define ASSOC_RETRIES	2

/**
 * Step 802.11 association process
 *
 * @v proc	Association process
 */
static void net80211_step_associate ( struct process *proc )
{
	struct net80211_device *dev =
	    container_of ( proc, struct net80211_device, proc_assoc );
	int rc = 0;
	int status = dev->state & NET80211_STATUS_MASK;

	/*
	 * We use a sort of state machine implemented using bits in
	 * the dev->state variable. At each call, we take the
	 * logically first step that has not yet succeeded; either it
	 * has not been tried yet, it's being retried, or it failed.
	 * If it failed, we return an error indication; otherwise we
	 * perform the step. If it succeeds, RX handling code will set
	 * the appropriate status bit for us.
	 *
	 * Probe works a bit differently, since we have to step it
	 * on every call instead of waiting for a packet to arrive
	 * that will set the completion bit for us.
	 */

	/* If we're waiting for a reply, check for timeout condition */
	if ( dev->state & NET80211_WAITING ) {
		/* Sanity check */
		if ( ! dev->associating )
			return;

		if ( currticks() - dev->ctx.assoc->last_packet > ASSOC_TIMEOUT ) {
			/* Timed out - fail if too many retries, or retry */
			dev->ctx.assoc->times_tried++;
			if ( ++dev->ctx.assoc->times_tried > ASSOC_RETRIES ) {
				rc = -ETIMEDOUT;
				goto fail;
			}
		} else {
			/* Didn't time out - let it keep going */
			return;
		}
	} else {
		if ( dev->state & NET80211_PROBED )
			dev->ctx.assoc->times_tried = 0;
	}

	if ( ! ( dev->state & NET80211_PROBED ) ) {
		/* state: probe */

		if ( ! dev->ctx.probe ) {
			/* start probe */
			int active = fetch_intz_setting ( NULL,
						&net80211_active_setting );
			int band = dev->hw->bands;

			if ( active )
				band &= ~NET80211_BAND_BIT_5GHZ;

			rc = net80211_prepare_probe ( dev, band, active );
			if ( rc )
				goto fail;

			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 ) {
			if ( rc > 0 ) /* "successful" probe found nothing */
				rc = -ETIMEDOUT;
			goto fail;
		}

		/* If we probed using a broadcast SSID, record that
		   fact for the settings applicator before we clobber
		   it with the specific SSID we've chosen. */
		if ( ! dev->essid[0] )
			dev->state |= NET80211_AUTO_SSID;

		DBGC ( dev, "802.11 %p found network %s (%s)\n", dev,
		       dev->associating->essid,
		       eth_ntoa ( dev->associating->bssid ) );

		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;
	}

	/* Record time of sending the packet we're about to send, for timeout */
	dev->ctx.assoc->last_packet = currticks();

	if ( ! ( dev->state & NET80211_AUTHENTICATED ) ) {
		/* state: prepare and authenticate */

		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? */
				dev->ctx.assoc->method =
					IEEE80211_AUTH_SHARED_KEY;
			} else {
				goto fail;
			}
		}

		DBGC ( dev, "802.11 %p authenticating with method %d\n", dev,
		       dev->ctx.assoc->method );

		rc = net80211_prepare_assoc ( dev, dev->associating );
		if ( rc )
			goto fail;

		rc = net80211_send_auth ( dev, dev->associating,
					  dev->ctx.assoc->method );
		if ( rc )
			goto fail;

		return;
	}

	if ( ! ( dev->state & NET80211_ASSOCIATED ) ) {
		/* state: associate */

		if ( status != IEEE80211_STATUS_SUCCESS )
			goto fail;

		DBGC ( dev, "802.11 %p associating\n", dev );

		if ( dev->handshaker && dev->handshaker->start &&
		     ! dev->handshaker->started ) {
			rc = dev->handshaker->start ( dev );
			if ( rc < 0 )
				goto fail;
			dev->handshaker->started = 1;
		}

		rc = net80211_send_assoc ( dev, dev->associating );
		if ( rc )
			goto fail;

		return;
	}

	if ( ! ( dev->state & NET80211_CRYPTO_SYNCED ) ) {
		/* state: crypto sync */
		DBGC ( dev, "802.11 %p security handshaking\n", dev );

		if ( ! dev->handshaker || ! dev->handshaker->step ) {
			dev->state |= NET80211_CRYPTO_SYNCED;
			return;
		}

		rc = dev->handshaker->step ( dev );

		if ( rc < 0 ) {
			/* Only record the returned error if we're
			   still marked as associated, because an
			   asynchronous error will have already been
			   reported to net80211_deauthenticate() and
			   assoc_rc thereby set. */
			if ( dev->state & NET80211_ASSOCIATED )
				dev->assoc_rc = rc;
			rc = 0;
			goto fail;
		}

		if ( rc > 0 ) {
			dev->assoc_rc = 0;
			dev->state |= NET80211_CRYPTO_SYNCED;
		}
		return;
	}

	/* state: done! */
	netdev_link_up ( dev->netdev );
	dev->assoc_rc = 0;
	dev->state &= ~NET80211_WORKING;

	free ( dev->ctx.assoc );
	dev->ctx.assoc = NULL;

	net80211_free_wlan ( dev->associating );
	dev->associating = NULL;

	dev->rctl = rc80211_init ( dev );

	process_del ( proc );

	DBGC ( dev, "802.11 %p associated with %s (%s)\n", dev,
	       dev->essid, eth_ntoa ( dev->bssid ) );

	return;

 fail:
	dev->state &= ~( NET80211_WORKING | NET80211_WAITING );
	if ( rc )
		dev->assoc_rc = rc;

	netdev_link_err ( dev->netdev, dev->assoc_rc );

	/* We never reach here from the middle of a probe, so we don't
	   need to worry about freeing dev->ctx.probe. */

	if ( dev->state & NET80211_PROBED ) {
		free ( dev->ctx.assoc );
		dev->ctx.assoc = NULL;
	}

	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 ) );

	/* Try it again: */
	net80211_autoassociate ( dev );
}

/**
 * Check for 802.11 SSID or key updates
 *
 * This acts as a settings applicator; if the user changes netX/ssid,
 * and netX is currently open, the association task will be invoked
 * again. If the user changes the encryption key, the current security
 * handshaker will be asked to update its state to match; if that is
 * impossible without reassociation, we reassociate.
 */
static int net80211_check_settings_update ( void )
{
	struct net80211_device *dev;
	char ssid[IEEE80211_MAX_SSID_LEN + 1];
	int key_reassoc;

	list_for_each_entry ( dev, &net80211_devices, list ) {
		if ( ! ( dev->netdev->state & NETDEV_OPEN ) )
			continue;

		key_reassoc = 0;
		if ( dev->handshaker && dev->handshaker->change_key &&
		     dev->handshaker->change_key ( dev ) < 0 )
			key_reassoc = 1;

		fetch_string_setting ( netdev_settings ( dev->netdev ),
				       &net80211_ssid_setting, ssid,
				       IEEE80211_MAX_SSID_LEN + 1 );

		if ( key_reassoc ||
		     ( ! ( ! ssid[0] && ( dev->state & NET80211_AUTO_SSID ) ) &&
		       strcmp ( ssid, dev->essid ) != 0 ) ) {
			DBGC ( dev, "802.11 %p updating association: "
			       "%s -> %s\n", dev, dev->essid, ssid );
			net80211_autoassociate ( dev );
		}
	}

	return 0;
}

/**
 * Start 802.11 association process
 *
 * @v dev	802.11 device
 *
 * If the association process is running, it will be restarted.
 */
void net80211_autoassociate ( struct net80211_device *dev )
{
	if ( ! ( dev->state & NET80211_WORKING ) ) {
		DBGC2 ( dev, "802.11 %p spawning association process\n", dev );
		process_add ( &dev->proc_assoc );
	} else {
		DBGC2 ( dev, "802.11 %p restarting association\n", dev );
	}

	/* Clean up everything an earlier association process might
	   have been in the middle of using */
	if ( dev->associating )
		net80211_free_wlan ( dev->associating );

	if ( ! ( dev->state & NET80211_PROBED ) )
		net80211_free_wlan (
			net80211_probe_finish_best ( dev->ctx.probe ) );
	else
		free ( dev->ctx.assoc );

	/* Reset to a clean state */
	fetch_string_setting ( netdev_settings ( dev->netdev ),
			       &net80211_ssid_setting, dev->essid,
			       IEEE80211_MAX_SSID_LEN + 1 );
	dev->ctx.probe = NULL;
	dev->associating = NULL;
	dev->assoc_rc = 0;
	net80211_set_state ( dev, NET80211_PROBED, NET80211_WORKING, 0 );
}

/**
 * Pick TX rate for RTS/CTS packets based on data rate
 *
 * @v dev	802.11 device
 *
 * The RTS/CTS rate is the fastest TX rate marked as "basic" that is
 * not faster than the data rate.
 */
static void net80211_set_rtscts_rate ( struct net80211_device *dev )
{
	u16 datarate = dev->rates[dev->rate];
	u16 rtsrate = 0;
	int rts_idx = -1;
	int i;

	for ( i = 0; i < dev->nr_rates; i++ ) {
		u16 rate = dev->rates[i];

		if ( ! ( dev->basic_rates & ( 1 << i ) ) || rate > datarate )
			continue;

		if ( rate > rtsrate ) {
			rtsrate = rate;
			rts_idx = i;
		}
	}

	/* If this is in initialization, we might not have any basic
	   rates; just use the first data rate in that case. */
	if ( rts_idx < 0 )
		rts_idx = 0;

	dev->rtscts_rate = rts_idx;
}

/**
 * Set data transmission rate for 802.11 device
 *
 * @v dev	802.11 device
 * @v rate	Rate to set, as index into @c dev->rates array
 */
void net80211_set_rate_idx ( struct net80211_device *dev, int rate )
{
	assert ( dev->netdev->state & NETDEV_OPEN );

	if ( rate >= 0 && rate < dev->nr_rates && rate != dev->rate ) {
		DBGC2 ( dev, "802.11 %p changing rate from %d->%d Mbps\n",
			dev, dev->rates[dev->rate] / 10,
			dev->rates[rate] / 10 );

		dev->rate = rate;
		net80211_set_rtscts_rate ( dev );
		dev->op->config ( dev, NET80211_CFG_RATE );
	}
}

/**
 * Configure 802.11 device to transmit on a certain channel
 *
 * @v dev	802.11 device
 * @v channel	Channel number (1-11 for 2.4GHz) to transmit on
 */
int net80211_change_channel ( struct net80211_device *dev, int channel )
{
	int i, oldchan = dev->channel;

	assert ( dev->netdev->state & NETDEV_OPEN );

	for ( i = 0; i < dev->nr_channels; i++ ) {
		if ( dev->channels[i].channel_nr == channel ) {
			dev->channel = i;
			break;
		}
	}

	if ( i == dev->nr_channels )
		return -ENOENT;

	if ( i != oldchan )
		return dev->op->config ( dev, NET80211_CFG_CHANNEL );

	return 0;
}

/**
 * Prepare 802.11 device channel and rate set for scanning
 *
 * @v dev	802.11 device
 * @v band	RF band(s) on which to prepare for scanning
 * @v active	Whether the scanning will be active
 * @ret rc	Return status code
 */
int net80211_prepare_probe ( struct net80211_device *dev, int band,
			     int active )
{
	assert ( dev->netdev->state & NETDEV_OPEN );

	if ( active && ( band & NET80211_BAND_BIT_5GHZ ) ) {
		DBGC ( dev, "802.11 %p cannot perform active scanning on "
		       "5GHz band\n", dev );
		return -EINVAL_ACTIVE_SCAN;
	}

	if ( band == 0 ) {
		/* This can happen for a 5GHz-only card with 5GHz
		   scanning masked out by an active request. */
		DBGC ( dev, "802.11 %p asked to prepare for scanning nothing\n",
		       dev );
		return -EINVAL_ACTIVE_SCAN;
	}

	dev->nr_channels = 0;

	if ( active )
		net80211_add_channels ( dev, 1, 11, NET80211_REG_TXPOWER );
	else {
		if ( band & NET80211_BAND_BIT_2GHZ )
			net80211_add_channels ( dev, 1, 14,
						NET80211_REG_TXPOWER );
		if ( band & NET80211_BAND_BIT_5GHZ )
			net80211_add_channels ( dev, 36, 8,
						NET80211_REG_TXPOWER );
	}

	net80211_filter_hw_channels ( dev );

	/* Use channel 1 for now */
	dev->channel = 0;
	dev->op->config ( dev, NET80211_CFG_CHANNEL );

	/* Always do active probes at lowest (presumably first) speed */
	dev->rate = 0;
	dev->nr_rates = 1;
	dev->rates[0] = dev->hw->rates[dev->channels[0].band][0];
	dev->op->config ( dev, NET80211_CFG_RATE );

	return 0;
}

/**
 * Prepare 802.11 device channel and rate set for communication
 *
 * @v dev	802.11 device
 * @v wlan	WLAN to prepare for communication with
 * @ret rc	Return status code
 */
int net80211_prepare_assoc ( struct net80211_device *dev,
			     struct net80211_wlan *wlan )
{
	struct ieee80211_frame *hdr = wlan->beacon->data;
	struct ieee80211_beacon *beacon =
		( struct ieee80211_beacon * ) hdr->data;
	struct net80211_handshaker *handshaker;
	int rc;

	assert ( dev->netdev->state & NETDEV_OPEN );

	net80211_set_state ( dev, NET80211_ASSOCIATED, 0, 0 );
	memcpy ( dev->bssid, wlan->bssid, ETH_ALEN );
	strcpy ( dev->essid, wlan->essid );

	free ( dev->rsn_ie );
	dev->rsn_ie = NULL;

	dev->last_beacon_timestamp = beacon->timestamp;
	dev->tx_beacon_interval = 1024 * beacon->beacon_interval;

	/* Barring an IE that tells us the channel outright, assume
	   the channel we heard this AP best on is the channel it's
	   communicating on. */
	net80211_change_channel ( dev, wlan->channel );

	rc = net80211_process_capab ( dev, beacon->capability );
	if ( rc )
		return rc;

	rc = net80211_process_ie ( dev, beacon->info_element,
				   wlan->beacon->tail );
	if ( rc )
		return rc;

	/* Associate at the lowest rate so we know it'll get through */
	dev->rate = 0;
	dev->op->config ( dev, NET80211_CFG_RATE );

	/* Free old handshaker and crypto, if they exist */
	if ( dev->handshaker && dev->handshaker->stop &&
	     dev->handshaker->started )
		dev->handshaker->stop ( dev );
	free ( dev->handshaker );
	dev->handshaker = NULL;
	free ( dev->crypto );
	free ( dev->gcrypto );
	dev->crypto = dev->gcrypto = NULL;

	/* Find new security handshaker to use */
	for_each_table_entry ( handshaker, NET80211_HANDSHAKERS ) {
		if ( handshaker->protocol == wlan->handshaking ) {
			dev->handshaker = zalloc ( sizeof ( *handshaker ) +
						   handshaker->priv_len );
			if ( ! dev->handshaker )
				return -ENOMEM;

			memcpy ( dev->handshaker, handshaker,
				 sizeof ( *handshaker ) );
			dev->handshaker->priv = ( ( void * ) dev->handshaker +
						  sizeof ( *handshaker ) );
			break;
		}
	}

	if ( ( wlan->handshaking != NET80211_SECPROT_NONE ) &&
	     ! dev->handshaker ) {
		DBGC ( dev, "802.11 %p no support for handshaking scheme %d\n",
		       dev, wlan->handshaking );
		return -( ENOTSUP | ( wlan->handshaking << 8 ) );
	}

	/* Initialize security handshaker */
	if ( dev->handshaker ) {
		rc = dev->handshaker->init ( dev );
		if ( rc < 0 )
			return rc;
	}

	return 0;
}

/**
 * Send 802.11 initial authentication frame
 *
 * @v dev	802.11 device
 * @v wlan	WLAN to authenticate with
 * @v method	Authentication method
 * @ret rc	Return status code
 *
 * @a method may be 0 for Open System authentication or 1 for Shared
 * Key authentication. Open System provides no security in association
 * whatsoever, relying on encryption for confidentiality, but Shared
 * Key actively introduces security problems and is very rarely used.
 */
int net80211_send_auth ( struct net80211_device *dev,
			 struct net80211_wlan *wlan, int method )
{
	struct io_buffer *iob = alloc_iob ( 64 );
	struct ieee80211_auth *auth;

	net80211_set_state ( dev, 0, NET80211_WAITING, 0 );
	iob_reserve ( iob, IEEE80211_TYP_FRAME_HEADER_LEN );
	auth = iob_put ( iob, sizeof ( *auth ) );
	auth->algorithm = method;
	auth->tx_seq = 1;
	auth->status = 0;

	return net80211_tx_mgmt ( dev, IEEE80211_STYPE_AUTH, wlan->bssid, iob );
}

/**
 * Handle receipt of 802.11 authentication frame
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer
 *
 * If the authentication method being used is Shared Key, and the
 * frame that was received included challenge text, the frame is
 * encrypted using the cryptosystem currently in effect and sent back
 * to the AP to complete the authentication.
 */
static void net80211_handle_auth ( struct net80211_device *dev,
				   struct io_buffer *iob )
{
	struct ieee80211_frame *hdr = iob->data;
	struct ieee80211_auth *auth =
	    ( struct ieee80211_auth * ) hdr->data;

	if ( auth->tx_seq & 1 ) {
		DBGC ( dev, "802.11 %p authentication received improperly "
		       "directed frame (seq. %d)\n", dev, auth->tx_seq );
		net80211_set_state ( dev, NET80211_WAITING, 0,
				     IEEE80211_STATUS_FAILURE );
		return;
	}

	if ( auth->status != IEEE80211_STATUS_SUCCESS ) {
		DBGC ( dev, "802.11 %p authentication failed: status %d\n",
		       dev, auth->status );
		net80211_set_state ( dev, NET80211_WAITING, 0,
				     auth->status );
		return;
	}

	if ( auth->algorithm == IEEE80211_AUTH_SHARED_KEY && ! dev->crypto ) {
		DBGC ( dev, "802.11 %p can't perform shared-key authentication "
		       "without a cryptosystem\n", dev );
		net80211_set_state ( dev, NET80211_WAITING, 0,
				     IEEE80211_STATUS_FAILURE );
		return;
	}

	if ( auth->algorithm == IEEE80211_AUTH_SHARED_KEY &&
	     auth->tx_seq == 2 ) {
		/* Since the iob we got is going to be freed as soon
		   as we return, we can do some in-place
		   modification. */
		auth->tx_seq = 3;
		auth->status = 0;

		memcpy ( hdr->addr2, hdr->addr1, ETH_ALEN );
		memcpy ( hdr->addr1, hdr->addr3, ETH_ALEN );

		netdev_tx ( dev->netdev,
			    dev->crypto->encrypt ( dev->crypto, iob ) );
		return;
	}

	net80211_set_state ( dev, NET80211_WAITING, NET80211_AUTHENTICATED,
			     IEEE80211_STATUS_SUCCESS );

	return;
}

/**
 * Send 802.11 association frame
 *
 * @v dev	802.11 device
 * @v wlan	WLAN to associate with
 * @ret rc	Return status code
 */
int net80211_send_assoc ( struct net80211_device *dev,
			  struct net80211_wlan *wlan )
{
	struct io_buffer *iob = alloc_iob ( 128 );
	struct ieee80211_assoc_req *assoc;
	union ieee80211_ie *ie;

	net80211_set_state ( dev, 0, NET80211_WAITING, 0 );

	iob_reserve ( iob, IEEE80211_TYP_FRAME_HEADER_LEN );
	assoc = iob->data;

	assoc->capability = IEEE80211_CAPAB_MANAGED;
	if ( ! ( dev->hw->flags & NET80211_HW_NO_SHORT_PREAMBLE ) )
		assoc->capability |= IEEE80211_CAPAB_SHORT_PMBL;
	if ( ! ( dev->hw->flags & NET80211_HW_NO_SHORT_SLOT ) )
		assoc->capability |= IEEE80211_CAPAB_SHORT_SLOT;
	if ( wlan->crypto )
		assoc->capability |= IEEE80211_CAPAB_PRIVACY;

	assoc->listen_interval = 1;

	ie = net80211_marshal_request_info ( dev, assoc->info_element );

	DBGP ( "802.11 %p about to send association request:\n", dev );
	DBGP_HD ( iob->data, ( void * ) ie - iob->data );

	iob_put ( iob, ( void * ) ie - iob->data );

	return net80211_tx_mgmt ( dev, IEEE80211_STYPE_ASSOC_REQ,
				  wlan->bssid, iob );
}

/**
 * Handle receipt of 802.11 association reply frame
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer
 */
static void net80211_handle_assoc_reply ( struct net80211_device *dev,
					  struct io_buffer *iob )
{
	struct ieee80211_frame *hdr = iob->data;
	struct ieee80211_assoc_resp *assoc =
		( struct ieee80211_assoc_resp * ) hdr->data;

	net80211_process_capab ( dev, assoc->capability );
	net80211_process_ie ( dev, assoc->info_element, iob->tail );

	if ( assoc->status != IEEE80211_STATUS_SUCCESS ) {
		DBGC ( dev, "802.11 %p association failed: status %d\n",
		       dev, assoc->status );
		net80211_set_state ( dev, NET80211_WAITING, 0,
				     assoc->status );
		return;
	}

	/* ESSID was filled before the association request was sent */
	memcpy ( dev->bssid, hdr->addr3, ETH_ALEN );
	dev->aid = assoc->aid;

	net80211_set_state ( dev, NET80211_WAITING, NET80211_ASSOCIATED,
			     IEEE80211_STATUS_SUCCESS );
}


/**
 * Send 802.11 disassociation frame
 *
 * @v dev	802.11 device
 * @v reason	Reason for disassociation
 * @v deauth	If TRUE, send deauthentication instead of disassociation
 * @ret rc	Return status code
 */
static int net80211_send_disassoc ( struct net80211_device *dev, int reason,
				    int deauth )
{
	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, deauth ? IEEE80211_STYPE_DEAUTH :
				  IEEE80211_STYPE_DISASSOC, dev->bssid, iob );
}


/**
 * Deauthenticate from current network and try again
 *
 * @v dev	802.11 device
 * @v rc	Return status code indicating reason
 *
 * The deauthentication will be sent using an 802.11 "unspecified
 * reason", as is common, but @a rc will be set as a link-up
 * error to aid the user in debugging.
 */
void net80211_deauthenticate ( struct net80211_device *dev, int rc )
{
	net80211_send_disassoc ( dev, IEEE80211_REASON_UNSPECIFIED, 1 );
	dev->assoc_rc = rc;
	netdev_link_err ( dev->netdev, rc );

	net80211_autoassociate ( dev );
}


/** Smoothing factor (1-7) for link quality calculation */
#define LQ_SMOOTH	7

/**
 * Update link quality information based on received beacon
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer containing beacon
 * @ret rc	Return status code
 */
static void net80211_update_link_quality ( struct net80211_device *dev,
					   struct io_buffer *iob )
{
	struct ieee80211_frame *hdr = iob->data;
	struct ieee80211_beacon *beacon;
	u32 dt, rxi;

	if ( ! ( dev->state & NET80211_ASSOCIATED ) )
		return;

	beacon = ( struct ieee80211_beacon * ) hdr->data;
	dt = ( u32 ) ( beacon->timestamp - dev->last_beacon_timestamp );
	rxi = dev->rx_beacon_interval;

	rxi = ( LQ_SMOOTH * rxi ) + ( ( 8 - LQ_SMOOTH ) * dt );
	dev->rx_beacon_interval = rxi >> 3;

	dev->last_beacon_timestamp = beacon->timestamp;
}


/**
 * Handle receipt of 802.11 management frame
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer
 * @v signal	Signal strength of received frame
 */
static void net80211_handle_mgmt ( struct net80211_device *dev,
				   struct io_buffer *iob, int signal )
{
	struct ieee80211_frame *hdr = iob->data;
	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 );
		return;		/* only handle management frames */
	}

	switch ( stype ) {
		/* We reconnect on deauthentication and disassociation. */
	case IEEE80211_STYPE_DEAUTH:
	case IEEE80211_STYPE_DISASSOC:
		disassoc = ( struct ieee80211_disassoc * ) hdr->data;
		net80211_set_state ( dev, is_deauth ? NET80211_AUTHENTICATED :
				     NET80211_ASSOCIATED, 0,
				     NET80211_IS_REASON | disassoc->reason );
		DBGC ( dev, "802.11 %p %s: reason %d\n",
		       dev, is_deauth ? "deauthenticated" : "disassociated",
		       disassoc->reason );

		/* Try to reassociate, in case it's transient. */
		net80211_autoassociate ( dev );

		break;

		/* We handle authentication and association. */
	case IEEE80211_STYPE_AUTH:
		if ( ! ( dev->state & NET80211_AUTHENTICATED ) )
			net80211_handle_auth ( dev, iob );
		break;

	case IEEE80211_STYPE_ASSOC_RESP:
	case IEEE80211_STYPE_REASSOC_RESP:
		if ( ! ( dev->state & NET80211_ASSOCIATED ) )
			net80211_handle_assoc_reply ( dev, iob );
		break;

		/* We pass probes and beacons onto network scanning
		   code. Pass actions for future extensibility. */
	case IEEE80211_STYPE_BEACON:
		net80211_update_link_quality ( dev, iob );
		/* fall through */
	case IEEE80211_STYPE_PROBE_RESP:
	case IEEE80211_STYPE_ACTION:
		if ( dev->keep_mgmt ) {
			struct net80211_rx_info *rxinf;
			rxinf = zalloc ( sizeof ( *rxinf ) );
			if ( ! rxinf ) {
				DBGC ( dev, "802.11 %p out of memory\n", dev );
				break;
			}
			rxinf->signal = signal;
			list_add_tail ( &iob->list, &dev->mgmt_queue );
			list_add_tail ( &rxinf->list, &dev->mgmt_info_queue );
			keep = 1;
		}
		break;

	case IEEE80211_STYPE_PROBE_REQ:
		/* Some nodes send these broadcast. Ignore them. */
		break;

	case IEEE80211_STYPE_ASSOC_REQ:
	case IEEE80211_STYPE_REASSOC_REQ:
		/* We should never receive these, only send them. */
		DBGC ( dev, "802.11 %p received strange management request "
		       "(%04x)\n", dev, stype );
		break;

	default:
		DBGC ( dev, "802.11 %p received unimplemented management "
		       "packet (%04x)\n", dev, stype );
		break;
	}

	if ( ! keep )
		free_iob ( iob );
}

/* ---------- Packet handling functions ---------- */

/**
 * Free buffers used by 802.11 fragment cache entry
 *
 * @v dev	802.11 device
 * @v fcid	Fragment cache entry index
 *
 * After this function, the referenced entry will be marked 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->start_ticks = 0;
	frag->in_use = 0;
}

/**
 * Accumulate 802.11 fragments into one I/O buffer
 *
 * @v dev	802.11 device
 * @v fcid	Fragment cache entry index
 * @v nfrags	Number of fragments received
 * @v size	Sum of sizes of all fragments, including headers
 * @ret iob	I/O buffer containing reassembled packet
 *
 * This function does not free the fragment buffers.
 */
static struct io_buffer *net80211_accum_frags ( struct net80211_device *dev,
						int fcid, int nfrags, int size )
{
	struct net80211_frag_cache *frag = &dev->frags[fcid];
	int hdrsize = IEEE80211_TYP_FRAME_HEADER_LEN;
	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 < 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;
}

/**
 * Handle receipt of 802.11 fragment
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer containing fragment
 * @v signal	Signal strength with which fragment was received
 */
static void net80211_rx_frag ( struct net80211_device *dev,
			       struct io_buffer *iob, int signal )
{
	struct ieee80211_frame *hdr = iob->data;
	int fragnr = IEEE80211_FRAG ( hdr->seq );

	if ( fragnr == 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].in_use == 0 )
				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
		   older ones have time to complete. */
		if ( i == NET80211_NR_CONCURRENT_FRAGS ) {
			i = newest;
			net80211_free_frags ( dev, i );
		}

		dev->frags[i].in_use = 1;
		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].in_use && dev->frags[i].seqnr ==
			     IEEE80211_SEQNR ( hdr->seq ) )
				break;
		}
		if ( i == NET80211_NR_CONCURRENT_FRAGS ) {
			/* Drop non-first not-in-cache fragments */
			DBGC ( dev, "802.11 %p dropped fragment fc=%04x "
			       "seq=%04x\n", dev, hdr->fc, hdr->seq );
			free_iob ( iob );
			return;
		}

		dev->frags[i].iob[fragnr] = iob;

		if ( ! ( hdr->fc & IEEE80211_FC_MORE_FRAG ) ) {
			int j, size = 0;
			for ( j = 0; j < fragnr; j++ ) {
				size += iob_len ( dev->frags[i].iob[j] );
				if ( dev->frags[i].iob[j] == NULL )
					break;
			}
			if ( j == fragnr ) {
				/* We've got everything */
				struct io_buffer *niob =
				    net80211_accum_frags ( dev, i, fragnr,
							   size );
				net80211_free_frags ( dev, i );
				net80211_rx ( dev, niob, signal, 0 );
			} else {
				DBGC ( dev, "802.11 %p dropping fragmented "
				       "packet due to out-of-order arrival, "
				       "fc=%04x seq=%04x\n", dev, hdr->fc,
				       hdr->seq );
				net80211_free_frags ( dev, i );
			}
		}
	}
}

/**
 * Handle receipt of 802.11 frame
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer
 * @v signal	Received signal strength
 * @v rate	Bitrate at which frame was received, in 100 kbps units
 *
 * If the rate or signal is unknown, 0 should be passed.
 */
void net80211_rx ( struct net80211_device *dev, struct io_buffer *iob,
		   int signal, u16 rate )
{
	struct ieee80211_frame *hdr = iob->data;
	u16 type = hdr->fc & IEEE80211_FC_TYPE;
	if ( ( hdr->fc & IEEE80211_FC_VERSION ) != IEEE80211_THIS_VERSION )
		goto drop;	/* drop invalid-version packets */

	if ( type == IEEE80211_TYPE_CTRL )
		goto drop;	/* we don't handle control packets,
				   the hardware does */

	if ( dev->last_rx_seq == hdr->seq )
		goto drop;	/* 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 );
	}

	/* Only decrypt packets from our BSSID, to avoid spurious errors */
	if ( ( hdr->fc & IEEE80211_FC_PROTECTED ) &&
	     ! memcmp ( hdr->addr2, dev->bssid, ETH_ALEN ) ) {
		/* Decrypt packet; record and drop if it fails */
		struct io_buffer *niob;
		struct net80211_crypto *crypto = dev->crypto;

		if ( ! dev->crypto ) {
			DBGC ( dev, "802.11 %p cannot decrypt packet "
			       "without a cryptosystem\n", dev );
			goto drop_crypt;
		}

		if ( ( hdr->addr1[0] & 1 ) && dev->gcrypto ) {
			/* Use group decryption if needed */
			crypto = dev->gcrypto;
		}

		niob = crypto->decrypt ( crypto, iob );
		if ( ! niob ) {
			DBGC ( dev, "802.11 %p decryption error\n", dev );
			goto drop_crypt;
		}
		free_iob ( iob );
		iob = niob;
	}

	dev->last_signal = signal;

	/* Fragments go into the frag cache or get dropped. */
	if ( IEEE80211_FRAG ( hdr->seq ) != 0
	     || ( hdr->fc & IEEE80211_FC_MORE_FRAG ) ) {
		net80211_rx_frag ( dev, iob, signal );
		return;
	}

	/* Management frames get handled, enqueued, or dropped. */
	if ( type == IEEE80211_TYPE_MGMT ) {
		net80211_handle_mgmt ( dev, iob, signal );
		return;
	}

	/* Data frames get dropped or sent to the net_device. */
	if ( ( hdr->fc & IEEE80211_FC_SUBTYPE ) != IEEE80211_STYPE_DATA )
		goto drop;	/* drop QoS, CFP, or null data packets */

	/* Update rate-control algorithm */
	if ( dev->rctl )
		rc80211_update_rx ( dev, hdr->fc & IEEE80211_FC_RETRY, rate );

	/* Pass packet onward */
	if ( dev->state & NET80211_ASSOCIATED ) {
		netdev_rx ( dev->netdev, iob );
		return;
	}

	/* No association? Drop it. */
	goto drop;

 drop_crypt:
	netdev_rx_err ( dev->netdev, NULL, EINVAL_CRYPTO_REQUEST );
 drop:
	DBGC2 ( dev, "802.11 %p dropped packet fc=%04x seq=%04x\n", dev,
		hdr->fc, hdr->seq );
	free_iob ( iob );
	return;
}

/** Indicate an error in receiving a packet
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer with received packet, or NULL
 * @v rc	Error code
 *
 * This logs the error with the wrapping net_device, and frees iob if
 * it is passed.
 */
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
 *
 * @v dev	802.11 device
 * @v iob	I/O buffer of transmitted packet
 * @v retries	Number of times this packet was retransmitted
 * @v rc	Error code, or 0 for success
 *
 * This logs an error with the wrapping net_device if one occurred,
 * and removes and frees the I/O buffer from its TX queue. The
 * provided retry information is used to tune our transmission rate.
 *
 * If the packet did not need to be retransmitted because it was
 * properly ACKed the first time, @a retries should be 0.
 */
void net80211_tx_complete ( struct net80211_device *dev,
			    struct io_buffer *iob, int retries, int rc )
{
	/* Update rate-control algorithm */
	if ( dev->rctl )
		rc80211_update_tx ( dev, retries, rc );

	/* Pass completion onward */
	netdev_tx_complete_err ( dev->netdev, iob, rc );
}