1 /* 2 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * The contents of this file constitute Original Code as defined in and 7 * are subject to the Apple Public Source License Version 1.1 (the 8 * "License"). You may not use this file except in compliance with the 9 * License. Please obtain a copy of the License at 10 * http://www.apple.com/publicsource and read it before using this file. 11 * 12 * This Original Code and all software distributed under the License are 13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER 14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the 17 * License for the specific language governing rights and limitations 18 * under the License. 19 * 20 * @APPLE_LICENSE_HEADER_END@ 21 */ 22 /* 23 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. 24 * 25 * DRI: Josh de Cesare 26 * 27 */ 28 29 30 #if __ppc__ 31 #include <ppc/proc_reg.h> 32 #endif 33 34 #include <IOKit/IOLib.h> 35 #include <IOKit/IOService.h> 36 #include <IOKit/IOPlatformExpert.h> 37 #include <IOKit/IODeviceTreeSupport.h> 38 #include <IOKit/IOInterrupts.h> 39 #include <IOKit/IOInterruptController.h> 40 41 42 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 43 44 #define super IOService 45 46 OSDefineMetaClassAndAbstractStructors(IOInterruptController, IOService); 47 48 OSMetaClassDefineReservedUnused(IOInterruptController, 0); 49 OSMetaClassDefineReservedUnused(IOInterruptController, 1); 50 OSMetaClassDefineReservedUnused(IOInterruptController, 2); 51 OSMetaClassDefineReservedUnused(IOInterruptController, 3); 52 OSMetaClassDefineReservedUnused(IOInterruptController, 4); 53 OSMetaClassDefineReservedUnused(IOInterruptController, 5); 54 55 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 56 57 IOReturn IOInterruptController::registerInterrupt(IOService *nub, int source, 58 void *target, 59 IOInterruptHandler handler, 60 void *refCon) 61 { 62 IOInterruptSource *interruptSources; 63 long vectorNumber; 64 IOInterruptVector *vector; 65 long wasDisabledSoft; 66 IOReturn error; 67 OSData *vectorData; 68 IOService *originalNub; 69 int originalSource; 70 IOOptionBits options; 71 bool canBeShared, shouldBeShared, wasAlreadyRegisterd; 72 73 interruptSources = nub->_interruptSources; 74 vectorData = interruptSources[source].vectorData; 75 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 76 vector = &vectors[vectorNumber]; 77 78 // Get the lock for this vector. 79 IOTakeLock(vector->interruptLock); 80 81 // Check if the interrupt source can/should be shared. 82 canBeShared = vectorCanBeShared(vectorNumber, vector); 83 IODTGetInterruptOptions(nub, source, &options); 84 shouldBeShared = canBeShared && (options & kIODTInterruptShared); 85 wasAlreadyRegisterd = vector->interruptRegistered; 86 87 // If the vector is registered and can not be shared return error. 88 if (wasAlreadyRegisterd && !canBeShared) { 89 IOUnlock(vector->interruptLock); 90 return kIOReturnNoResources; 91 } 92 93 // If this vector is already in use, and can be shared (implied), 94 // or it is not registered and should be shared, 95 // register as a shared interrupt. 96 if (wasAlreadyRegisterd || shouldBeShared) { 97 // If this vector is not already shared, break it out. 98 if (vector->sharedController == 0) { 99 // Make the IOShareInterruptController instance 100 vector->sharedController = new IOSharedInterruptController; 101 if (vector->sharedController == 0) { 102 IOUnlock(vector->interruptLock); 103 return kIOReturnNoMemory; 104 } 105 106 if (wasAlreadyRegisterd) { 107 // Save the nub and source for the original consumer. 108 originalNub = vector->nub; 109 originalSource = vector->source; 110 111 // Physically disable the interrupt, but mark it as being enabled in the hardware. 112 // The interruptDisabledSoft now indicates the driver's request for enablement. 113 disableVectorHard(vectorNumber, vector); 114 vector->interruptDisabledHard = 0; 115 } 116 117 // Initialize the new shared interrupt controller. 118 error = vector->sharedController->initInterruptController(this, vectorData); 119 // If the IOSharedInterruptController could not be initalized, 120 // if needed, put the original consumer's interrupt back to normal and 121 // get rid of whats left of the shared controller. 122 if (error != kIOReturnSuccess) { 123 if (wasAlreadyRegisterd) enableInterrupt(originalNub, originalSource); 124 vector->sharedController->release(); 125 vector->sharedController = 0; 126 IOUnlock(vector->interruptLock); 127 return error; 128 } 129 130 // If there was an original consumer try to register it on the shared controller. 131 if (wasAlreadyRegisterd) { 132 error = vector->sharedController->registerInterrupt(originalNub, 133 originalSource, 134 vector->target, 135 vector->handler, 136 vector->refCon); 137 // If the original consumer could not be moved to the shared controller, 138 // put the original consumor's interrupt back to normal and 139 // get rid of whats left of the shared controller. 140 if (error != kIOReturnSuccess) { 141 // Save the driver's interrupt enablement state. 142 wasDisabledSoft = vector->interruptDisabledSoft; 143 144 // Make the interrupt really hard disabled. 145 vector->interruptDisabledSoft = 1; 146 vector->interruptDisabledHard = 1; 147 148 // Enable the original consumer's interrupt if needed. 149 if (!wasDisabledSoft) originalNub->enableInterrupt(originalSource); 150 enableInterrupt(originalNub, originalSource); 151 152 vector->sharedController->release(); 153 vector->sharedController = 0; 154 IOUnlock(vector->interruptLock); 155 return error; 156 } 157 } 158 159 // Fill in vector with the shared controller's info. 160 vector->handler = (IOInterruptHandler)vector->sharedController->getInterruptHandlerAddress(); 161 vector->nub = vector->sharedController; 162 vector->source = 0; 163 vector->target = vector->sharedController; 164 vector->refCon = 0; 165 166 // If the interrupt was already registered, 167 // save the driver's interrupt enablement state. 168 if (wasAlreadyRegisterd) wasDisabledSoft = vector->interruptDisabledSoft; 169 else wasDisabledSoft = true; 170 171 // Do any specific initalization for this vector if it has not yet been used. 172 if (!wasAlreadyRegisterd) initVector(vectorNumber, vector); 173 174 // Make the interrupt really hard disabled. 175 vector->interruptDisabledSoft = 1; 176 vector->interruptDisabledHard = 1; 177 vector->interruptRegistered = 1; 178 179 // Enable the original consumer's interrupt if needed. 180 if (!wasDisabledSoft) originalNub->enableInterrupt(originalSource); 181 } 182 183 error = vector->sharedController->registerInterrupt(nub, source, target, 184 handler, refCon); 185 IOUnlock(vector->interruptLock); 186 return error; 187 } 188 189 // Fill in vector with the client's info. 190 vector->handler = handler; 191 vector->nub = nub; 192 vector->source = source; 193 vector->target = target; 194 vector->refCon = refCon; 195 196 // Do any specific initalization for this vector. 197 initVector(vectorNumber, vector); 198 199 // Get the vector ready. It starts hard disabled. 200 vector->interruptDisabledHard = 1; 201 vector->interruptDisabledSoft = 1; 202 vector->interruptRegistered = 1; 203 204 IOUnlock(vector->interruptLock); 205 return kIOReturnSuccess; 206 } 207 208 IOReturn IOInterruptController::unregisterInterrupt(IOService *nub, int source) 209 { 210 IOInterruptSource *interruptSources; 211 long vectorNumber; 212 IOInterruptVector *vector; 213 OSData *vectorData; 214 215 interruptSources = nub->_interruptSources; 216 vectorData = interruptSources[source].vectorData; 217 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 218 vector = &vectors[vectorNumber]; 219 220 // Get the lock for this vector. 221 IOTakeLock(vector->interruptLock); 222 223 // Return success if it is not already registered 224 if (!vector->interruptRegistered) { 225 IOUnlock(vector->interruptLock); 226 return kIOReturnSuccess; 227 } 228 229 // Soft disable the source. 230 disableInterrupt(nub, source); 231 232 // Turn the source off at hardware. 233 disableVectorHard(vectorNumber, vector); 234 235 // Clear all the storage for the vector except for interruptLock. 236 vector->interruptActive = 0; 237 vector->interruptDisabledSoft = 0; 238 vector->interruptDisabledHard = 0; 239 vector->interruptRegistered = 0; 240 vector->nub = 0; 241 vector->source = 0; 242 vector->handler = 0; 243 vector->target = 0; 244 vector->refCon = 0; 245 246 IOUnlock(vector->interruptLock); 247 return kIOReturnSuccess; 248 } 249 250 IOReturn IOInterruptController::getInterruptType(IOService *nub, int source, 251 int *interruptType) 252 { 253 IOInterruptSource *interruptSources; 254 long vectorNumber; 255 IOInterruptVector *vector; 256 OSData *vectorData; 257 258 if (interruptType == 0) return kIOReturnBadArgument; 259 260 interruptSources = nub->_interruptSources; 261 vectorData = interruptSources[source].vectorData; 262 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 263 vector = &vectors[vectorNumber]; 264 265 *interruptType = getVectorType(vectorNumber, vector); 266 267 return kIOReturnSuccess; 268 } 269 270 IOReturn IOInterruptController::enableInterrupt(IOService *nub, int source) 271 { 272 IOInterruptSource *interruptSources; 273 long vectorNumber; 274 IOInterruptVector *vector; 275 OSData *vectorData; 276 277 interruptSources = nub->_interruptSources; 278 vectorData = interruptSources[source].vectorData; 279 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 280 vector = &vectors[vectorNumber]; 281 282 if (vector->interruptDisabledSoft) { 283 vector->interruptDisabledSoft = 0; 284 #if __ppc__ 285 sync(); 286 isync(); 287 #endif 288 289 if (!getPlatform()->atInterruptLevel()) { 290 while (vector->interruptActive); 291 #if __ppc__ 292 isync(); 293 #endif 294 } 295 if (vector->interruptDisabledHard) { 296 vector->interruptDisabledHard = 0; 297 298 enableVector(vectorNumber, vector); 299 } 300 } 301 302 return kIOReturnSuccess; 303 } 304 305 IOReturn IOInterruptController::disableInterrupt(IOService *nub, int source) 306 { 307 IOInterruptSource *interruptSources; 308 long vectorNumber; 309 IOInterruptVector *vector; 310 OSData *vectorData; 311 312 interruptSources = nub->_interruptSources; 313 vectorData = interruptSources[source].vectorData; 314 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 315 vector = &vectors[vectorNumber]; 316 317 vector->interruptDisabledSoft = 1; 318 #if __ppc__ 319 sync(); 320 isync(); 321 #endif 322 323 if (!getPlatform()->atInterruptLevel()) { 324 while (vector->interruptActive); 325 #if __ppc__ 326 isync(); 327 #endif 328 } 329 330 return kIOReturnSuccess; 331 } 332 333 IOReturn IOInterruptController::causeInterrupt(IOService *nub, int source) 334 { 335 IOInterruptSource *interruptSources; 336 long vectorNumber; 337 IOInterruptVector *vector; 338 OSData *vectorData; 339 340 interruptSources = nub->_interruptSources; 341 vectorData = interruptSources[source].vectorData; 342 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 343 vector = &vectors[vectorNumber]; 344 345 causeVector(vectorNumber, vector); 346 347 return kIOReturnSuccess; 348 } 349 350 IOInterruptAction IOInterruptController::getInterruptHandlerAddress(void) 351 { 352 return 0; 353 } 354 355 IOReturn IOInterruptController::handleInterrupt(void *refCon, IOService *nub, 356 int source) 357 { 358 return kIOReturnInvalid; 359 } 360 361 362 // Methods to be overridden for simplifed interrupt controller subclasses. 363 364 bool IOInterruptController::vectorCanBeShared(long /*vectorNumber*/, 365 IOInterruptVector */*vector*/) 366 { 367 return false; 368 } 369 370 void IOInterruptController::initVector(long /*vectorNumber*/, 371 IOInterruptVector */*vector*/) 372 { 373 } 374 375 int IOInterruptController::getVectorType(long /*vectorNumber*/, 376 IOInterruptVector */*vector*/) 377 { 378 return kIOInterruptTypeEdge; 379 } 380 381 void IOInterruptController::disableVectorHard(long /*vectorNumber*/, 382 IOInterruptVector */*vector*/) 383 { 384 } 385 386 void IOInterruptController::enableVector(long /*vectorNumber*/, 387 IOInterruptVector */*vector*/) 388 { 389 } 390 391 void IOInterruptController::causeVector(long /*vectorNumber*/, 392 IOInterruptVector */*vector*/) 393 { 394 } 395 396 397 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 398 399 #undef super 400 #define super IOInterruptController 401 402 OSDefineMetaClassAndStructors(IOSharedInterruptController, IOInterruptController); 403 404 OSMetaClassDefineReservedUnused(IOSharedInterruptController, 0); 405 OSMetaClassDefineReservedUnused(IOSharedInterruptController, 1); 406 OSMetaClassDefineReservedUnused(IOSharedInterruptController, 2); 407 OSMetaClassDefineReservedUnused(IOSharedInterruptController, 3); 408 409 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 410 411 #define kIOSharedInterruptControllerDefaultVectors (128) 412 413 IOReturn IOSharedInterruptController::initInterruptController(IOInterruptController *parentController, OSData *parentSource) 414 { 415 int cnt, interruptType; 416 IOReturn error; 417 418 if (!super::init()) 419 return kIOReturnNoResources; 420 421 // Set provider to this so enable/disable nub stuff works. 422 provider = this; 423 424 // Allocate the IOInterruptSource so this can act like a nub. 425 _interruptSources = (IOInterruptSource *)IOMalloc(sizeof(IOInterruptSource)); 426 if (_interruptSources == 0) return kIOReturnNoMemory; 427 _numInterruptSources = 1; 428 429 // Set up the IOInterruptSource to point at this. 430 _interruptSources[0].interruptController = parentController; 431 _interruptSources[0].vectorData = parentSource; 432 433 sourceIsLevel = false; 434 error = provider->getInterruptType(0, &interruptType); 435 if (error == kIOReturnSuccess) { 436 if (interruptType & kIOInterruptTypeLevel) 437 sourceIsLevel = true; 438 } 439 440 // Allocate the memory for the vectors 441 numVectors = kIOSharedInterruptControllerDefaultVectors; // For now a constant number. 442 vectors = (IOInterruptVector *)IOMalloc(numVectors * sizeof(IOInterruptVector)); 443 if (vectors == NULL) { 444 IOFree(_interruptSources, sizeof(IOInterruptSource)); 445 return kIOReturnNoMemory; 446 } 447 bzero(vectors, numVectors * sizeof(IOInterruptVector)); 448 449 // Allocate the lock for the controller. 450 controllerLock = IOSimpleLockAlloc(); 451 if (controllerLock == 0) return kIOReturnNoResources; 452 453 // Allocate locks for the vectors. 454 for (cnt = 0; cnt < numVectors; cnt++) { 455 vectors[cnt].interruptLock = IOLockAlloc(); 456 if (vectors[cnt].interruptLock == NULL) { 457 for (cnt = 0; cnt < numVectors; cnt++) { 458 if (vectors[cnt].interruptLock != NULL) 459 IOLockFree(vectors[cnt].interruptLock); 460 } 461 return kIOReturnNoResources; 462 } 463 } 464 465 numVectors = 0; // reset the high water mark for used vectors 466 vectorsRegistered = 0; 467 vectorsEnabled = 0; 468 controllerDisabled = 1; 469 470 return kIOReturnSuccess; 471 } 472 473 IOReturn IOSharedInterruptController::registerInterrupt(IOService *nub, 474 int source, 475 void *target, 476 IOInterruptHandler handler, 477 void *refCon) 478 { 479 IOInterruptSource *interruptSources; 480 long vectorNumber; 481 IOInterruptVector *vector = 0; 482 OSData *vectorData; 483 IOInterruptState interruptState; 484 485 interruptSources = nub->_interruptSources; 486 487 // Find a free vector. 488 vectorNumber = kIOSharedInterruptControllerDefaultVectors; 489 while (vectorsRegistered != kIOSharedInterruptControllerDefaultVectors) { 490 for (vectorNumber = 0; vectorNumber < kIOSharedInterruptControllerDefaultVectors; vectorNumber++) { 491 vector = &vectors[vectorNumber]; 492 493 // Get the lock for this vector. 494 IOTakeLock(vector->interruptLock); 495 496 // Is it unregistered? 497 if (!vector->interruptRegistered) break; 498 499 // Move along to the next one. 500 IOUnlock(vector->interruptLock); 501 } 502 503 if (vectorNumber != kIOSharedInterruptControllerDefaultVectors) break; 504 } 505 506 // Could not find a free one, so give up. 507 if (vectorNumber == kIOSharedInterruptControllerDefaultVectors) { 508 return kIOReturnNoResources; 509 } 510 511 // Create the vectorData for the IOInterruptSource. 512 vectorData = OSData::withBytes(&vectorNumber, sizeof(vectorNumber)); 513 if (vectorData == 0) { 514 return kIOReturnNoMemory; 515 } 516 517 // Fill in the IOInterruptSource with the controller's info. 518 interruptSources[source].interruptController = this; 519 interruptSources[source].vectorData = vectorData; 520 521 // Fill in vector with the client's info. 522 vector->handler = handler; 523 vector->nub = nub; 524 vector->source = source; 525 vector->target = target; 526 vector->refCon = refCon; 527 528 // Get the vector ready. It starts off soft disabled. 529 vector->interruptDisabledSoft = 1; 530 vector->interruptRegistered = 1; 531 532 interruptState = IOSimpleLockLockDisableInterrupt(controllerLock); 533 // Move the high water mark if needed 534 if (++vectorsRegistered > numVectors) numVectors = vectorsRegistered; 535 IOSimpleLockUnlockEnableInterrupt(controllerLock, interruptState); 536 537 IOUnlock(vector->interruptLock); 538 return kIOReturnSuccess; 539 } 540 541 IOReturn IOSharedInterruptController::unregisterInterrupt(IOService *nub, 542 int source) 543 { 544 IOInterruptSource *interruptSources; 545 long vectorNumber; 546 IOInterruptVector *vector; 547 OSData *vectorData; 548 IOInterruptState interruptState; 549 550 interruptSources = nub->_interruptSources; 551 vectorData = interruptSources[source].vectorData; 552 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 553 vector = &vectors[vectorNumber]; 554 555 // Get the lock for this vector. 556 IOTakeLock(vector->interruptLock); 557 558 // Return success if it is not already registered 559 if (!vector->interruptRegistered) { 560 IOUnlock(vector->interruptLock); 561 return kIOReturnSuccess; 562 } 563 564 // Soft disable the source and the controller too. 565 disableInterrupt(nub, source); 566 567 // Clear all the storage for the vector except for interruptLock. 568 vector->interruptActive = 0; 569 vector->interruptDisabledSoft = 0; 570 vector->interruptDisabledHard = 0; 571 vector->interruptRegistered = 0; 572 vector->nub = 0; 573 vector->source = 0; 574 vector->handler = 0; 575 vector->target = 0; 576 vector->refCon = 0; 577 578 interruptState = IOSimpleLockLockDisableInterrupt(controllerLock); 579 vectorsRegistered--; 580 IOSimpleLockUnlockEnableInterrupt(controllerLock, interruptState); 581 582 IOUnlock(vector->interruptLock); 583 584 // Re-enable the controller if all vectors are enabled. 585 if (vectorsEnabled == vectorsRegistered) { 586 controllerDisabled = 0; 587 provider->enableInterrupt(0); 588 } 589 590 return kIOReturnSuccess; 591 } 592 593 IOReturn IOSharedInterruptController::getInterruptType(IOService */*nub*/, 594 int /*source*/, 595 int *interruptType) 596 { 597 return provider->getInterruptType(0, interruptType); 598 } 599 600 IOReturn IOSharedInterruptController::enableInterrupt(IOService *nub, 601 int source) 602 { 603 IOInterruptSource *interruptSources; 604 long vectorNumber; 605 IOInterruptVector *vector; 606 OSData *vectorData; 607 IOInterruptState interruptState; 608 609 interruptSources = nub->_interruptSources; 610 vectorData = interruptSources[source].vectorData; 611 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 612 vector = &vectors[vectorNumber]; 613 614 interruptState = IOSimpleLockLockDisableInterrupt(controllerLock); 615 if (!vector->interruptDisabledSoft) { 616 IOSimpleLockUnlockEnableInterrupt(controllerLock, interruptState); 617 return kIOReturnSuccess; 618 } 619 620 vector->interruptDisabledSoft = 0; 621 vectorsEnabled++; 622 IOSimpleLockUnlockEnableInterrupt(controllerLock, interruptState); 623 624 if (controllerDisabled && (vectorsEnabled == vectorsRegistered)) { 625 controllerDisabled = 0; 626 provider->enableInterrupt(0); 627 } 628 629 return kIOReturnSuccess; 630 } 631 632 IOReturn IOSharedInterruptController::disableInterrupt(IOService *nub, 633 int source) 634 { 635 IOInterruptSource *interruptSources; 636 long vectorNumber; 637 IOInterruptVector *vector; 638 OSData *vectorData; 639 IOInterruptState interruptState; 640 641 interruptSources = nub->_interruptSources; 642 vectorData = interruptSources[source].vectorData; 643 vectorNumber = *(long *)vectorData->getBytesNoCopy(); 644 vector = &vectors[vectorNumber]; 645 646 interruptState = IOSimpleLockLockDisableInterrupt(controllerLock); 647 if (!vector->interruptDisabledSoft) { 648 vector->interruptDisabledSoft = 1; 649 #if __ppc__ 650 sync(); 651 isync(); 652 #endif 653 vectorsEnabled--; 654 } 655 IOSimpleLockUnlockEnableInterrupt(controllerLock, interruptState); 656 657 if (!getPlatform()->atInterruptLevel()) { 658 while (vector->interruptActive); 659 #if __ppc__ 660 isync(); 661 #endif 662 } 663 664 return kIOReturnSuccess; 665 } 666 667 IOInterruptAction IOSharedInterruptController::getInterruptHandlerAddress(void) 668 { 669 return (IOInterruptAction)&IOSharedInterruptController::handleInterrupt; 670 } 671 672 IOReturn IOSharedInterruptController::handleInterrupt(void * /*refCon*/, 673 IOService * nub, 674 int /*source*/) 675 { 676 long vectorNumber; 677 IOInterruptVector *vector; 678 679 for (vectorNumber = 0; vectorNumber < numVectors; vectorNumber++) { 680 vector = &vectors[vectorNumber]; 681 682 vector->interruptActive = 1; 683 #if __ppc__ 684 sync(); 685 isync(); 686 #endif 687 if (!vector->interruptDisabledSoft) { 688 #if __ppc__ 689 isync(); 690 #endif 691 692 // Call the handler if it exists. 693 if (vector->interruptRegistered) { 694 vector->handler(vector->target, vector->refCon, 695 vector->nub, vector->source); 696 } 697 } 698 699 vector->interruptActive = 0; 700 } 701 702 // if any of the vectors are dissabled, then dissable this controller. 703 IOSimpleLockLock(controllerLock); 704 if (vectorsEnabled != vectorsRegistered) { 705 nub->disableInterrupt(0); 706 controllerDisabled = 1; 707 } 708 IOSimpleLockUnlock(controllerLock); 709 710 return kIOReturnSuccess; 711 } 712 713