]> git.proxmox.com Git - mirror_edk2.git/blob - EdkModulePkg/Universal/PCD/Dxe/Service.c
e1aded74f99cbed9993d58152675f07fc07f062b
[mirror_edk2.git] / EdkModulePkg / Universal / PCD / Dxe / Service.c
1 /** @file
2 Private functions used by PCD DXE driver.s
3
4 Copyright (c) 2006, Intel Corporation
5 All rights reserved. This program and the accompanying materials
6 are licensed and made available under the terms and conditions of the BSD License
7 which accompanies this distribution. The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php
9
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13
14 Module Name: Service.c
15
16 **/
17 #include "Service.h"
18
19
20 //
21 // Build Tool will generate DXE_PCD_DB_INIT_VALUE in Autogen.h
22 // Compression Algorithm will take care of the size optimization.
23 //
24
25 PCD_DATABASE * mPcdDatabase;
26
27 LIST_ENTRY mCallbackFnTable[PCD_TOTAL_TOKEN_NUMBER];
28
29 VOID *
30 GetWorker (
31 PCD_TOKEN_NUMBER TokenNumber,
32 UINTN GetSize
33 )
34 {
35 UINT32 *LocalTokenNumberTable;
36 UINT16 *SizeTable;
37 BOOLEAN IsPeiDb;
38 UINTN Size;
39 UINT32 Offset;
40 EFI_GUID *GuidTable;
41 UINT16 *StringTable;
42 EFI_GUID *Guid;
43 UINT16 *Name;
44 VARIABLE_HEAD *VariableHead;
45 EFI_STATUS Status;
46 UINTN DataSize;
47 VOID *Data;
48 VPD_HEAD *VpdHead;
49 UINT8 *PcdDb;
50 UINT16 StringTableIdx;
51 UINT32 LocalTokenNumber;
52
53
54 ASSERT (TokenNumber < PCD_TOTAL_TOKEN_NUMBER);
55
56 Size = DxePcdGetSize (TokenNumber);
57 ASSERT (GetSize == Size || GetSize == 0);
58
59
60 IsPeiDb = (TokenNumber <= PEI_LOCAL_TOKEN_NUMBER) ? TRUE : FALSE;
61
62 LocalTokenNumberTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.LocalTokenNumberTable :
63 mPcdDatabase->DxeDb.Init.LocalTokenNumberTable;
64
65 SizeTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.SizeTable:
66 mPcdDatabase->DxeDb.Init.SizeTable;
67
68 TokenNumber = IsPeiDb ? TokenNumber :
69 TokenNumber - PEI_LOCAL_TOKEN_NUMBER;
70
71 LocalTokenNumber = LocalTokenNumberTable[TokenNumber];
72
73 if ((LocalTokenNumber & PCD_TYPE_SKU_ENABLED) == PCD_TYPE_SKU_ENABLED) {
74 LocalTokenNumber = GetSkuEnabledTokenNumber (LocalTokenNumber & ~PCD_TYPE_SKU_ENABLED, Size, IsPeiDb);
75 }
76
77 PcdDb = IsPeiDb ? ((UINT8 *) &mPcdDatabase->PeiDb) : ((UINT8 *) &mPcdDatabase->DxeDb);
78 StringTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.StringTable :
79 mPcdDatabase->DxeDb.Init.StringTable;
80
81 Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;
82
83 switch (LocalTokenNumber & ~PCD_DATABASE_OFFSET_MASK) {
84 case PCD_TYPE_VPD:
85 VpdHead = (VPD_HEAD *) ((UINT8 *) PcdDb + Offset);
86 return (VOID *) (FixedPcdGet32(PcdVpdBaseAddress) + VpdHead->Offset);
87
88 case PCD_TYPE_HII:
89 GuidTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.GuidTable :
90 mPcdDatabase->DxeDb.Init.GuidTable;
91
92 VariableHead = (VARIABLE_HEAD *) (PcdDb + Offset);
93
94 Guid = &(GuidTable[VariableHead->GuidTableIndex]);
95 Name = &(StringTable[VariableHead->StringIndex]);
96
97 Status = GetHiiVariable (Guid, Name, &Data, &DataSize);
98 ASSERT_EFI_ERROR (Status);
99 ASSERT (DataSize >= (UINTN) (VariableHead->Offset + Size));
100
101 return (UINT8 *) Data + VariableHead->Offset;
102
103 case PCD_TYPE_STRING:
104 StringTableIdx = (UINT16) *((UINT8 *) PcdDb + Offset);
105 return (VOID *) &StringTable[StringTableIdx];
106
107 case PCD_TYPE_DATA:
108 return (VOID *) ((UINT8 *) PcdDb + Offset);
109 break;
110
111 default:
112 ASSERT (FALSE);
113 break;
114
115 }
116
117 ASSERT (FALSE);
118
119 return NULL;
120
121 }
122
123
124
125 EFI_STATUS
126 DxeRegisterCallBackWorker (
127 IN PCD_TOKEN_NUMBER TokenNumber,
128 IN CONST GUID *Guid, OPTIONAL
129 IN PCD_PROTOCOL_CALLBACK CallBackFunction
130 )
131 {
132 CALLBACK_FN_ENTRY *FnTableEntry;
133 LIST_ENTRY *ListHead;
134 LIST_ENTRY *ListNode;
135
136 if (Guid != NULL) {
137 TokenNumber = GetExPcdTokenNumber (Guid, TokenNumber);
138 }
139
140 ListHead = &mCallbackFnTable[TokenNumber];
141 ListNode = GetFirstNode (ListHead);
142
143 while (ListNode != ListHead) {
144 FnTableEntry = CR_FNENTRY_FROM_LISTNODE(ListNode, CALLBACK_FN_ENTRY, Node);
145
146 if (FnTableEntry->CallbackFn == CallBackFunction) {
147 //
148 // We only allow a Callback function to be register once
149 // for a TokenNumber. So just return EFI_SUCCESS
150 //
151 return EFI_SUCCESS;
152 }
153 ListNode = GetNextNode (ListHead, ListNode);
154 }
155
156 FnTableEntry = AllocatePool (sizeof(CALLBACK_FN_ENTRY));
157 ASSERT (FnTableEntry != NULL);
158
159 FnTableEntry->CallbackFn = CallBackFunction;
160 InsertTailList (ListHead, &FnTableEntry->Node);
161
162 return EFI_SUCCESS;
163 }
164
165
166
167
168 EFI_STATUS
169 DxeUnRegisterCallBackWorker (
170 IN PCD_TOKEN_NUMBER TokenNumber,
171 IN CONST GUID *Guid, OPTIONAL
172 IN PCD_PROTOCOL_CALLBACK CallBackFunction
173 )
174 {
175 CALLBACK_FN_ENTRY *FnTableEntry;
176 LIST_ENTRY *ListHead;
177 LIST_ENTRY *ListNode;
178
179 if (Guid != NULL) {
180 TokenNumber = GetExPcdTokenNumber (Guid, TokenNumber);
181 }
182
183 ListHead = &mCallbackFnTable[TokenNumber];
184 ListNode = GetFirstNode (ListHead);
185
186 while (ListNode != ListHead) {
187 FnTableEntry = CR_FNENTRY_FROM_LISTNODE(ListNode, CALLBACK_FN_ENTRY, Node);
188
189 if (FnTableEntry->CallbackFn == CallBackFunction) {
190 //
191 // We only allow a Callback function to be register once
192 // for a TokenNumber. So we can safely remove the Node from
193 // the Link List and return EFI_SUCCESS.
194 //
195 RemoveEntryList (ListNode);
196 FreePool (FnTableEntry);
197
198 return EFI_SUCCESS;
199 }
200 ListNode = GetNextNode (ListHead, ListNode);
201 }
202
203 return EFI_INVALID_PARAMETER;
204 }
205
206
207
208 PCD_TOKEN_NUMBER
209 ExGetNextTokeNumber (
210 IN CONST EFI_GUID *Guid,
211 IN PCD_TOKEN_NUMBER TokenNumber,
212 IN EFI_GUID *GuidTable,
213 IN UINTN SizeOfGuidTable,
214 IN DYNAMICEX_MAPPING *ExMapTable,
215 IN UINTN SizeOfExMapTable
216 )
217 {
218 EFI_GUID *MatchGuid;
219 UINTN Idx;
220 UINTN GuidTableIdx;
221 BOOLEAN Found;
222
223 MatchGuid = ScanGuid (GuidTable, SizeOfGuidTable, Guid);
224 if (MatchGuid == NULL) {
225 return PCD_INVALID_TOKEN_NUMBER;
226 }
227
228 Found = FALSE;
229 GuidTableIdx = MatchGuid - GuidTable;
230 for (Idx = 0; Idx < SizeOfExMapTable; Idx++) {
231 if (ExMapTable[Idx].ExGuidIndex == GuidTableIdx) {
232 Found = TRUE;
233 break;
234 }
235 }
236
237 if (Found) {
238 if (TokenNumber == PCD_INVALID_TOKEN_NUMBER) {
239 return ExMapTable[Idx].ExTokenNumber;
240 }
241
242 for ( ; Idx < SizeOfExMapTable; Idx++) {
243 if (ExMapTable[Idx].ExTokenNumber == TokenNumber) {
244 Idx++;
245 if (Idx == SizeOfExMapTable) {
246 //
247 // Exceed the length of ExMap Table
248 //
249 return PCD_INVALID_TOKEN_NUMBER;
250 } else if (ExMapTable[Idx].ExGuidIndex == GuidTableIdx) {
251 //
252 // Found the next match
253 //
254 return ExMapTable[Idx].ExTokenNumber;
255 } else {
256 //
257 // Guid has been changed. It is the next Token Space Guid.
258 // We should flag no more TokenNumber.
259 //
260 return PCD_INVALID_TOKEN_NUMBER;
261 }
262 }
263 }
264 }
265
266 return PCD_INVALID_TOKEN_NUMBER;
267 }
268
269
270
271
272 VOID
273 BuildPcdDxeDataBase (
274 VOID
275 )
276 {
277 PEI_PCD_DATABASE *PeiDatabase;
278 EFI_HOB_GUID_TYPE *GuidHob;
279 UINTN Idx;
280
281 mPcdDatabase = AllocateZeroPool (sizeof(PCD_DATABASE));
282 ASSERT (mPcdDatabase != NULL);
283
284 GuidHob = GetFirstGuidHob (&gPcdDataBaseHobGuid);
285 ASSERT (GuidHob != NULL);
286
287 PeiDatabase = (PEI_PCD_DATABASE *) GET_GUID_HOB_DATA (GuidHob);
288 //
289 // Copy PCD Entries refereneced in PEI phase to PCD DATABASE
290 //
291 CopyMem (&mPcdDatabase->PeiDb, PeiDatabase, sizeof (PEI_PCD_DATABASE));
292
293 //
294 // Copy PCD Entries with default value to PCD DATABASE
295 //
296 CopyMem (&mPcdDatabase->DxeDb.Init, &gDXEPcdDbInit, sizeof(DXE_PCD_DATABASE_INIT));
297
298
299 //
300 // Initialized the Callback Function Table
301 //
302 for (Idx = 0; Idx < PCD_TOTAL_TOKEN_NUMBER; Idx++) {
303 InitializeListHead (&mCallbackFnTable[Idx]);
304 }
305
306 return;
307 }
308
309
310
311 EFI_STATUS
312 GetHiiVariable (
313 IN EFI_GUID *VariableGuid,
314 IN UINT16 *VariableName,
315 OUT VOID ** VariableData,
316 OUT UINTN *VariableSize
317 )
318 {
319 UINTN Size;
320 EFI_STATUS Status;
321 VOID *Buffer;
322
323 Status = EfiGetVariable (
324 (UINT16 *)VariableName,
325 VariableGuid,
326 NULL,
327 &Size,
328 NULL
329 );
330 ASSERT (Status == EFI_BUFFER_TOO_SMALL);
331
332 Buffer = AllocatePool (Size);
333
334 ASSERT (Buffer != NULL);
335
336 Status = EfiGetVariable (
337 VariableName,
338 VariableGuid,
339 NULL,
340 &Size,
341 Buffer
342 );
343
344 return Status;
345
346 }
347
348
349 UINT32
350 GetSkuEnabledTokenNumber (
351 UINT32 LocalTokenNumber,
352 UINTN Size,
353 BOOLEAN IsPeiDb
354 )
355 {
356 SKU_HEAD *SkuHead;
357 SKU_ID *SkuIdTable;
358 INTN i;
359 UINT8 *Value;
360 SKU_ID *PhaseSkuIdTable;
361 UINT8 *PcdDb;
362
363 ASSERT ((LocalTokenNumber & PCD_TYPE_SKU_ENABLED) == 0);
364
365 PcdDb = IsPeiDb ? (UINT8 *) &mPcdDatabase->PeiDb : (UINT8 *) &mPcdDatabase->DxeDb;
366
367 SkuHead = (SKU_HEAD *) (PcdDb + (LocalTokenNumber & PCD_DATABASE_OFFSET_MASK));
368 Value = (UINT8 *) (PcdDb + SkuHead->SkuDataStartOffset);
369
370 PhaseSkuIdTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.SkuIdTable :
371 mPcdDatabase->DxeDb.Init.SkuIdTable;
372
373 SkuIdTable = &PhaseSkuIdTable[SkuHead->SkuIdTableOffset];
374
375 for (i = 0; i < SkuIdTable[0]; i++) {
376 if (mPcdDatabase->PeiDb.Init.SystemSkuId == SkuIdTable[i + 1]) {
377 break;
378 }
379 }
380 ASSERT (i < SkuIdTable[0]);
381
382 switch (LocalTokenNumber & ~PCD_DATABASE_OFFSET_MASK) {
383 case PCD_TYPE_VPD:
384 Value = (UINT8 *) &(((VPD_HEAD *) Value)[i]);
385 return ((Value - PcdDb) | PCD_TYPE_VPD);
386
387 case PCD_TYPE_HII:
388 Value = (UINT8 *) &(((VARIABLE_HEAD *) Value)[i]);
389 return ((Value - PcdDb) | PCD_TYPE_HII);
390
391 case PCD_TYPE_DATA:
392 Value += Size * i;
393 return (Value - PcdDb);
394
395 default:
396 ASSERT (FALSE);
397 }
398
399 ASSERT (FALSE);
400
401 return 0;
402
403 }
404
405
406
407
408
409 VOID
410 InvokeCallbackOnSet (
411 UINT32 ExTokenNumber,
412 CONST EFI_GUID *Guid, OPTIONAL
413 UINTN TokenNumber,
414 VOID *Data,
415 UINTN Size
416 )
417 {
418 CALLBACK_FN_ENTRY *FnTableEntry;
419 LIST_ENTRY *ListHead;
420 LIST_ENTRY *ListNode;
421
422 ListHead = &mCallbackFnTable[TokenNumber];
423 ListNode = GetFirstNode (ListHead);
424
425 while (ListNode != ListHead) {
426 FnTableEntry = CR_FNENTRY_FROM_LISTNODE(ListNode, CALLBACK_FN_ENTRY, Node);
427
428 FnTableEntry->CallbackFn(Guid,
429 (Guid == NULL) ? TokenNumber : ExTokenNumber,
430 Data,
431 Size);
432
433 ListNode = GetNextNode (ListHead, ListNode);
434 }
435
436 return;
437 }
438
439
440
441
442 EFI_STATUS
443 SetWorker (
444 PCD_TOKEN_NUMBER TokenNumber,
445 VOID *Data,
446 UINTN Size,
447 BOOLEAN PtrType
448 )
449 {
450 UINT32 *LocalTokenNumberTable;
451 BOOLEAN IsPeiDb;
452 UINT32 LocalTokenNumber;
453 EFI_GUID *GuidTable;
454 UINT16 *StringTable;
455 EFI_GUID *Guid;
456 UINT16 *Name;
457 VOID *InternalData;
458 VARIABLE_HEAD *VariableHead;
459 UINTN Offset;
460 UINT8 *PcdDb;
461
462
463 ASSERT (TokenNumber < PCD_TOTAL_TOKEN_NUMBER);
464
465 if (PtrType) {
466 ASSERT (Size <= DxePcdGetSize (TokenNumber));
467 } else {
468 ASSERT (Size == DxePcdGetSize (TokenNumber));
469 }
470
471 IsPeiDb = (TokenNumber <= PEI_LOCAL_TOKEN_NUMBER) ? TRUE : FALSE;
472
473 LocalTokenNumberTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.LocalTokenNumberTable :
474 mPcdDatabase->DxeDb.Init.LocalTokenNumberTable;
475
476 if ((TokenNumber < PEI_NEX_TOKEN_NUMBER) ||
477 (TokenNumber >= PEI_LOCAL_TOKEN_NUMBER || TokenNumber < (PEI_LOCAL_TOKEN_NUMBER + DXE_NEX_TOKEN_NUMBER))) {
478 InvokeCallbackOnSet (0, NULL, TokenNumber, Data, Size);
479 }
480
481 TokenNumber = IsPeiDb ? TokenNumber
482 : TokenNumber - PEI_LOCAL_TOKEN_NUMBER;
483
484 LocalTokenNumber = LocalTokenNumberTable[TokenNumber];
485
486 if ((LocalTokenNumber & PCD_TYPE_SKU_ENABLED) == PCD_TYPE_SKU_ENABLED) {
487 LocalTokenNumber = GetSkuEnabledTokenNumber (LocalTokenNumber & ~PCD_TYPE_SKU_ENABLED, Size, IsPeiDb);
488 }
489
490 Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;
491
492 PcdDb = IsPeiDb ? ((UINT8 *) &mPcdDatabase->PeiDb) : ((UINT8 *) &mPcdDatabase->DxeDb);
493
494 StringTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.StringTable :
495 mPcdDatabase->DxeDb.Init.StringTable;
496
497 InternalData = PcdDb + Offset;
498
499 switch (LocalTokenNumber & ~PCD_DATABASE_OFFSET_MASK) {
500 case PCD_TYPE_VPD:
501 ASSERT (FALSE);
502 return EFI_INVALID_PARAMETER;
503
504 case PCD_TYPE_STRING:
505 CopyMem (&StringTable[*((UINT16 *)InternalData)], Data, Size);
506 break;
507
508 case PCD_TYPE_HII:
509 //
510 // Bug Bug: Please implement this
511 //
512 GuidTable = IsPeiDb ? mPcdDatabase->PeiDb.Init.GuidTable :
513 mPcdDatabase->DxeDb.Init.GuidTable;
514
515 VariableHead = (VARIABLE_HEAD *) (PcdDb + Offset);
516
517 Guid = &(GuidTable[VariableHead->GuidTableIndex]);
518 Name = &(StringTable[VariableHead->StringIndex]);
519
520 return EFI_SUCCESS;
521
522 case PCD_TYPE_DATA:
523 if (PtrType) {
524 CopyMem (InternalData, Data, Size);
525 return EFI_SUCCESS;
526 }
527
528 switch (Size) {
529 case sizeof(UINT8):
530 *((UINT8 *) InternalData) = *((UINT8 *) Data);
531 return EFI_SUCCESS;
532
533 case sizeof(UINT16):
534 *((UINT16 *) InternalData) = *((UINT16 *) Data);
535 return EFI_SUCCESS;
536
537 case sizeof(UINT32):
538 *((UINT32 *) InternalData) = *((UINT32 *) Data);
539 return EFI_SUCCESS;
540
541 case sizeof(UINT64):
542 *((UINT64 *) InternalData) = *((UINT64 *) Data);
543 return EFI_SUCCESS;
544
545 default:
546 ASSERT (FALSE);
547 return EFI_NOT_FOUND;
548 }
549
550 default:
551 ASSERT (FALSE);
552 break;
553 }
554
555 ASSERT (FALSE);
556 return EFI_NOT_FOUND;
557 }
558
559
560
561
562
563 VOID *
564 ExGetWorker (
565 IN CONST EFI_GUID *Guid,
566 IN UINTN ExTokenNumber,
567 IN UINTN GetSize
568 )
569 {
570 return GetWorker(GetExPcdTokenNumber (Guid, ExTokenNumber), GetSize);
571 }
572
573
574
575
576
577 EFI_STATUS
578 ExSetWorker (
579 IN PCD_TOKEN_NUMBER ExTokenNumber,
580 IN CONST EFI_GUID *Guid,
581 VOID *Data,
582 UINTN SetSize,
583 BOOLEAN PtrType
584 )
585 {
586 PCD_TOKEN_NUMBER TokenNumber;
587
588 TokenNumber = GetExPcdTokenNumber (Guid, ExTokenNumber);
589
590 InvokeCallbackOnSet (ExTokenNumber, Guid, TokenNumber, Data, SetSize);
591
592 SetWorker (TokenNumber, Data, SetSize, PtrType);
593
594 return EFI_SUCCESS;
595
596 }
597
598
599
600
601 EFI_STATUS
602 SetHiiVariable (
603 IN EFI_GUID *VariableGuid,
604 IN UINT16 *VariableName,
605 IN CONST VOID *Data,
606 IN UINTN DataSize,
607 IN UINTN Offset
608 )
609 {
610 UINTN Size;
611 VOID *Buffer;
612 EFI_STATUS Status;
613 UINT32 Attribute;
614
615 Size = 0;
616
617 Status = EfiGetVariable (
618 (UINT16 *)VariableName,
619 VariableGuid,
620 &Attribute,
621 &Size,
622 NULL
623 );
624
625 ASSERT (Status == EFI_BUFFER_TOO_SMALL);
626
627 Buffer = AllocatePool (Size);
628
629 ASSERT (Buffer != NULL);
630
631 Status = EfiGetVariable (
632 VariableName,
633 VariableGuid,
634 &Attribute,
635 &Size,
636 Buffer
637 );
638
639
640 CopyMem ((UINT8 *)Buffer + Offset, Data, DataSize);
641
642 return EfiSetVariable (
643 VariableName,
644 VariableGuid,
645 Attribute,
646 Size,
647 Buffer
648 );
649
650 }
651
652
653
654
655
656 PCD_TOKEN_NUMBER
657 GetExPcdTokenNumber (
658 IN CONST EFI_GUID *Guid,
659 IN PCD_TOKEN_NUMBER ExTokenNumber
660 )
661 {
662 UINT32 i;
663 DYNAMICEX_MAPPING *ExMap;
664 EFI_GUID *GuidTable;
665 EFI_GUID *MatchGuid;
666 UINTN MatchGuidIdx;
667
668 ExMap = mPcdDatabase->PeiDb.Init.ExMapTable;
669 GuidTable = mPcdDatabase->PeiDb.Init.GuidTable;
670
671 MatchGuid = ScanGuid (GuidTable, sizeof(mPcdDatabase->PeiDb.Init.GuidTable), Guid);
672 ASSERT (MatchGuid != NULL);
673
674 MatchGuidIdx = MatchGuid - GuidTable;
675
676 for (i = 0; i < PEI_EXMAPPING_TABLE_SIZE; i++) {
677 if ((ExTokenNumber == ExMap[i].ExTokenNumber) &&
678 (MatchGuidIdx == ExMap[i].ExGuidIndex)) {
679 return ExMap[i].LocalTokenNumber;
680
681 }
682 }
683
684 ExMap = mPcdDatabase->DxeDb.Init.ExMapTable;
685 GuidTable = mPcdDatabase->DxeDb.Init.GuidTable;
686
687 MatchGuid = ScanGuid (GuidTable, sizeof(mPcdDatabase->DxeDb.Init.GuidTable), Guid);
688 ASSERT (MatchGuid != NULL);
689
690 MatchGuidIdx = MatchGuid - GuidTable;
691
692 for (i = 0; i < DXE_EXMAPPING_TABLE_SIZE; i++) {
693 if ((ExTokenNumber == ExMap[i].ExTokenNumber) &&
694 (MatchGuidIdx == ExMap[i].ExGuidIndex)) {
695 return ExMap[i].LocalTokenNumber + PEI_LOCAL_TOKEN_NUMBER;
696 }
697 }
698
699 ASSERT (FALSE);
700
701 return 0;
702 }
703