aboutsummaryrefslogtreecommitdiff
path: root/src/drivers/net/prism2.c
blob: 4331f2cd0e50431ba8e5f3d0720b6b9f46c263f4 (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
/**************************************************************************
Etherboot -  BOOTP/TFTP Bootstrap Program
Prism2 NIC driver for Etherboot

Written by Michael Brown of Fen Systems Ltd
$Id$
***************************************************************************/

/*
 * 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

FILE_LICENCE ( GPL2_OR_LATER );

#include <etherboot.h>
#include <nic.h>
#include <ipxe/pci.h>
#include <ipxe/ethernet.h>

/*
 * Hard-coded SSID
 * Leave blank in order to connect to any available SSID
 */

static const char hardcoded_ssid[] = "";

/*
 * Maximum number of info packets to wait for on a join attempt.
 * Some APs (including the Linksys WAP11) will send a "you are disconnected" packet
 * before sending the "you are connected" packet, if the card has previously been
 * attached to the AP.
 *
 * 2 is probably a sensible value, but YMMV.
 */

#define MAX_JOIN_INFO_COUNT 2

/*
 * Type of Prism2 interface to support
 * If not already defined, select PLX
 */
#ifndef WLAN_HOSTIF
#define WLAN_HOSTIF WLAN_PLX
#endif

/*
 * Include wlan_compat, p80211 and hfa384x header files from Linux Prism2 driver
 * We need to hack some defines in order to avoid compiling kernel-specific routines
 */

#define __LINUX_WLAN__
#undef __KERNEL__
#define __I386__
#include "wlan_compat.h"
#include "p80211hdr.h"
#include "hfa384x.h"
#define BAP_TIMEOUT ( 5000 )

/*
 * A few hacks to make the coding environment more Linux-like.  This makes it somewhat
 * quicker to convert code from the Linux Prism2 driver.
 */
#include <errno.h>
#define __le16_to_cpu(x) (x)
#define __le32_to_cpu(x) (x)
#define __cpu_to_le16(x) (x)
#define __cpu_to_le32(x) (x)

#define hfa384x2host_16(n)	(__le16_to_cpu((uint16_t)(n)))
#define hfa384x2host_32(n)	(__le32_to_cpu((uint32_t)(n)))
#define host2hfa384x_16(n)	(__cpu_to_le16((uint16_t)(n)))
#define host2hfa384x_32(n)	(__cpu_to_le32((uint32_t)(n)))

/*
 * PLX9052 PCI register offsets
 * Taken from PLX9052 datasheet available from http://www.plxtech.com/download/9052/databook/9052db-20.pdf
 */

#define PLX_LOCAL_CONFIG_REGISTER_BASE ( PCI_BASE_ADDRESS_1 )
#define PLX_LOCAL_ADDRESS_SPACE_0_BASE ( PCI_BASE_ADDRESS_2 )
#define PLX_LOCAL_ADDRESS_SPACE_1_BASE ( PCI_BASE_ADDRESS_3 )
#define PLX_LOCAL_ADDRESS_SPACE_2_BASE ( PCI_BASE_ADDRESS_4 )
#define PLX_LOCAL_ADDRESS_SPACE_3_BASE ( PCI_BASE_ADDRESS_5 )

#define PRISM2_PLX_ATTR_MEM_BASE       ( PLX_LOCAL_ADDRESS_SPACE_0_BASE )
#define PRISM2_PLX_IO_BASE             ( PLX_LOCAL_ADDRESS_SPACE_1_BASE )

#define PRISM2_PCI_MEM_BASE            ( PCI_BASE_ADDRESS_0 )

/*
 * PCMCIA CIS types
 * Taken from cistpl.h in pcmcia-cs
 */

#define CISTPL_VERS_1           ( 0x15 )
#define CISTPL_END              ( 0xff )

#define CIS_STEP                ( 2 )
#define CISTPL_HEADER_LEN       ( 2 * CIS_STEP )
#define CISTPL_LEN_OFF          ( 1 * CIS_STEP )
#define CISTPL_VERS_1_STR_OFF   ( 4 * CIS_STEP )

/*
 * Prism2 constants
 * Taken from prism2sta.c in linux-wlan-ng
 */

#define COR_OFFSET      ( 0x3e0 )   /* COR attribute offset of Prism2 PC card */
#define COR_VALUE       ( 0x41 )    /* Enable PC card with irq in level trigger (but interrupts disabled) */

/* NIC specific static variables */

/* The hfa384x_t structure is used extensively in the Linux driver but is ifdef'd out in our include since __KERNEL__ is not defined.
 * This is a dummy version that contains only the fields we are interested in.
 */

typedef struct hfa384x
{
  uint32_t iobase;
  void *membase;
  uint16_t lastcmd;
  uint16_t status;         /* in host order */
  uint16_t resp0;          /* in host order */
  uint16_t resp1;          /* in host order */
  uint16_t resp2;          /* in host order */
  uint8_t  bssid[WLAN_BSSID_LEN];
} hfa384x_t;

/* The global instance of the hardware (i.e. where we store iobase and membase, in the absence of anywhere better to put them */
static hfa384x_t hw_global;

/*
 * 802.11 headers in addition to those in hfa384x_tx_frame_t (LLC and SNAP)
 * Taken from p80211conv.h
 */

typedef struct wlan_llc
{
  uint8_t   dsap;
  uint8_t   ssap;
  uint8_t   ctl;
}  wlan_llc_t;

static const wlan_llc_t wlan_llc_snap = { 0xaa, 0xaa, 0x03 }; /* LLC header indicating SNAP (?) */

#define WLAN_IEEE_OUI_LEN 3
typedef struct wlan_snap
{
  uint8_t   oui[WLAN_IEEE_OUI_LEN];
  uint16_t  type;
} wlan_snap_t;

typedef struct wlan_80211hdr
{
  wlan_llc_t llc;
  wlan_snap_t snap;
} wlan_80211hdr_t;

/*
 * Function prototypes
 */

/*
 * Hardware-level hfa384x functions
 * These are based on the ones in hfa384x.h (which are ifdef'd out since __KERNEL__ is not defined).
 * Basically, these functions are the result of hand-evaluating all the ifdefs and defines in the hfa384x.h versions.
 */

/* Retrieve the value of one of the MAC registers. */
static inline uint16_t hfa384x_getreg( hfa384x_t *hw, unsigned int reg )
{
#if (WLAN_HOSTIF == WLAN_PLX)
  return inw ( hw->iobase + reg );
#elif (WLAN_HOSTIF == WLAN_PCI)
  return readw ( hw->membase + reg );
#endif
}

/* Set the value of one of the MAC registers. */
static inline void hfa384x_setreg( hfa384x_t *hw, uint16_t val, unsigned int reg )
{
#if (WLAN_HOSTIF == WLAN_PLX)
  outw ( val, hw->iobase + reg );
#elif (WLAN_HOSTIF == WLAN_PCI)
  writew ( val, hw->membase + reg );
#endif
  return;
}

/*
 * Noswap versions
 * Etherboot is i386 only, so swap and noswap are the same...
 */
static inline uint16_t hfa384x_getreg_noswap( hfa384x_t *hw, unsigned int reg )
{
  return hfa384x_getreg ( hw, reg );
}
static inline void hfa384x_setreg_noswap( hfa384x_t *hw, uint16_t val, unsigned int reg )
{
  hfa384x_setreg ( hw, val, reg );
}

/*
 * Low-level hfa384x functions
 * These are based on the ones in hfa384x.c, modified to work in the Etherboot environment.
 */

/*
 * hfa384x_docmd_wait
 *
 * Waits for availability of the Command register, then
 * issues the given command.  Then polls the Evstat register
 * waiting for command completion.
 * Arguments:
 *       hw              device structure
 *       cmd             Command in host order
 *       parm0           Parameter0 in host order
 *       parm1           Parameter1 in host order
 *       parm2           Parameter2 in host order
 * Returns:
 *       0               success
 *       >0              command indicated error, Status and Resp0-2 are
 *                       in hw structure.
 */
static int hfa384x_docmd_wait( hfa384x_t *hw, uint16_t cmd, uint16_t parm0, uint16_t parm1, uint16_t parm2)
{
  uint16_t reg = 0;
  uint16_t counter = 0;

  /* wait for the busy bit to clear */
  counter = 0;
  reg = hfa384x_getreg(hw, HFA384x_CMD);
  while ( HFA384x_CMD_ISBUSY(reg) && (counter < 10) ) {
    reg = hfa384x_getreg(hw, HFA384x_CMD);
    counter++;
    udelay(10);
  }
  if (HFA384x_CMD_ISBUSY(reg)) {
    printf("hfa384x_cmd timeout(1), reg=0x%0hx.\n", reg);
    return -ETIMEDOUT;
  }

  /* busy bit clear, write command */
  hfa384x_setreg(hw, parm0, HFA384x_PARAM0);
  hfa384x_setreg(hw, parm1, HFA384x_PARAM1);
  hfa384x_setreg(hw, parm2, HFA384x_PARAM2);
  hw->lastcmd = cmd;
  hfa384x_setreg(hw, cmd, HFA384x_CMD);

  /* Now wait for completion */
  counter = 0;
  reg = hfa384x_getreg(hw, HFA384x_EVSTAT);
  /* Initialization is the problem.  It takes about
     100ms. "normal" commands are typically is about
     200-400 us (I've never seen less than 200).  Longer
     is better so that we're not hammering the bus. */
  while ( !HFA384x_EVSTAT_ISCMD(reg) && (counter < 5000)) {
    reg = hfa384x_getreg(hw, HFA384x_EVSTAT);
    counter++;
    udelay(200);
  }
  if ( ! HFA384x_EVSTAT_ISCMD(reg) ) {
    printf("hfa384x_cmd timeout(2), reg=0x%0hx.\n", reg);
    return -ETIMEDOUT;
  }

  /* Read status and response */
  hw->status = hfa384x_getreg(hw, HFA384x_STATUS);
  hw->resp0 = hfa384x_getreg(hw, HFA384x_RESP0);
  hw->resp1 = hfa384x_getreg(hw, HFA384x_RESP1);
  hw->resp2 = hfa384x_getreg(hw, HFA384x_RESP2);
  hfa384x_setreg(hw, HFA384x_EVACK_CMD, HFA384x_EVACK);
  return HFA384x_STATUS_RESULT_GET(hw->status);
}

/*
 * Prepare BAP for access.  Assigns FID and RID, sets offset register
 * and waits for BAP to become available.
 *
 * Arguments:
 *	hw		device structure
 *	id		FID or RID, destined for the select register (host order)
 *	offset		An _even_ offset into the buffer for the given FID/RID.
 * Returns:
 *	0		success
 */
static int hfa384x_prepare_bap(hfa384x_t *hw, uint16_t id, uint16_t offset)
{
  int result = 0;
  uint16_t reg;
  uint16_t i;

  /* Validate offset, buf, and len */
  if ( (offset > HFA384x_BAP_OFFSET_MAX) || (offset % 2) ) {
    result = -EINVAL;
  } else {
    /* Write fid/rid and offset */
    hfa384x_setreg(hw, id, HFA384x_SELECT0);
    udelay(10);
    hfa384x_setreg(hw, offset, HFA384x_OFFSET0);
    /* Wait for offset[busy] to clear (see BAP_TIMEOUT) */
    i = 0;
    do {
      reg = hfa384x_getreg(hw, HFA384x_OFFSET0);
      if ( i > 0 ) udelay(2);
      i++;
    } while ( i < BAP_TIMEOUT && HFA384x_OFFSET_ISBUSY(reg));
    if ( i >= BAP_TIMEOUT ) {
      /* failure */
      result = reg;
    } else if ( HFA384x_OFFSET_ISERR(reg) ){
      /* failure */
      result = reg;
    }
  }
  return result;
}

/*
 * Copy data from BAP to memory.
 *
 * Arguments:
 *	hw		device structure
 *	id		FID or RID, destined for the select register (host order)
 *	offset		An _even_ offset into the buffer for the given FID/RID.
 *	buf		ptr to array of bytes
 *	len		length of data to transfer in bytes
 * Returns:
 *	0		success
 */
static int hfa384x_copy_from_bap(hfa384x_t *hw, uint16_t id, uint16_t offset,
			  void *buf, unsigned int len)
{
  int result = 0;
  uint8_t	*d = (uint8_t*)buf;
  uint16_t i;
  uint16_t reg = 0;

  /* Prepare BAP */
  result = hfa384x_prepare_bap ( hw, id, offset );
  if ( result == 0 ) {
    /* Read even(len) buf contents from data reg */
    for ( i = 0; i < (len & 0xfffe); i+=2 ) {
      *(uint16_t*)(&(d[i])) = hfa384x_getreg_noswap(hw, HFA384x_DATA0);
    }
    /* If len odd, handle last byte */
    if ( len % 2 ){
      reg = hfa384x_getreg_noswap(hw, HFA384x_DATA0);
      d[len-1] = ((uint8_t*)(&reg))[0];
    }
  }
  if (result) {
    printf ( "copy_from_bap(%#hx, %#hx, %d) failed, result=%#hx\n", id, offset, len, result);
  }
  return result;
}

/*
 * Copy data from memory to BAP.
 *
 * Arguments:
 *	hw		device structure
 *	id		FID or RID, destined for the select register (host order)
 *	offset		An _even_ offset into the buffer for the given FID/RID.
 *	buf		ptr to array of bytes
 *	len		length of data to transfer in bytes
 * Returns:
 *	0		success
 */
static int hfa384x_copy_to_bap(hfa384x_t *hw, uint16_t id, uint16_t offset,
			void *buf, unsigned int len)
{
  int result = 0;
  uint8_t	*d = (uint8_t*)buf;
  uint16_t i;
  uint16_t savereg;

  /* Prepare BAP */
  result = hfa384x_prepare_bap ( hw, id, offset );
  if ( result == 0 ) {
    /* Write even(len) buf contents to data reg */
    for ( i = 0; i < (len & 0xfffe); i+=2 ) {
      hfa384x_setreg_noswap(hw, *(uint16_t*)(&(d[i])), HFA384x_DATA0);
    }
    /* If len odd, handle last byte */
    if ( len % 2 ){
      savereg = hfa384x_getreg_noswap(hw, HFA384x_DATA0);
      result = hfa384x_prepare_bap ( hw, id, offset + (len & 0xfffe) );
      if ( result == 0 ) {
	((uint8_t*)(&savereg))[0] = d[len-1];
	hfa384x_setreg_noswap(hw, savereg, HFA384x_DATA0);
      }
    }
  }
  if (result) {
    printf ( "copy_to_bap(%#hx, %#hx, %d) failed, result=%#hx\n", id, offset, len, result);
  }
  return result;
}

/*
 * Request a given record to be copied to/from the record buffer.
 *
 * Arguments:
 *	hw		device structure
 *	write		[0|1] copy the record buffer to the given
 *			configuration record. (host order)
 *	rid		RID of the record to read/write. (host order)
 *
 * Returns:
 *	0		success
 */
static inline int hfa384x_cmd_access(hfa384x_t *hw, uint16_t write, uint16_t rid)
{
  return hfa384x_docmd_wait(hw, HFA384x_CMD_CMDCODE_SET(HFA384x_CMDCODE_ACCESS) | HFA384x_CMD_WRITE_SET(write), rid, 0, 0);
}

/*
 * Performs the sequence necessary to read a config/info item.
 *
 * Arguments:
 *	hw		device structure
 *	rid		config/info record id (host order)
 *	buf		host side record buffer.  Upon return it will
 *			contain the body portion of the record (minus the
 *			RID and len).
 *	len		buffer length (in bytes, should match record length)
 *
 * Returns:
 *	0		success
 */
static int hfa384x_drvr_getconfig(hfa384x_t *hw, uint16_t rid, void *buf, uint16_t len)
{
  int result = 0;
  hfa384x_rec_t	rec;

  /* Request read of RID */
  result = hfa384x_cmd_access( hw, 0, rid);
  if ( result ) {
    printf("Call to hfa384x_cmd_access failed\n");
    return -1;
  }
  /* Copy out record length */
  result = hfa384x_copy_from_bap( hw, rid, 0, &rec, sizeof(rec));
  if ( result ) {
    return -1;
  }
  /* Validate the record length */
  if ( ((hfa384x2host_16(rec.reclen)-1)*2) != len ) {  /* note body len calculation in bytes */
    printf ( "RID len mismatch, rid=%#hx hlen=%d fwlen=%d\n", rid, len, (hfa384x2host_16(rec.reclen)-1)*2);
    return -1;
  }
  /* Copy out record data */
  result = hfa384x_copy_from_bap( hw, rid, sizeof(rec), buf, len);
  return result;
}

/*
 * Performs the sequence necessary to read a 16/32 bit config/info item
 * and convert it to host order.
 *
 * Arguments:
 *	hw		device structure
 *	rid		config/info record id (in host order)
 *	val		ptr to 16/32 bit buffer to receive value (in host order)
 *
 * Returns:
 *	0		success
 */
#if 0 /* Not actually used anywhere */
static int hfa384x_drvr_getconfig16(hfa384x_t *hw, uint16_t rid, void *val)
{
  int result = 0;
  result = hfa384x_drvr_getconfig(hw, rid, val, sizeof(uint16_t));
  if ( result == 0 ) {
    *((uint16_t*)val) = hfa384x2host_16(*((uint16_t*)val));
  }
  return result;
}
#endif
#if 0 /* Not actually used anywhere */
static int hfa384x_drvr_getconfig32(hfa384x_t *hw, uint16_t rid, void *val)
{
  int result = 0;
  result = hfa384x_drvr_getconfig(hw, rid, val, sizeof(uint32_t));
  if ( result == 0 ) {
    *((uint32_t*)val) = hfa384x2host_32(*((uint32_t*)val));
  }
  return result;
}
#endif

/*
 * Performs the sequence necessary to write a config/info item.
 *
 * Arguments:
 *	hw		device structure
 *	rid		config/info record id (in host order)
 *	buf		host side record buffer
 *	len		buffer length (in bytes)
 *
 * Returns:
 *	0		success
 */
static int hfa384x_drvr_setconfig(hfa384x_t *hw, uint16_t rid, void *buf, uint16_t len)
{
  int result = 0;
  hfa384x_rec_t	rec;

  rec.rid = host2hfa384x_16(rid);
  rec.reclen = host2hfa384x_16((len/2) + 1); /* note conversion to words, +1 for rid field */
  /* write the record header */
  result = hfa384x_copy_to_bap( hw, rid, 0, &rec, sizeof(rec));
  if ( result ) {
    printf("Failure writing record header\n");
    return -1;
  }
  /* write the record data (if there is any) */
  if ( len > 0 ) {
    result = hfa384x_copy_to_bap( hw, rid, sizeof(rec), buf, len);
    if ( result ) {
      printf("Failure writing record data\n");
      return -1;
    }
  }
  /* Trigger setting of record */
  result = hfa384x_cmd_access( hw, 1, rid);
  return result;
}

/*
 * Performs the sequence necessary to write a 16/32 bit config/info item.
 *
 * Arguments:
 *	hw		device structure
 *	rid		config/info record id (in host order)
 *	val		16/32 bit value to store (in host order)
 *
 * Returns:
 *	0		success
 */
static int hfa384x_drvr_setconfig16(hfa384x_t *hw, uint16_t rid, uint16_t *val)
{
  uint16_t value;
  value = host2hfa384x_16(*val);
  return hfa384x_drvr_setconfig(hw, rid, &value, sizeof(uint16_t));
}
#if 0 /* Not actually used anywhere */
static int hfa384x_drvr_setconfig32(hfa384x_t *hw, uint16_t rid, uint32_t *val)
{
  uint32_t value;
  value = host2hfa384x_32(*val);
  return hfa384x_drvr_setconfig(hw, rid, &value, sizeof(uint32_t));
}
#endif

/*
 * Wait for an event, with specified checking interval and timeout.
 * Automatically acknolwedges events.
 *
 * Arguments:
 *	hw		device structure
 *      event_mask      EVSTAT register mask of events to wait for
 *	event_ack	EVACK register set of events to be acknowledged if they happen (can be
 *			used to acknowledge "ignorable" events in addition to the "main" event)
 *      wait            Time (in us) to wait between each poll of the register
 *      timeout         Maximum number of polls before timing out
 *      descr           Descriptive text string of what is being waited for
 *                      (will be printed out if a timeout happens)
 *
 * Returns:
 *      value of EVSTAT register, or 0 on failure
 */
static int hfa384x_wait_for_event(hfa384x_t *hw, uint16_t event_mask, uint16_t event_ack, int wait, int timeout, const char *descr)
{
  uint16_t reg;
  int count = 0;

  do {
    reg = hfa384x_getreg(hw, HFA384x_EVSTAT);
    if ( count > 0 ) udelay(wait);
    count++;
  } while ( !(reg & event_mask) && count < timeout);
  if ( count >= timeout ) {
    printf("hfa384x: Timed out waiting for %s\n", descr);
    return 0; /* Return failure */
  }
  /* Acknowledge all events that we were waiting on */
  hfa384x_setreg(hw, reg & ( event_mask | event_ack ), HFA384x_EVACK);
  return reg;
}

/**************************************************************************
POLL - Wait for a frame
***************************************************************************/
static int prism2_poll(struct nic *nic, int retrieve)
{
  uint16_t reg;
  uint16_t rxfid;
  uint16_t result;
  hfa384x_rx_frame_t rxdesc;
  hfa384x_t *hw = &hw_global;

  /* Check for received packet */
  reg = hfa384x_getreg(hw, HFA384x_EVSTAT);
  if ( ! HFA384x_EVSTAT_ISRX(reg) ) {
    /* No packet received - return 0 */
    return 0;
  }

  if ( ! retrieve ) return 1;

  /* Acknowledge RX event */
  hfa384x_setreg(hw, HFA384x_EVACK_RX_SET(1), HFA384x_EVACK);
  /* Get RX FID */
  rxfid = hfa384x_getreg(hw, HFA384x_RXFID);
  /* Get the descriptor (including headers) */
  result = hfa384x_copy_from_bap(hw, rxfid, 0, &rxdesc, sizeof(rxdesc));
  if ( result ) {
    return 0; /* fail */
  }
  /* Byte order convert once up front. */
  rxdesc.status = hfa384x2host_16(rxdesc.status);
  rxdesc.time = hfa384x2host_32(rxdesc.time);
  rxdesc.data_len = hfa384x2host_16(rxdesc.data_len);

  /* Fill in nic->packetlen */
  nic->packetlen = rxdesc.data_len;
  if ( nic->packetlen > 0 ) {
    /* Fill in nic->packet */
    /*
     * NOTE: Packets as received have an 8-byte header (LLC+SNAP(?)) terminating with the packet type.
     * Etherboot expects a 14-byte header terminating with the packet type (it ignores the rest of the
     * header), so we use a quick hack to achieve this.
     */
    result = hfa384x_copy_from_bap(hw, rxfid, HFA384x_RX_DATA_OFF,
				   nic->packet + ETH_HLEN - sizeof(wlan_80211hdr_t), nic->packetlen);
    if ( result ) {
      return 0; /* fail */
    }
  }
  return 1; /* Packet successfully received */
}

/**************************************************************************
TRANSMIT - Transmit a frame
***************************************************************************/
static void prism2_transmit(
			    struct nic *nic,
			    const char *d,			/* Destination */
			    unsigned int t,			/* Type */
			    unsigned int s,			/* size */
			    const char *p)			/* Packet */
{
  hfa384x_t *hw = &hw_global;
  hfa384x_tx_frame_t txdesc;
  wlan_80211hdr_t p80211hdr = { wlan_llc_snap, {{0,0,0},0} };
  uint16_t fid;
  uint16_t status;
  int result;

  // Request FID allocation
  result = hfa384x_docmd_wait(hw, HFA384x_CMD_CMDCODE_SET(HFA384x_CMDCODE_ALLOC), HFA384x_DRVR_TXBUF_MAX, 0, 0);
  if (result != 0) {
    printf("hfa384x: Tx FID allocate command failed: Aborting transmit..\n");
    return;
  }
  if ( !hfa384x_wait_for_event(hw, HFA384x_EVSTAT_ALLOC, HFA384x_EVACK_INFO, 10, 50, "Tx FID to be allocated\n" ) ) return;
  fid = hfa384x_getreg(hw, HFA384x_ALLOCFID);

  /* Build Tx frame structure */
  memset(&txdesc, 0, sizeof(txdesc));
  txdesc.tx_control = host2hfa384x_16( HFA384x_TX_MACPORT_SET(0) | HFA384x_TX_STRUCTYPE_SET(1) |
				       HFA384x_TX_TXEX_SET(1) | HFA384x_TX_TXOK_SET(1) );
  txdesc.frame_control =  host2ieee16( WLAN_SET_FC_FTYPE(WLAN_FTYPE_DATA) |
				       WLAN_SET_FC_FSTYPE(WLAN_FSTYPE_DATAONLY) |
				       WLAN_SET_FC_TODS(1) );
  memcpy(txdesc.address1, hw->bssid, WLAN_ADDR_LEN);
  memcpy(txdesc.address2, nic->node_addr, WLAN_ADDR_LEN);
  memcpy(txdesc.address3, d, WLAN_ADDR_LEN);
  txdesc.data_len = host2hfa384x_16( sizeof(txdesc) + sizeof(p80211hdr) + s );
  /* Set up SNAP header */
  /* Let OUI default to RFC1042 (0x000000) */
  p80211hdr.snap.type = htons(t);

  /* Copy txdesc, p80211hdr and payload parts to FID */
  result = hfa384x_copy_to_bap(hw, fid, 0, &txdesc, sizeof(txdesc));
  if ( result ) return; /* fail */
  result = hfa384x_copy_to_bap( hw, fid, sizeof(txdesc), &p80211hdr, sizeof(p80211hdr) );
  if ( result ) return; /* fail */
  result = hfa384x_copy_to_bap( hw, fid, sizeof(txdesc) + sizeof(p80211hdr), (uint8_t*)p, s );
  if ( result ) return; /* fail */

  /* Issue Tx command */
  result = hfa384x_docmd_wait(hw, HFA384x_CMD_CMDCODE_SET(HFA384x_CMDCODE_TX), fid, 0, 0);
  if ( result != 0 ) {
    printf("hfa384x: Transmit failed with result %#hx.\n", result);
    return;
  }

  /* Wait for transmit completion (or exception) */
  result = hfa384x_wait_for_event(hw, HFA384x_EVSTAT_TXEXC | HFA384x_EVSTAT_TX, HFA384x_EVACK_INFO,
				  200, 500, "Tx to complete\n" );
  if ( !result ) return; /* timeout failure */
  if ( HFA384x_EVSTAT_ISTXEXC(result) ) {
    fid = hfa384x_getreg(hw, HFA384x_TXCOMPLFID);
    printf ( "Tx exception occurred with fid %#hx\n", fid );
    result = hfa384x_copy_from_bap(hw, fid, 0, &status, sizeof(status));
    if ( result ) return; /* fail */
    printf("hfa384x: Tx error occurred (status %#hx):\n", status);
    if ( HFA384x_TXSTATUS_ISACKERR(status) ) { printf(" ...acknowledgement error\n"); }
    if ( HFA384x_TXSTATUS_ISFORMERR(status) ) { printf(" ...format error\n"); }
    if ( HFA384x_TXSTATUS_ISDISCON(status) ) { printf(" ...disconnected error\n"); }
    if ( HFA384x_TXSTATUS_ISAGEDERR(status) ) { printf(" ...AGED error\n"); }
    if ( HFA384x_TXSTATUS_ISRETRYERR(status) ) { printf(" ...retry error\n"); }
    return; /* fail */
  }
}

/**************************************************************************
DISABLE - Turn off ethernet interface
***************************************************************************/
static void prism2_disable ( struct nic *nic __unused ) {
  /* put the card in its initial state */
}

/**************************************************************************
IRQ - Enable, Disable, or Force interrupts
***************************************************************************/
static void prism2_irq(struct nic *nic __unused, irq_action_t action __unused)
{
  switch ( action ) {
  case DISABLE :
    break;
  case ENABLE :
    break;
  case FORCE :
    break;
  }
}

/**************************************************************************
Operations table
***************************************************************************/
static struct nic_operations prism2_operations = {
	.connect	= dummy_connect,
	.poll		= prism2_poll,
	.transmit	= prism2_transmit,
	.irq		= prism2_irq,
};

/**************************************************************************
PROBE - Look for an adapter, this routine's visible to the outside
You should omit the last argument struct pci_device * for a non-PCI NIC
***************************************************************************/
static int prism2_probe ( struct nic *nic, hfa384x_t *hw ) {
  int result;
  uint16_t tmp16 = 0;
  uint16_t infofid;
  hfa384x_InfFrame_t inf;
  char ssid[HFA384x_RID_CNFDESIREDSSID_LEN];
  int info_count = 0;

  nic->irqno  = 0;

  /* Initialize card */
  result = hfa384x_docmd_wait(hw, HFA384x_CMDCODE_INIT, 0,0,0); /* Send initialize command */
  if ( result ) printf ( "Initialize command returned %#hx\n", result );
  hfa384x_setreg(hw, 0, HFA384x_INTEN); /* Disable interrupts */
  hfa384x_setreg(hw, 0xffff, HFA384x_EVACK); /* Acknowledge any spurious events */

  DBG ( "MAC address %s\n", eth_ntoa ( nic->node_addr ) );

  /* Retrieve MAC address (and fill out nic->node_addr) */
  hfa384x_drvr_getconfig ( hw, HFA384x_RID_CNFOWNMACADDR, nic->node_addr, HFA384x_RID_CNFOWNMACADDR_LEN );

  /* Prepare card for autojoin */
  /* This procedure is reverse-engineered from a register-level trace of the Linux driver's join process */
  tmp16 = WLAN_DATA_MAXLEN; /* Set maximum data length */
  result = hfa384x_drvr_setconfig16(hw, HFA384x_RID_CNFMAXDATALEN, &tmp16);
  if ( result ) printf ( "Set Max Data Length command returned %#hx\n", result );
  tmp16 = 0x000f; /* Set transmit rate(?) */
  result = hfa384x_drvr_setconfig16(hw, HFA384x_RID_TXRATECNTL, &tmp16);
  if ( result ) printf ( "Set Transmit Rate command returned %#hx\n", result );
  tmp16 = HFA384x_CNFAUTHENTICATION_OPENSYSTEM; /* Set authentication type to OpenSystem */
  result = hfa384x_drvr_setconfig16(hw, HFA384x_RID_CNFAUTHENTICATION, &tmp16);
  if ( result ) printf ( "Set Authentication Type command returned %#hx\n", result );
  /* Set SSID */
  memset(ssid, 0, HFA384x_RID_CNFDESIREDSSID_LEN);
  for ( tmp16=0; tmp16<sizeof(hardcoded_ssid); tmp16++ ) { ssid[2+tmp16] = hardcoded_ssid[tmp16]; }
  ssid[0] = sizeof(hardcoded_ssid) - 1; /* Ignore terminating zero */
  result = hfa384x_drvr_setconfig(hw, HFA384x_RID_CNFDESIREDSSID, ssid, HFA384x_RID_CNFDESIREDSSID_LEN); /* Set the SSID */
  if ( result ) printf ( "Set SSID command returned %#hx\n", result );
  tmp16 = 1; /* Set port type to ESS port */
  result = hfa384x_drvr_setconfig16(hw, HFA384x_RID_CNFPORTTYPE, &tmp16);
  if ( result ) printf ( "Set port type command returned %#hx\n", result );
  /* Enable card */
  result = hfa384x_docmd_wait(hw, HFA384x_CMD_CMDCODE_SET(HFA384x_CMDCODE_ENABLE) | HFA384x_CMD_MACPORT_SET(0), 0,0,0);
  if ( result ) printf ( "Enable command returned %#hx\n", result );

  do {
    /* Increment info_count, abort if too many attempts.
     * See comment next to definition of MAX_JOIN_INFO_COUNT for explanation.
     */
    info_count++;
    if ( info_count > MAX_JOIN_INFO_COUNT ) {
      printf ( "Too many failed attempts - aborting\n" );
      return 0;
    }

    /* Wait for info frame to indicate link status */
    if ( sizeof(hardcoded_ssid) == 1 ) {
      /* Empty SSID => join to any SSID */
      printf ( "Attempting to autojoin to any available access point (attempt %d)...", info_count );
    } else {
      printf ( "Attempting to autojoin to SSID %s (attempt %d)...", &ssid[2], info_count );
    }

    if ( !hfa384x_wait_for_event(hw, HFA384x_EVSTAT_INFO, 0, 1000, 2000, "Info event" ) ) return 0;
    printf("done\n");
    infofid = hfa384x_getreg(hw, HFA384x_INFOFID);
    /* Retrieve the length */
    result = hfa384x_copy_from_bap( hw, infofid, 0, &inf.framelen, sizeof(uint16_t));
    if ( result ) return 0; /* fail */
    inf.framelen = hfa384x2host_16(inf.framelen);
    /* Retrieve the rest */
    result = hfa384x_copy_from_bap( hw, infofid, sizeof(uint16_t),
				    &(inf.infotype), inf.framelen * sizeof(uint16_t));
    if ( result ) return 0; /* fail */
    if ( inf.infotype != HFA384x_IT_LINKSTATUS ) {
      /* Not a Link Status info frame: die */
      printf ( "Unexpected info frame type %#hx (not LinkStatus type)\n", inf.infotype );
      return 0;
    }
    inf.info.linkstatus.linkstatus = hfa384x2host_16(inf.info.linkstatus.linkstatus);
    if ( inf.info.linkstatus.linkstatus != HFA384x_LINK_CONNECTED ) {
      /* Link not connected - retry */
      printf ( "Link not connected (status %#hx)\n", inf.info.linkstatus.linkstatus );
    }
  } while ( inf.info.linkstatus.linkstatus != HFA384x_LINK_CONNECTED );

  /* Retrieve BSSID and print Connected message */
  result = hfa384x_drvr_getconfig(hw, HFA384x_RID_CURRENTBSSID, hw->bssid, WLAN_BSSID_LEN);

  DBG ( "Link connected (BSSID %s - ", eth_ntoa ( hw->bssid ) );
  DBG ( " MAC address %s)\n", eth_ntoa (nic->node_addr ) );

  /* point to NIC specific routines */
  nic->nic_op	= &prism2_operations;
  return 1;
}