d6db6f7c15e9f4b53e4ceed93f43ae8f0ab91479
[people/mcb30/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/io.h>
27 #include <gpxe/iobuf.h>
28 #include <gpxe/netdevice.h>
29 #include <gpxe/if_ether.h>
30 #include <gpxe/ethernet.h>
31 #include <undi.h>
32 #include <undinet.h>
33
34
35 /** @file
36  *
37  * UNDI network device driver
38  *
39  */
40
41 /** An UNDI NIC */
42 struct undi_nic {
43         /** Assigned IRQ number */
44         unsigned int irq;
45         /** Currently processing ISR */
46         int isr_processing;
47         /** Bug workarounds */
48         int hacks;
49 };
50
51 /**
52  * @defgroup undi_hacks UNDI workarounds
53  * @{
54  */
55
56 /** Work around Etherboot 5.4 bugs */
57 #define UNDI_HACK_EB54          0x0001
58
59 /** @} */
60
61 static void undinet_close ( struct net_device *netdev );
62
63 /*****************************************************************************
64  *
65  * UNDI API call
66  *
67  *****************************************************************************
68  */
69
70 /**
71  * Name UNDI API call
72  *
73  * @v function          API call number
74  * @ret name            API call name
75  */
76 static inline __attribute__ (( always_inline )) const char *
77 undinet_function_name ( unsigned int function ) {
78         switch ( function ) {
79         case PXENV_START_UNDI:
80                 return "PXENV_START_UNDI";
81         case PXENV_STOP_UNDI:
82                 return "PXENV_STOP_UNDI";
83         case PXENV_UNDI_STARTUP:
84                 return "PXENV_UNDI_STARTUP";
85         case PXENV_UNDI_CLEANUP:
86                 return "PXENV_UNDI_CLEANUP";
87         case PXENV_UNDI_INITIALIZE:
88                 return "PXENV_UNDI_INITIALIZE";
89         case PXENV_UNDI_RESET_ADAPTER:
90                 return "PXENV_UNDI_RESET_ADAPTER";
91         case PXENV_UNDI_SHUTDOWN:
92                 return "PXENV_UNDI_SHUTDOWN";
93         case PXENV_UNDI_OPEN:
94                 return "PXENV_UNDI_OPEN";
95         case PXENV_UNDI_CLOSE:
96                 return "PXENV_UNDI_CLOSE";
97         case PXENV_UNDI_TRANSMIT:
98                 return "PXENV_UNDI_TRANSMIT";
99         case PXENV_UNDI_SET_MCAST_ADDRESS:
100                 return "PXENV_UNDI_SET_MCAST_ADDRESS";
101         case PXENV_UNDI_SET_STATION_ADDRESS:
102                 return "PXENV_UNDI_SET_STATION_ADDRESS";
103         case PXENV_UNDI_SET_PACKET_FILTER:
104                 return "PXENV_UNDI_SET_PACKET_FILTER";
105         case PXENV_UNDI_GET_INFORMATION:
106                 return "PXENV_UNDI_GET_INFORMATION";
107         case PXENV_UNDI_GET_STATISTICS:
108                 return "PXENV_UNDI_GET_STATISTICS";
109         case PXENV_UNDI_CLEAR_STATISTICS:
110                 return "PXENV_UNDI_CLEAR_STATISTICS";
111         case PXENV_UNDI_INITIATE_DIAGS:
112                 return "PXENV_UNDI_INITIATE_DIAGS";
113         case PXENV_UNDI_FORCE_INTERRUPT:
114                 return "PXENV_UNDI_FORCE_INTERRUPT";
115         case PXENV_UNDI_GET_MCAST_ADDRESS:
116                 return "PXENV_UNDI_GET_MCAST_ADDRESS";
117         case PXENV_UNDI_GET_NIC_TYPE:
118                 return "PXENV_UNDI_GET_NIC_TYPE";
119         case PXENV_UNDI_GET_IFACE_INFO:
120                 return "PXENV_UNDI_GET_IFACE_INFO";
121         /*
122          * Duplicate case value; this is a bug in the PXE specification.
123          *
124          *      case PXENV_UNDI_GET_STATE:
125          *              return "PXENV_UNDI_GET_STATE";
126          */
127         case PXENV_UNDI_ISR:
128                 return "PXENV_UNDI_ISR";
129         default:
130                 return "UNKNOWN API CALL";
131         }
132 }
133
134 /**
135  * UNDI parameter block
136  *
137  * Used as the paramter block for all UNDI API calls.  Resides in base
138  * memory.
139  */
140 static union u_PXENV_ANY __bss16 ( undinet_params );
141 #define undinet_params __use_data16 ( undinet_params )
142
143 /** UNDI entry point
144  *
145  * Used as the indirection vector for all UNDI API calls.  Resides in
146  * base memory.
147  */
148 SEGOFF16_t __bss16 ( undinet_entry_point );
149 #define undinet_entry_point __use_data16 ( undinet_entry_point )
150
151 /**
152  * Issue UNDI API call
153  *
154  * @v undinic           UNDI NIC
155  * @v function          API call number
156  * @v params            UNDI parameter block
157  * @v params_len        Length of UNDI parameter block
158  * @ret rc              Return status code
159  */
160 static int undinet_call ( struct undi_nic *undinic, unsigned int function,
161                           void *params, size_t params_len ) {
162         PXENV_EXIT_t exit;
163         int discard_b, discard_D;
164         int rc;
165
166         /* Copy parameter block and entry point */
167         assert ( params_len <= sizeof ( undinet_params ) );
168         memcpy ( &undinet_params, params, params_len );
169
170         /* Call real-mode entry point.  This calling convention will
171          * work with both the !PXE and the PXENV+ entry points.
172          */
173         __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t"
174                                            "pushw %%di\n\t"
175                                            "pushw %%bx\n\t"
176                                            "lcall *%c3\n\t"
177                                            "addw $6, %%sp\n\t" )
178                                : "=a" ( exit ), "=b" ( discard_b ),
179                                  "=D" ( discard_D )
180                                : "p" ( __from_data16 ( &undinet_entry_point )),
181                                  "b" ( function ),
182                                  "D" ( __from_data16 ( &undinet_params ) )
183                                : "ecx", "edx", "esi", "ebp" );
184
185         /* UNDI API calls may rudely change the status of A20 and not
186          * bother to restore it afterwards.  Intel is known to be
187          * guilty of this.
188          *
189          * Note that we will return to this point even if A20 gets
190          * screwed up by the UNDI driver, because Etherboot always
191          * resides in an even megabyte of RAM.
192          */     
193         gateA20_set();
194
195         /* Determine return status code based on PXENV_EXIT and
196          * PXENV_STATUS
197          */
198         if ( exit == PXENV_EXIT_SUCCESS ) {
199                 rc = 0;
200         } else {
201                 rc = -undinet_params.Status;
202                 /* Paranoia; don't return success for the combination
203                  * of PXENV_EXIT_FAILURE but PXENV_STATUS_SUCCESS
204                  */
205                 if ( rc == 0 )
206                         rc = -EIO;
207         }
208
209         /* If anything goes wrong, print as much debug information as
210          * it's possible to give.
211          */
212         if ( rc != 0 ) {
213                 SEGOFF16_t rm_params = {
214                         .segment = rm_ds,
215                         .offset = __from_data16 ( &undinet_params ),
216                 };
217
218                 DBGC ( undinic, "UNDINIC %p %s failed: %s\n", undinic,
219                        undinet_function_name ( function ), strerror ( rc ) );
220                 DBGC ( undinic, "UNDINIC %p parameters at %04x:%04x length "
221                        "%#02zx, entry point at %04x:%04x\n", undinic,
222                        rm_params.segment, rm_params.offset, params_len,
223                        undinet_entry_point.segment,
224                        undinet_entry_point.offset );
225                 DBGC ( undinic, "UNDINIC %p parameters provided:\n", undinic );
226                 DBGC_HDA ( undinic, rm_params, params, params_len );
227                 DBGC ( undinic, "UNDINIC %p parameters returned:\n", undinic );
228                 DBGC_HDA ( undinic, rm_params, &undinet_params, params_len );
229         }
230
231         /* Copy parameter block back */
232         memcpy ( params, &undinet_params, params_len );
233
234         return rc;
235 }
236
237 /*****************************************************************************
238  *
239  * UNDI interrupt service routine
240  *
241  *****************************************************************************
242  */
243
244 /**
245  * UNDI interrupt service routine
246  *
247  * The UNDI ISR increments a counter (@c trigger_count) and exits.
248  */
249 extern void undiisr ( void );
250
251 /** IRQ number */
252 uint8_t __data16 ( undiisr_irq );
253 #define undiisr_irq __use_data16 ( undiisr_irq )
254
255 /** IRQ chain vector */
256 struct segoff __data16 ( undiisr_next_handler );
257 #define undiisr_next_handler __use_data16 ( undiisr_next_handler )
258
259 /** IRQ trigger count */
260 volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
261 #define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
262
263 /** Last observed trigger count */
264 static unsigned int last_trigger_count = 0;
265
266 /**
267  * Hook UNDI interrupt service routine
268  *
269  * @v irq               IRQ number
270  */
271 static void undinet_hook_isr ( unsigned int irq ) {
272
273         assert ( irq <= IRQ_MAX );
274         assert ( undiisr_irq == 0 );
275
276         undiisr_irq = irq;
277         hook_bios_interrupt ( IRQ_INT ( irq ),
278                               ( ( unsigned int ) undiisr ),
279                               &undiisr_next_handler );
280 }
281
282 /**
283  * Unhook UNDI interrupt service routine
284  *
285  * @v irq               IRQ number
286  */
287 static void undinet_unhook_isr ( unsigned int irq ) {
288
289         assert ( irq <= IRQ_MAX );
290
291         unhook_bios_interrupt ( IRQ_INT ( irq ),
292                                 ( ( unsigned int ) undiisr ),
293                                 &undiisr_next_handler );
294         undiisr_irq = 0;
295 }
296
297 /**
298  * Test to see if UNDI ISR has been triggered
299  *
300  * @ret triggered       ISR has been triggered since last check
301  */
302 static int undinet_isr_triggered ( void ) {
303         unsigned int this_trigger_count;
304
305         /* Read trigger_count.  Do this only once; it is volatile */
306         this_trigger_count = undiisr_trigger_count;
307
308         if ( this_trigger_count == last_trigger_count ) {
309                 /* Not triggered */
310                 return 0;
311         } else {
312                 /* Triggered */
313                 last_trigger_count = this_trigger_count;
314                 return 1;
315         }
316 }
317
318 /*****************************************************************************
319  *
320  * UNDI network device interface
321  *
322  *****************************************************************************
323  */
324
325 /** UNDI transmit buffer descriptor */
326 static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
327 #define undinet_tbd __use_data16 ( undinet_tbd )
328
329 /**
330  * Transmit packet
331  *
332  * @v netdev            Network device
333  * @v iobuf             I/O buffer
334  * @ret rc              Return status code
335  */
336 static int undinet_transmit ( struct net_device *netdev,
337                               struct io_buffer *iobuf ) {
338         struct undi_nic *undinic = netdev->priv;
339         struct s_PXENV_UNDI_TRANSMIT undi_transmit;
340         size_t len = iob_len ( iobuf );
341         int rc;
342
343         /* Technically, we ought to make sure that the previous
344          * transmission has completed before we re-use the buffer.
345          * However, many PXE stacks (including at least some Intel PXE
346          * stacks and Etherboot 5.4) fail to generate TX completions.
347          * In practice this won't be a problem, since our TX datapath
348          * has a very low packet volume and we can get away with
349          * assuming that a TX will be complete by the time we want to
350          * transmit the next packet.
351          */
352
353         /* Copy packet to UNDI I/O buffer */
354         if ( len > sizeof ( basemem_packet ) )
355                 len = sizeof ( basemem_packet );
356         memcpy ( &basemem_packet, iobuf->data, len );
357
358         /* Create PXENV_UNDI_TRANSMIT data structure */
359         memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
360         undi_transmit.DestAddr.segment = rm_ds;
361         undi_transmit.DestAddr.offset = __from_data16 ( &undinet_tbd );
362         undi_transmit.TBD.segment = rm_ds;
363         undi_transmit.TBD.offset = __from_data16 ( &undinet_tbd );
364
365         /* Create PXENV_UNDI_TBD data structure */
366         undinet_tbd.ImmedLength = len;
367         undinet_tbd.Xmit.segment = rm_ds;
368         undinet_tbd.Xmit.offset = __from_data16 ( basemem_packet );
369
370         /* Issue PXE API call */
371         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_TRANSMIT,
372                                    &undi_transmit,
373                                    sizeof ( undi_transmit ) ) ) != 0 )
374                 goto done;
375
376         /* Free I/O buffer */
377         netdev_tx_complete ( netdev, iobuf );
378
379  done:
380         return rc;
381 }
382
383 /** 
384  * Poll for received packets
385  *
386  * @v netdev            Network device
387  *
388  * Fun, fun, fun.  UNDI drivers don't use polling; they use
389  * interrupts.  We therefore cheat and pretend that an interrupt has
390  * occurred every time undinet_poll() is called.  This isn't too much
391  * of a hack; PCI devices share IRQs and so the first thing that a
392  * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
393  * not the UNDI NIC generated the interrupt; there is no harm done by
394  * spurious calls to PXENV_UNDI_ISR.  Similarly, we wouldn't be
395  * handling them any more rapidly than the usual rate of
396  * undinet_poll() being called even if we did implement a full ISR.
397  * So it should work.  Ha!
398  *
399  * Addendum (21/10/03).  Some cards don't play nicely with this trick,
400  * so instead of doing it the easy way we have to go to all the hassle
401  * of installing a genuine interrupt service routine and dealing with
402  * the wonderful 8259 Programmable Interrupt Controller.  Joy.
403  *
404  * Addendum (10/07/07).  When doing things such as iSCSI boot, in
405  * which we have to co-operate with a running OS, we can't get away
406  * with the "ISR-just-increments-a-counter-and-returns" trick at all,
407  * because it involves tying up the PIC for far too long, and other
408  * interrupt-dependent components (e.g. local disks) start breaking.
409  * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR
410  * from within interrupt context in order to deassert the device
411  * interrupt, and sends EOI if applicable.
412  */
413 static void undinet_poll ( struct net_device *netdev ) {
414         struct undi_nic *undinic = netdev->priv;
415         struct s_PXENV_UNDI_ISR undi_isr;
416         struct io_buffer *iobuf = NULL;
417         size_t len;
418         size_t frag_len;
419         size_t max_frag_len;
420         int rc;
421
422         if ( ! undinic->isr_processing ) {
423                 /* Do nothing unless ISR has been triggered */
424                 if ( ! undinet_isr_triggered() ) {
425                         /* Allow interrupt to occur */
426                         __asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
427                                                            "nop\n\t"
428                                                            "nop\n\t"
429                                                            "cli\n\t" ) : : );
430                         return;
431                 }
432
433                 /* Start ISR processing */
434                 undinic->isr_processing = 1;
435                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
436         } else {
437                 /* Continue ISR processing */
438                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
439         }
440
441         /* Run through the ISR loop */
442         while ( 1 ) {
443                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
444                                            sizeof ( undi_isr ) ) ) != 0 )
445                         break;
446                 switch ( undi_isr.FuncFlag ) {
447                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
448                         /* We don't care about transmit completions */
449                         break;
450                 case PXENV_UNDI_ISR_OUT_RECEIVE:
451                         /* Packet fragment received */
452                         len = undi_isr.FrameLength;
453                         frag_len = undi_isr.BufferLength;
454                         if ( ( len == 0 ) || ( len < frag_len ) ) {
455                                 /* Don't laugh.  VMWare does it. */
456                                 DBGC ( undinic, "UNDINIC %p reported insane "
457                                        "fragment (%zd of %zd bytes)\n",
458                                        undinic, frag_len, len );
459                                 netdev_rx_err ( netdev, NULL, -EINVAL );
460                                 break;
461                         }
462                         if ( ! iobuf )
463                                 iobuf = alloc_iob ( len );
464                         if ( ! iobuf ) {
465                                 DBGC ( undinic, "UNDINIC %p could not "
466                                        "allocate %zd bytes for RX buffer\n",
467                                        undinic, len );
468                                 /* Fragment will be dropped */
469                                 netdev_rx_err ( netdev, NULL, -ENOMEM );
470                                 goto done;
471                         }
472                         max_frag_len = iob_tailroom ( iobuf );
473                         if ( frag_len > max_frag_len ) {
474                                 DBGC ( undinic, "UNDINIC %p fragment too big "
475                                        "(%zd+%zd does not fit into %zd)\n",
476                                        undinic, iob_len ( iobuf ), frag_len,
477                                        ( iob_len ( iobuf ) + max_frag_len ) );
478                                 frag_len = max_frag_len;
479                         }
480                         copy_from_real ( iob_put ( iobuf, frag_len ),
481                                          undi_isr.Frame.segment,
482                                          undi_isr.Frame.offset, frag_len );
483                         if ( iob_len ( iobuf ) == len ) {
484                                 /* Whole packet received; deliver it */
485                                 netdev_rx ( netdev, iob_disown ( iobuf ) );
486                                 /* Etherboot 5.4 fails to return all packets
487                                  * under mild load; pretend it retriggered.
488                                  */
489                                 if ( undinic->hacks & UNDI_HACK_EB54 )
490                                         --last_trigger_count;
491                         }
492                         break;
493                 case PXENV_UNDI_ISR_OUT_DONE:
494                         /* Processing complete */
495                         undinic->isr_processing = 0;
496                         goto done;
497                 default:
498                         /* Should never happen.  VMWare does it routinely. */
499                         DBGC ( undinic, "UNDINIC %p ISR returned invalid "
500                                "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
501                         undinic->isr_processing = 0;
502                         goto done;
503                 }
504                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
505         }
506
507  done:
508         if ( iobuf ) {
509                 DBGC ( undinic, "UNDINIC %p returned incomplete packet "
510                        "(%zd of %zd)\n", undinic, iob_len ( iobuf ),
511                        ( iob_len ( iobuf ) + iob_tailroom ( iobuf ) ) );
512                 netdev_rx_err ( netdev, iobuf, -EINVAL );
513         }
514 }
515
516 /**
517  * Open NIC
518  *
519  * @v netdev            Net device
520  * @ret rc              Return status code
521  */
522 static int undinet_open ( struct net_device *netdev ) {
523         struct undi_nic *undinic = netdev->priv;
524         struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
525         struct s_PXENV_UNDI_OPEN undi_open;
526         int rc;
527
528         /* Hook interrupt service routine and enable interrupt */
529         undinet_hook_isr ( undinic->irq );
530         enable_irq ( undinic->irq );
531         send_eoi ( undinic->irq );
532
533         /* Set station address.  Required for some PXE stacks; will
534          * spuriously fail on others.  Ignore failures.  We only ever
535          * use it to set the MAC address to the card's permanent value
536          * anyway.
537          */
538         memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
539                  sizeof ( undi_set_address.StationAddress ) );
540         undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS,
541                        &undi_set_address, sizeof ( undi_set_address ) );
542
543         /* Open NIC.  We ask for promiscuous operation, since it's the
544          * only way to ask for all multicast addresses.  On any
545          * switched network, it shouldn't really make a difference to
546          * performance.
547          */
548         memset ( &undi_open, 0, sizeof ( undi_open ) );
549         undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST | FLTR_PRMSCS );
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  * Enable/disable interrupts
604  *
605  * @v netdev            Net device
606  * @v enable            Interrupts should be enabled
607  */
608 static void undinet_irq ( struct net_device *netdev, int enable ) {
609         struct undi_nic *undinic = netdev->priv;
610
611         /* Cannot support interrupts yet */
612         DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
613                undinic, ( enable ? "enable" : "disable" ) );
614 }
615
616 /** UNDI network device operations */
617 static struct net_device_operations undinet_operations = {
618         .open           = undinet_open,
619         .close          = undinet_close,
620         .transmit       = undinet_transmit,
621         .poll           = undinet_poll,
622         .irq            = undinet_irq,
623 };
624
625 /**
626  * Probe UNDI device
627  *
628  * @v undi              UNDI device
629  * @ret rc              Return status code
630  */
631 int undinet_probe ( struct undi_device *undi ) {
632         struct net_device *netdev;
633         struct undi_nic *undinic;
634         struct s_PXENV_START_UNDI start_undi;
635         struct s_PXENV_UNDI_STARTUP undi_startup;
636         struct s_PXENV_UNDI_INITIALIZE undi_initialize;
637         struct s_PXENV_UNDI_GET_INFORMATION undi_info;
638         struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
639         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
640         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
641         struct s_PXENV_STOP_UNDI stop_undi;
642         int rc;
643
644         /* Allocate net device */
645         netdev = alloc_etherdev ( sizeof ( *undinic ) );
646         if ( ! netdev )
647                 return -ENOMEM;
648         netdev_init ( netdev, &undinet_operations );
649         undinic = netdev->priv;
650         undi_set_drvdata ( undi, netdev );
651         netdev->dev = &undi->dev;
652         memset ( undinic, 0, sizeof ( *undinic ) );
653         undinet_entry_point = undi->entry;
654         DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
655
656         /* Hook in UNDI stack */
657         if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
658                 memset ( &start_undi, 0, sizeof ( start_undi ) );
659                 start_undi.AX = undi->pci_busdevfn;
660                 start_undi.BX = undi->isapnp_csn;
661                 start_undi.DX = undi->isapnp_read_port;
662                 start_undi.ES = BIOS_SEG;
663                 start_undi.DI = find_pnp_bios();
664                 if ( ( rc = undinet_call ( undinic, PXENV_START_UNDI,
665                                            &start_undi,
666                                            sizeof ( start_undi ) ) ) != 0 )
667                         goto err_start_undi;
668         }
669         undi->flags |= UNDI_FL_STARTED;
670
671         /* Bring up UNDI stack */
672         if ( ! ( undi->flags & UNDI_FL_INITIALIZED ) ) {
673                 memset ( &undi_startup, 0, sizeof ( undi_startup ) );
674                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP,
675                                            &undi_startup,
676                                            sizeof ( undi_startup ) ) ) != 0 )
677                         goto err_undi_startup;
678                 memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
679                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE,
680                                            &undi_initialize,
681                                            sizeof ( undi_initialize ))) != 0 )
682                         goto err_undi_initialize;
683         }
684         undi->flags |= UNDI_FL_INITIALIZED;
685
686         /* Get device information */
687         memset ( &undi_info, 0, sizeof ( undi_info ) );
688         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION,
689                                    &undi_info, sizeof ( undi_info ) ) ) != 0 )
690                 goto err_undi_get_information;
691         memcpy ( netdev->ll_addr, undi_info.PermNodeAddress, ETH_ALEN );
692         undinic->irq = undi_info.IntNumber;
693         if ( undinic->irq > IRQ_MAX ) {
694                 DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
695                        undinic, undinic->irq );
696                 goto err_bad_irq;
697         }
698         DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
699                undinic, eth_ntoa ( netdev->ll_addr ), undinic->irq );
700
701         /* Get interface information */
702         memset ( &undi_iface, 0, sizeof ( undi_iface ) );
703         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO,
704                                    &undi_iface,
705                                    sizeof ( undi_iface ) ) ) != 0 )
706                 goto err_undi_get_iface_info;
707         DBGC ( undinic, "UNDINIC %p has type %s and link speed %d\n",
708                undinic, undi_iface.IfaceType, undi_iface.LinkSpeed );
709         if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
710                        sizeof ( undi_iface.IfaceType ) ) == 0 ) {
711                 DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
712                        undinic );
713                 undinic->hacks |= UNDI_HACK_EB54;
714         }
715
716         /* Mark as link up; we don't handle link state */
717         netdev_link_up ( netdev );
718
719         /* Register network device */
720         if ( ( rc = register_netdev ( netdev ) ) != 0 )
721                 goto err_register;
722
723         DBGC ( undinic, "UNDINIC %p added\n", undinic );
724         return 0;
725
726  err_register:
727  err_undi_get_iface_info:
728  err_bad_irq:
729  err_undi_get_information:
730  err_undi_initialize:
731         /* Shut down UNDI stack */
732         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
733         undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
734                        sizeof ( undi_shutdown ) );
735         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
736         undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
737                        sizeof ( undi_cleanup ) );
738         undi->flags &= ~UNDI_FL_INITIALIZED;
739  err_undi_startup:
740         /* Unhook UNDI stack */
741         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
742         undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
743                        sizeof ( stop_undi ) );
744         undi->flags &= ~UNDI_FL_STARTED;
745  err_start_undi:
746         netdev_nullify ( netdev );
747         netdev_put ( netdev );
748         undi_set_drvdata ( undi, NULL );
749         return rc;
750 }
751
752 /**
753  * Remove UNDI device
754  *
755  * @v undi              UNDI device
756  */
757 void undinet_remove ( struct undi_device *undi ) {
758         struct net_device *netdev = undi_get_drvdata ( undi );
759         struct undi_nic *undinic = netdev->priv;
760         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
761         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
762         struct s_PXENV_STOP_UNDI stop_undi;
763
764         /* Unregister net device */
765         unregister_netdev ( netdev );
766
767         /* If we are preparing for an OS boot, or if we cannot exit
768          * via the PXE stack, then shut down the PXE stack.
769          */
770         if ( ! ( undi->flags & UNDI_FL_KEEP_ALL ) ) {
771
772                 /* Shut down UNDI stack */
773                 memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
774                 undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
775                                sizeof ( undi_shutdown ) );
776                 memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
777                 undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
778                                sizeof ( undi_cleanup ) );
779                 undi->flags &= ~UNDI_FL_INITIALIZED;
780
781                 /* Unhook UNDI stack */
782                 memset ( &stop_undi, 0, sizeof ( stop_undi ) );
783                 undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
784                                sizeof ( stop_undi ) );
785                 undi->flags &= ~UNDI_FL_STARTED;
786         }
787
788         /* Clear entry point */
789         memset ( &undinet_entry_point, 0, sizeof ( undinet_entry_point ) );
790
791         /* Free network device */
792         netdev_nullify ( netdev );
793         netdev_put ( netdev );
794
795         DBGC ( undinic, "UNDINIC %p removed\n", undinic );
796 }