Working code to call the PXE stack from within the ISR.
[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 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 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  * The UNDI ISR specifically does @b not chain to the previous
273  * interrupt handler.  BIOSes seem to install somewhat perverse
274  * default interrupt handlers; some do nothing other than an iret (and
275  * so will cause a screaming interrupt if there really is another
276  * interrupting device) and some disable the interrupt at the PIC (and
277  * so will bring our own interrupts to a shuddering halt).
278  */
279 static void undinet_hook_isr ( unsigned int irq ) {
280
281         assert ( irq <= IRQ_MAX );
282         assert ( undiisr_irq == 0 );
283
284         undiisr_irq = irq;
285         hook_bios_interrupt ( IRQ_INT ( irq ),
286                               ( ( unsigned int ) undiisr ),
287                               &undiisr_next_handler );
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 ) undiisr ),
301                                 &undiisr_next_handler );
302         undiisr_irq = 0;
303 }
304
305 /**
306  * Test to see if UNDI ISR has been triggered
307  *
308  * @ret triggered       ISR has been triggered since last check
309  */
310 static int undinet_isr_triggered ( void ) {
311         unsigned int this_trigger_count;
312
313         /* Read trigger_count.  Do this only once; it is volatile */
314         this_trigger_count = undiisr_trigger_count;
315
316         if ( this_trigger_count == last_trigger_count ) {
317                 /* Not triggered */
318                 return 0;
319         } else {
320                 /* Triggered */
321                 last_trigger_count = this_trigger_count;
322                 return 1;
323         }
324 }
325
326 /*****************************************************************************
327  *
328  * UNDI network device interface
329  *
330  *****************************************************************************
331  */
332
333 /** UNDI transmit buffer descriptor */
334 static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
335 #define undinet_tbd __use_data16 ( undinet_tbd )
336
337 /**
338  * Transmit packet
339  *
340  * @v netdev            Network device
341  * @v iobuf             I/O buffer
342  * @ret rc              Return status code
343  */
344 static int undinet_transmit ( struct net_device *netdev,
345                               struct io_buffer *iobuf ) {
346         struct undi_nic *undinic = netdev->priv;
347         struct s_PXENV_UNDI_TRANSMIT undi_transmit;
348         size_t len = iob_len ( iobuf );
349         int rc;
350
351         /* Technically, we ought to make sure that the previous
352          * transmission has completed before we re-use the buffer.
353          * However, many PXE stacks (including at least some Intel PXE
354          * stacks and Etherboot 5.4) fail to generate TX completions.
355          * In practice this won't be a problem, since our TX datapath
356          * has a very low packet volume and we can get away with
357          * assuming that a TX will be complete by the time we want to
358          * transmit the next packet.
359          */
360
361         /* Copy packet to UNDI I/O buffer */
362         if ( len > sizeof ( basemem_packet ) )
363                 len = sizeof ( basemem_packet );
364         memcpy ( &basemem_packet, iobuf->data, len );
365
366         /* Create PXENV_UNDI_TRANSMIT data structure */
367         memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
368         undi_transmit.DestAddr.segment = rm_ds;
369         undi_transmit.DestAddr.offset
370                 = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
371         undi_transmit.TBD.segment = rm_ds;
372         undi_transmit.TBD.offset
373                 = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
374
375         /* Create PXENV_UNDI_TBD data structure */
376         undinet_tbd.ImmedLength = len;
377         undinet_tbd.Xmit.segment = rm_ds;
378         undinet_tbd.Xmit.offset 
379                 = ( ( unsigned ) & __from_data16 ( basemem_packet ) );
380
381         /* Issue PXE API call */
382         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_TRANSMIT,
383                                    &undi_transmit,
384                                    sizeof ( undi_transmit ) ) ) != 0 )
385                 goto done;
386
387         /* Free I/O buffer */
388         netdev_tx_complete ( netdev, iobuf );
389
390  done:
391         return rc;
392 }
393
394 /** 
395  * Poll for received packets
396  *
397  * @v netdev            Network device
398  *
399  * Fun, fun, fun.  UNDI drivers don't use polling; they use
400  * interrupts.  We therefore cheat and pretend that an interrupt has
401  * occurred every time undinet_poll() is called.  This isn't too much
402  * of a hack; PCI devices share IRQs and so the first thing that a
403  * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
404  * not the UNDI NIC generated the interrupt; there is no harm done by
405  * spurious calls to PXENV_UNDI_ISR.  Similarly, we wouldn't be
406  * handling them any more rapidly than the usual rate of
407  * undinet_poll() being called even if we did implement a full ISR.
408  * So it should work.  Ha!
409  *
410  * Addendum (21/10/03).  Some cards don't play nicely with this trick,
411  * so instead of doing it the easy way we have to go to all the hassle
412  * of installing a genuine interrupt service routine and dealing with
413  * the wonderful 8259 Programmable Interrupt Controller.  Joy.
414  */
415 static void undinet_poll ( struct net_device *netdev ) {
416         struct undi_nic *undinic = netdev->priv;
417         struct s_PXENV_UNDI_ISR undi_isr;
418         struct io_buffer *iobuf = NULL;
419         size_t len;
420         size_t frag_len;
421         int rc;
422
423         if ( ! undinic->isr_processing ) {
424                 /* Do nothing unless ISR has been triggered */
425                 if ( ! undinet_isr_triggered() )
426                         return;
427
428 #if 0
429                 /* See if this was our interrupt */
430                 memset ( &undi_isr, 0, sizeof ( undi_isr ) );
431                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_START;
432                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
433                                            sizeof ( undi_isr ) ) ) != 0 )
434                         return;
435
436                 /* Send EOI to the PIC.  In an ideal world, we'd do
437                  * this only for interrupts which the UNDI stack
438                  * reports as "ours".  However, since we don't (can't)
439                  * chain to the previous interrupt handler, we have to
440                  * acknowledge all interrupts.  See undinet_hook_isr()
441                  * for more background.
442                  */
443                 send_eoi ( undinic->irq );
444
445                 /* If this wasn't our interrupt, exit now */
446                 if ( undi_isr.FuncFlag != PXENV_UNDI_ISR_OUT_OURS )
447                         return;
448 #endif
449                 
450                 /* Start ISR processing */
451                 undinic->isr_processing = 1;
452                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
453         } else {
454                 /* Continue ISR processing */
455                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
456         }
457
458         /* Run through the ISR loop */
459         while ( 1 ) {
460                 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
461                                            sizeof ( undi_isr ) ) ) != 0 )
462                         break;
463                 switch ( undi_isr.FuncFlag ) {
464                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
465                         /* We don't care about transmit completions */
466                         break;
467                 case PXENV_UNDI_ISR_OUT_RECEIVE:
468                         /* Packet fragment received */
469                         len = undi_isr.FrameLength;
470                         frag_len = undi_isr.BufferLength;
471                         if ( ! iobuf )
472                                 iobuf = alloc_iob ( len );
473                         if ( ! iobuf ) {
474                                 DBGC ( undinic, "UNDINIC %p could not "
475                                        "allocate %zd bytes for RX buffer\n",
476                                        undinic, len );
477                                 /* Fragment will be dropped */
478                                 goto done;
479                         }
480                         if ( frag_len > iob_tailroom ( iobuf ) ) {
481                                 DBGC ( undinic, "UNDINIC %p fragment too "
482                                        "large\n", undinic );
483                                 frag_len = iob_tailroom ( iobuf );
484                         }
485                         copy_from_real ( iob_put ( iobuf, frag_len ),
486                                          undi_isr.Frame.segment,
487                                          undi_isr.Frame.offset, frag_len );
488                         if ( iob_len ( iobuf ) == len ) {
489                                 /* Whole packet received; deliver it */
490                                 netdev_rx ( netdev, iobuf );
491                                 iobuf = NULL;
492                                 /* Etherboot 5.4 fails to return all packets
493                                  * under mild load; pretend it retriggered.
494                                  */
495                                 if ( undinic->hacks & UNDI_HACK_EB54 )
496                                         --last_trigger_count;
497                         }
498                         break;
499                 case PXENV_UNDI_ISR_OUT_DONE:
500                         /* Processing complete */
501                         undinic->isr_processing = 0;
502                         goto done;
503                 default:
504                         /* Should never happen */
505                         DBGC ( undinic, "UNDINIC %p ISR returned invalid "
506                                "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
507                         undinic->isr_processing = 0;
508                         goto done;
509                 }
510                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
511         }
512
513  done:
514         if ( iobuf ) {
515                 DBGC ( undinic, "UNDINIC %p returned incomplete packet\n",
516                        undinic );
517                 netdev_rx ( netdev, iobuf );
518         }
519 }
520
521 /**
522  * Open NIC
523  *
524  * @v netdev            Net device
525  * @ret rc              Return status code
526  */
527 static int undinet_open ( struct net_device *netdev ) {
528         struct undi_nic *undinic = netdev->priv;
529         struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
530         struct s_PXENV_UNDI_OPEN undi_open;
531         int rc;
532
533         /* Hook interrupt service routine and enable interrupt */
534         undinet_hook_isr ( undinic->irq );
535         enable_irq ( undinic->irq );
536         send_eoi ( undinic->irq );
537
538         /* Set station address.  Required for some PXE stacks; will
539          * spuriously fail on others.  Ignore failures.  We only ever
540          * use it to set the MAC address to the card's permanent value
541          * anyway.
542          */
543         memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
544                  sizeof ( undi_set_address.StationAddress ) );
545         undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS,
546                        &undi_set_address, sizeof ( undi_set_address ) );
547
548         /* Open NIC */
549         memset ( &undi_open, 0, sizeof ( undi_open ) );
550         undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST );
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         undinic->entry = 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         memset ( &undi_startup, 0, sizeof ( undi_startup ) );
674         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP, &undi_startup,
675                                    sizeof ( undi_startup ) ) ) != 0 )
676                 goto err_undi_startup;
677         memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
678         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE,
679                                    &undi_initialize,
680                                    sizeof ( undi_initialize ) ) ) != 0 )
681                 goto err_undi_initialize;
682
683         /* Get device information */
684         memset ( &undi_info, 0, sizeof ( undi_info ) );
685         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION,
686                                    &undi_info, sizeof ( undi_info ) ) ) != 0 )
687                 goto err_undi_get_information;
688         memcpy ( netdev->ll_addr, undi_info.PermNodeAddress, ETH_ALEN );
689         undinic->irq = undi_info.IntNumber;
690         if ( undinic->irq > IRQ_MAX ) {
691                 DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
692                        undinic, undinic->irq );
693                 goto err_bad_irq;
694         }
695         DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
696                undinic, eth_ntoa ( netdev->ll_addr ), undinic->irq );
697
698         /* Get interface information */
699         memset ( &undi_iface, 0, sizeof ( undi_iface ) );
700         if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO,
701                                    &undi_iface,
702                                    sizeof ( undi_iface ) ) ) != 0 )
703                 goto err_undi_get_iface_info;
704         DBGC ( undinic, "UNDINIC %p has type %s and link speed %ld\n",
705                undinic, undi_iface.IfaceType, undi_iface.LinkSpeed );
706         if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
707                        sizeof ( undi_iface.IfaceType ) ) == 0 ) {
708                 DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
709                        undinic );
710                 undinic->hacks |= UNDI_HACK_EB54;
711         }
712
713         /* Register network device */
714         if ( ( rc = register_netdev ( netdev ) ) != 0 )
715                 goto err_register;
716
717         DBGC ( undinic, "UNDINIC %p added\n", undinic );
718         return 0;
719
720  err_register:
721  err_undi_get_iface_info:
722  err_bad_irq:
723  err_undi_get_information:
724  err_undi_initialize:
725         /* Shut down UNDI stack */
726         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
727         undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
728                        sizeof ( undi_shutdown ) );
729         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
730         undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
731                        sizeof ( undi_cleanup ) );
732  err_undi_startup:
733         /* Unhook UNDI stack */
734         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
735         undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
736                        sizeof ( stop_undi ) );
737  err_start_undi:
738         netdev_nullify ( netdev );
739         netdev_put ( netdev );
740         undi_set_drvdata ( undi, NULL );
741         return rc;
742 }
743
744 /**
745  * Remove UNDI device
746  *
747  * @v undi              UNDI device
748  */
749 void undinet_remove ( struct undi_device *undi ) {
750         struct net_device *netdev = undi_get_drvdata ( undi );
751         struct undi_nic *undinic = netdev->priv;
752         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
753         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
754         struct s_PXENV_STOP_UNDI stop_undi;
755
756         /* Unregister net device */
757         unregister_netdev ( netdev );
758
759         /* Shut down UNDI stack */
760         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
761         undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
762                        sizeof ( undi_shutdown ) );
763         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
764         undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
765                        sizeof ( undi_cleanup ) );
766
767         /* Unhook UNDI stack */
768         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
769         undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
770                        sizeof ( stop_undi ) );
771         undi->flags &= ~UNDI_FL_STARTED;
772
773         /* Free network device */
774         netdev_nullify ( netdev );
775         netdev_put ( netdev );
776
777         DBGC ( undinic, "UNDINIC %p removed\n", undinic );
778 }