1 /****************************************************************************** 2 * 3 * Module Name: dtcompile.c - Front-end for data table compiler 4 * 5 *****************************************************************************/ 6 7 /* 8 * Copyright (C) 2000 - 2023, Intel Corp. 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions, and the following disclaimer, 16 * without modification. 17 * 2. Redistributions in binary form must reproduce at minimum a disclaimer 18 * substantially similar to the "NO WARRANTY" disclaimer below 19 * ("Disclaimer") and any redistribution must be conditioned upon 20 * including a substantially similar Disclaimer requirement for further 21 * binary redistribution. 22 * 3. Neither the names of the above-listed copyright holders nor the names 23 * of any contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * Alternatively, this software may be distributed under the terms of the 27 * GNU General Public License ("GPL") version 2 as published by the Free 28 * Software Foundation. 29 * 30 * NO WARRANTY 31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 32 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 33 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 34 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 35 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 36 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 37 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 38 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 40 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 41 * POSSIBILITY OF SUCH DAMAGES. 42 */ 43 44 #define _DECLARE_DT_GLOBALS 45 46 #include "aslcompiler.h" 47 48 #define _COMPONENT DT_COMPILER 49 ACPI_MODULE_NAME ("dtcompile") 50 51 static char VersionString[9]; 52 53 54 /* Local prototypes */ 55 56 void 57 DtInitialize ( 58 void); 59 60 static ACPI_STATUS 61 DtCompileDataTable ( 62 DT_FIELD **Field); 63 64 static void 65 DtInsertCompilerIds ( 66 DT_FIELD *FieldList); 67 68 69 /****************************************************************************** 70 * 71 * FUNCTION: DtDoCompile 72 * 73 * PARAMETERS: None 74 * 75 * RETURN: Status 76 * 77 * DESCRIPTION: Main entry point for the data table compiler. 78 * 79 * Note: Assumes AslGbl_Files[ASL_FILE_INPUT] is initialized and the file is 80 * open at seek offset zero. 81 * 82 *****************************************************************************/ 83 84 ACPI_STATUS 85 DtDoCompile ( 86 void) 87 { 88 ACPI_STATUS Status; 89 UINT8 Event; 90 DT_FIELD *FieldList; 91 ASL_GLOBAL_FILE_NODE *FileNode; 92 93 94 /* Initialize globals */ 95 96 DtInitialize (); 97 98 /* Preprocessor */ 99 100 if (AslGbl_PreprocessFlag) 101 { 102 /* Preprocessor */ 103 104 Event = UtBeginEvent ("Preprocess input file"); 105 PrDoPreprocess (); 106 UtEndEvent (Event); 107 108 if (AslGbl_PreprocessOnly) 109 { 110 return (AE_OK); 111 } 112 } 113 114 /* Compile the parse tree */ 115 116 if (AslGbl_DtLexBisonPrototype) 117 { 118 Event = UtBeginEvent ("Parse data table in prototype mode"); 119 120 DtCompilerInitLexer (AslGbl_Files[ASL_FILE_INPUT].Handle); 121 DtCompilerParserparse (); 122 FieldList = AslGbl_FieldList; 123 DtCompilerTerminateLexer (); 124 125 UtEndEvent (Event); 126 } 127 else 128 { 129 /* 130 * Scan the input file (file is already open) and 131 * build the parse tree 132 */ 133 Event = UtBeginEvent ("Scan and parse input file"); 134 FieldList = DtScanFile (AslGbl_Files[ASL_FILE_INPUT].Handle); 135 UtEndEvent (Event); 136 } 137 138 /* Did the parse tree get successfully constructed? */ 139 140 if (!FieldList) 141 { 142 /* TBD: temporary error message. Msgs should come from function above */ 143 144 DtError (ASL_ERROR, ASL_MSG_SYNTAX, NULL, 145 "Input file does not appear to be an ASL or data table source file"); 146 147 return (AE_ERROR); 148 } 149 150 Event = UtBeginEvent ("Compile parse tree"); 151 152 Status = DtCompileDataTable (&FieldList); 153 UtEndEvent (Event); 154 155 FileNode = FlGetCurrentFileNode (); 156 157 FileNode->TotalLineCount = AslGbl_CurrentLineNumber; 158 FileNode->OriginalInputFileSize = AslGbl_InputByteCount; 159 DbgPrint (ASL_PARSE_OUTPUT, "Line count: %u input file size: %u\n", 160 FileNode->TotalLineCount, FileNode->OriginalInputFileSize); 161 162 if (ACPI_FAILURE (Status)) 163 { 164 FileNode->ParserErrorDetected = TRUE; 165 166 /* TBD: temporary error message. Msgs should come from function above */ 167 168 DtError (ASL_ERROR, ASL_MSG_SYNTAX, NULL, 169 "Could not compile input file"); 170 171 return (Status); 172 } 173 174 /* Create/open the binary output file */ 175 176 AslGbl_Files[ASL_FILE_AML_OUTPUT].Filename = NULL; 177 Status = FlOpenAmlOutputFile (AslGbl_OutputFilenamePrefix); 178 if (ACPI_FAILURE (Status)) 179 { 180 return (Status); 181 } 182 183 /* Write the binary, then the optional hex file */ 184 185 DtOutputBinary (AslGbl_RootTable); 186 HxDoHexOutput (); 187 DtWriteTableToListing (); 188 189 /* Save the compile time statistics to the current file node */ 190 191 FileNode->TotalFields = AslGbl_InputFieldCount; 192 FileNode->OutputByteLength = AslGbl_TableLength; 193 194 return (Status); 195 } 196 197 198 /****************************************************************************** 199 * 200 * FUNCTION: DtInitialize 201 * 202 * PARAMETERS: None 203 * 204 * RETURN: Status 205 * 206 * DESCRIPTION: Initialize data table compiler globals. Enables multiple 207 * compiles per invocation. 208 * 209 *****************************************************************************/ 210 211 void 212 DtInitialize ( 213 void) 214 { 215 216 217 AcpiUtSetIntegerWidth (2); /* Set width to 64 bits */ 218 219 AslGbl_FieldList = NULL; 220 AslGbl_RootTable = NULL; 221 AslGbl_SubtableStack = NULL; 222 223 snprintf (VersionString, sizeof(VersionString), "%X", 224 (UINT32) ACPI_CA_VERSION); 225 return; 226 } 227 228 229 /****************************************************************************** 230 * 231 * FUNCTION: DtInsertCompilerIds 232 * 233 * PARAMETERS: FieldList - Current field list pointer 234 * 235 * RETURN: None 236 * 237 * DESCRIPTION: Insert the IDs (Name, Version) of the current compiler into 238 * the original ACPI table header. 239 * 240 *****************************************************************************/ 241 242 static void 243 DtInsertCompilerIds ( 244 DT_FIELD *FieldList) 245 { 246 DT_FIELD *Next; 247 UINT32 i; 248 249 250 /* 251 * Don't insert current compiler ID if requested. Used for compiler 252 * debug/validation only. 253 */ 254 if (AslGbl_UseOriginalCompilerId) 255 { 256 return; 257 } 258 259 /* Walk to the Compiler fields at the end of the header */ 260 261 Next = FieldList; 262 for (i = 0; i < 7; i++) 263 { 264 Next = Next->Next; 265 } 266 267 Next->Value = ASL_CREATOR_ID; 268 Next->Flags = DT_FIELD_NOT_ALLOCATED; 269 270 Next = Next->Next; 271 Next->Value = VersionString; 272 Next->Flags = DT_FIELD_NOT_ALLOCATED; 273 } 274 275 276 /****************************************************************************** 277 * 278 * FUNCTION: DtCompileDataTable 279 * 280 * PARAMETERS: FieldList - Current field list pointer 281 * 282 * RETURN: Status 283 * 284 * DESCRIPTION: Entry point to compile one data table 285 * 286 *****************************************************************************/ 287 288 static ACPI_STATUS 289 DtCompileDataTable ( 290 DT_FIELD **FieldList) 291 { 292 const ACPI_DMTABLE_DATA *TableData; 293 DT_SUBTABLE *Subtable; 294 char *Signature; 295 ACPI_TABLE_HEADER *AcpiTableHeader; 296 ACPI_STATUS Status; 297 DT_FIELD *RootField = *FieldList; 298 299 300 /* Verify that we at least have a table signature and save it */ 301 302 Signature = DtGetFieldValue (*FieldList); 303 if (!Signature) 304 { 305 snprintf (AslGbl_MsgBuffer, sizeof(AslGbl_MsgBuffer), "Expected \"%s\"", "Signature"); 306 DtNameError (ASL_ERROR, ASL_MSG_INVALID_FIELD_NAME, 307 *FieldList, AslGbl_MsgBuffer); 308 return (AE_ERROR); 309 } 310 311 AslGbl_Signature = UtLocalCacheCalloc (strlen (Signature) + 1); 312 strcpy (AslGbl_Signature, Signature); 313 314 /* 315 * Handle tables that don't use the common ACPI table header structure. 316 * Currently, these are the FACS and RSDP. Also check for an OEMx table, 317 * these tables have user-defined contents. 318 */ 319 if (ACPI_COMPARE_NAMESEG (Signature, ACPI_SIG_FACS)) 320 { 321 Status = DtCompileFacs (FieldList); 322 if (ACPI_FAILURE (Status)) 323 { 324 return (Status); 325 } 326 327 DtSetTableLength (); 328 return (Status); 329 } 330 else if (ACPI_VALIDATE_RSDP_SIG (Signature)) 331 { 332 Status = DtCompileRsdp (FieldList); 333 return (Status); 334 } 335 else if (ACPI_COMPARE_NAMESEG (Signature, ACPI_SIG_S3PT)) 336 { 337 Status = DtCompileS3pt (FieldList); 338 if (ACPI_FAILURE (Status)) 339 { 340 return (Status); 341 } 342 343 DtSetTableLength (); 344 return (Status); 345 } 346 347 /* 348 * If the first field is named "CDAT Table Length" (not "Signature"), 349 * assume that we have a CDAT table (whose table header does not have 350 * a signature). Instead, the TableLength field is where the 351 * signature would (normally) be. 352 */ 353 else if (!strcmp ((*FieldList)->Name, "CDAT Table Length")) 354 { 355 /* No longer true: (However, use this technique in the disassembler) 356 * We are assuming that there 357 * should be at least one non-ASCII byte in the 4-character 358 * Signature field, (At least the high-order byte should be zero). 359 */ 360 Status = DtCompileTable (FieldList, AcpiDmTableInfoCdatTableHdr, 361 &AslGbl_RootTable); 362 if (ACPI_FAILURE (Status)) 363 { 364 return (Status); 365 } 366 367 /* Compile the CDAT */ 368 369 DtPushSubtable (AslGbl_RootTable); 370 Status = DtCompileCdat ((void **) FieldList); 371 if (ACPI_FAILURE (Status)) 372 { 373 return (Status); 374 } 375 376 /* 377 * Set the overall table length and the table checksum. 378 * The entire compiled table (including the CDAT table header with 379 * the table length and checksum) is in AslGbl_RootTable->Buffer. 380 */ 381 DtSetTableLength (); 382 DtSetTableChecksum (&ACPI_CAST_PTR (ACPI_TABLE_CDAT, AslGbl_RootTable->Buffer)->Checksum); 383 384 DtDumpFieldList (RootField); 385 DtDumpSubtableList (); 386 return (AE_OK); 387 } 388 389 /* 390 * All other tables must use the common ACPI table header. Insert the 391 * current iASL IDs (name, version), and compile the header now. 392 */ 393 DtInsertCompilerIds (*FieldList); 394 395 Status = DtCompileTable (FieldList, AcpiDmTableInfoHeader, 396 &AslGbl_RootTable); 397 if (ACPI_FAILURE (Status)) 398 { 399 return (Status); 400 } 401 402 DtPushSubtable (AslGbl_RootTable); 403 404 /* Validate the signature via the ACPI table list */ 405 406 TableData = AcpiDmGetTableData (Signature); 407 if (!TableData || AslGbl_CompileGeneric) 408 { 409 /* Unknown table signature and/or force generic compile */ 410 411 DtCompileGeneric ((void **) FieldList, NULL, NULL); 412 goto FinishHeader; 413 } 414 415 /* Dispatch to per-table compile */ 416 417 if (TableData->CmTableHandler) 418 { 419 /* Complex table, has a handler */ 420 421 Status = TableData->CmTableHandler ((void **) FieldList); 422 if (ACPI_FAILURE (Status)) 423 { 424 return (Status); 425 } 426 } 427 else if (TableData->TableInfo) 428 { 429 /* Simple table, just walk the info table, unless its empty */ 430 431 if (FieldList && *FieldList) 432 { 433 Subtable = NULL; 434 Status = DtCompileTable (FieldList, TableData->TableInfo, 435 &Subtable); 436 if (ACPI_FAILURE (Status)) 437 { 438 return (Status); 439 } 440 441 DtInsertSubtable (AslGbl_RootTable, Subtable); 442 DtPopSubtable (); 443 } 444 } 445 else 446 { 447 DtFatal (ASL_MSG_COMPILER_INTERNAL, *FieldList, 448 "Missing table dispatch info"); 449 return (AE_ERROR); 450 } 451 452 FinishHeader: 453 454 /* Set the final table length and then the checksum */ 455 456 DtSetTableLength (); 457 AcpiTableHeader = ACPI_CAST_PTR ( 458 ACPI_TABLE_HEADER, AslGbl_RootTable->Buffer); 459 DtSetTableChecksum (&AcpiTableHeader->Checksum); 460 461 DtDumpFieldList (RootField); 462 DtDumpSubtableList (); 463 return (AE_OK); 464 } 465 466 467 /****************************************************************************** 468 * 469 * FUNCTION: DtCompileTable 470 * 471 * PARAMETERS: Field - Current field list pointer 472 * Info - Info table for this ACPI table 473 * RetSubtable - Compile result of table 474 * 475 * RETURN: Status 476 * 477 * DESCRIPTION: Compile a subtable 478 * 479 *****************************************************************************/ 480 481 ACPI_STATUS 482 DtCompileTable ( 483 DT_FIELD **Field, 484 ACPI_DMTABLE_INFO *Info, 485 DT_SUBTABLE **RetSubtable) 486 { 487 DT_FIELD *LocalField; 488 UINT32 Length; 489 DT_SUBTABLE *Subtable; 490 DT_SUBTABLE *InlineSubtable = NULL; 491 UINT32 FieldLength = 0; 492 UINT8 FieldType; 493 UINT8 *Buffer; 494 UINT8 *FlagBuffer = NULL; 495 char *String; 496 UINT32 CurrentFlagByteOffset = 0; 497 ACPI_STATUS Status = AE_OK; 498 499 500 if (!Field || !Info) 501 { 502 return (AE_BAD_PARAMETER); 503 } 504 if (!*Field) 505 { 506 /* 507 * The field list is empty, this means that we are out of fields to 508 * parse. In other words, we are at the end of the table. 509 */ 510 return (AE_END_OF_TABLE); 511 } 512 513 /* Ignore optional subtable if name does not match */ 514 515 if ((Info->Flags & DT_OPTIONAL) && 516 strcmp ((*Field)->Name, Info->Name)) 517 { 518 *RetSubtable = NULL; 519 return (AE_OK); 520 } 521 522 Length = DtGetSubtableLength (*Field, Info); 523 if (Length == ASL_EOF) 524 { 525 return (AE_ERROR); 526 } 527 528 Subtable = UtSubtableCacheCalloc (); 529 530 if (Length > 0) 531 { 532 String = UtLocalCacheCalloc (Length); 533 Subtable->Buffer = ACPI_CAST_PTR (UINT8, String); 534 } 535 536 Subtable->Length = Length; 537 Subtable->TotalLength = Length; 538 Buffer = Subtable->Buffer; 539 540 LocalField = *Field; 541 Subtable->Name = LocalField->Name; 542 543 /* 544 * Main loop walks the info table for this ACPI table or subtable 545 */ 546 for (; Info->Name; Info++) 547 { 548 if (Info->Opcode == ACPI_DMT_EXTRA_TEXT) 549 { 550 continue; 551 } 552 553 if (!LocalField) 554 { 555 snprintf (AslGbl_MsgBuffer, sizeof(AslGbl_MsgBuffer), "Found NULL field - Field name \"%s\" needed", 556 Info->Name); 557 DtFatal (ASL_MSG_COMPILER_INTERNAL, NULL, AslGbl_MsgBuffer); 558 Status = AE_BAD_DATA; 559 goto Error; 560 } 561 562 /* Maintain table offsets */ 563 564 LocalField->TableOffset = AslGbl_CurrentTableOffset; 565 FieldLength = DtGetFieldLength (LocalField, Info); 566 AslGbl_CurrentTableOffset += FieldLength; 567 568 FieldType = DtGetFieldType (Info); 569 AslGbl_InputFieldCount++; 570 571 if (FieldType != DT_FIELD_TYPE_INLINE_SUBTABLE && 572 strcmp (Info->Name, LocalField->Name)) 573 { 574 sprintf (AslGbl_MsgBuffer, "found \"%s\" expected \"%s\"", 575 LocalField->Name, Info->Name); 576 DtError (ASL_ERROR, ASL_MSG_INVALID_LABEL, LocalField, AslGbl_MsgBuffer); 577 } 578 579 switch (FieldType) 580 { 581 case DT_FIELD_TYPE_FLAGS_INTEGER: 582 /* 583 * Start of the definition of a flags field. 584 * This master flags integer starts at value zero, in preparation 585 * to compile and insert the flag fields from the individual bits 586 */ 587 LocalField = LocalField->Next; 588 *Field = LocalField; 589 590 FlagBuffer = Buffer; 591 CurrentFlagByteOffset = Info->Offset; 592 break; 593 594 case DT_FIELD_TYPE_FLAG: 595 596 /* Individual Flag field, can be multiple bits */ 597 598 if (FlagBuffer) 599 { 600 /* 601 * We must increment the FlagBuffer when we have crossed 602 * into the next flags byte within the flags field 603 * of type DT_FIELD_TYPE_FLAGS_INTEGER. 604 */ 605 FlagBuffer += (Info->Offset - CurrentFlagByteOffset); 606 CurrentFlagByteOffset = Info->Offset; 607 608 DtCompileFlag (FlagBuffer, LocalField, Info); 609 } 610 else 611 { 612 /* TBD - this is an internal error */ 613 } 614 615 LocalField = LocalField->Next; 616 *Field = LocalField; 617 break; 618 619 case DT_FIELD_TYPE_INLINE_SUBTABLE: 620 /* 621 * Recursion (one level max): compile GAS (Generic Address) 622 * or Notify in-line subtable 623 */ 624 *Field = LocalField; 625 626 switch (Info->Opcode) 627 { 628 case ACPI_DMT_GAS: 629 630 Status = DtCompileTable (Field, AcpiDmTableInfoGas, 631 &InlineSubtable); 632 break; 633 634 case ACPI_DMT_HESTNTFY: 635 636 Status = DtCompileTable (Field, AcpiDmTableInfoHestNotify, 637 &InlineSubtable); 638 break; 639 640 case ACPI_DMT_IORTMEM: 641 642 Status = DtCompileTable (Field, AcpiDmTableInfoIortAcc, 643 &InlineSubtable); 644 break; 645 646 default: 647 sprintf (AslGbl_MsgBuffer, "Invalid DMT opcode: 0x%.2X", 648 Info->Opcode); 649 DtFatal (ASL_MSG_COMPILER_INTERNAL, NULL, AslGbl_MsgBuffer); 650 Status = AE_BAD_DATA; 651 break; 652 } 653 654 if (ACPI_FAILURE (Status)) 655 { 656 goto Error; 657 } 658 659 DtSetSubtableLength (InlineSubtable); 660 661 memcpy (Buffer, InlineSubtable->Buffer, FieldLength); 662 LocalField = *Field; 663 break; 664 665 case DT_FIELD_TYPE_LABEL: 666 667 DtWriteFieldToListing (Buffer, LocalField, 0); 668 LocalField = LocalField->Next; 669 break; 670 671 default: 672 673 /* Normal case for most field types (Integer, String, etc.) */ 674 675 DtCompileOneField (Buffer, LocalField, 676 FieldLength, FieldType, Info->Flags); 677 678 DtWriteFieldToListing (Buffer, LocalField, FieldLength); 679 LocalField = LocalField->Next; 680 681 if (Info->Flags & DT_LENGTH) 682 { 683 /* Field is an Integer that will contain a subtable length */ 684 685 Subtable->LengthField = Buffer; 686 Subtable->SizeOfLengthField = FieldLength; 687 } 688 break; 689 } 690 691 Buffer += FieldLength; 692 } 693 694 *Field = LocalField; 695 *RetSubtable = Subtable; 696 return (AE_OK); 697 698 Error: 699 ACPI_FREE (Subtable->Buffer); 700 ACPI_FREE (Subtable); 701 return (Status); 702 } 703 704 705 /****************************************************************************** 706 * 707 * FUNCTION: DtCompileTwoSubtables 708 * 709 * PARAMETERS: List - Current field list pointer 710 * TableInfo1 - Info table 1 711 * TableInfo1 - Info table 2 712 * 713 * RETURN: Status 714 * 715 * DESCRIPTION: Compile tables with a header and one or more same subtables. 716 * Include CPEP, EINJ, ERST, MCFG, MSCT, WDAT 717 * 718 *****************************************************************************/ 719 720 ACPI_STATUS 721 DtCompileTwoSubtables ( 722 void **List, 723 ACPI_DMTABLE_INFO *TableInfo1, 724 ACPI_DMTABLE_INFO *TableInfo2) 725 { 726 ACPI_STATUS Status; 727 DT_SUBTABLE *Subtable; 728 DT_SUBTABLE *ParentTable; 729 DT_FIELD **PFieldList = (DT_FIELD **) List; 730 731 732 Status = DtCompileTable (PFieldList, TableInfo1, &Subtable); 733 if (ACPI_FAILURE (Status)) 734 { 735 return (Status); 736 } 737 738 ParentTable = DtPeekSubtable (); 739 DtInsertSubtable (ParentTable, Subtable); 740 741 while (*PFieldList) 742 { 743 Status = DtCompileTable (PFieldList, TableInfo2, &Subtable); 744 if (ACPI_FAILURE (Status)) 745 { 746 return (Status); 747 } 748 749 DtInsertSubtable (ParentTable, Subtable); 750 } 751 752 return (AE_OK); 753 } 754 755 756 /****************************************************************************** 757 * 758 * FUNCTION: DtCompilePadding 759 * 760 * PARAMETERS: Length - Padding field size 761 * RetSubtable - Compile result of table 762 * 763 * RETURN: Status 764 * 765 * DESCRIPTION: Compile a subtable for padding purpose 766 * 767 *****************************************************************************/ 768 769 ACPI_STATUS 770 DtCompilePadding ( 771 UINT32 Length, 772 DT_SUBTABLE **RetSubtable) 773 { 774 DT_SUBTABLE *Subtable; 775 /* UINT8 *Buffer; */ 776 char *String; 777 778 779 Subtable = UtSubtableCacheCalloc (); 780 781 if (Length > 0) 782 { 783 String = UtLocalCacheCalloc (Length); 784 Subtable->Buffer = ACPI_CAST_PTR (UINT8, String); 785 } 786 787 Subtable->Length = Length; 788 Subtable->TotalLength = Length; 789 /* Buffer = Subtable->Buffer; */ 790 791 *RetSubtable = Subtable; 792 return (AE_OK); 793 } 794