2 * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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.
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>
36 * UNDI network device driver
42 /** Assigned IRQ number */
44 /** Currently processing ISR */
46 /** Bug workarounds */
51 * @defgroup undi_hacks UNDI workarounds
55 /** Work around Etherboot 5.4 bugs */
56 #define UNDI_HACK_EB54 0x0001
60 static void undinet_close ( struct net_device *netdev );
62 /*****************************************************************************
66 *****************************************************************************
72 * @v function API call number
73 * @ret name API call name
75 static inline __attribute__ (( always_inline )) const char *
76 undinet_function_name ( unsigned int function ) {
78 case PXENV_START_UNDI:
79 return "PXENV_START_UNDI";
81 return "PXENV_STOP_UNDI";
82 case PXENV_UNDI_STARTUP:
83 return "PXENV_UNDI_STARTUP";
84 case PXENV_UNDI_CLEANUP:
85 return "PXENV_UNDI_CLEANUP";
86 case PXENV_UNDI_INITIALIZE:
87 return "PXENV_UNDI_INITIALIZE";
88 case PXENV_UNDI_RESET_ADAPTER:
89 return "PXENV_UNDI_RESET_ADAPTER";
90 case PXENV_UNDI_SHUTDOWN:
91 return "PXENV_UNDI_SHUTDOWN";
93 return "PXENV_UNDI_OPEN";
94 case PXENV_UNDI_CLOSE:
95 return "PXENV_UNDI_CLOSE";
96 case PXENV_UNDI_TRANSMIT:
97 return "PXENV_UNDI_TRANSMIT";
98 case PXENV_UNDI_SET_MCAST_ADDRESS:
99 return "PXENV_UNDI_SET_MCAST_ADDRESS";
100 case PXENV_UNDI_SET_STATION_ADDRESS:
101 return "PXENV_UNDI_SET_STATION_ADDRESS";
102 case PXENV_UNDI_SET_PACKET_FILTER:
103 return "PXENV_UNDI_SET_PACKET_FILTER";
104 case PXENV_UNDI_GET_INFORMATION:
105 return "PXENV_UNDI_GET_INFORMATION";
106 case PXENV_UNDI_GET_STATISTICS:
107 return "PXENV_UNDI_GET_STATISTICS";
108 case PXENV_UNDI_CLEAR_STATISTICS:
109 return "PXENV_UNDI_CLEAR_STATISTICS";
110 case PXENV_UNDI_INITIATE_DIAGS:
111 return "PXENV_UNDI_INITIATE_DIAGS";
112 case PXENV_UNDI_FORCE_INTERRUPT:
113 return "PXENV_UNDI_FORCE_INTERRUPT";
114 case PXENV_UNDI_GET_MCAST_ADDRESS:
115 return "PXENV_UNDI_GET_MCAST_ADDRESS";
116 case PXENV_UNDI_GET_NIC_TYPE:
117 return "PXENV_UNDI_GET_NIC_TYPE";
118 case PXENV_UNDI_GET_IFACE_INFO:
119 return "PXENV_UNDI_GET_IFACE_INFO";
121 * Duplicate case value; this is a bug in the PXE specification.
123 * case PXENV_UNDI_GET_STATE:
124 * return "PXENV_UNDI_GET_STATE";
127 return "PXENV_UNDI_ISR";
129 return "UNKNOWN API CALL";
134 * UNDI parameter block
136 * Used as the paramter block for all UNDI API calls. Resides in base
139 static union u_PXENV_ANY __data16 ( undinet_params );
140 #define undinet_params __use_data16 ( undinet_params )
144 * Used as the indirection vector for all UNDI API calls. Resides in
147 SEGOFF16_t __data16 ( undinet_entry_point );
148 #define undinet_entry_point __use_data16 ( undinet_entry_point )
151 * Issue UNDI API call
153 * @v undinic UNDI NIC
154 * @v function API call number
155 * @v params UNDI parameter block
156 * @v params_len Length of UNDI parameter block
157 * @ret rc Return status code
159 static int undinet_call ( struct undi_nic *undinic, unsigned int function,
160 void *params, size_t params_len ) {
162 int discard_b, discard_D;
165 /* Copy parameter block and entry point */
166 assert ( params_len <= sizeof ( undinet_params ) );
167 memcpy ( &undinet_params, params, params_len );
169 /* Call real-mode entry point. This calling convention will
170 * work with both the !PXE and the PXENV+ entry points.
172 __asm__ __volatile__ ( REAL_CODE ( "pushw %%es\n\t"
176 "addw $6, %%sp\n\t" )
177 : "=a" ( exit ), "=b" ( discard_b ),
179 : "p" ( &__from_data16 ( undinet_entry_point )),
181 "D" ( &__from_data16 ( undinet_params ) )
182 : "ecx", "edx", "esi", "ebp" );
184 /* UNDI API calls may rudely change the status of A20 and not
185 * bother to restore it afterwards. Intel is known to be
188 * Note that we will return to this point even if A20 gets
189 * screwed up by the UNDI driver, because Etherboot always
190 * resides in an even megabyte of RAM.
194 /* Determine return status code based on PXENV_EXIT and
197 if ( exit == PXENV_EXIT_SUCCESS ) {
200 rc = -undinet_params.Status;
201 /* Paranoia; don't return success for the combination
202 * of PXENV_EXIT_FAILURE but PXENV_STATUS_SUCCESS
208 /* If anything goes wrong, print as much debug information as
209 * it's possible to give.
212 SEGOFF16_t rm_params = {
214 .offset = (intptr_t) &__from_data16 ( undinet_params ),
217 DBGC ( undinic, "UNDINIC %p %s failed: %s\n", undinic,
218 undinet_function_name ( function ), strerror ( rc ) );
219 DBGC ( undinic, "UNDINIC %p parameters at %04x:%04x length "
220 "%#02x, entry point at %04x:%04x\n", undinic,
221 rm_params.segment, rm_params.offset, params_len,
222 undinet_entry_point.segment,
223 undinet_entry_point.offset );
224 DBGC ( undinic, "UNDINIC %p parameters provided:\n", undinic );
225 DBGC_HDA ( undinic, rm_params, params, params_len );
226 DBGC ( undinic, "UNDINIC %p parameters returned:\n", undinic );
227 DBGC_HDA ( undinic, rm_params, &undinet_params, params_len );
230 /* Copy parameter block back */
231 memcpy ( params, &undinet_params, params_len );
236 /*****************************************************************************
238 * UNDI interrupt service routine
240 *****************************************************************************
244 * UNDI interrupt service routine
246 * The UNDI ISR increments a counter (@c trigger_count) and exits.
248 extern void undiisr ( void );
251 uint8_t __data16 ( undiisr_irq );
252 #define undiisr_irq __use_data16 ( undiisr_irq )
254 /** IRQ chain vector */
255 struct segoff __data16 ( undiisr_next_handler );
256 #define undiisr_next_handler __use_data16 ( undiisr_next_handler )
258 /** IRQ trigger count */
259 volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
260 #define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
262 /** Last observed trigger count */
263 static unsigned int last_trigger_count = 0;
266 * Hook UNDI interrupt service routine
270 static void undinet_hook_isr ( unsigned int irq ) {
272 assert ( irq <= IRQ_MAX );
273 assert ( undiisr_irq == 0 );
276 hook_bios_interrupt ( IRQ_INT ( irq ),
277 ( ( unsigned int ) undiisr ),
278 &undiisr_next_handler );
282 * Unhook UNDI interrupt service routine
286 static void undinet_unhook_isr ( unsigned int irq ) {
288 assert ( irq <= IRQ_MAX );
290 unhook_bios_interrupt ( IRQ_INT ( irq ),
291 ( ( unsigned int ) undiisr ),
292 &undiisr_next_handler );
297 * Test to see if UNDI ISR has been triggered
299 * @ret triggered ISR has been triggered since last check
301 static int undinet_isr_triggered ( void ) {
302 unsigned int this_trigger_count;
304 /* Read trigger_count. Do this only once; it is volatile */
305 this_trigger_count = undiisr_trigger_count;
307 if ( this_trigger_count == last_trigger_count ) {
312 last_trigger_count = this_trigger_count;
317 /*****************************************************************************
319 * UNDI network device interface
321 *****************************************************************************
324 /** UNDI transmit buffer descriptor */
325 static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
326 #define undinet_tbd __use_data16 ( undinet_tbd )
331 * @v netdev Network device
332 * @v iobuf I/O buffer
333 * @ret rc Return status code
335 static int undinet_transmit ( struct net_device *netdev,
336 struct io_buffer *iobuf ) {
337 struct undi_nic *undinic = netdev->priv;
338 struct s_PXENV_UNDI_TRANSMIT undi_transmit;
339 size_t len = iob_len ( iobuf );
342 /* Technically, we ought to make sure that the previous
343 * transmission has completed before we re-use the buffer.
344 * However, many PXE stacks (including at least some Intel PXE
345 * stacks and Etherboot 5.4) fail to generate TX completions.
346 * In practice this won't be a problem, since our TX datapath
347 * has a very low packet volume and we can get away with
348 * assuming that a TX will be complete by the time we want to
349 * transmit the next packet.
352 /* Copy packet to UNDI I/O buffer */
353 if ( len > sizeof ( basemem_packet ) )
354 len = sizeof ( basemem_packet );
355 memcpy ( &basemem_packet, iobuf->data, len );
357 /* Create PXENV_UNDI_TRANSMIT data structure */
358 memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
359 undi_transmit.DestAddr.segment = rm_ds;
360 undi_transmit.DestAddr.offset
361 = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
362 undi_transmit.TBD.segment = rm_ds;
363 undi_transmit.TBD.offset
364 = ( ( unsigned ) & __from_data16 ( undinet_tbd ) );
366 /* Create PXENV_UNDI_TBD data structure */
367 undinet_tbd.ImmedLength = len;
368 undinet_tbd.Xmit.segment = rm_ds;
369 undinet_tbd.Xmit.offset
370 = ( ( unsigned ) & __from_data16 ( basemem_packet ) );
372 /* Issue PXE API call */
373 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_TRANSMIT,
375 sizeof ( undi_transmit ) ) ) != 0 )
378 /* Free I/O buffer */
379 netdev_tx_complete ( netdev, iobuf );
386 * Poll for received packets
388 * @v netdev Network device
390 * Fun, fun, fun. UNDI drivers don't use polling; they use
391 * interrupts. We therefore cheat and pretend that an interrupt has
392 * occurred every time undinet_poll() is called. This isn't too much
393 * of a hack; PCI devices share IRQs and so the first thing that a
394 * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
395 * not the UNDI NIC generated the interrupt; there is no harm done by
396 * spurious calls to PXENV_UNDI_ISR. Similarly, we wouldn't be
397 * handling them any more rapidly than the usual rate of
398 * undinet_poll() being called even if we did implement a full ISR.
399 * So it should work. Ha!
401 * Addendum (21/10/03). Some cards don't play nicely with this trick,
402 * so instead of doing it the easy way we have to go to all the hassle
403 * of installing a genuine interrupt service routine and dealing with
404 * the wonderful 8259 Programmable Interrupt Controller. Joy.
406 * Addendum (10/07/07). When doing things such as iSCSI boot, in
407 * which we have to co-operate with a running OS, we can't get away
408 * with the "ISR-just-increments-a-counter-and-returns" trick at all,
409 * because it involves tying up the PIC for far too long, and other
410 * interrupt-dependent components (e.g. local disks) start breaking.
411 * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR
412 * from within interrupt context in order to deassert the device
413 * interrupt, and sends EOI if applicable.
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;
423 if ( ! undinic->isr_processing ) {
424 /* Do nothing unless ISR has been triggered */
425 if ( ! undinet_isr_triggered() )
428 /* Start ISR processing */
429 undinic->isr_processing = 1;
430 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
432 /* Continue ISR processing */
433 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
436 /* Run through the ISR loop */
438 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
439 sizeof ( undi_isr ) ) ) != 0 )
441 switch ( undi_isr.FuncFlag ) {
442 case PXENV_UNDI_ISR_OUT_TRANSMIT:
443 /* We don't care about transmit completions */
445 case PXENV_UNDI_ISR_OUT_RECEIVE:
446 /* Packet fragment received */
447 len = undi_isr.FrameLength;
448 frag_len = undi_isr.BufferLength;
450 iobuf = alloc_iob ( len );
452 DBGC ( undinic, "UNDINIC %p could not "
453 "allocate %zd bytes for RX buffer\n",
455 /* Fragment will be dropped */
458 if ( frag_len > iob_tailroom ( iobuf ) ) {
459 DBGC ( undinic, "UNDINIC %p fragment too "
460 "large\n", undinic );
461 frag_len = iob_tailroom ( iobuf );
463 copy_from_real ( iob_put ( iobuf, frag_len ),
464 undi_isr.Frame.segment,
465 undi_isr.Frame.offset, frag_len );
466 if ( iob_len ( iobuf ) == len ) {
467 /* Whole packet received; deliver it */
468 netdev_rx ( netdev, iobuf );
470 /* Etherboot 5.4 fails to return all packets
471 * under mild load; pretend it retriggered.
473 if ( undinic->hacks & UNDI_HACK_EB54 )
474 --last_trigger_count;
477 case PXENV_UNDI_ISR_OUT_DONE:
478 /* Processing complete */
479 undinic->isr_processing = 0;
482 /* Should never happen */
483 DBGC ( undinic, "UNDINIC %p ISR returned invalid "
484 "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
485 undinic->isr_processing = 0;
488 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
493 DBGC ( undinic, "UNDINIC %p returned incomplete packet\n",
495 netdev_rx ( netdev, iobuf );
502 * @v netdev Net device
503 * @ret rc Return status code
505 static int undinet_open ( struct net_device *netdev ) {
506 struct undi_nic *undinic = netdev->priv;
507 struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
508 struct s_PXENV_UNDI_OPEN undi_open;
511 /* Hook interrupt service routine and enable interrupt */
512 undinet_hook_isr ( undinic->irq );
513 enable_irq ( undinic->irq );
514 send_eoi ( undinic->irq );
516 /* Set station address. Required for some PXE stacks; will
517 * spuriously fail on others. Ignore failures. We only ever
518 * use it to set the MAC address to the card's permanent value
521 memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
522 sizeof ( undi_set_address.StationAddress ) );
523 undinet_call ( undinic, PXENV_UNDI_SET_STATION_ADDRESS,
524 &undi_set_address, sizeof ( undi_set_address ) );
527 memset ( &undi_open, 0, sizeof ( undi_open ) );
528 undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST );
529 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_OPEN, &undi_open,
530 sizeof ( undi_open ) ) ) != 0 )
533 DBGC ( undinic, "UNDINIC %p opened\n", undinic );
537 undinet_close ( netdev );
544 * @v netdev Net device
546 static void undinet_close ( struct net_device *netdev ) {
547 struct undi_nic *undinic = netdev->priv;
548 struct s_PXENV_UNDI_ISR undi_isr;
549 struct s_PXENV_UNDI_CLOSE undi_close;
552 /* Ensure ISR has exited cleanly */
553 while ( undinic->isr_processing ) {
554 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
555 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
556 sizeof ( undi_isr ) ) ) != 0 )
558 switch ( undi_isr.FuncFlag ) {
559 case PXENV_UNDI_ISR_OUT_TRANSMIT:
560 case PXENV_UNDI_ISR_OUT_RECEIVE:
561 /* Continue draining */
564 /* Stop processing */
565 undinic->isr_processing = 0;
571 undinet_call ( undinic, PXENV_UNDI_CLOSE, &undi_close,
572 sizeof ( undi_close ) );
574 /* Disable interrupt and unhook ISR */
575 disable_irq ( undinic->irq );
576 undinet_unhook_isr ( undinic->irq );
578 DBGC ( undinic, "UNDINIC %p closed\n", undinic );
582 * Enable/disable interrupts
584 * @v netdev Net device
585 * @v enable Interrupts should be enabled
587 static void undinet_irq ( struct net_device *netdev, int enable ) {
588 struct undi_nic *undinic = netdev->priv;
590 /* Cannot support interrupts yet */
591 DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
592 undinic, ( enable ? "enable" : "disable" ) );
595 /** UNDI network device operations */
596 static struct net_device_operations undinet_operations = {
597 .open = undinet_open,
598 .close = undinet_close,
599 .transmit = undinet_transmit,
600 .poll = undinet_poll,
607 * @v undi UNDI device
608 * @ret rc Return status code
610 int undinet_probe ( struct undi_device *undi ) {
611 struct net_device *netdev;
612 struct undi_nic *undinic;
613 struct s_PXENV_START_UNDI start_undi;
614 struct s_PXENV_UNDI_STARTUP undi_startup;
615 struct s_PXENV_UNDI_INITIALIZE undi_initialize;
616 struct s_PXENV_UNDI_GET_INFORMATION undi_info;
617 struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
618 struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
619 struct s_PXENV_UNDI_CLEANUP undi_cleanup;
620 struct s_PXENV_STOP_UNDI stop_undi;
623 /* Allocate net device */
624 netdev = alloc_etherdev ( sizeof ( *undinic ) );
627 netdev_init ( netdev, &undinet_operations );
628 undinic = netdev->priv;
629 undi_set_drvdata ( undi, netdev );
630 netdev->dev = &undi->dev;
631 memset ( undinic, 0, sizeof ( *undinic ) );
632 undinet_entry_point = undi->entry;
633 DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
635 /* Hook in UNDI stack */
636 if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
637 memset ( &start_undi, 0, sizeof ( start_undi ) );
638 start_undi.AX = undi->pci_busdevfn;
639 start_undi.BX = undi->isapnp_csn;
640 start_undi.DX = undi->isapnp_read_port;
641 start_undi.ES = BIOS_SEG;
642 start_undi.DI = find_pnp_bios();
643 if ( ( rc = undinet_call ( undinic, PXENV_START_UNDI,
645 sizeof ( start_undi ) ) ) != 0 )
648 undi->flags |= UNDI_FL_STARTED;
650 /* Bring up UNDI stack */
651 memset ( &undi_startup, 0, sizeof ( undi_startup ) );
652 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_STARTUP, &undi_startup,
653 sizeof ( undi_startup ) ) ) != 0 )
654 goto err_undi_startup;
655 memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
656 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_INITIALIZE,
658 sizeof ( undi_initialize ) ) ) != 0 )
659 goto err_undi_initialize;
661 /* Get device information */
662 memset ( &undi_info, 0, sizeof ( undi_info ) );
663 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION,
664 &undi_info, sizeof ( undi_info ) ) ) != 0 )
665 goto err_undi_get_information;
666 memcpy ( netdev->ll_addr, undi_info.PermNodeAddress, ETH_ALEN );
667 undinic->irq = undi_info.IntNumber;
668 if ( undinic->irq > IRQ_MAX ) {
669 DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
670 undinic, undinic->irq );
673 DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
674 undinic, eth_ntoa ( netdev->ll_addr ), undinic->irq );
676 /* Get interface information */
677 memset ( &undi_iface, 0, sizeof ( undi_iface ) );
678 if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_IFACE_INFO,
680 sizeof ( undi_iface ) ) ) != 0 )
681 goto err_undi_get_iface_info;
682 DBGC ( undinic, "UNDINIC %p has type %s and link speed %ld\n",
683 undinic, undi_iface.IfaceType, undi_iface.LinkSpeed );
684 if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
685 sizeof ( undi_iface.IfaceType ) ) == 0 ) {
686 DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
688 undinic->hacks |= UNDI_HACK_EB54;
691 /* Register network device */
692 if ( ( rc = register_netdev ( netdev ) ) != 0 )
695 DBGC ( undinic, "UNDINIC %p added\n", undinic );
699 err_undi_get_iface_info:
701 err_undi_get_information:
703 /* Shut down UNDI stack */
704 memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
705 undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
706 sizeof ( undi_shutdown ) );
707 memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
708 undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
709 sizeof ( undi_cleanup ) );
711 /* Unhook UNDI stack */
712 memset ( &stop_undi, 0, sizeof ( stop_undi ) );
713 undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
714 sizeof ( stop_undi ) );
716 netdev_nullify ( netdev );
717 netdev_put ( netdev );
718 undi_set_drvdata ( undi, NULL );
725 * @v undi UNDI device
727 void undinet_remove ( struct undi_device *undi ) {
728 struct net_device *netdev = undi_get_drvdata ( undi );
729 struct undi_nic *undinic = netdev->priv;
730 struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
731 struct s_PXENV_UNDI_CLEANUP undi_cleanup;
732 struct s_PXENV_STOP_UNDI stop_undi;
734 /* Unregister net device */
735 unregister_netdev ( netdev );
737 /* Shut down UNDI stack */
738 memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
739 undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
740 sizeof ( undi_shutdown ) );
741 memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
742 undinet_call ( undinic, PXENV_UNDI_CLEANUP, &undi_cleanup,
743 sizeof ( undi_cleanup ) );
745 /* Unhook UNDI stack */
746 memset ( &stop_undi, 0, sizeof ( stop_undi ) );
747 undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
748 sizeof ( stop_undi ) );
749 undi->flags &= ~UNDI_FL_STARTED;
751 /* Clear entry point */
752 memset ( &undinet_entry_point, 0, sizeof ( undinet_entry_point ) );
754 /* Free network device */
755 netdev_nullify ( netdev );
756 netdev_put ( netdev );
758 DBGC ( undinic, "UNDINIC %p removed\n", undinic );