Allow recording of TX and RX errors to aid in end-user debugging.
[people/xl0/gpxe-arm.git] / src / interface / pxe / pxe_undi.c
1 /** @file
2  *
3  * PXE UNDI API
4  *
5  */
6
7 /*
8  * Copyright (C) 2004 Michael Brown <mbrown@fensystems.co.uk>.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2 of the
13  * License, or any later version.
14  *
15  * This program is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  */
24
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <byteswap.h>
29 #include <basemem_packet.h>
30 #include <gpxe/netdevice.h>
31 #include <gpxe/iobuf.h>
32 #include <gpxe/device.h>
33 #include <gpxe/pci.h>
34 #include <gpxe/if_ether.h>
35 #include <gpxe/ip.h>
36 #include <gpxe/arp.h>
37 #include <gpxe/rarp.h>
38 #include "pxe.h"
39
40 /**
41  * Count of outstanding transmitted packets
42  *
43  * This is incremented each time PXENV_UNDI_TRANSMIT is called, and
44  * decremented each time that PXENV_UNDI_ISR is called with the TX
45  * queue empty, stopping when the count reaches zero.  This allows us
46  * to provide a pessimistic approximation of TX completion events to
47  * the PXE NBP simply by monitoring the netdev's TX queue.
48  */
49 static int undi_tx_count = 0;
50
51 struct net_device *pxe_netdev = NULL;
52
53 /**
54  * Set network device as current PXE network device
55  *
56  * @v netdev            Network device, or NULL
57  */
58 void pxe_set_netdev ( struct net_device *netdev ) {
59         if ( pxe_netdev )
60                 netdev_put ( pxe_netdev );
61         pxe_netdev = NULL;
62         if ( netdev )
63                 pxe_netdev = netdev_get ( netdev );
64 }
65
66 /**
67  * Open PXE network device
68  *
69  * @ret rc              Return status code
70  */
71 static int pxe_netdev_open ( void ) {
72         return netdev_open ( pxe_netdev );
73 }
74
75 /**
76  * Close PXE network device
77  *
78  */
79 static void pxe_netdev_close ( void ) {
80         netdev_close ( pxe_netdev );
81         undi_tx_count = 0;
82 }
83
84 /* PXENV_UNDI_STARTUP
85  *
86  * Status: working
87  */
88 PXENV_EXIT_t pxenv_undi_startup ( struct s_PXENV_UNDI_STARTUP *undi_startup ) {
89         DBG ( "PXENV_UNDI_STARTUP" );
90
91         undi_startup->Status = PXENV_STATUS_SUCCESS;
92         return PXENV_EXIT_SUCCESS;
93 }
94
95 /* PXENV_UNDI_CLEANUP
96  *
97  * Status: working
98  */
99 PXENV_EXIT_t pxenv_undi_cleanup ( struct s_PXENV_UNDI_CLEANUP *undi_cleanup ) {
100         DBG ( "PXENV_UNDI_CLEANUP" );
101
102         pxe_netdev_close();
103
104         undi_cleanup->Status = PXENV_STATUS_SUCCESS;
105         return PXENV_EXIT_SUCCESS;
106 }
107
108 /* PXENV_UNDI_INITIALIZE
109  *
110  * Status: working
111  */
112 PXENV_EXIT_t pxenv_undi_initialize ( struct s_PXENV_UNDI_INITIALIZE
113                                      *undi_initialize ) {
114         DBG ( "PXENV_UNDI_INITIALIZE" );
115
116         undi_initialize->Status = PXENV_STATUS_SUCCESS;
117         return PXENV_EXIT_SUCCESS;
118 }
119
120 /* PXENV_UNDI_RESET_ADAPTER
121  *
122  * Status: working
123  */
124 PXENV_EXIT_t pxenv_undi_reset_adapter ( struct s_PXENV_UNDI_RESET
125                                         *undi_reset_adapter ) {
126         int rc;
127
128         DBG ( "PXENV_UNDI_RESET_ADAPTER" );
129
130         pxe_netdev_close();
131         if ( ( rc = pxe_netdev_open() ) != 0 ) {
132                 undi_reset_adapter->Status = PXENV_STATUS ( rc );
133                 return PXENV_EXIT_FAILURE;
134         }
135
136         undi_reset_adapter->Status = PXENV_STATUS_SUCCESS;
137         return PXENV_EXIT_SUCCESS;
138 }
139
140 /* PXENV_UNDI_SHUTDOWN
141  *
142  * Status: working
143  */
144 PXENV_EXIT_t pxenv_undi_shutdown ( struct s_PXENV_UNDI_SHUTDOWN
145                                    *undi_shutdown ) {
146         DBG ( "PXENV_UNDI_SHUTDOWN" );
147
148         pxe_netdev_close();
149
150         undi_shutdown->Status = PXENV_STATUS_SUCCESS;
151         return PXENV_EXIT_SUCCESS;
152 }
153
154 /* PXENV_UNDI_OPEN
155  *
156  * Status: working
157  */
158 PXENV_EXIT_t pxenv_undi_open ( struct s_PXENV_UNDI_OPEN *undi_open ) {
159         int rc;
160
161         DBG ( "PXENV_UNDI_OPEN" );
162
163         if ( ( rc = pxe_netdev_open() ) != 0 ) {
164                 undi_open->Status = PXENV_STATUS ( rc );
165                 return PXENV_EXIT_FAILURE;
166         }
167
168         undi_open->Status = PXENV_STATUS_SUCCESS;
169         return PXENV_EXIT_SUCCESS;
170 }
171
172 /* PXENV_UNDI_CLOSE
173  *
174  * Status: working
175  */
176 PXENV_EXIT_t pxenv_undi_close ( struct s_PXENV_UNDI_CLOSE *undi_close ) {
177         DBG ( "PXENV_UNDI_CLOSE" );
178
179         pxe_netdev_close();
180
181         undi_close->Status = PXENV_STATUS_SUCCESS;
182         return PXENV_EXIT_SUCCESS;
183 }
184
185 /* PXENV_UNDI_TRANSMIT
186  *
187  * Status: working
188  */
189 PXENV_EXIT_t pxenv_undi_transmit ( struct s_PXENV_UNDI_TRANSMIT
190                                    *undi_transmit ) {
191         struct s_PXENV_UNDI_TBD tbd;
192         struct DataBlk *datablk;
193         struct io_buffer *iobuf;
194         struct net_protocol *net_protocol;
195         char destaddr[MAX_LL_ADDR_LEN];
196         const void *ll_dest;
197         size_t ll_hlen = pxe_netdev->ll_protocol->ll_header_len;
198         size_t len;
199         unsigned int i;
200         int rc;
201
202         DBG ( "PXENV_UNDI_TRANSMIT" );
203
204         /* Identify network-layer protocol */
205         switch ( undi_transmit->Protocol ) {
206         case P_IP:      net_protocol = &ipv4_protocol;  break;
207         case P_ARP:     net_protocol = &arp_protocol;   break;
208         case P_RARP:    net_protocol = &rarp_protocol;  break;
209         case P_UNKNOWN:
210                 net_protocol = NULL;
211                 ll_hlen = 0;
212                 break;
213         default:
214                 undi_transmit->Status = PXENV_STATUS_UNDI_INVALID_PARAMETER;
215                 return PXENV_EXIT_FAILURE;
216         }
217         DBG ( " %s", ( net_protocol ? net_protocol->name : "UNKNOWN" ) );
218
219         /* Calculate total packet length */
220         copy_from_real ( &tbd, undi_transmit->TBD.segment,
221                          undi_transmit->TBD.offset, sizeof ( tbd ) );
222         len = tbd.ImmedLength;
223         DBG ( " %zd", tbd.ImmedLength );
224         for ( i = 0 ; i < tbd.DataBlkCount ; i++ ) {
225                 datablk = &tbd.DataBlock[i];
226                 len += datablk->TDDataLen;
227                 DBG ( "+%zd", datablk->TDDataLen );
228         }
229
230         /* Allocate and fill I/O buffer */
231         iobuf = alloc_iob ( ll_hlen + len );
232         if ( ! iobuf ) {
233                 undi_transmit->Status = PXENV_STATUS_OUT_OF_RESOURCES;
234                 return PXENV_EXIT_FAILURE;
235         }
236         iob_reserve ( iobuf, ll_hlen );
237         copy_from_real ( iob_put ( iobuf, tbd.ImmedLength ), tbd.Xmit.segment,
238                          tbd.Xmit.offset, tbd.ImmedLength );
239         for ( i = 0 ; i < tbd.DataBlkCount ; i++ ) {
240                 datablk = &tbd.DataBlock[i];
241                 copy_from_real ( iob_put ( iobuf, datablk->TDDataLen ),
242                                  datablk->TDDataPtr.segment,
243                                  datablk->TDDataPtr.offset,
244                                  datablk->TDDataLen );
245         }
246
247         /* Transmit packet */
248         if ( net_protocol == NULL ) {
249                 /* Link-layer header already present */
250                 rc = netdev_tx ( pxe_netdev, iobuf );
251         } else {
252                 /* Calculate destination address */
253                 if ( undi_transmit->XmitFlag == XMT_DESTADDR ) {
254                         copy_from_real ( destaddr,
255                                          undi_transmit->DestAddr.segment,
256                                          undi_transmit->DestAddr.offset,
257                                          pxe_netdev->ll_protocol->ll_addr_len );
258                         ll_dest = destaddr;
259                 } else {
260                         ll_dest = pxe_netdev->ll_protocol->ll_broadcast;
261                 }
262                 rc = net_tx ( iobuf, pxe_netdev, net_protocol, ll_dest );
263         }
264
265         /* Flag transmission as in-progress */
266         undi_tx_count++;
267
268         undi_transmit->Status = PXENV_STATUS ( rc );
269         return ( ( rc == 0 ) ? PXENV_EXIT_SUCCESS : PXENV_EXIT_FAILURE );
270 }
271
272 /* PXENV_UNDI_SET_MCAST_ADDRESS
273  *
274  * Status: stub (no PXE multicast support)
275  */
276 PXENV_EXIT_t
277 pxenv_undi_set_mcast_address ( struct s_PXENV_UNDI_SET_MCAST_ADDRESS
278                                *undi_set_mcast_address ) {
279         DBG ( "PXENV_UNDI_SET_MCAST_ADDRESS" );
280
281         undi_set_mcast_address->Status = PXENV_STATUS_UNSUPPORTED;
282         return PXENV_EXIT_FAILURE;
283 }
284
285 /* PXENV_UNDI_SET_STATION_ADDRESS
286  *
287  * Status: working
288  */
289 PXENV_EXIT_t 
290 pxenv_undi_set_station_address ( struct s_PXENV_UNDI_SET_STATION_ADDRESS
291                                  *undi_set_station_address ) {
292
293         DBG ( "PXENV_UNDI_SET_STATION_ADDRESS" );
294
295         /* If adapter is open, the change will have no effect; return
296          * an error
297          */
298         if ( pxe_netdev->state & NETDEV_OPEN ) {
299                 undi_set_station_address->Status =
300                         PXENV_STATUS_UNDI_INVALID_STATE;
301                 return PXENV_EXIT_FAILURE;
302         }
303
304         /* Update MAC address */
305         memcpy ( pxe_netdev->ll_addr,
306                  &undi_set_station_address->StationAddress,
307                  pxe_netdev->ll_protocol->ll_addr_len );
308
309         undi_set_station_address->Status = PXENV_STATUS_SUCCESS;
310         return PXENV_EXIT_SUCCESS;
311 }
312
313 /* PXENV_UNDI_SET_PACKET_FILTER
314  *
315  * Status: won't implement (would require driver API changes for no
316  * real benefit)
317  */
318 PXENV_EXIT_t
319 pxenv_undi_set_packet_filter ( struct s_PXENV_UNDI_SET_PACKET_FILTER
320                                *undi_set_packet_filter ) {
321         DBG ( "PXENV_UNDI_SET_PACKET_FILTER" );
322
323         undi_set_packet_filter->Status = PXENV_STATUS_UNSUPPORTED;
324         return PXENV_EXIT_FAILURE;
325 }
326
327 /* PXENV_UNDI_GET_INFORMATION
328  *
329  * Status: working
330  */
331 PXENV_EXIT_t pxenv_undi_get_information ( struct s_PXENV_UNDI_GET_INFORMATION
332                                           *undi_get_information ) {
333         struct device *dev = pxe_netdev->dev;
334         struct ll_protocol *ll_protocol = pxe_netdev->ll_protocol;
335
336         DBG ( "PXENV_UNDI_GET_INFORMATION" );
337
338         undi_get_information->BaseIo = dev->desc.ioaddr;
339         undi_get_information->IntNumber = dev->desc.irq;
340         /* Cheat: assume all cards can cope with this */
341         undi_get_information->MaxTranUnit = ETH_MAX_MTU;
342         undi_get_information->HwType = ntohs ( ll_protocol->ll_proto );
343         undi_get_information->HwAddrLen = ll_protocol->ll_addr_len;
344         /* Cheat: assume card is always configured with its permanent
345          * node address.  This is a valid assumption within Etherboot
346          * at the time of writing.
347          */
348         memcpy ( &undi_get_information->CurrentNodeAddress,
349                  pxe_netdev->ll_addr,
350                  sizeof ( undi_get_information->CurrentNodeAddress ) );
351         memcpy ( &undi_get_information->PermNodeAddress,
352                  pxe_netdev->ll_addr,
353                  sizeof ( undi_get_information->PermNodeAddress ) );
354         undi_get_information->ROMAddress = 0;
355                 /* nic.rom_info->rom_segment; */
356         /* We only provide the ability to receive or transmit a single
357          * packet at a time.  This is a bootloader, not an OS.
358          */
359         undi_get_information->RxBufCt = 1;
360         undi_get_information->TxBufCt = 1;
361
362         undi_get_information->Status = PXENV_STATUS_SUCCESS;
363         return PXENV_EXIT_SUCCESS;
364 }
365
366 /* PXENV_UNDI_GET_STATISTICS
367  *
368  * Status: working
369  */
370 PXENV_EXIT_t pxenv_undi_get_statistics ( struct s_PXENV_UNDI_GET_STATISTICS
371                                          *undi_get_statistics ) {
372         DBG ( "PXENV_UNDI_GET_STATISTICS" );
373
374         undi_get_statistics->XmtGoodFrames = pxe_netdev->stats.tx_ok;
375         undi_get_statistics->RcvGoodFrames = pxe_netdev->stats.rx_ok;
376         undi_get_statistics->RcvCRCErrors = pxe_netdev->stats.rx_err;
377         undi_get_statistics->RcvResourceErrors = pxe_netdev->stats.rx_err;
378
379         undi_get_statistics->Status = PXENV_STATUS_SUCCESS;
380         return PXENV_EXIT_SUCCESS;
381 }
382
383 /* PXENV_UNDI_CLEAR_STATISTICS
384  *
385  * Status: working
386  */
387 PXENV_EXIT_t pxenv_undi_clear_statistics ( struct s_PXENV_UNDI_CLEAR_STATISTICS
388                                            *undi_clear_statistics ) {
389         DBG ( "PXENV_UNDI_CLEAR_STATISTICS" );
390
391         memset ( &pxe_netdev->stats, 0, sizeof ( pxe_netdev->stats ) );
392
393         undi_clear_statistics->Status = PXENV_STATUS_SUCCESS;
394         return PXENV_EXIT_SUCCESS;
395 }
396
397 /* PXENV_UNDI_INITIATE_DIAGS
398  *
399  * Status: won't implement (would require driver API changes for no
400  * real benefit)
401  */
402 PXENV_EXIT_t pxenv_undi_initiate_diags ( struct s_PXENV_UNDI_INITIATE_DIAGS
403                                          *undi_initiate_diags ) {
404         DBG ( "PXENV_UNDI_INITIATE_DIAGS" );
405
406         undi_initiate_diags->Status = PXENV_STATUS_UNSUPPORTED;
407         return PXENV_EXIT_FAILURE;
408 }
409
410 /* PXENV_UNDI_FORCE_INTERRUPT
411  *
412  * Status: won't implement (would require driver API changes for no
413  * perceptible benefit)
414  */
415 PXENV_EXIT_t pxenv_undi_force_interrupt ( struct s_PXENV_UNDI_FORCE_INTERRUPT
416                                           *undi_force_interrupt ) {
417         DBG ( "PXENV_UNDI_FORCE_INTERRUPT" );
418
419         undi_force_interrupt->Status = PXENV_STATUS_UNSUPPORTED;
420         return PXENV_EXIT_FAILURE;
421 }
422
423 /* PXENV_UNDI_GET_MCAST_ADDRESS
424  *
425  * Status: stub (no PXE multicast support)
426  */
427 PXENV_EXIT_t
428 pxenv_undi_get_mcast_address ( struct s_PXENV_UNDI_GET_MCAST_ADDRESS
429                                *undi_get_mcast_address ) {
430         DBG ( "PXENV_UNDI_GET_MCAST_ADDRESS" );
431
432         undi_get_mcast_address->Status = PXENV_STATUS_UNSUPPORTED;
433         return PXENV_EXIT_FAILURE;
434 }
435
436 /* PXENV_UNDI_GET_NIC_TYPE
437  *
438  * Status: working
439  */
440 PXENV_EXIT_t pxenv_undi_get_nic_type ( struct s_PXENV_UNDI_GET_NIC_TYPE
441                                        *undi_get_nic_type ) {
442         struct device *dev = pxe_netdev->dev;
443
444         DBG ( "PXENV_UNDI_GET_NIC_TYPE" );
445
446         memset ( &undi_get_nic_type->info, 0,
447                  sizeof ( undi_get_nic_type->info ) );
448
449         switch ( dev->desc.bus_type ) {
450         case BUS_TYPE_PCI: {
451                 struct pci_nic_info *info = &undi_get_nic_type->info.pci;
452
453                 undi_get_nic_type->NicType = PCI_NIC;
454                 info->Vendor_ID = dev->desc.vendor;
455                 info->Dev_ID = dev->desc.device;
456                 info->Base_Class = PCI_BASE_CLASS ( dev->desc.class );
457                 info->Sub_Class = PCI_SUB_CLASS ( dev->desc.class );
458                 info->Prog_Intf = PCI_PROG_INTF ( dev->desc.class );
459                 info->BusDevFunc = dev->desc.location;
460                 /* Cheat: remaining fields are probably unnecessary,
461                  * and would require adding extra code to pci.c.
462                  */
463                 undi_get_nic_type->info.pci.SubVendor_ID = 0xffff;
464                 undi_get_nic_type->info.pci.SubDevice_ID = 0xffff;
465                 break; }
466         case BUS_TYPE_ISAPNP: {
467                 struct pnp_nic_info *info = &undi_get_nic_type->info.pnp;
468
469                 undi_get_nic_type->NicType = PnP_NIC;
470                 info->EISA_Dev_ID = ( ( dev->desc.vendor << 16 ) |
471                                       dev->desc.device );
472                 info->CardSelNum = dev->desc.location;
473                 /* Cheat: remaining fields are probably unnecessary,
474                  * and would require adding extra code to isapnp.c.
475                  */
476                 break; }
477         default:
478                 undi_get_nic_type->Status = PXENV_STATUS_FAILURE;
479                 return PXENV_EXIT_FAILURE;
480         }
481
482         undi_get_nic_type->Status = PXENV_STATUS_SUCCESS;
483         return PXENV_EXIT_SUCCESS;
484 }
485
486 /* PXENV_UNDI_GET_IFACE_INFO
487  *
488  * Status: working
489  */
490 PXENV_EXIT_t pxenv_undi_get_iface_info ( struct s_PXENV_UNDI_GET_IFACE_INFO
491                                          *undi_get_iface_info ) {
492         DBG ( "PXENV_UNDI_GET_IFACE_INFO" );
493
494         /* Just hand back some info, doesn't really matter what it is.
495          * Most PXE stacks seem to take this approach.
496          */
497         snprintf ( ( char * ) undi_get_iface_info->IfaceType,
498                    sizeof ( undi_get_iface_info->IfaceType ), "gPXE" );
499         undi_get_iface_info->LinkSpeed = 10000000; /* 10 Mbps */
500         undi_get_iface_info->ServiceFlags = 0;
501         memset ( undi_get_iface_info->Reserved, 0,
502                  sizeof(undi_get_iface_info->Reserved) );
503
504         undi_get_iface_info->Status = PXENV_STATUS_SUCCESS;
505         return PXENV_EXIT_SUCCESS;
506 }
507
508 /* PXENV_UNDI_GET_STATE
509  *
510  * Status: impossible
511  */
512 PXENV_EXIT_t pxenv_undi_get_state ( struct s_PXENV_UNDI_GET_STATE
513                                     *undi_get_state ) {
514         DBG ( "PXENV_UNDI_GET_STATE" );
515
516         undi_get_state->Status = PXENV_STATUS_UNSUPPORTED;
517         return PXENV_EXIT_FAILURE;
518 };
519
520 /* PXENV_UNDI_ISR
521  *
522  * Status: working
523  */
524 PXENV_EXIT_t pxenv_undi_isr ( struct s_PXENV_UNDI_ISR *undi_isr ) {
525         struct io_buffer *iobuf;
526         size_t len;
527
528         DBG ( "PXENV_UNDI_ISR" );
529
530         /* Just in case some idiot actually looks at these fields when
531          * we weren't meant to fill them in...
532          */
533         undi_isr->BufferLength = 0;
534         undi_isr->FrameLength = 0;
535         undi_isr->FrameHeaderLength = 0;
536         undi_isr->ProtType = 0;
537         undi_isr->PktType = 0;
538
539         switch ( undi_isr->FuncFlag ) {
540         case PXENV_UNDI_ISR_IN_START :
541                 DBG ( " START" );
542
543                 /* Call poll().  This should acknowledge the device
544                  * interrupt and queue up any received packet.
545                  */
546                 if ( netdev_poll ( pxe_netdev, -1U ) ) {
547                         /* Packet waiting in queue */
548                         DBG ( " OURS" );
549                         undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_OURS;
550                 } else {
551                         DBG ( " NOT_OURS" );
552                         undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_NOT_OURS;
553                 }
554                 break;
555         case PXENV_UNDI_ISR_IN_PROCESS :
556         case PXENV_UNDI_ISR_IN_GET_NEXT :
557                 DBG ( " PROCESS/GET_NEXT" );
558
559                 /* If we have not yet marked a TX as complete, and the
560                  * netdev TX queue is empty, report the TX completion.
561                  */
562                 if ( undi_tx_count && list_empty ( &pxe_netdev->tx_queue ) ) {
563                         undi_tx_count--;
564                         undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_TRANSMIT;
565                         break;
566                 }
567
568                 /* Remove first packet from netdev RX queue */
569                 iobuf = netdev_rx_dequeue ( pxe_netdev );
570                 if ( ! iobuf ) {
571                         /* No more packets remaining */
572                         undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_DONE;
573                         break;
574                 }
575
576                 /* Copy packet to base memory buffer */
577                 len = iob_len ( iobuf );
578                 DBG ( " RECEIVE %zd", len );
579                 if ( len > sizeof ( basemem_packet ) ) {
580                         /* Should never happen */
581                         len = sizeof ( basemem_packet );
582                 }
583                 memcpy ( basemem_packet, iobuf->data, len );
584
585                 /* Fill in UNDI_ISR structure */
586                 undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_RECEIVE;
587                 undi_isr->BufferLength = len;
588                 undi_isr->FrameLength = len;
589                 undi_isr->FrameHeaderLength =
590                         pxe_netdev->ll_protocol->ll_header_len;
591                 undi_isr->Frame.segment = rm_ds;
592                 undi_isr->Frame.offset =
593                         ( ( unsigned ) & __from_data16 ( basemem_packet ) );
594                 /* Probably ought to fill in packet type */
595                 undi_isr->ProtType = P_UNKNOWN;
596                 undi_isr->PktType = XMT_DESTADDR;
597
598                 /* Free packet */
599                 free_iob ( iobuf );
600                 break;
601         default :
602                 DBG ( " INVALID(%04x)", undi_isr->FuncFlag );
603
604                 /* Should never happen */
605                 undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_DONE;
606                 undi_isr->Status = PXENV_STATUS_UNDI_INVALID_PARAMETER;
607                 return PXENV_EXIT_FAILURE;
608         }
609
610         undi_isr->Status = PXENV_STATUS_SUCCESS;
611         return PXENV_EXIT_SUCCESS;
612 }