Use net_device_operations structure and netdev_nullify() to allow for
[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  *
398  * Fun, fun, fun.  UNDI drivers don't use polling; they use
399  * interrupts.  We therefore cheat and pretend that an interrupt has
400  * occurred every time undinet_poll() is called.  This isn't too much
401  * of a hack; PCI devices share IRQs and so the first thing that a
402  * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
403  * not the UNDI NIC generated the interrupt; there is no harm done by
404  * spurious calls to PXENV_UNDI_ISR.  Similarly, we wouldn't be
405  * handling them any more rapidly than the usual rate of
406  * undinet_poll() being called even if we did implement a full ISR.
407  * So it should work.  Ha!
408  *
409  * Addendum (21/10/03).  Some cards don't play nicely with this trick,
410  * so instead of doing it the easy way we have to go to all the hassle
411  * of installing a genuine interrupt service routine and dealing with
412  * the wonderful 8259 Programmable Interrupt Controller.  Joy.
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         int rc;
421
422         if ( ! undinic->isr_processing ) {
423                 /* Do nothing unless ISR has been triggered */
424                 if ( ! undinet_isr_triggered() )
425                         return;
426
427                 /* See if this was our interrupt */
428                 memset ( &undi_isr, 0, sizeof ( undi_isr ) );
429                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_START;
430                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
431                                            sizeof ( undi_isr ) ) ) != 0 )
432                         return;
433
434                 /* Send EOI to the PIC.  In an ideal world, we'd do
435                  * this only for interrupts which the UNDI stack
436                  * reports as "ours".  However, since we don't (can't)
437                  * chain to the previous interrupt handler, we have to
438                  * acknowledge all interrupts.  See undinet_hook_isr()
439                  * for more background.
440                  */
441                 send_eoi ( undinic->irq );
442
443                 /* If this wasn't our interrupt, exit now */
444                 if ( undi_isr.FuncFlag != PXENV_UNDI_ISR_OUT_OURS )
445                         return;
446                 
447                 /* Start ISR processing */
448                 undinic->isr_processing = 1;
449                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
450         } else {
451                 /* Continue ISR processing */
452                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
453         }
454
455         /* Run through the ISR loop */
456         while ( 1 ) {
457                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
458                                            sizeof ( undi_isr ) ) ) != 0 )
459                         break;
460                 switch ( undi_isr.FuncFlag ) {
461                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
462                         /* We don't care about transmit completions */
463                         break;
464                 case PXENV_UNDI_ISR_OUT_RECEIVE:
465                         /* Packet fragment received */
466                         len = undi_isr.FrameLength;
467                         frag_len = undi_isr.BufferLength;
468                         if ( ! iobuf )
469                                 iobuf = alloc_iob ( len );
470                         if ( ! iobuf ) {
471                                 DBGC ( undinic, "UNDINIC %p could not "
472                                        "allocate %zd bytes for RX buffer\n",
473                                        undinic, len );
474                                 /* Fragment will be dropped */
475                                 goto done;
476                         }
477                         if ( frag_len > iob_tailroom ( iobuf ) ) {
478                                 DBGC ( undinic, "UNDINIC %p fragment too "
479                                        "large\n", undinic );
480                                 frag_len = iob_tailroom ( iobuf );
481                         }
482                         copy_from_real ( iob_put ( iobuf, frag_len ),
483                                          undi_isr.Frame.segment,
484                                          undi_isr.Frame.offset, frag_len );
485                         if ( iob_len ( iobuf ) == len ) {
486                                 /* Whole packet received; deliver it */
487                                 netdev_rx ( netdev, iobuf );
488                                 iobuf = NULL;
489                                 /* Etherboot 5.4 fails to return all packets
490                                  * under mild load; pretend it retriggered.
491                                  */
492                                 if ( undinic->hacks & UNDI_HACK_EB54 )
493                                         --last_trigger_count;
494                         }
495                         break;
496                 case PXENV_UNDI_ISR_OUT_DONE:
497                         /* Processing complete */
498                         undinic->isr_processing = 0;
499                         goto done;
500                 default:
501                         /* Should never happen */
502                         DBGC ( undinic, "UNDINIC %p ISR returned invalid "
503                                "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
504                         undinic->isr_processing = 0;
505                         goto done;
506                 }
507                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
508         }
509
510  done:
511         if ( iobuf ) {
512                 DBGC ( undinic, "UNDINIC %p returned incomplete packet\n",
513                        undinic );
514                 netdev_rx ( netdev, iobuf );
515         }
516 }
517
518 /**
519  * Open NIC
520  *
521  * @v netdev            Net device
522  * @ret rc              Return status code
523  */
524 static int undinet_open ( struct net_device *netdev ) {
525         struct undi_nic *undinic = netdev->priv;
526         struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
527         struct s_PXENV_UNDI_OPEN undi_open;
528         int rc;
529
530         /* Hook interrupt service routine and enable interrupt */
531         undinet_hook_isr ( undinic->irq );
532         enable_irq ( undinic->irq );
533         send_eoi ( undinic->irq );
534
535         /* Set station address.  Required for some PXE stacks; will
536          * spuriously fail on others.  Ignore failures.  We only ever
537          * use it to set the MAC address to the card's permanent value
538          * anyway.
539          */
540         memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
541                  sizeof ( undi_set_address.StationAddress ) );
542         undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS,
543                        &undi_set_address, sizeof ( undi_set_address ) );
544
545         /* Open NIC */
546         memset ( &undi_open, 0, sizeof ( undi_open ) );
547         undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST );
548         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_OPEN, &undi_open,
549                                    sizeof ( undi_open ) ) ) != 0 )
550                 goto err;
551
552         DBGC ( undinic, "UNDINIC %p opened\n", undinic );
553         return 0;
554
555  err:
556         undinet_close ( netdev );
557         return rc;
558 }
559
560 /**
561  * Close NIC
562  *
563  * @v netdev            Net device
564  */
565 static void undinet_close ( struct net_device *netdev ) {
566         struct undi_nic *undinic = netdev->priv;
567         struct s_PXENV_UNDI_ISR undi_isr;
568         struct s_PXENV_UNDI_CLOSE undi_close;
569         int rc;
570
571         /* Ensure ISR has exited cleanly */
572         while ( undinic->isr_processing ) {
573                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
574                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
575                                            sizeof ( undi_isr ) ) ) != 0 )
576                         break;
577                 switch ( undi_isr.FuncFlag ) {
578                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
579                 case PXENV_UNDI_ISR_OUT_RECEIVE:
580                         /* Continue draining */
581                         break;
582                 default:
583                         /* Stop processing */
584                         undinic->isr_processing = 0;
585                         break;
586                 }
587         }
588
589         /* Close NIC */
590         undinet_call ( undinic, PXENV_UNDI_CLOSE, &undi_close,
591                        sizeof ( undi_close ) );
592
593         /* Disable interrupt and unhook ISR */
594         disable_irq ( undinic->irq );
595         undinet_unhook_isr ( undinic->irq );
596
597         DBGC ( undinic, "UNDINIC %p closed\n", undinic );
598 }
599
600 /**
601  * Enable/disable interrupts
602  *
603  * @v netdev            Net device
604  * @v enable            Interrupts should be enabled
605  */
606 static void undinet_irq ( struct net_device *netdev, int enable ) {
607         struct undi_nic *undinic = netdev->priv;
608
609         /* Cannot support interrupts yet */
610         DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
611                undinic, ( enable ? "enable" : "disable" ) );
612 }
613
614 /** UNDI network device operations */
615 static struct net_device_operations undinet_operations = {
616         .open           = undinet_open,
617         .close          = undinet_close,
618         .transmit       = undinet_transmit,
619         .poll           = undinet_poll,
620         .irq            = undinet_irq,
621 };
622
623 /**
624  * Probe UNDI device
625  *
626  * @v undi              UNDI device
627  * @ret rc              Return status code
628  */
629 int undinet_probe ( struct undi_device *undi ) {
630         struct net_device *netdev;
631         struct undi_nic *undinic;
632         struct s_PXENV_START_UNDI start_undi;
633         struct s_PXENV_UNDI_STARTUP undi_startup;
634         struct s_PXENV_UNDI_INITIALIZE undi_initialize;
635         struct s_PXENV_UNDI_GET_INFORMATION undi_info;
636         struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
637         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
638         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
639         struct s_PXENV_STOP_UNDI stop_undi;
640         int rc;
641
642         /* Allocate net device */
643         netdev = alloc_etherdev ( sizeof ( *undinic ) );
644         if ( ! netdev )
645                 return -ENOMEM;
646         netdev_init ( netdev, &undinet_operations );
647         undinic = netdev->priv;
648         undi_set_drvdata ( undi, netdev );
649         netdev->dev = &undi->dev;
650         memset ( undinic, 0, sizeof ( *undinic ) );
651         undinic->entry = undi->entry;
652         DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
653
654         /* Hook in UNDI stack */
655         if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
656                 memset ( &start_undi, 0, sizeof ( start_undi ) );
657                 start_undi.AX = undi->pci_busdevfn;
658                 start_undi.BX = undi->isapnp_csn;
659                 start_undi.DX = undi->isapnp_read_port;
660                 start_undi.ES = BIOS_SEG;
661                 start_undi.DI = find_pnp_bios();
662                 if ( ( rc = undinet_call ( undinic, PXENV_START_UNDI,
663                                            &start_undi,
664                                            sizeof ( start_undi ) ) ) != 0 )
665                         goto err_start_undi;
666         }
667         undi->flags |= UNDI_FL_STARTED;
668
669         /* Bring up UNDI stack */
670         memset ( &undi_startup, 0, sizeof ( undi_startup ) );
671         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP, &undi_startup,
672                                    sizeof ( undi_startup ) ) ) != 0 )
673                 goto err_undi_startup;
674         memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
675         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE,
676                                    &undi_initialize,
677                                    sizeof ( undi_initialize ) ) ) != 0 )
678                 goto err_undi_initialize;
679
680         /* Get device information */
681         memset ( &undi_info, 0, sizeof ( undi_info ) );
682         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION,
683                                    &undi_info, sizeof ( undi_info ) ) ) != 0 )
684                 goto err_undi_get_information;
685         memcpy ( netdev->ll_addr, undi_info.PermNodeAddress, ETH_ALEN );
686         undinic->irq = undi_info.IntNumber;
687         if ( undinic->irq > IRQ_MAX ) {
688                 DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
689                        undinic, undinic->irq );
690                 goto err_bad_irq;
691         }
692         DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
693                undinic, eth_ntoa ( netdev->ll_addr ), undinic->irq );
694
695         /* Get interface information */
696         memset ( &undi_iface, 0, sizeof ( undi_iface ) );
697         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO,
698                                    &undi_iface,
699                                    sizeof ( undi_iface ) ) ) != 0 )
700                 goto err_undi_get_iface_info;
701         DBGC ( undinic, "UNDINIC %p has type %s and link speed %ld\n",
702                undinic, undi_iface.IfaceType, undi_iface.LinkSpeed );
703         if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
704                        sizeof ( undi_iface.IfaceType ) ) == 0 ) {
705                 DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
706                        undinic );
707                 undinic->hacks |= UNDI_HACK_EB54;
708         }
709
710         /* Register network device */
711         if ( ( rc = register_netdev ( netdev ) ) != 0 )
712                 goto err_register;
713
714         DBGC ( undinic, "UNDINIC %p added\n", undinic );
715         return 0;
716
717  err_register:
718  err_undi_get_iface_info:
719  err_bad_irq:
720  err_undi_get_information:
721  err_undi_initialize:
722         /* Shut down UNDI stack */
723         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
724         undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
725                        sizeof ( undi_shutdown ) );
726         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
727         undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
728                        sizeof ( undi_cleanup ) );
729  err_undi_startup:
730         /* Unhook UNDI stack */
731         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
732         undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
733                        sizeof ( stop_undi ) );
734  err_start_undi:
735         netdev_nullify ( netdev );
736         netdev_put ( netdev );
737         undi_set_drvdata ( undi, NULL );
738         return rc;
739 }
740
741 /**
742  * Remove UNDI device
743  *
744  * @v undi              UNDI device
745  */
746 void undinet_remove ( struct undi_device *undi ) {
747         struct net_device *netdev = undi_get_drvdata ( undi );
748         struct undi_nic *undinic = netdev->priv;
749         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
750         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
751         struct s_PXENV_STOP_UNDI stop_undi;
752
753         /* Unregister net device */
754         unregister_netdev ( netdev );
755
756         /* Shut down UNDI stack */
757         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
758         undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
759                        sizeof ( undi_shutdown ) );
760         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
761         undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
762                        sizeof ( undi_cleanup ) );
763
764         /* Unhook UNDI stack */
765         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
766         undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
767                        sizeof ( stop_undi ) );
768         undi->flags &= ~UNDI_FL_STARTED;
769
770         /* Free network device */
771         netdev_nullify ( netdev );
772         netdev_put ( netdev );
773
774         DBGC ( undinic, "UNDINIC %p removed\n", undinic );
775 }