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