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