Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2007 Michael Brown <mbrown (at) 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 }
    632