hwc.cpp revision 0ddfa307452987336b5ec22967ba701c221f8402
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * Copyright (C) 2012-2013, The Linux Foundation. All rights reserved. 4 * 5 * Not a Contribution, Apache license notifications and license are retained 6 * for attribution purposes only. 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20#define ATRACE_TAG (ATRACE_TAG_GRAPHICS | ATRACE_TAG_HAL) 21#include <fcntl.h> 22#include <errno.h> 23 24#include <cutils/log.h> 25#include <cutils/atomic.h> 26#include <EGL/egl.h> 27#include <utils/Trace.h> 28#include <sys/ioctl.h> 29#include <overlay.h> 30#include <overlayRotator.h> 31#include <overlayWriteback.h> 32#include <mdp_version.h> 33#include "hwc_utils.h" 34#include "hwc_fbupdate.h" 35#include "hwc_mdpcomp.h" 36#include "external.h" 37#include "hwc_copybit.h" 38#include "profiler.h" 39 40using namespace qhwc; 41using namespace overlay; 42 43#define VSYNC_DEBUG 0 44#define BLANK_DEBUG 1 45 46static int hwc_device_open(const struct hw_module_t* module, 47 const char* name, 48 struct hw_device_t** device); 49 50static struct hw_module_methods_t hwc_module_methods = { 51 open: hwc_device_open 52}; 53 54hwc_module_t HAL_MODULE_INFO_SYM = { 55 common: { 56 tag: HARDWARE_MODULE_TAG, 57 version_major: 2, 58 version_minor: 0, 59 id: HWC_HARDWARE_MODULE_ID, 60 name: "Qualcomm Hardware Composer Module", 61 author: "CodeAurora Forum", 62 methods: &hwc_module_methods, 63 dso: 0, 64 reserved: {0}, 65 } 66}; 67 68/* 69 * Save callback functions registered to HWC 70 */ 71static void hwc_registerProcs(struct hwc_composer_device_1* dev, 72 hwc_procs_t const* procs) 73{ 74 ALOGI("%s", __FUNCTION__); 75 hwc_context_t* ctx = (hwc_context_t*)(dev); 76 if(!ctx) { 77 ALOGE("%s: Invalid context", __FUNCTION__); 78 return; 79 } 80 ctx->proc = procs; 81 82 // Now that we have the functions needed, kick off 83 // the uevent & vsync threads 84 init_uevent_thread(ctx); 85 init_vsync_thread(ctx); 86} 87 88//Helper 89static void reset(hwc_context_t *ctx, int numDisplays, 90 hwc_display_contents_1_t** displays) { 91 for(int i = 0; i < MAX_DISPLAYS; i++) { 92 hwc_display_contents_1_t *list = displays[i]; 93 // XXX:SurfaceFlinger no longer guarantees that this 94 // value is reset on every prepare. However, for the layer 95 // cache we need to reset it. 96 // We can probably rethink that later on 97 if (LIKELY(list && list->numHwLayers > 1)) { 98 for(uint32_t j = 0; j < list->numHwLayers; j++) { 99 if(list->hwLayers[j].compositionType != HWC_FRAMEBUFFER_TARGET) 100 list->hwLayers[j].compositionType = HWC_FRAMEBUFFER; 101 } 102 } 103 104 if(ctx->mFBUpdate[i]) 105 ctx->mFBUpdate[i]->reset(); 106 if(ctx->mCopyBit[i]) 107 ctx->mCopyBit[i]->reset(); 108 if(ctx->mLayerRotMap[i]) 109 ctx->mLayerRotMap[i]->reset(); 110 } 111} 112 113//clear prev layer prop flags and realloc for current frame 114static void reset_layer_prop(hwc_context_t* ctx, int dpy, int numAppLayers) { 115 if(ctx->layerProp[dpy]) { 116 delete[] ctx->layerProp[dpy]; 117 ctx->layerProp[dpy] = NULL; 118 } 119 ctx->layerProp[dpy] = new LayerProp[numAppLayers]; 120} 121 122static int display_commit(hwc_context_t *ctx, int dpy) { 123 int fbFd = ctx->dpyAttr[dpy].fd; 124 if(fbFd == -1) { 125 ALOGE("%s: Invalid FB fd for display: %d", __FUNCTION__, dpy); 126 return -1; 127 } 128 129 struct mdp_display_commit commit_info; 130 memset(&commit_info, 0, sizeof(struct mdp_display_commit)); 131 commit_info.flags = MDP_DISPLAY_COMMIT_OVERLAY; 132 if(ioctl(fbFd, MSMFB_DISPLAY_COMMIT, &commit_info) == -1) { 133 ALOGE("%s: MSMFB_DISPLAY_COMMIT for primary failed", __FUNCTION__); 134 return -errno; 135 } 136 return 0; 137} 138 139static int hwc_prepare_primary(hwc_composer_device_1 *dev, 140 hwc_display_contents_1_t *list) { 141 hwc_context_t* ctx = (hwc_context_t*)(dev); 142 const int dpy = HWC_DISPLAY_PRIMARY; 143 if(UNLIKELY(!ctx->mBasePipeSetup)) 144 setupBasePipe(ctx); 145 if (LIKELY(list && list->numHwLayers > 1) && 146 ctx->dpyAttr[dpy].isActive) { 147 reset_layer_prop(ctx, dpy, list->numHwLayers - 1); 148 uint32_t last = list->numHwLayers - 1; 149 hwc_layer_1_t *fbLayer = &list->hwLayers[last]; 150 if(fbLayer->handle) { 151 setListStats(ctx, list, dpy); 152 if(ctx->mMDPComp[dpy]->prepare(ctx, list) < 0) { 153 const int fbZ = 0; 154 ctx->mFBUpdate[dpy]->prepare(ctx, list, fbZ); 155 } 156 if (ctx->mMDP.version < qdutils::MDP_V4_0) { 157 if(ctx->mCopyBit[dpy]) 158 ctx->mCopyBit[dpy]->prepare(ctx, list, dpy); 159 } 160 } 161 } 162 return 0; 163} 164 165static int hwc_prepare_external(hwc_composer_device_1 *dev, 166 hwc_display_contents_1_t *list, int dpy) { 167 168 hwc_context_t* ctx = (hwc_context_t*)(dev); 169 170 if (LIKELY(list && list->numHwLayers > 1) && 171 ctx->dpyAttr[dpy].isActive && 172 ctx->dpyAttr[dpy].connected) { 173 reset_layer_prop(ctx, dpy, list->numHwLayers - 1); 174 uint32_t last = list->numHwLayers - 1; 175 hwc_layer_1_t *fbLayer = &list->hwLayers[last]; 176 if(!ctx->dpyAttr[dpy].isPause) { 177 if(fbLayer->handle) { 178 ctx->mExtDispConfiguring = false; 179 setListStats(ctx, list, dpy); 180 if(ctx->mMDPComp[dpy]->prepare(ctx, list) < 0) { 181 const int fbZ = 0; 182 ctx->mFBUpdate[dpy]->prepare(ctx, list, fbZ); 183 } 184 185 /* Temporarily commenting out C2D until we support partial 186 copybit composition for mixed mode MDP 187 188 if((fbZOrder >= 0) && ctx->mCopyBit[dpy]) 189 ctx->mCopyBit[dpy]->prepare(ctx, list, dpy); 190 */ 191 } 192 } else { 193 // External Display is in Pause state. 194 // ToDo: 195 // Mark all application layers as OVERLAY so that 196 // GPU will not compose. This is done for power 197 // optimization 198 } 199 } 200 return 0; 201} 202 203static int hwc_prepare_virtual(hwc_composer_device_1 *dev, 204 hwc_display_contents_1_t *list, int dpy) { 205 //XXX: Fix when framework support is added 206 return 0; 207} 208 209static int hwc_prepare(hwc_composer_device_1 *dev, size_t numDisplays, 210 hwc_display_contents_1_t** displays) 211{ 212 int ret = 0; 213 hwc_context_t* ctx = (hwc_context_t*)(dev); 214 Locker::Autolock _bl(ctx->mBlankLock); 215 //Will be unlocked at the end of set 216 ctx->mExtLock.lock(); 217 reset(ctx, numDisplays, displays); 218 219 ctx->mOverlay->configBegin(); 220 ctx->mRotMgr->configBegin(); 221 overlay::Writeback::configBegin(); 222 223 Overlay::setDMAMode(Overlay::DMA_LINE_MODE); 224 225 for (int32_t i = numDisplays - 1; i >= 0; i--) { 226 hwc_display_contents_1_t *list = displays[i]; 227 switch(i) { 228 case HWC_DISPLAY_PRIMARY: 229 ret = hwc_prepare_primary(dev, list); 230 break; 231 case HWC_DISPLAY_EXTERNAL: 232 ret = hwc_prepare_external(dev, list, i); 233 break; 234 case HWC_DISPLAY_VIRTUAL: 235 ret = hwc_prepare_virtual(dev, list, i); 236 break; 237 default: 238 ret = -EINVAL; 239 } 240 } 241 242 ctx->mOverlay->configDone(); 243 ctx->mRotMgr->configDone(); 244 overlay::Writeback::configDone(); 245 246 return ret; 247} 248 249static int hwc_eventControl(struct hwc_composer_device_1* dev, int dpy, 250 int event, int enable) 251{ 252 int ret = 0; 253 hwc_context_t* ctx = (hwc_context_t*)(dev); 254 switch(event) { 255 case HWC_EVENT_VSYNC: 256 if(!ctx->dpyAttr[dpy].isActive) { 257 ALOGE("Display is blanked - Cannot %s vsync", 258 enable ? "enable" : "disable"); 259 return -EINVAL; 260 } 261 262 if (ctx->vstate.enable == enable) 263 break; 264 ret = hwc_vsync_control(ctx, dpy, enable); 265 if(ret == 0) 266 ctx->vstate.enable = !!enable; 267 ALOGD_IF (VSYNC_DEBUG, "VSYNC state changed to %s", 268 (enable)?"ENABLED":"DISABLED"); 269 break; 270 default: 271 ret = -EINVAL; 272 } 273 return ret; 274} 275 276static int hwc_blank(struct hwc_composer_device_1* dev, int dpy, int blank) 277{ 278 ATRACE_CALL(); 279 hwc_context_t* ctx = (hwc_context_t*)(dev); 280 281 Locker::Autolock _l(ctx->mBlankLock); 282 int ret = 0; 283 ALOGD_IF(BLANK_DEBUG, "%s: %s display: %d", __FUNCTION__, 284 blank==1 ? "Blanking":"Unblanking", dpy); 285 if(blank) { 286 // free up all the overlay pipes in use 287 // when we get a blank for either display 288 // makes sure that all pipes are freed 289 ctx->mOverlay->configBegin(); 290 ctx->mOverlay->configDone(); 291 ctx->mRotMgr->clear(); 292 overlay::Writeback::clear(); 293 } 294 switch(dpy) { 295 case HWC_DISPLAY_PRIMARY: 296 if(blank) { 297 ret = ioctl(ctx->dpyAttr[dpy].fd, FBIOBLANK, 298 FB_BLANK_POWERDOWN); 299 } else { 300 ret = ioctl(ctx->dpyAttr[dpy].fd, FBIOBLANK,FB_BLANK_UNBLANK); 301 } 302 break; 303 case HWC_DISPLAY_EXTERNAL: 304 case HWC_DISPLAY_VIRTUAL: 305 if(blank) { 306 // call external framebuffer commit on blank, 307 // so that any pipe unsets gets committed 308 if (display_commit(ctx, dpy) < 0) { 309 ret = -1; 310 ALOGE("%s:post failed for external display !! ", 311 __FUNCTION__); 312 } 313 } else { 314 } 315 break; 316 default: 317 return -EINVAL; 318 } 319 // Enable HPD here, as during bootup unblank is called 320 // when SF is completely initialized 321 ctx->mExtDisplay->setHPD(1); 322 if(ret == 0){ 323 ctx->dpyAttr[dpy].isActive = !blank; 324 } else { 325 ALOGE("%s: Failed in %s display: %d error:%s", __FUNCTION__, 326 blank==1 ? "blanking":"unblanking", dpy, strerror(errno)); 327 return ret; 328 } 329 330 ALOGD_IF(BLANK_DEBUG, "%s: Done %s display: %d", __FUNCTION__, 331 blank==1 ? "blanking":"unblanking", dpy); 332 return 0; 333} 334 335static int hwc_query(struct hwc_composer_device_1* dev, 336 int param, int* value) 337{ 338 hwc_context_t* ctx = (hwc_context_t*)(dev); 339 int supported = HWC_DISPLAY_PRIMARY_BIT; 340 341 switch (param) { 342 case HWC_BACKGROUND_LAYER_SUPPORTED: 343 // Not supported for now 344 value[0] = 0; 345 break; 346 case HWC_DISPLAY_TYPES_SUPPORTED: 347 if(ctx->mMDP.hasOverlay) 348 supported |= HWC_DISPLAY_EXTERNAL_BIT; 349 value[0] = supported; 350 break; 351 default: 352 return -EINVAL; 353 } 354 return 0; 355 356} 357 358 359static int hwc_set_primary(hwc_context_t *ctx, hwc_display_contents_1_t* list) { 360 ATRACE_CALL(); 361 int ret = 0; 362 const int dpy = HWC_DISPLAY_PRIMARY; 363 364 if (LIKELY(list) && ctx->dpyAttr[dpy].isActive) { 365 uint32_t last = list->numHwLayers - 1; 366 hwc_layer_1_t *fbLayer = &list->hwLayers[last]; 367 int fd = -1; //FenceFD from the Copybit(valid in async mode) 368 bool copybitDone = false; 369 if(ctx->mCopyBit[dpy]) 370 copybitDone = ctx->mCopyBit[dpy]->draw(ctx, list, dpy, &fd); 371 if(list->numHwLayers > 1) 372 hwc_sync(ctx, list, dpy, fd); 373 374 if (!ctx->mMDPComp[dpy]->draw(ctx, list)) { 375 ALOGE("%s: MDPComp draw failed", __FUNCTION__); 376 ret = -1; 377 } 378 379 //TODO We dont check for SKIP flag on this layer because we need PAN 380 //always. Last layer is always FB 381 private_handle_t *hnd = (private_handle_t *)fbLayer->handle; 382 if(copybitDone) { 383 hnd = ctx->mCopyBit[dpy]->getCurrentRenderBuffer(); 384 } 385 386 if(hnd) { 387 if (!ctx->mFBUpdate[dpy]->draw(ctx, hnd)) { 388 ALOGE("%s: FBUpdate draw failed", __FUNCTION__); 389 ret = -1; 390 } 391 } 392 393 if (display_commit(ctx, dpy) < 0) { 394 ALOGE("%s: display commit fail!", __FUNCTION__); 395 ret = -1; 396 } 397 } 398 399 closeAcquireFds(list); 400 return ret; 401} 402 403static int hwc_set_external(hwc_context_t *ctx, 404 hwc_display_contents_1_t* list, int dpy) 405{ 406 ATRACE_CALL(); 407 int ret = 0; 408 409 if (LIKELY(list) && ctx->dpyAttr[dpy].isActive && 410 !ctx->dpyAttr[dpy].isPause && 411 ctx->dpyAttr[dpy].connected) { 412 uint32_t last = list->numHwLayers - 1; 413 hwc_layer_1_t *fbLayer = &list->hwLayers[last]; 414 int fd = -1; //FenceFD from the Copybit(valid in async mode) 415 bool copybitDone = false; 416 if(ctx->mCopyBit[dpy]) 417 copybitDone = ctx->mCopyBit[dpy]->draw(ctx, list, dpy, &fd); 418 419 if(list->numHwLayers > 1) 420 hwc_sync(ctx, list, dpy, fd); 421 422 if (!ctx->mMDPComp[dpy]->draw(ctx, list)) { 423 ALOGE("%s: MDPComp draw failed", __FUNCTION__); 424 ret = -1; 425 } 426 427 private_handle_t *hnd = (private_handle_t *)fbLayer->handle; 428 if(copybitDone) { 429 hnd = ctx->mCopyBit[dpy]->getCurrentRenderBuffer(); 430 } 431 432 if(hnd) { 433 if (!ctx->mFBUpdate[dpy]->draw(ctx, hnd)) { 434 ALOGE("%s: FBUpdate::draw fail!", __FUNCTION__); 435 ret = -1; 436 } 437 } 438 439 if (display_commit(ctx, dpy) < 0) { 440 ALOGE("%s: display commit fail!", __FUNCTION__); 441 ret = -1; 442 } 443 } 444 445 closeAcquireFds(list); 446 return ret; 447} 448 449static int hwc_set_virtual(hwc_context_t *ctx, 450 hwc_display_contents_1_t* list, int dpy) 451{ 452 //XXX: Implement set. 453 closeAcquireFds(list); 454 if (list) { 455 // SF assumes HWC waits for the acquire fence and returns a new fence 456 // that signals when we're done. Since we don't wait, and also don't 457 // touch the buffer, we can just handle the acquire fence back to SF 458 // as the retire fence. 459 list->retireFenceFd = list->outbufAcquireFenceFd; 460 } 461 return 0; 462} 463 464 465static int hwc_set(hwc_composer_device_1 *dev, 466 size_t numDisplays, 467 hwc_display_contents_1_t** displays) 468{ 469 int ret = 0; 470 hwc_context_t* ctx = (hwc_context_t*)(dev); 471 Locker::Autolock _bl(ctx->mBlankLock); 472 for (uint32_t i = 0; i < numDisplays; i++) { 473 hwc_display_contents_1_t* list = displays[i]; 474 switch(i) { 475 case HWC_DISPLAY_PRIMARY: 476 ret = hwc_set_primary(ctx, list); 477 break; 478 case HWC_DISPLAY_EXTERNAL: 479 ret = hwc_set_external(ctx, list, i); 480 break; 481 case HWC_DISPLAY_VIRTUAL: 482 ret = hwc_set_virtual(ctx, list, i); 483 break; 484 default: 485 ret = -EINVAL; 486 } 487 } 488 // This is only indicative of how many times SurfaceFlinger posts 489 // frames to the display. 490 CALC_FPS(); 491 MDPComp::resetIdleFallBack(); 492 ctx->mVideoTransFlag = false; 493 //Was locked at the beginning of prepare 494 ctx->mExtLock.unlock(); 495 return ret; 496} 497 498int hwc_getDisplayConfigs(struct hwc_composer_device_1* dev, int disp, 499 uint32_t* configs, size_t* numConfigs) { 500 int ret = 0; 501 hwc_context_t* ctx = (hwc_context_t*)(dev); 502 //in 1.1 there is no way to choose a config, report as config id # 0 503 //This config is passed to getDisplayAttributes. Ignore for now. 504 switch(disp) { 505 case HWC_DISPLAY_PRIMARY: 506 if(*numConfigs > 0) { 507 configs[0] = 0; 508 *numConfigs = 1; 509 } 510 ret = 0; //NO_ERROR 511 break; 512 case HWC_DISPLAY_EXTERNAL: 513 ret = -1; //Not connected 514 if(ctx->dpyAttr[HWC_DISPLAY_EXTERNAL].connected) { 515 ret = 0; //NO_ERROR 516 if(*numConfigs > 0) { 517 configs[0] = 0; 518 *numConfigs = 1; 519 } 520 } 521 break; 522 } 523 return ret; 524} 525 526int hwc_getDisplayAttributes(struct hwc_composer_device_1* dev, int disp, 527 uint32_t config, const uint32_t* attributes, int32_t* values) { 528 529 hwc_context_t* ctx = (hwc_context_t*)(dev); 530 //If hotpluggable displays are inactive return error 531 if(disp == HWC_DISPLAY_EXTERNAL && !ctx->dpyAttr[disp].connected) { 532 return -1; 533 } 534 535 //From HWComposer 536 static const uint32_t DISPLAY_ATTRIBUTES[] = { 537 HWC_DISPLAY_VSYNC_PERIOD, 538 HWC_DISPLAY_WIDTH, 539 HWC_DISPLAY_HEIGHT, 540 HWC_DISPLAY_DPI_X, 541 HWC_DISPLAY_DPI_Y, 542 HWC_DISPLAY_NO_ATTRIBUTE, 543 }; 544 545 const int NUM_DISPLAY_ATTRIBUTES = (sizeof(DISPLAY_ATTRIBUTES) / 546 sizeof(DISPLAY_ATTRIBUTES)[0]); 547 548 for (size_t i = 0; i < NUM_DISPLAY_ATTRIBUTES - 1; i++) { 549 switch (attributes[i]) { 550 case HWC_DISPLAY_VSYNC_PERIOD: 551 values[i] = ctx->dpyAttr[disp].vsync_period; 552 break; 553 case HWC_DISPLAY_WIDTH: 554 values[i] = ctx->dpyAttr[disp].xres; 555 ALOGD("%s disp = %d, width = %d",__FUNCTION__, disp, 556 ctx->dpyAttr[disp].xres); 557 break; 558 case HWC_DISPLAY_HEIGHT: 559 values[i] = ctx->dpyAttr[disp].yres; 560 ALOGD("%s disp = %d, height = %d",__FUNCTION__, disp, 561 ctx->dpyAttr[disp].yres); 562 break; 563 case HWC_DISPLAY_DPI_X: 564 values[i] = (int32_t) (ctx->dpyAttr[disp].xdpi*1000.0); 565 break; 566 case HWC_DISPLAY_DPI_Y: 567 values[i] = (int32_t) (ctx->dpyAttr[disp].ydpi*1000.0); 568 break; 569 default: 570 ALOGE("Unknown display attribute %d", 571 attributes[i]); 572 return -EINVAL; 573 } 574 } 575 return 0; 576} 577 578void hwc_dump(struct hwc_composer_device_1* dev, char *buff, int buff_len) 579{ 580 hwc_context_t* ctx = (hwc_context_t*)(dev); 581 android::String8 aBuf(""); 582 dumpsys_log(aBuf, "Qualcomm HWC state:\n"); 583 dumpsys_log(aBuf, " MDPVersion=%d\n", ctx->mMDP.version); 584 dumpsys_log(aBuf, " DisplayPanel=%c\n", ctx->mMDP.panel); 585 for(int dpy = 0; dpy < MAX_DISPLAYS; dpy++) { 586 if(ctx->mMDPComp[dpy]) 587 ctx->mMDPComp[dpy]->dump(aBuf); 588 } 589 char ovDump[2048] = {'\0'}; 590 ctx->mOverlay->getDump(ovDump, 2048); 591 dumpsys_log(aBuf, ovDump); 592 ovDump[0] = '\0'; 593 ctx->mRotMgr->getDump(ovDump, 1024); 594 dumpsys_log(aBuf, ovDump); 595 strlcpy(buff, aBuf.string(), buff_len); 596} 597 598static int hwc_device_close(struct hw_device_t *dev) 599{ 600 if(!dev) { 601 ALOGE("%s: NULL device pointer", __FUNCTION__); 602 return -1; 603 } 604 closeContext((hwc_context_t*)dev); 605 free(dev); 606 607 return 0; 608} 609 610static int hwc_device_open(const struct hw_module_t* module, const char* name, 611 struct hw_device_t** device) 612{ 613 int status = -EINVAL; 614 615 if (!strcmp(name, HWC_HARDWARE_COMPOSER)) { 616 struct hwc_context_t *dev; 617 dev = (hwc_context_t*)malloc(sizeof(*dev)); 618 memset(dev, 0, sizeof(*dev)); 619 620 //Initialize hwc context 621 initContext(dev); 622 623 //Setup HWC methods 624 dev->device.common.tag = HARDWARE_DEVICE_TAG; 625 dev->device.common.version = HWC_DEVICE_API_VERSION_1_2; 626 dev->device.common.module = const_cast<hw_module_t*>(module); 627 dev->device.common.close = hwc_device_close; 628 dev->device.prepare = hwc_prepare; 629 dev->device.set = hwc_set; 630 dev->device.eventControl = hwc_eventControl; 631 dev->device.blank = hwc_blank; 632 dev->device.query = hwc_query; 633 dev->device.registerProcs = hwc_registerProcs; 634 dev->device.dump = hwc_dump; 635 dev->device.getDisplayConfigs = hwc_getDisplayConfigs; 636 dev->device.getDisplayAttributes = hwc_getDisplayAttributes; 637 *device = &dev->device.common; 638 status = 0; 639 } 640 return status; 641} 642