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