[pxe] Separate parent PXE API caller from UNDINET driver
[people/cooldavid/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 #include <pxeparent.h>
36
37
38 /** @file
39  *
40  * UNDI network device driver
41  *
42  */
43
44 /** An UNDI NIC */
45 struct undi_nic {
46         /** Assigned IRQ number */
47         unsigned int irq;
48         /** Currently processing ISR */
49         int isr_processing;
50         /** Bug workarounds */
51         int hacks;
52 };
53
54 /**
55  * @defgroup undi_hacks UNDI workarounds
56  * @{
57  */
58
59 /** Work around Etherboot 5.4 bugs */
60 #define UNDI_HACK_EB54          0x0001
61
62 /** @} */
63
64 static void undinet_close ( struct net_device *netdev );
65
66 /** Address of UNDI entry point */
67 static SEGOFF16_t undinet_entry;
68
69 /*****************************************************************************
70  *
71  * UNDI interrupt service routine
72  *
73  *****************************************************************************
74  */
75
76 /**
77  * UNDI interrupt service routine
78  *
79  * The UNDI ISR increments a counter (@c trigger_count) and exits.
80  */
81 extern void undiisr ( void );
82
83 /** IRQ number */
84 uint8_t __data16 ( undiisr_irq );
85 #define undiisr_irq __use_data16 ( undiisr_irq )
86
87 /** IRQ chain vector */
88 struct segoff __data16 ( undiisr_next_handler );
89 #define undiisr_next_handler __use_data16 ( undiisr_next_handler )
90
91 /** IRQ trigger count */
92 volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
93 #define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
94
95 /** Last observed trigger count */
96 static unsigned int last_trigger_count = 0;
97
98 /**
99  * Hook UNDI interrupt service routine
100  *
101  * @v irq               IRQ number
102  */
103 static void undinet_hook_isr ( unsigned int irq ) {
104
105         assert ( irq <= IRQ_MAX );
106         assert ( undiisr_irq == 0 );
107
108         undiisr_irq = irq;
109         hook_bios_interrupt ( IRQ_INT ( irq ),
110                               ( ( unsigned int ) undiisr ),
111                               &undiisr_next_handler );
112 }
113
114 /**
115  * Unhook UNDI interrupt service routine
116  *
117  * @v irq               IRQ number
118  */
119 static void undinet_unhook_isr ( unsigned int irq ) {
120
121         assert ( irq <= IRQ_MAX );
122
123         unhook_bios_interrupt ( IRQ_INT ( irq ),
124                                 ( ( unsigned int ) undiisr ),
125                                 &undiisr_next_handler );
126         undiisr_irq = 0;
127 }
128
129 /**
130  * Test to see if UNDI ISR has been triggered
131  *
132  * @ret triggered       ISR has been triggered since last check
133  */
134 static int undinet_isr_triggered ( void ) {
135         unsigned int this_trigger_count;
136
137         /* Read trigger_count.  Do this only once; it is volatile */
138         this_trigger_count = undiisr_trigger_count;
139
140         if ( this_trigger_count == last_trigger_count ) {
141                 /* Not triggered */
142                 return 0;
143         } else {
144                 /* Triggered */
145                 last_trigger_count = this_trigger_count;
146                 return 1;
147         }
148 }
149
150 /*****************************************************************************
151  *
152  * UNDI network device interface
153  *
154  *****************************************************************************
155  */
156
157 /** UNDI transmit buffer descriptor */
158 static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
159 #define undinet_tbd __use_data16 ( undinet_tbd )
160
161 /**
162  * Transmit packet
163  *
164  * @v netdev            Network device
165  * @v iobuf             I/O buffer
166  * @ret rc              Return status code
167  */
168 static int undinet_transmit ( struct net_device *netdev,
169                               struct io_buffer *iobuf ) {
170         struct s_PXENV_UNDI_TRANSMIT undi_transmit;
171         size_t len = iob_len ( iobuf );
172         int rc;
173
174         /* Technically, we ought to make sure that the previous
175          * transmission has completed before we re-use the buffer.
176          * However, many PXE stacks (including at least some Intel PXE
177          * stacks and Etherboot 5.4) fail to generate TX completions.
178          * In practice this won't be a problem, since our TX datapath
179          * has a very low packet volume and we can get away with
180          * assuming that a TX will be complete by the time we want to
181          * transmit the next packet.
182          */
183
184         /* Copy packet to UNDI I/O buffer */
185         if ( len > sizeof ( basemem_packet ) )
186                 len = sizeof ( basemem_packet );
187         memcpy ( &basemem_packet, iobuf->data, len );
188
189         /* Create PXENV_UNDI_TRANSMIT data structure */
190         memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
191         undi_transmit.DestAddr.segment = rm_ds;
192         undi_transmit.DestAddr.offset = __from_data16 ( &undinet_tbd );
193         undi_transmit.TBD.segment = rm_ds;
194         undi_transmit.TBD.offset = __from_data16 ( &undinet_tbd );
195
196         /* Create PXENV_UNDI_TBD data structure */
197         undinet_tbd.ImmedLength = len;
198         undinet_tbd.Xmit.segment = rm_ds;
199         undinet_tbd.Xmit.offset = __from_data16 ( basemem_packet );
200
201         /* Issue PXE API call */
202         if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_TRANSMIT,
203                                      &undi_transmit,
204                                      sizeof ( undi_transmit ) ) ) != 0 )
205                 goto done;
206
207         /* Free I/O buffer */
208         netdev_tx_complete ( netdev, iobuf );
209
210  done:
211         return rc;
212 }
213
214 /** 
215  * Poll for received packets
216  *
217  * @v netdev            Network device
218  *
219  * Fun, fun, fun.  UNDI drivers don't use polling; they use
220  * interrupts.  We therefore cheat and pretend that an interrupt has
221  * occurred every time undinet_poll() is called.  This isn't too much
222  * of a hack; PCI devices share IRQs and so the first thing that a
223  * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
224  * not the UNDI NIC generated the interrupt; there is no harm done by
225  * spurious calls to PXENV_UNDI_ISR.  Similarly, we wouldn't be
226  * handling them any more rapidly than the usual rate of
227  * undinet_poll() being called even if we did implement a full ISR.
228  * So it should work.  Ha!
229  *
230  * Addendum (21/10/03).  Some cards don't play nicely with this trick,
231  * so instead of doing it the easy way we have to go to all the hassle
232  * of installing a genuine interrupt service routine and dealing with
233  * the wonderful 8259 Programmable Interrupt Controller.  Joy.
234  *
235  * Addendum (10/07/07).  When doing things such as iSCSI boot, in
236  * which we have to co-operate with a running OS, we can't get away
237  * with the "ISR-just-increments-a-counter-and-returns" trick at all,
238  * because it involves tying up the PIC for far too long, and other
239  * interrupt-dependent components (e.g. local disks) start breaking.
240  * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR
241  * from within interrupt context in order to deassert the device
242  * interrupt, and sends EOI if applicable.
243  */
244 static void undinet_poll ( struct net_device *netdev ) {
245         struct undi_nic *undinic = netdev->priv;
246         struct s_PXENV_UNDI_ISR undi_isr;
247         struct io_buffer *iobuf = NULL;
248         size_t len;
249         size_t frag_len;
250         size_t max_frag_len;
251         int rc;
252
253         if ( ! undinic->isr_processing ) {
254                 /* Do nothing unless ISR has been triggered */
255                 if ( ! undinet_isr_triggered() ) {
256                         /* Allow interrupt to occur */
257                         __asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
258                                                            "nop\n\t"
259                                                            "nop\n\t"
260                                                            "cli\n\t" ) : : );
261                         return;
262                 }
263
264                 /* Start ISR processing */
265                 undinic->isr_processing = 1;
266                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
267         } else {
268                 /* Continue ISR processing */
269                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
270         }
271
272         /* Run through the ISR loop */
273         while ( 1 ) {
274                 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
275                                              &undi_isr,
276                                              sizeof ( undi_isr ) ) ) != 0 )
277                         break;
278                 switch ( undi_isr.FuncFlag ) {
279                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
280                         /* We don't care about transmit completions */
281                         break;
282                 case PXENV_UNDI_ISR_OUT_RECEIVE:
283                         /* Packet fragment received */
284                         len = undi_isr.FrameLength;
285                         frag_len = undi_isr.BufferLength;
286                         if ( ( len == 0 ) || ( len < frag_len ) ) {
287                                 /* Don't laugh.  VMWare does it. */
288                                 DBGC ( undinic, "UNDINIC %p reported insane "
289                                        "fragment (%zd of %zd bytes)\n",
290                                        undinic, frag_len, len );
291                                 netdev_rx_err ( netdev, NULL, -EINVAL );
292                                 break;
293                         }
294                         if ( ! iobuf )
295                                 iobuf = alloc_iob ( len );
296                         if ( ! iobuf ) {
297                                 DBGC ( undinic, "UNDINIC %p could not "
298                                        "allocate %zd bytes for RX buffer\n",
299                                        undinic, len );
300                                 /* Fragment will be dropped */
301                                 netdev_rx_err ( netdev, NULL, -ENOMEM );
302                                 goto done;
303                         }
304                         max_frag_len = iob_tailroom ( iobuf );
305                         if ( frag_len > max_frag_len ) {
306                                 DBGC ( undinic, "UNDINIC %p fragment too big "
307                                        "(%zd+%zd does not fit into %zd)\n",
308                                        undinic, iob_len ( iobuf ), frag_len,
309                                        ( iob_len ( iobuf ) + max_frag_len ) );
310                                 frag_len = max_frag_len;
311                         }
312                         copy_from_real ( iob_put ( iobuf, frag_len ),
313                                          undi_isr.Frame.segment,
314                                          undi_isr.Frame.offset, frag_len );
315                         if ( iob_len ( iobuf ) == len ) {
316                                 /* Whole packet received; deliver it */
317                                 netdev_rx ( netdev, iob_disown ( iobuf ) );
318                                 /* Etherboot 5.4 fails to return all packets
319                                  * under mild load; pretend it retriggered.
320                                  */
321                                 if ( undinic->hacks & UNDI_HACK_EB54 )
322                                         --last_trigger_count;
323                         }
324                         break;
325                 case PXENV_UNDI_ISR_OUT_DONE:
326                         /* Processing complete */
327                         undinic->isr_processing = 0;
328                         goto done;
329                 default:
330                         /* Should never happen.  VMWare does it routinely. */
331                         DBGC ( undinic, "UNDINIC %p ISR returned invalid "
332                                "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
333                         undinic->isr_processing = 0;
334                         goto done;
335                 }
336                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
337         }
338
339  done:
340         if ( iobuf ) {
341                 DBGC ( undinic, "UNDINIC %p returned incomplete packet "
342                        "(%zd of %zd)\n", undinic, iob_len ( iobuf ),
343                        ( iob_len ( iobuf ) + iob_tailroom ( iobuf ) ) );
344                 netdev_rx_err ( netdev, iobuf, -EINVAL );
345         }
346 }
347
348 /**
349  * Open NIC
350  *
351  * @v netdev            Net device
352  * @ret rc              Return status code
353  */
354 static int undinet_open ( struct net_device *netdev ) {
355         struct undi_nic *undinic = netdev->priv;
356         struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
357         struct s_PXENV_UNDI_OPEN undi_open;
358         int rc;
359
360         /* Hook interrupt service routine and enable interrupt */
361         undinet_hook_isr ( undinic->irq );
362         enable_irq ( undinic->irq );
363         send_eoi ( undinic->irq );
364
365         /* Set station address.  Required for some PXE stacks; will
366          * spuriously fail on others.  Ignore failures.  We only ever
367          * use it to set the MAC address to the card's permanent value
368          * anyway.
369          */
370         memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
371                  sizeof ( undi_set_address.StationAddress ) );
372         pxeparent_call ( undinet_entry, PXENV_UNDI_SET_STATION_ADDRESS,
373                          &undi_set_address, sizeof ( undi_set_address ) );
374
375         /* Open NIC.  We ask for promiscuous operation, since it's the
376          * only way to ask for all multicast addresses.  On any
377          * switched network, it shouldn't really make a difference to
378          * performance.
379          */
380         memset ( &undi_open, 0, sizeof ( undi_open ) );
381         undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST | FLTR_PRMSCS );
382         if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_OPEN,
383                                      &undi_open, sizeof ( undi_open ) ) ) != 0 )
384                 goto err;
385
386         DBGC ( undinic, "UNDINIC %p opened\n", undinic );
387         return 0;
388
389  err:
390         undinet_close ( netdev );
391         return rc;
392 }
393
394 /**
395  * Close NIC
396  *
397  * @v netdev            Net device
398  */
399 static void undinet_close ( struct net_device *netdev ) {
400         struct undi_nic *undinic = netdev->priv;
401         struct s_PXENV_UNDI_ISR undi_isr;
402         struct s_PXENV_UNDI_CLOSE undi_close;
403         int rc;
404
405         /* Ensure ISR has exited cleanly */
406         while ( undinic->isr_processing ) {
407                 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
408                 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
409                                              &undi_isr,
410                                              sizeof ( undi_isr ) ) ) != 0 )
411                         break;
412                 switch ( undi_isr.FuncFlag ) {
413                 case PXENV_UNDI_ISR_OUT_TRANSMIT:
414                 case PXENV_UNDI_ISR_OUT_RECEIVE:
415                         /* Continue draining */
416                         break;
417                 default:
418                         /* Stop processing */
419                         undinic->isr_processing = 0;
420                         break;
421                 }
422         }
423
424         /* Close NIC */
425         pxeparent_call ( undinet_entry, PXENV_UNDI_CLOSE,
426                          &undi_close, sizeof ( undi_close ) );
427
428         /* Disable interrupt and unhook ISR */
429         disable_irq ( undinic->irq );
430         undinet_unhook_isr ( undinic->irq );
431
432         DBGC ( undinic, "UNDINIC %p closed\n", undinic );
433 }
434
435 /**
436  * Enable/disable interrupts
437  *
438  * @v netdev            Net device
439  * @v enable            Interrupts should be enabled
440  */
441 static void undinet_irq ( struct net_device *netdev, int enable ) {
442         struct undi_nic *undinic = netdev->priv;
443
444         /* Cannot support interrupts yet */
445         DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
446                undinic, ( enable ? "enable" : "disable" ) );
447 }
448
449 /** UNDI network device operations */
450 static struct net_device_operations undinet_operations = {
451         .open           = undinet_open,
452         .close          = undinet_close,
453         .transmit       = undinet_transmit,
454         .poll           = undinet_poll,
455         .irq            = undinet_irq,
456 };
457
458 /**
459  * Probe UNDI device
460  *
461  * @v undi              UNDI device
462  * @ret rc              Return status code
463  */
464 int undinet_probe ( struct undi_device *undi ) {
465         struct net_device *netdev;
466         struct undi_nic *undinic;
467         struct s_PXENV_START_UNDI start_undi;
468         struct s_PXENV_UNDI_STARTUP undi_startup;
469         struct s_PXENV_UNDI_INITIALIZE undi_initialize;
470         struct s_PXENV_UNDI_GET_INFORMATION undi_info;
471         struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
472         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
473         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
474         struct s_PXENV_STOP_UNDI stop_undi;
475         int rc;
476
477         /* Allocate net device */
478         netdev = alloc_etherdev ( sizeof ( *undinic ) );
479         if ( ! netdev )
480                 return -ENOMEM;
481         netdev_init ( netdev, &undinet_operations );
482         undinic = netdev->priv;
483         undi_set_drvdata ( undi, netdev );
484         netdev->dev = &undi->dev;
485         memset ( undinic, 0, sizeof ( *undinic ) );
486         undinet_entry = undi->entry;
487         DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
488
489         /* Hook in UNDI stack */
490         if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
491                 memset ( &start_undi, 0, sizeof ( start_undi ) );
492                 start_undi.AX = undi->pci_busdevfn;
493                 start_undi.BX = undi->isapnp_csn;
494                 start_undi.DX = undi->isapnp_read_port;
495                 start_undi.ES = BIOS_SEG;
496                 start_undi.DI = find_pnp_bios();
497                 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_START_UNDI,
498                                              &start_undi,
499                                              sizeof ( start_undi ) ) ) != 0 )
500                         goto err_start_undi;
501         }
502         undi->flags |= UNDI_FL_STARTED;
503
504         /* Bring up UNDI stack */
505         if ( ! ( undi->flags & UNDI_FL_INITIALIZED ) ) {
506                 memset ( &undi_startup, 0, sizeof ( undi_startup ) );
507                 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_STARTUP,
508                                              &undi_startup,
509                                              sizeof ( undi_startup ) ) ) != 0 )
510                         goto err_undi_startup;
511                 memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
512                 if ( ( rc = pxeparent_call ( undinet_entry,
513                                              PXENV_UNDI_INITIALIZE,
514                                              &undi_initialize,
515                                              sizeof ( undi_initialize ))) != 0 )
516                         goto err_undi_initialize;
517         }
518         undi->flags |= UNDI_FL_INITIALIZED;
519
520         /* Get device information */
521         memset ( &undi_info, 0, sizeof ( undi_info ) );
522         if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_INFORMATION,
523                                      &undi_info, sizeof ( undi_info ) ) ) != 0 )
524                 goto err_undi_get_information;
525         memcpy ( netdev->hw_addr, undi_info.PermNodeAddress, ETH_ALEN );
526         undinic->irq = undi_info.IntNumber;
527         if ( undinic->irq > IRQ_MAX ) {
528                 DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
529                        undinic, undinic->irq );
530                 goto err_bad_irq;
531         }
532         DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
533                undinic, eth_ntoa ( netdev->hw_addr ), undinic->irq );
534
535         /* Get interface information */
536         memset ( &undi_iface, 0, sizeof ( undi_iface ) );
537         if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_IFACE_INFO,
538                                      &undi_iface,
539                                      sizeof ( undi_iface ) ) ) != 0 )
540                 goto err_undi_get_iface_info;
541         DBGC ( undinic, "UNDINIC %p has type %s, speed %d, flags %08x\n",
542                undinic, undi_iface.IfaceType, undi_iface.LinkSpeed,
543                undi_iface.ServiceFlags );
544         if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
545                        sizeof ( undi_iface.IfaceType ) ) == 0 ) {
546                 DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
547                        undinic );
548                 undinic->hacks |= UNDI_HACK_EB54;
549         }
550
551         /* Mark as link up; we don't handle link state */
552         netdev_link_up ( netdev );
553
554         /* Register network device */
555         if ( ( rc = register_netdev ( netdev ) ) != 0 )
556                 goto err_register;
557
558         DBGC ( undinic, "UNDINIC %p added\n", undinic );
559         return 0;
560
561  err_register:
562  err_undi_get_iface_info:
563  err_bad_irq:
564  err_undi_get_information:
565  err_undi_initialize:
566         /* Shut down UNDI stack */
567         memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
568         pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
569                          sizeof ( undi_shutdown ) );
570         memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
571         pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP, &undi_cleanup,
572                          sizeof ( undi_cleanup ) );
573         undi->flags &= ~UNDI_FL_INITIALIZED;
574  err_undi_startup:
575         /* Unhook UNDI stack */
576         memset ( &stop_undi, 0, sizeof ( stop_undi ) );
577         pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
578                          sizeof ( stop_undi ) );
579         undi->flags &= ~UNDI_FL_STARTED;
580  err_start_undi:
581         netdev_nullify ( netdev );
582         netdev_put ( netdev );
583         undi_set_drvdata ( undi, NULL );
584         return rc;
585 }
586
587 /**
588  * Remove UNDI device
589  *
590  * @v undi              UNDI device
591  */
592 void undinet_remove ( struct undi_device *undi ) {
593         struct net_device *netdev = undi_get_drvdata ( undi );
594         struct undi_nic *undinic = netdev->priv;
595         struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
596         struct s_PXENV_UNDI_CLEANUP undi_cleanup;
597         struct s_PXENV_STOP_UNDI stop_undi;
598
599         /* Unregister net device */
600         unregister_netdev ( netdev );
601
602         /* If we are preparing for an OS boot, or if we cannot exit
603          * via the PXE stack, then shut down the PXE stack.
604          */
605         if ( ! ( undi->flags & UNDI_FL_KEEP_ALL ) ) {
606
607                 /* Shut down UNDI stack */
608                 memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
609                 pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN,
610                                  &undi_shutdown, sizeof ( undi_shutdown ) );
611                 memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
612                 pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP,
613                                  &undi_cleanup, sizeof ( undi_cleanup ) );
614                 undi->flags &= ~UNDI_FL_INITIALIZED;
615
616                 /* Unhook UNDI stack */
617                 memset ( &stop_undi, 0, sizeof ( stop_undi ) );
618                 pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
619                                  sizeof ( stop_undi ) );
620                 undi->flags &= ~UNDI_FL_STARTED;
621         }
622
623         /* Clear entry point */
624         memset ( &undinet_entry, 0, sizeof ( undinet_entry ) );
625
626         /* Free network device */
627         netdev_nullify ( netdev );
628         netdev_put ( netdev );
629
630         DBGC ( undinic, "UNDINIC %p removed\n", undinic );
631 }