• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
undinet_hook_isr(unsigned int irq)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  */
undinet_unhook_isr(unsigned int irq)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  */
undinet_isr_triggered(void)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  */
undinet_transmit(struct net_device * netdev,struct io_buffer * iobuf)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  */
undinet_poll(struct net_device * netdev)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  */
undinet_open(struct net_device * netdev)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  */
undinet_close(struct net_device * netdev)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  */
undinet_irq(struct net_device * netdev,int enable)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  */
undinet_probe(struct undi_device * undi)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  */
undinet_remove(struct undi_device * undi)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