source: bootcd/isolinux/syslinux-6.03/gpxe/src/arch/i386/drivers/net/undinet.c

Last change on this file was e16e8f2, checked in by Edwin Eefting <edwin@datux.nl>, 3 years ago

bootstuff

  • Property mode set to 100644
File size: 18.4 KB
Line 
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
19FILE_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 */
45struct 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
64static void undinet_close ( struct net_device *netdev );
65
66/** Address of UNDI entry point */
67static 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 */
81extern void undiisr ( void );
82
83/** IRQ number */
84uint8_t __data16 ( undiisr_irq );
85#define undiisr_irq __use_data16 ( undiisr_irq )
86
87/** IRQ chain vector */
88struct segoff __data16 ( undiisr_next_handler );
89#define undiisr_next_handler __use_data16 ( undiisr_next_handler )
90
91/** IRQ trigger count */
92volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
93#define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
94
95/** Last observed trigger count */
96static unsigned int last_trigger_count = 0;
97
98/**
99 * Hook UNDI interrupt service routine
100 *
101 * @v irq               IRQ number
102 */
103static 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 */
119static 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 */
134static 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 */
158static 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 */
168static 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 */
244static 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 */
354static 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 */
399static 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 */
441static 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 */
450static 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 */
464int 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 */
592void 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}
Note: See TracBrowser for help on using the repository browser.