Merge branch 'master' of /pub/scm/gpxe
[people/holger/gpxe.git] / src / arch / i386 / drivers / net / undinet.c
1 /*
2  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <string.h>
20 #include <pxe.h>
21 #include <realmode.h>
22 #include <pic8259.h>
23 #include <biosint.h>
24 #include <pnpbios.h>
25 #include <basemem_packet.h>
26 #include <gpxe/iobuf.h>
27 #include <gpxe/netdevice.h>
28 #include <gpxe/if_ether.h>
29 #include <gpxe/ethernet.h>
30 #include <undi.h>
31 #include <undinet.h>
32
33
34 /** @file
35  *
36  * UNDI network device driver
37  *
38  */
39
40 /** An UNDI NIC */
41 struct undi_nic {
42         /** Entry point */
43         SEGOFF16_t entry;
44         /** Assigned IRQ number */
45         unsigned int irq;
46         /** Currently processing ISR */
47         int isr_processing;
48         /** Bug workarounds */
49         int hacks;
50 };
51
52 /**
53  * @defgroup undi_hacks UNDI workarounds
54  * @{
55  */
56
57 /** Work around Etherboot 5.4 bugs */
58 #define UNDI_HACK_EB54          0x0001
59
60 /** @} */
61
62 static void undinet_close ( struct net_device *netdev );
63
64 /*****************************************************************************
65  *
66  * UNDI API call
67  *
68  *****************************************************************************
69  */
70
71 /**
72  * Name UNDI API call
73  *
74  * @v function          API call number
75  * @ret name            API call name
76  */
77 static inline __attribute__ (( always_inline )) const char *
78 undinet_function_name ( unsigned int function ) {
79         switch ( function ) {
80         case PXENV_START_UNDI:
81                 return "PXENV_START_UNDI";
82         case PXENV_STOP_UNDI:
83                 return "PXENV_STOP_UNDI";
84         case PXENV_UNDI_STARTUP:
85                 return "PXENV_UNDI_STARTUP";
86         case PXENV_UNDI_CLEANUP:
87                 return "PXENV_UNDI_CLEANUP";
88         case PXENV_UNDI_INITIALIZE:
89                 return "PXENV_UNDI_INITIALIZE";
90         case PXENV_UNDI_RESET_ADAPTER:
91                 return "PXENV_UNDI_RESET_ADAPTER";
92         case PXENV_UNDI_SHUTDOWN:
93                 return "PXENV_UNDI_SHUTDOWN";
94         case PXENV_UNDI_OPEN:
95                 return "PXENV_UNDI_OPEN";
96         case PXENV_UNDI_CLOSE:
97                 return "PXENV_UNDI_CLOSE";
98         case PXENV_UNDI_TRANSMIT:
99                 return "PXENV_UNDI_TRANSMIT";
100         case PXENV_UNDI_SET_MCAST_ADDRESS:
101                 return "PXENV_UNDI_SET_MCAST_ADDRESS";
102         case PXENV_UNDI_SET_STATION_ADDRESS:
103                 return "PXENV_UNDI_SET_STATION_ADDRESS";
104         case PXENV_UNDI_SET_PACKET_FILTER:
105                 return "PXENV_UNDI_SET_PACKET_FILTER";
106         case PXENV_UNDI_GET_INFORMATION:
107                 return "PXENV_UNDI_GET_INFORMATION";
108         case PXENV_UNDI_GET_STATISTICS:
109                 return "PXENV_UNDI_GET_STATISTICS";
110         case PXENV_UNDI_CLEAR_STATISTICS:
111                 return "PXENV_UNDI_CLEAR_STATISTICS";
112         case PXENV_UNDI_INITIATE_DIAGS:
113                 return "PXENV_UNDI_INITIATE_DIAGS";
114         case PXENV_UNDI_FORCE_INTERRUPT:
115                 return "PXENV_UNDI_FORCE_INTERRUPT";
116         case PXENV_UNDI_GET_MCAST_ADDRESS:
117                 return "PXENV_UNDI_GET_MCAST_ADDRESS";
118         case PXENV_UNDI_GET_NIC_TYPE:
119                 return "PXENV_UNDI_GET_NIC_TYPE";
120         case PXENV_UNDI_GET_IFACE_INFO:
121                 return "PXENV_UNDI_GET_IFACE_INFO";
122         /*
123          * Duplicate case value; this is a bug in the PXE specification.
124          *
125          *      case PXENV_UNDI_GET_STATE:
126          *              return "PXENV_UNDI_GET_STATE";
127          */
128         case PXENV_UNDI_ISR:
129                 return "PXENV_UNDI_ISR";
130         default:
131                 return "UNKNOWN API CALL";
132         }
133 }
134
135 /**
136  * UNDI parameter block
137  *
138  * Used as the paramter block for all UNDI API calls.  Resides in base
139  * memory.
140  */
141 static union u_PXENV_ANY __data16 ( undinet_params );
142 #define undinet_params __use_data16 ( undinet_params )
143
144 /** UNDI entry point
145  *
146  * Used as the indirection vector for all UNDI API calls.  Resides in
147  * base memory.
148  */
149 static SEGOFF16_t __data16 ( undinet_entry_point );
150 #define undinet_entry_point __use_data16 ( undinet_entry_point )
151
152 /**
153  * Issue UNDI API call
154  *
155  * @v undinic           UNDI NIC
156  * @v function          API call number
157  * @v params            UNDI parameter block
158  * @v params_len        Length of UNDI parameter block
159  * @ret rc              Return status code
160  */
161 static int undinet_call ( struct undi_nic *undinic, unsigned int function,
162                           void *params, size_t params_len ) {
163         PXENV_EXIT_t exit;
164         int discard_b, discard_D;
165         int rc;
166
167         /* Copy parameter block and entry point */
168         assert ( params_len <= sizeof ( undinet_params ) );
169         memcpy ( &undinet_params, params, params_len );
170         undinet_entry_point = undinic->entry;
171
172         /* Call real-mode entry point.  This calling convention will
173          * work with both the !PXE and the PXENV+ entry points.
174          */
175         __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t"
176                                            "pushw %%di\n\t"
177                                            "pushw %%bx\n\t"
178                                            "lcall *%c3\n\t"
179                                            "addw $6, %%sp\n\t" )
180                                : "=a" ( exit ), "=b" ( discard_b ),
181                                  "=D" ( discard_D )
182                                : "p" ( &__from_data16 ( undinet_entry_point )),
183                                  "b" ( function ),
184                                  "D" ( &__from_data16 ( undinet_params ) )
185                                : "ecx", "edx", "esi", "ebp" );
186
187         /* UNDI API calls may rudely change the status of A20 and not
188          * bother to restore it afterwards.  Intel is known to be
189          * guilty of this.
190          *
191          * Note that we will return to this point even if A20 gets
192          * screwed up by the UNDI driver, because Etherboot always
193          * resides in an even megabyte of RAM.
194          */     
195         gateA20_set();
196
197         /* Determine return status code based on PXENV_EXIT and
198          * PXENV_STATUS
199          */
200         if ( exit == PXENV_EXIT_SUCCESS ) {
201                 rc = 0;
202         } else {
203                 rc = -undinet_params.Status;
204                 /* Paranoia; don't return success for the combination
205                  * of PXENV_EXIT_FAILURE but PXENV_STATUS_SUCCESS
206                  */
207                 if ( rc == 0 )
208                         rc = -EIO;
209         }
210
211         /* If anything goes wrong, print as much debug information as
212          * it's possible to give.
213          */
214         if ( rc != 0 ) {
215                 SEGOFF16_t rm_params = {
216                         .segment = rm_ds,
217                         .offset = (intptr_t) &__from_data16 ( undinet_params ),
218                 };
219
220                 DBGC ( undinic, "UNDINIC %p %s failed: %s\n", undinic,
221                        undinet_function_name ( function ), strerror ( rc ) );
222                 DBGC ( undinic, "UNDINIC %p parameters at %04x:%04x length "
223                        "%#02x, entry point at %04x:%04x\n", undinic,
224                        rm_params.segment, rm_params.offset, params_len,
225                        undinic->entry.segment, undinic->entry.offset );
226                 DBGC ( undinic, "UNDINIC %p parameters provided:\n", undinic );
227                 DBGC_HDA ( undinic, rm_params, params, params_len );
228                 DBGC ( undinic, "UNDINIC %p parameters returned:\n", undinic );
229                 DBGC_HDA ( undinic, rm_params, &undinet_params, params_len );
230         }
231
232         /* Copy parameter block back */
233         memcpy ( params, &undinet_params, params_len );
234
235         return rc;
236 }
237
238 /*****************************************************************************
239  *
240  * UNDI interrupt service routine
241  *
242  *****************************************************************************
243  */
244
245 /**
246  * UNDI interrupt service routine
247  *
248  * The UNDI ISR simply increments a counter (@c trigger_count) and
249  * exits.
250  */
251 extern void undinet_isr ( void );
252
253 /** Dummy chain vector */
254 static struct segoff prev_handler[ IRQ_MAX + 1 ];
255
256 /** IRQ trigger count */
257 static volatile uint8_t __text16 ( trigger_count ) = 0;
258 #define trigger_count __use_text16 ( trigger_count )
259
260 /** Last observed trigger count */
261 static unsigned int last_trigger_count = 0;
262
263 /**
264  * Hook UNDI interrupt service routine
265  *
266  * @v irq               IRQ number
267  *
268  * The UNDI ISR specifically does @b not chain to the previous
269  * interrupt handler.  BIOSes seem to install somewhat perverse
270  * default interrupt handlers; some do nothing other than an iret (and
271  * so will cause a screaming interrupt if there really is another
272  * interrupting device) and some disable the interrupt at the PIC (and
273  * so will bring our own interrupts to a shuddering halt).
274  */
275 static void undinet_hook_isr ( unsigned int irq ) {
276
277         assert ( irq <= IRQ_MAX );
278
279         __asm__ __volatile__ ( TEXT16_CODE ( "\nundinet_isr:\n\t"
280                                              "incb %%cs:%c0\n\t"
281                                              "iret\n\t" )
282                                : : "p" ( & __from_text16 ( trigger_count ) ) );
283
284         hook_bios_interrupt ( IRQ_INT ( irq ),
285                               ( ( unsigned int ) undinet_isr ),
286                               &prev_handler[irq] );
287
288 }
289
290 /**
291  * Unhook UNDI interrupt service routine
292  *
293  * @v irq               IRQ number
294  */
295 static void undinet_unhook_isr ( unsigned int irq ) {
296
297         assert ( irq <= IRQ_MAX );
298
299         unhook_bios_interrupt ( IRQ_INT ( irq ),
300                                 ( ( unsigned int ) undinet_isr ),
301                                 &prev_handler[irq] );
302 }
303
304 /**
305  * Test to see if UNDI ISR has been triggered
306  *
307  * @ret triggered       ISR has been triggered since last check
308  */
309 static int undinet_isr_triggered ( void ) {
310         unsigned int this_trigger_count;
311
312         /* Read trigger_count.  Do this only once; it is volatile */
313         this_trigger_count = trigger_count;
314
315         if ( this_trigger_count == last_trigger_count ) {
316                 /* Not triggered */
317                 return 0;
318         } else {
319                 /* Triggered */
320                 last_trigger_count = this_trigger_count;
321                 return 1;
322         }
323 }
324
325 /*****************************************************************************
326  *
327  * UNDI network device interface
328  *
329  *****************************************************************************
330  */
331
332 /** UNDI transmit buffer descriptor */
333 static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
334 #define undinet_tbd __use_data16 ( undinet_tbd )
335
336 /**
337  * Transmit packet
338  *
339  * @v netdev            Network device
340  * @v iobuf             I/O buffer
341  * @ret rc              Return status code
342  */
343 static int undinet_transmit ( struct net_device *netdev,
344                               struct io_buffer *iobuf ) {
345         struct undi_nic *undinic = netdev->priv;
346         struct s_PXENV_UNDI_TRANSMIT undi_transmit;
347         size_t len = iob_len ( iobuf );
348         int rc;
349
350         /* Technically, we ought to make sure that the previous
351          * transmission has completed before we re-use the buffer.
352          * However, many PXE stacks (including at least some Intel PXE
353          * stacks and Etherboot 5.4) fail to generate TX completions.
354          * In practice this won't be a problem, since our TX datapath
355          * has a very low packet volume and we can get away with
356          * assuming that a TX will be complete by the time we want to
357          * transmit the next packet.
358          */
359
360         /* Copy packet to UNDI I/O buffer */
361         if ( len > sizeof ( basemem_packet ) )
362                 len = sizeof ( basemem_packet );
363         memcpy ( &basemem_packet, iobuf->data, len );
364
365         /* Create PXENV_UNDI_TRANSMIT data structure */
366         memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
367         undi_transmit.DestAddr.segment = rm_ds;
368         undi_transmit.DestAddr.offset
369                 = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
370         undi_transmit.TBD.segment = rm_ds;
371         undi_transmit.TBD.offset
372                 = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
373
374         /* Create PXENV_UNDI_TBD data structure */
375         undinet_tbd.ImmedLength = len;
376         undinet_tbd.Xmit.segment = rm_ds;
377         undinet_tbd.Xmit.offset 
378                 = ( ( unsigned ) & __from_data16 ( basemem_packet ) );
379
380         /* Issue PXE API call */
381         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_TRANSMIT,
382                                    &undi_transmit,
383                                    sizeof ( undi_transmit ) ) ) != 0 )
384                 goto done;
385
386         /* Free I/O buffer */
387         netdev_tx_complete ( netdev, iobuf );
388
389  done:
390         return rc;
391 }
392
393 /** 
394  * Poll for received packets
395  *
396  * @v netdev            Network device
397  * @v rx_quota          Maximum number of packets to receive
398  *
399  * Fun, fun, fun.  UNDI drivers don't use polling; they use
400  * interrupts.  We therefore cheat and pretend that an interrupt has
401  * occurred every time undinet_poll() is called.  This isn't too much
402  * of a hack; PCI devices share IRQs and so the first thing that a
403  * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
404  * not the UNDI NIC generated the interrupt; there is no harm done by
405  * spurious calls to PXENV_UNDI_ISR.  Similarly, we wouldn't be
406  * handling them any more rapidly than the usual rate of
407  * undinet_poll() being called even if we did implement a full ISR.
408  * So it should work.  Ha!
409  *
410  * Addendum (21/10/03).  Some cards don't play nicely with this trick,
411  * so instead of doing it the easy way we have to go to all the hassle
412  * of installing a genuine interrupt service routine and dealing with
413  * the wonderful 8259 Programmable Interrupt Controller.  Joy.
414  */
415 static void undinet_poll ( struct net_device *netdev, unsigned int rx_quota ) {
416         struct undi_nic *undinic = netdev->priv;
417         struct s_PXENV_UNDI_ISR undi_isr;
418         struct io_buffer *iobuf = NULL;
419         size_t len;
420         size_t frag_len;
421         int rc;
422
423         if ( ! undinic->isr_processing ) {
424                 /* Do nothing unless ISR has been triggered */
425                 if ( ! undinet_isr_triggered() )
426                         return;
427
428                 /* See if this was our interrupt */
429                 memset ( &undi_isr, 0, sizeof ( undi_isr ) );
430                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_START;
431                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
432                                            sizeof ( undi_isr ) ) ) != 0 )
433                         return;
434
435                 /* Send EOI to the PIC.  In an ideal world, we'd do
436                  * this only for interrupts which the UNDI stack
437                  * reports as "ours".  However, since we don't (can't)
438                  * chain to the previous interrupt handler, we have to
439                  * acknowledge all interrupts.  See undinet_hook_isr()
440                  * for more background.
441                  */
442                 send_eoi ( undinic->irq );
443
444                 /* If this wasn't our interrupt, exit now */
445                 if ( undi_isr.FuncFlag != PXENV_UNDI_ISR_OUT_OURS )
446                         return;
447                 
448                 /* Start ISR processing */
449                 undinic->isr_processing = 1;
450                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
451         } else {
452                 /* Continue ISR processing */
453                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
454         }
455
456         /* Run through the ISR loop */
457         while ( rx_quota ) {
458                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
459                                            sizeof ( undi_isr ) ) ) != 0 )
460                         break;
461                 switch ( undi_isr.FuncFlag ) {
462                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
463                         /* We don't care about transmit completions */
464                         break;
465                 case PXENV_UNDI_ISR_OUT_RECEIVE:
466                         /* Packet fragment received */
467                         len = undi_isr.FrameLength;
468                         frag_len = undi_isr.BufferLength;
469                         if ( ! iobuf )
470                                 iobuf = alloc_iob ( len );
471                         if ( ! iobuf ) {
472                                 DBGC ( undinic, "UNDINIC %p could not "
473                                        "allocate %zd bytes for RX buffer\n",
474                                        undinic, len );
475                                 /* Fragment will be dropped */
476                                 goto done;
477                         }
478                         if ( frag_len > iob_tailroom ( iobuf ) ) {
479                                 DBGC ( undinic, "UNDINIC %p fragment too "
480                                        "large\n", undinic );
481                                 frag_len = iob_tailroom ( iobuf );
482                         }
483                         copy_from_real ( iob_put ( iobuf, frag_len ),
484                                          undi_isr.Frame.segment,
485                                          undi_isr.Frame.offset, frag_len );
486                         if ( iob_len ( iobuf ) == len ) {
487                                 /* Whole packet received; deliver it */
488                                 netdev_rx ( netdev, iobuf );
489                                 iobuf = NULL;
490                                 --rx_quota;
491                                 /* Etherboot 5.4 fails to return all packets
492                                  * under mild load; pretend it retriggered.
493                                  */
494                                 if ( undinic->hacks & UNDI_HACK_EB54 )
495                                         --last_trigger_count;
496                         }
497                         break;
498                 case PXENV_UNDI_ISR_OUT_DONE:
499                         /* Processing complete */
500                         undinic->isr_processing = 0;
501                         goto done;
502                 default:
503                         /* Should never happen */
504                         DBGC ( undinic, "UNDINIC %p ISR returned invalid "
505                                "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
506                         undinic->isr_processing = 0;
507                         goto done;
508                 }
509                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
510         }
511
512  done:
513         if ( iobuf ) {
514                 DBGC ( undinic, "UNDINIC %p returned incomplete packet\n",
515                        undinic );
516                 netdev_rx ( netdev, iobuf );
517         }
518 }
519
520 /**
521  * Open NIC
522  *
523  * @v netdev            Net device
524  * @ret rc              Return status code
525  */
526 static int undinet_open ( struct net_device *netdev ) {
527         struct undi_nic *undinic = netdev->priv;
528         struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
529         struct s_PXENV_UNDI_OPEN undi_open;
530         int rc;
531
532         /* Hook interrupt service routine and enable interrupt */
533         undinet_hook_isr ( undinic->irq );
534         enable_irq ( undinic->irq );
535         send_eoi ( undinic->irq );
536
537         /* Set station address.  Required for some PXE stacks; will
538          * spuriously fail on others.  Ignore failures.  We only ever
539          * use it to set the MAC address to the card's permanent value
540          * anyway.
541          */
542         memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
543                  sizeof ( undi_set_address.StationAddress ) );
544         undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS,
545                        &undi_set_address, sizeof ( undi_set_address ) );
546
547         /* Open NIC */
548         memset ( &undi_open, 0, sizeof ( undi_open ) );
549         undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST );
550         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_OPEN, &undi_open,
551                                    sizeof ( undi_open ) ) ) != 0 )
552                 goto err;
553
554         DBGC ( undinic, "UNDINIC %p opened\n", undinic );
555         return 0;
556
557  err:
558         undinet_close ( netdev );
559         return rc;
560 }
561
562 /**
563  * Close NIC
564  *
565  * @v netdev            Net device
566  */
567 static void undinet_close ( struct net_device *netdev ) {
568         struct undi_nic *undinic = netdev->priv;
569         struct s_PXENV_UNDI_ISR undi_isr;
570         struct s_PXENV_UNDI_CLOSE undi_close;
571         int rc;
572
573         /* Ensure ISR has exited cleanly */
574         while ( undinic->isr_processing ) {
575                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
576                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
577                                            sizeof ( undi_isr ) ) ) != 0 )
578                         break;
579                 switch ( undi_isr.FuncFlag ) {
580                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
581                 case PXENV_UNDI_ISR_OUT_RECEIVE:
582                         /* Continue draining */
583                         break;
584                 default:
585                         /* Stop processing */
586                         undinic->isr_processing = 0;
587                         break;
588                 }
589         }
590
591         /* Close NIC */
592         undinet_call ( undinic, PXENV_UNDI_CLOSE, &undi_close,
593                        sizeof ( undi_close ) );
594
595         /* Disable interrupt and unhook ISR */
596         disable_irq ( undinic->irq );
597         undinet_unhook_isr ( undinic->irq );
598
599         DBGC ( undinic, "UNDINIC %p closed\n", undinic );
600 }
601
602 /**
603  * Probe UNDI device
604  *
605  * @v undi              UNDI device
606  * @ret rc              Return status code
607  */
608 int undinet_probe ( struct undi_device *undi ) {
609         struct net_device *netdev;
610         struct undi_nic *undinic;
611         struct s_PXENV_START_UNDI start_undi;
612         struct s_PXENV_UNDI_STARTUP undi_startup;
613         struct s_PXENV_UNDI_INITIALIZE undi_initialize;
614         struct s_PXENV_UNDI_GET_INFORMATION undi_info;
615         struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
616         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
617         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
618         struct s_PXENV_STOP_UNDI stop_undi;
619         int rc;
620
621         /* Allocate net device */
622         netdev = alloc_etherdev ( sizeof ( *undinic ) );
623         if ( ! netdev )
624                 return -ENOMEM;
625         undinic = netdev->priv;
626         undi_set_drvdata ( undi, netdev );
627         netdev->dev = &undi->dev;
628         memset ( undinic, 0, sizeof ( *undinic ) );
629         undinic->entry = undi->entry;
630         DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
631
632         /* Hook in UNDI stack */
633         if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
634                 memset ( &start_undi, 0, sizeof ( start_undi ) );
635                 start_undi.AX = undi->pci_busdevfn;
636                 start_undi.BX = undi->isapnp_csn;
637                 start_undi.DX = undi->isapnp_read_port;
638                 start_undi.ES = BIOS_SEG;
639                 start_undi.DI = find_pnp_bios();
640                 if ( ( rc = undinet_call ( undinic, PXENV_START_UNDI,
641                                            &start_undi,
642                                            sizeof ( start_undi ) ) ) != 0 )
643                         goto err_start_undi;
644         }
645         undi->flags |= UNDI_FL_STARTED;
646
647         /* Bring up UNDI stack */
648         memset ( &undi_startup, 0, sizeof ( undi_startup ) );
649         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP, &undi_startup,
650                                    sizeof ( undi_startup ) ) ) != 0 )
651                 goto err_undi_startup;
652         memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
653         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE,
654                                    &undi_initialize,
655                                    sizeof ( undi_initialize ) ) ) != 0 )
656                 goto err_undi_initialize;
657
658         /* Get device information */
659         memset ( &undi_info, 0, sizeof ( undi_info ) );
660         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION,
661                                    &undi_info, sizeof ( undi_info ) ) ) != 0 )
662                 goto err_undi_get_information;
663         memcpy ( netdev->ll_addr, undi_info.PermNodeAddress, ETH_ALEN );
664         undinic->irq = undi_info.IntNumber;
665         if ( undinic->irq > IRQ_MAX ) {
666                 DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
667                        undinic, undinic->irq );
668                 goto err_bad_irq;
669         }
670         DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
671                undinic, eth_ntoa ( netdev->ll_addr ), undinic->irq );
672
673         /* Get interface information */
674         memset ( &undi_iface, 0, sizeof ( undi_iface ) );
675         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO,
676                                    &undi_iface,
677                                    sizeof ( undi_iface ) ) ) != 0 )
678                 goto err_undi_get_iface_info;
679         DBGC ( undinic, "UNDINIC %p has type %s and link speed %ld\n",
680                undinic, undi_iface.IfaceType, undi_iface.LinkSpeed );
681         if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
682                        sizeof ( undi_iface.IfaceType ) ) == 0 ) {
683                 DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
684                        undinic );
685                 undinic->hacks |= UNDI_HACK_EB54;
686         }
687
688         /* Point to NIC specific routines */
689         netdev->open     = undinet_open;
690         netdev->close    = undinet_close;
691         netdev->transmit = undinet_transmit;
692         netdev->poll     = undinet_poll;
693
694         /* Register network device */
695         if ( ( rc = register_netdev ( netdev ) ) != 0 )
696                 goto err_register;
697
698         DBGC ( undinic, "UNDINIC %p added\n", undinic );
699         return 0;
700
701  err_register:
702  err_undi_get_iface_info:
703  err_bad_irq:
704  err_undi_get_information:
705  err_undi_initialize:
706         /* Shut down UNDI stack */
707         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
708         undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
709                        sizeof ( undi_shutdown ) );
710         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
711         undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
712                        sizeof ( undi_cleanup ) );
713  err_undi_startup:
714         /* Unhook UNDI stack */
715         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
716         undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
717                        sizeof ( stop_undi ) );
718  err_start_undi:
719         netdev_put ( netdev );
720         undi_set_drvdata ( undi, NULL );
721         return rc;
722 }
723
724 /**
725  * Remove UNDI device
726  *
727  * @v undi              UNDI device
728  */
729 void undinet_remove ( struct undi_device *undi ) {
730         struct net_device *netdev = undi_get_drvdata ( undi );
731         struct undi_nic *undinic = netdev->priv;
732         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
733         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
734         struct s_PXENV_STOP_UNDI stop_undi;
735
736         /* Unregister net device */
737         unregister_netdev ( netdev );
738
739         /* Shut down UNDI stack */
740         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
741         undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
742                        sizeof ( undi_shutdown ) );
743         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
744         undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
745                        sizeof ( undi_cleanup ) );
746
747         /* Unhook UNDI stack */
748         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
749         undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
750                        sizeof ( stop_undi ) );
751         undi->flags &= ~UNDI_FL_STARTED;
752
753         /* Free network device */
754         netdev_put ( netdev );
755
756         DBGC ( undinic, "UNDINIC %p removed\n", undinic );
757 }