1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#include "IxEthDB_p.h"
22#include "IxEthDBLog_p.h"
23
24
25IX_ETH_DB_PUBLIC void ixEthDBELTShow(IxEthDBPortId portID);
26IX_ETH_DB_PUBLIC void ixEthDBShowNpeMsgHistory(void);
27
28
29UINT8* ixEthDBNPEUpdateArea[IX_ETH_DB_NUMBER_OF_PORTS];
30UINT32 dumpEltSize;
31
32
33IX_ETH_DB_PRIVATE IxEthDBNoteWriteFn ixEthDBNPENodeWrite[IX_ETH_DB_MAX_RECORD_TYPE_INDEX + 1];
34
35#define IX_ETH_DB_MAX_DELTA_ZONES (6)
36IX_ETH_DB_PRIVATE UINT32 ixEthDBEPDeltaOffset[IX_ETH_DB_MAX_RECORD_TYPE_INDEX + 1][IX_ETH_DB_MAX_DELTA_ZONES];
37IX_ETH_DB_PRIVATE UINT32 ixEthDBEPDelta[IX_ETH_DB_MAX_RECORD_TYPE_INDEX + 1][IX_ETH_DB_MAX_DELTA_ZONES];
38
39
40
41
42
43
44
45
46
47
48
49
50
51IX_ETH_DB_PUBLIC
52void ixEthDBNPEUpdateAreasInit(void)
53{
54 UINT32 portIndex;
55 PortUpdateMethod *update;
56
57 for (portIndex = 0 ; portIndex < IX_ETH_DB_NUMBER_OF_PORTS ; portIndex++)
58 {
59 update = &ixEthDBPortInfo[portIndex].updateMethod;
60
61 if (ixEthDBPortDefinitions[portIndex].type == IX_ETH_NPE)
62 {
63 update->npeUpdateZone = IX_OSAL_CACHE_DMA_MALLOC(FULL_ELT_BYTE_SIZE);
64 update->npeGwUpdateZone = IX_OSAL_CACHE_DMA_MALLOC(FULL_GW_BYTE_SIZE);
65 update->vlanUpdateZone = IX_OSAL_CACHE_DMA_MALLOC(FULL_VLAN_BYTE_SIZE);
66
67 if (update->npeUpdateZone == NULL
68 || update->npeGwUpdateZone == NULL
69 || update->vlanUpdateZone == NULL)
70 {
71 ERROR_LOG("Fatal error: IX_ACC_DRV_DMA_MALLOC() returned NULL, no NPE update zones available\n");
72 }
73 else
74 {
75 memset(update->npeUpdateZone, 0, FULL_ELT_BYTE_SIZE);
76 memset(update->npeGwUpdateZone, 0, FULL_GW_BYTE_SIZE);
77 memset(update->vlanUpdateZone, 0, FULL_VLAN_BYTE_SIZE);
78 }
79 }
80 else
81 {
82
83 update->npeUpdateZone = NULL;
84 update->npeGwUpdateZone = NULL;
85 update->vlanUpdateZone = NULL;
86 }
87 }
88}
89
90
91
92
93
94
95
96
97
98
99
100IX_ETH_DB_PUBLIC
101void ixEthDBNPEUpdateAreasUnload(void)
102{
103 UINT32 portIndex;
104
105 for (portIndex = 0 ; portIndex < IX_ETH_DB_NUMBER_OF_PORTS ; portIndex++)
106 {
107 if (ixEthDBPortDefinitions[portIndex].type == IX_ETH_NPE)
108 {
109 IX_OSAL_CACHE_DMA_FREE(ixEthDBPortInfo[portIndex].updateMethod.npeUpdateZone);
110 IX_OSAL_CACHE_DMA_FREE(ixEthDBPortInfo[portIndex].updateMethod.npeGwUpdateZone);
111 IX_OSAL_CACHE_DMA_FREE(ixEthDBPortInfo[portIndex].updateMethod.vlanUpdateZone);
112 }
113 }
114}
115
116
117
118
119
120
121
122
123
124
125
126
127IX_ETH_DB_PUBLIC
128void ixEthDBNpeMsgAck(IxNpeMhNpeId npeID, IxNpeMhMessage msg)
129{
130 IxEthDBPortId portID = IX_ETH_DB_NPE_TO_PORT_ID(npeID);
131 PortInfo *portInfo;
132
133 if (portID >= IX_ETH_DB_NUMBER_OF_PORTS)
134 {
135
136 return;
137 }
138
139 if (ixEthDBPortDefinitions[portID].type != IX_ETH_NPE)
140 {
141
142 return;
143 }
144
145 portInfo = &ixEthDBPortInfo[portID];
146
147 ixOsalMutexUnlock(&portInfo->npeAckLock);
148}
149
150
151
152
153
154
155
156
157
158
159
160
161IX_ETH_DB_PUBLIC
162void ixEthDBNPESyncScan(IxEthDBPortId portID, void *eltBaseAddress, UINT32 eltSize)
163{
164 UINT32 eltEntryOffset;
165 UINT32 entryPortID;
166
167
168 IX_OSAL_CACHE_INVALIDATE(eltBaseAddress, eltSize);
169
170 for (eltEntryOffset = ELT_ROOT_OFFSET ; eltEntryOffset < eltSize ; eltEntryOffset += ELT_ENTRY_SIZE)
171 {
172
173
174
175
176
177 void *eltNodeAddress = (void *) ((UINT32) eltBaseAddress + eltEntryOffset);
178
179
180 IX_ETH_DB_NPE_VERBOSE_TRACE("DB: (NPEAdaptor) checking node at offset %d...\n", eltEntryOffset / ELT_ENTRY_SIZE);
181
182 if (IX_EDB_NPE_NODE_VALID(eltNodeAddress) != true)
183 {
184 IX_ETH_DB_NPE_VERBOSE_TRACE("\t... node is empty\n");
185 }
186 else if (eltEntryOffset == ELT_ROOT_OFFSET)
187 {
188 IX_ETH_DB_NPE_VERBOSE_TRACE("\t... node is root\n");
189 }
190
191 if (IX_EDB_NPE_NODE_VALID(eltNodeAddress))
192 {
193 entryPortID = IX_ETH_DB_NPE_LOGICAL_ID_TO_PORT_ID(IX_EDB_NPE_NODE_PORT_ID(eltNodeAddress));
194
195
196 if (ixEthDBPortInfo[portID].agingEnabled && IX_EDB_NPE_NODE_ACTIVE(eltNodeAddress) && (portID == entryPortID)
197 && ((ixEthDBPortDefinitions[portID].capabilities & IX_ETH_ENTRY_AGING) == 0))
198 {
199
200 HashNode *node = ixEthDBSearch((IxEthDBMacAddr *) eltNodeAddress, IX_ETH_DB_ALL_FILTERING_RECORDS);
201
202
203 if (node != NULL)
204 {
205
206 MacDescriptor *descriptor = (MacDescriptor *) node->data;
207
208 IX_ETH_DB_NPE_VERBOSE_TRACE("DB: (NPEAdaptor) synced entry [%s] already in the database, updating fields\n", mac2string(eltNodeAddress));
209
210
211 if (!descriptor->recordData.filteringData.staticEntry)
212 {
213 if (descriptor->type == IX_ETH_DB_FILTERING_RECORD)
214 {
215 descriptor->recordData.filteringData.age = AGE_RESET;
216 }
217 else if (descriptor->type == IX_ETH_DB_FILTERING_VLAN_RECORD)
218 {
219 descriptor->recordData.filteringVlanData.age = AGE_RESET;
220 }
221 }
222
223
224 ixEthDBReleaseHashNode(node);
225 }
226 }
227 else
228 {
229 IX_ETH_DB_NPE_VERBOSE_TRACE("\t... found portID %d, we check only port %d\n", entryPortID, portID);
230 }
231 }
232 }
233}
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251IX_ETH_DB_PUBLIC
252void ixEthDBNPETreeWrite(IxEthDBRecordType type, UINT32 totalSize, void *baseAddress, MacTreeNode *tree, UINT32 *epDelta, UINT32 *blocks)
253{
254 MacTreeNodeStack *stack;
255 UINT32 maxOffset = 0;
256 UINT32 emptyOffset;
257
258 stack = ixOsalCacheDmaMalloc(sizeof (MacTreeNodeStack));
259
260 if (stack == NULL)
261 {
262 ERROR_LOG("DB: (NPEAdaptor) failed to allocate the node stack for learning tree linearization, out of memory?\n");
263 return;
264 }
265
266
267 memset(baseAddress, 0, ELT_ENTRY_SIZE);
268
269 NODE_STACK_INIT(stack);
270
271 if (tree != NULL)
272 {
273
274 NODE_STACK_PUSH(stack, tree, 1);
275
276 maxOffset = 1;
277 }
278
279 while (NODE_STACK_NONEMPTY(stack))
280 {
281 MacTreeNode *node;
282 UINT32 offset;
283
284 NODE_STACK_POP(stack, node, offset);
285
286
287 if (offset > maxOffset)
288 {
289 maxOffset = offset;
290 }
291
292 IX_ETH_DB_NPE_VERBOSE_TRACE("DB: (NPEAdaptor) writing MAC [%s] at offset %d\n", mac2string(node->descriptor->macAddress), offset);
293
294
295 if (offset < MAX_ELT_SIZE)
296 {
297 ixEthDBNPENodeWrite[type]((void *) (((UINT32) baseAddress) + offset * ELT_ENTRY_SIZE), node);
298 }
299
300 if (node->left != NULL)
301 {
302 NODE_STACK_PUSH(stack, node->left, LEFT_CHILD_OFFSET(offset));
303 }
304 else
305 {
306
307 memset((void *) ((UINT32) baseAddress + LEFT_CHILD_OFFSET(offset) * ELT_ENTRY_SIZE), 0, ELT_ENTRY_SIZE);
308 }
309
310 if (node->right != NULL)
311 {
312 NODE_STACK_PUSH(stack, node->right, RIGHT_CHILD_OFFSET(offset));
313 }
314 else
315 {
316
317 memset((void *) ((UINT32) baseAddress + RIGHT_CHILD_OFFSET(offset) * ELT_ENTRY_SIZE), 0, ELT_ENTRY_SIZE);
318 }
319 }
320
321 emptyOffset = maxOffset + 1;
322
323
324 IX_ETH_DB_NPE_TRACE("DB: (NPEAdaptor) Emptying tree from offset %d, address 0x%08X, %d bytes\n",
325 emptyOffset, ((UINT32) baseAddress) + emptyOffset * ELT_ENTRY_SIZE, totalSize - (emptyOffset * ELT_ENTRY_SIZE));
326
327 if (emptyOffset < MAX_ELT_SIZE - 1)
328 {
329 memset((void *) (((UINT32) baseAddress) + (emptyOffset * ELT_ENTRY_SIZE)), 0, totalSize - (emptyOffset * ELT_ENTRY_SIZE));
330 }
331
332
333 IX_OSAL_CACHE_FLUSH(baseAddress, totalSize);
334
335
336 IX_ETH_DB_NPE_TRACE("DB: (NPEAdaptor) Ethernet learning/filtering tree XScale wrote at address 0x%08X (max %d bytes):\n\n",
337 (UINT32) baseAddress, FULL_ELT_BYTE_SIZE);
338
339 IX_ETH_DB_NPE_DUMP_ELT(baseAddress, FULL_ELT_BYTE_SIZE);
340
341
342 if (blocks != NULL)
343 {
344 *blocks = maxOffset != 0 ? 1 + maxOffset / 8 : 0;
345
346 IX_ETH_DB_NPE_TRACE("DB: (NPEAdaptor) Wrote %d 64-byte blocks\n", *blocks);
347 }
348
349
350 if (epDelta != NULL)
351 {
352 UINT32 deltaIndex = 0;
353
354 *epDelta = 0;
355
356 for (; deltaIndex < IX_ETH_DB_MAX_DELTA_ZONES ; deltaIndex ++)
357 {
358 if (ixEthDBEPDeltaOffset[type][deltaIndex] >= maxOffset)
359 {
360 *epDelta = ixEthDBEPDelta[type][deltaIndex];
361 break;
362 }
363 }
364
365 IX_ETH_DB_NPE_TRACE("DB: (NPEAdaptor) Computed epDelta %d (based on maxOffset %d)\n", *epDelta, maxOffset);
366 }
367
368 ixOsalCacheDmaFree(stack);
369}
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385IX_ETH_DB_PRIVATE
386void ixEthDBNullSerialize(void *address, MacTreeNode *node)
387{
388 IX_ETH_DB_NPE_TRACE("DB: (NPEAdaptor) Warning, the NullSerialize function was called, wrong record type?\n");
389}
390
391
392
393
394
395
396
397
398
399
400
401
402IX_ETH_DB_PRIVATE
403void ixEthDBNPELearningNodeWrite(void *address, MacTreeNode *node)
404{
405
406 memcpy(address, node->descriptor->macAddress, IX_IEEE803_MAC_ADDRESS_SIZE);
407
408
409 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_ELT_PORT_ID_OFFSET) = IX_ETH_DB_PORT_ID_TO_NPE_LOGICAL_ID(node->descriptor->portID);
410
411
412 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_ELT_FLAGS_OFFSET) = (UINT8) IX_EDB_FLAGS_INACTIVE_VALID;
413
414 IX_ETH_DB_NPE_VERBOSE_TRACE("DB: (NPEAdaptor) writing ELT node 0x%08x:0x%08x\n", * (UINT32 *) address, * (((UINT32 *) (address)) + 1));
415}
416
417
418
419
420
421
422
423
424
425
426
427
428
429IX_ETH_DB_PRIVATE
430void ixEthDBNPEWiFiNodeWrite(void *address, MacTreeNode *node)
431{
432
433 memcpy(address, node->descriptor->macAddress, IX_IEEE803_MAC_ADDRESS_SIZE);
434
435
436 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_WIFI_INDEX_OFFSET) = node->descriptor->recordData.wifiData.gwAddressIndex;
437
438
439 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_WIFI_FLAGS_OFFSET) = node->descriptor->recordData.wifiData.type << 1 | IX_EDB_FLAGS_VALID;
440}
441
442
443
444
445
446
447
448
449
450
451
452
453
454IX_ETH_DB_PUBLIC
455void ixEthDBNPEGatewayNodeWrite(void *address, MacTreeNode *node)
456{
457
458 memcpy(address, node->descriptor->recordData.wifiData.gwMacAddress, IX_IEEE803_MAC_ADDRESS_SIZE);
459
460
461 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_FW_RESERVED_OFFSET) = 0;
462 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_FW_RESERVED_OFFSET + 1) = 0;
463}
464
465
466
467
468
469
470
471
472
473
474
475
476
477IX_ETH_DB_PRIVATE
478void ixEthDBNPEFirewallNodeWrite(void *address, MacTreeNode *node)
479{
480
481 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_FW_RESERVED_OFFSET) = 0;
482
483
484 NPE_NODE_BYTE(address, IX_EDB_NPE_NODE_FW_FLAGS_OFFSET) = IX_EDB_FLAGS_VALID;
485
486
487 memcpy((void *) ((UINT32) address + IX_EDB_NPE_NODE_FW_ADDR_OFFSET), node->descriptor->macAddress, IX_IEEE803_MAC_ADDRESS_SIZE);
488}
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508IX_ETH_DB_PUBLIC
509UINT32 ixEthDBRecordSerializeMethodsRegister()
510{
511 int i;
512
513
514 for ( i = 0 ; i < IX_ETH_DB_MAX_RECORD_TYPE_INDEX + 1 ; i++)
515 {
516 ixEthDBNPENodeWrite[i] = ixEthDBNullSerialize;
517 }
518
519
520 ixEthDBNPENodeWrite[IX_ETH_DB_FILTERING_RECORD] = ixEthDBNPELearningNodeWrite;
521 ixEthDBNPENodeWrite[IX_ETH_DB_FILTERING_VLAN_RECORD] = ixEthDBNPELearningNodeWrite;
522 ixEthDBNPENodeWrite[IX_ETH_DB_WIFI_RECORD] = ixEthDBNPEWiFiNodeWrite;
523 ixEthDBNPENodeWrite[IX_ETH_DB_FIREWALL_RECORD] = ixEthDBNPEFirewallNodeWrite;
524 ixEthDBNPENodeWrite[IX_ETH_DB_GATEWAY_RECORD] = ixEthDBNPEGatewayNodeWrite;
525
526
527 memset(ixEthDBEPDeltaOffset, 0, sizeof (ixEthDBEPDeltaOffset));
528 memset(ixEthDBEPDelta, 0, sizeof (ixEthDBEPDelta));
529
530
531 ixEthDBEPDeltaOffset[IX_ETH_DB_FILTERING_RECORD][0] = 1;
532 ixEthDBEPDelta[IX_ETH_DB_FILTERING_RECORD][0] = 0;
533
534 ixEthDBEPDeltaOffset[IX_ETH_DB_FILTERING_RECORD][1] = 3;
535 ixEthDBEPDelta[IX_ETH_DB_FILTERING_RECORD][1] = 7;
536
537 ixEthDBEPDeltaOffset[IX_ETH_DB_FILTERING_RECORD][2] = 511;
538 ixEthDBEPDelta[IX_ETH_DB_FILTERING_RECORD][2] = 14;
539
540
541 ixEthDBEPDeltaOffset[IX_ETH_DB_WIFI_RECORD][0] = 1;
542 ixEthDBEPDelta[IX_ETH_DB_WIFI_RECORD][0] = 0;
543
544 ixEthDBEPDeltaOffset[IX_ETH_DB_WIFI_RECORD][1] = 3;
545 ixEthDBEPDelta[IX_ETH_DB_WIFI_RECORD][1] = 7;
546
547 ixEthDBEPDeltaOffset[IX_ETH_DB_WIFI_RECORD][2] = 511;
548 ixEthDBEPDelta[IX_ETH_DB_WIFI_RECORD][2] = 14;
549
550
551 ixEthDBEPDeltaOffset[IX_ETH_DB_FIREWALL_RECORD][0] = 0;
552 ixEthDBEPDelta[IX_ETH_DB_FIREWALL_RECORD][0] = 0;
553
554 ixEthDBEPDeltaOffset[IX_ETH_DB_FIREWALL_RECORD][1] = 1;
555 ixEthDBEPDelta[IX_ETH_DB_FIREWALL_RECORD][1] = 5;
556
557 ixEthDBEPDeltaOffset[IX_ETH_DB_FIREWALL_RECORD][2] = 3;
558 ixEthDBEPDelta[IX_ETH_DB_FIREWALL_RECORD][2] = 13;
559
560 ixEthDBEPDeltaOffset[IX_ETH_DB_FIREWALL_RECORD][3] = 7;
561 ixEthDBEPDelta[IX_ETH_DB_FIREWALL_RECORD][3] = 21;
562
563 ixEthDBEPDeltaOffset[IX_ETH_DB_FIREWALL_RECORD][4] = 15;
564 ixEthDBEPDelta[IX_ETH_DB_FIREWALL_RECORD][4] = 29;
565
566 ixEthDBEPDeltaOffset[IX_ETH_DB_FIREWALL_RECORD][5] = 31;
567 ixEthDBEPDelta[IX_ETH_DB_FIREWALL_RECORD][5] = 37;
568
569 return 5;
570}
571
572#ifndef IX_NDEBUG
573
574IX_ETH_DB_PUBLIC UINT32 npeMsgHistory[IX_ETH_DB_NPE_MSG_HISTORY_DEPTH][2];
575IX_ETH_DB_PUBLIC UINT32 npeMsgHistoryLen = 0;
576
577
578
579
580
581IX_ETH_DB_PUBLIC
582void ixEthDBShowNpeMsgHistory()
583{
584 UINT32 i = 0;
585 UINT32 base, len;
586
587 if (npeMsgHistoryLen <= IX_ETH_DB_NPE_MSG_HISTORY_DEPTH)
588 {
589 base = 0;
590 len = npeMsgHistoryLen;
591 }
592 else
593 {
594 base = npeMsgHistoryLen % IX_ETH_DB_NPE_MSG_HISTORY_DEPTH;
595 len = IX_ETH_DB_NPE_MSG_HISTORY_DEPTH;
596 }
597
598 printf("NPE message history [last %d messages, from least to most recent]:\n", len);
599
600 for (; i < len ; i++)
601 {
602 UINT32 pos = (base + i) % IX_ETH_DB_NPE_MSG_HISTORY_DEPTH;
603 printf("msg[%d]: 0x%08x:0x%08x\n", i, npeMsgHistory[pos][0], npeMsgHistory[pos][1]);
604 }
605}
606
607IX_ETH_DB_PUBLIC
608void ixEthDBELTShow(IxEthDBPortId portID)
609{
610 IxNpeMhMessage message;
611 IX_STATUS result;
612
613
614 FILL_GETMACADDRESSDATABASE(message,
615 0 ,
616 IX_OSAL_MMU_VIRT_TO_PHYS(ixEthDBPortInfo[portID].updateMethod.npeUpdateZone));
617
618 IX_ETHDB_SEND_NPE_MSG(IX_ETH_DB_PORT_ID_TO_NPE(portID), message, result);
619
620 if (result == IX_SUCCESS)
621 {
622
623 UINT32 eltEntryOffset;
624 UINT32 entryPortID;
625
626 UINT32 eltBaseAddress = (UINT32) ixEthDBPortInfo[portID].updateMethod.npeUpdateZone;
627 UINT32 eltSize = FULL_ELT_BYTE_SIZE;
628
629
630 IX_OSAL_CACHE_INVALIDATE((void *) eltBaseAddress, eltSize);
631
632 printf("Listing records in main learning tree for port %d\n", portID);
633
634 for (eltEntryOffset = ELT_ROOT_OFFSET ; eltEntryOffset < eltSize ; eltEntryOffset += ELT_ENTRY_SIZE)
635 {
636
637
638
639
640
641 void *eltNodeAddress = (void *) ((UINT32) eltBaseAddress + eltEntryOffset);
642
643 if (IX_EDB_NPE_NODE_VALID(eltNodeAddress))
644 {
645 HashNode *node;
646
647 entryPortID = IX_ETH_DB_NPE_LOGICAL_ID_TO_PORT_ID(IX_EDB_NPE_NODE_PORT_ID(eltNodeAddress));
648
649
650 node = ixEthDBSearch((IxEthDBMacAddr *) eltNodeAddress, IX_ETH_DB_ALL_RECORD_TYPES);
651
652 printf("%s - port %d - %s ", mac2string((unsigned char *) eltNodeAddress), entryPortID,
653 IX_EDB_NPE_NODE_ACTIVE(eltNodeAddress) ? "active" : "inactive");
654
655
656 if (node != NULL)
657 {
658
659 MacDescriptor *descriptor = (MacDescriptor *) node->data;
660
661 printf("- %s ",
662 descriptor->type == IX_ETH_DB_FILTERING_RECORD ? "filtering" :
663 descriptor->type == IX_ETH_DB_FILTERING_VLAN_RECORD ? "vlan" :
664 descriptor->type == IX_ETH_DB_WIFI_RECORD ? "wifi" : "other (check main DB)");
665
666 if (descriptor->type == IX_ETH_DB_FILTERING_RECORD) printf("- age %d - %s ",
667 descriptor->recordData.filteringData.age,
668 descriptor->recordData.filteringData.staticEntry ? "static" : "dynamic");
669
670 if (descriptor->type == IX_ETH_DB_FILTERING_VLAN_RECORD) printf("- age %d - %s - tci %d ",
671 descriptor->recordData.filteringVlanData.age,
672 descriptor->recordData.filteringVlanData.staticEntry ? "static" : "dynamic",
673 descriptor->recordData.filteringVlanData.ieee802_1qTag);
674
675
676 ixEthDBReleaseHashNode(node);
677 }
678 else
679 {
680 printf("- not synced");
681 }
682
683 printf("\n");
684 }
685 }
686 }
687 else
688 {
689 ixOsalLog(IX_OSAL_LOG_LVL_FATAL, IX_OSAL_LOG_DEV_STDOUT,
690 "EthDB: (ShowELT) Could not complete action (communication failure)\n",
691 portID, 0, 0, 0, 0, 0);
692 }
693}
694
695#endif
696