hwc.cpp revision 5ceb9c6a763418d5e0cf5da4e74b7a7c733fb4b1
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 int fbZOrder = ctx->mMDPComp[dpy]->prepare(ctx, list); 153 if(fbZOrder >= 0) 154 ctx->mFBUpdate[dpy]->prepare(ctx, list, fbZOrder); 155 156 if (ctx->mMDP.version < qdutils::MDP_V4_0) { 157 if((fbZOrder >= 0) && 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 Locker::Autolock _l(ctx->mExtLock); 170 171 if (LIKELY(list && list->numHwLayers > 1) && 172 ctx->dpyAttr[dpy].isActive && 173 ctx->dpyAttr[dpy].connected) { 174 reset_layer_prop(ctx, dpy, list->numHwLayers - 1); 175 uint32_t last = list->numHwLayers - 1; 176 hwc_layer_1_t *fbLayer = &list->hwLayers[last]; 177 if(!ctx->dpyAttr[dpy].isPause) { 178 if(fbLayer->handle) { 179 ctx->mExtDispConfiguring = false; 180 setListStats(ctx, list, dpy); 181 int fbZOrder = ctx->mMDPComp[dpy]->prepare(ctx, list); 182 if(fbZOrder >= 0) 183 ctx->mFBUpdate[dpy]->prepare(ctx, list, fbZOrder); 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 _l(ctx->mBlankLock); 215 reset(ctx, numDisplays, displays); 216 217 ctx->mOverlay->configBegin(); 218 ctx->mRotMgr->configBegin(); 219 overlay::Writeback::configBegin(); 220 221 Overlay::setDMAMode(Overlay::DMA_LINE_MODE); 222 223 for (int32_t i = numDisplays - 1; i >= 0; i--) { 224 hwc_display_contents_1_t *list = displays[i]; 225 switch(i) { 226 case HWC_DISPLAY_PRIMARY: 227 ret = hwc_prepare_primary(dev, list); 228 break; 229 case HWC_DISPLAY_EXTERNAL: 230 ret = hwc_prepare_external(dev, list, i); 231 break; 232 case HWC_DISPLAY_VIRTUAL: 233 ret = hwc_prepare_virtual(dev, list, i); 234 break; 235 default: 236 ret = -EINVAL; 237 } 238 } 239 240 ctx->mOverlay->configDone(); 241 ctx->mRotMgr->configDone(); 242 overlay::Writeback::configDone(); 243 244 return ret; 245} 246 247static int hwc_eventControl(struct hwc_composer_device_1* dev, int dpy, 248 int event, int enable) 249{ 250 int ret = 0; 251 hwc_context_t* ctx = (hwc_context_t*)(dev); 252 switch(event) { 253 case HWC_EVENT_VSYNC: 254 if(!ctx->dpyAttr[dpy].isActive) { 255 ALOGE("Display is blanked - Cannot %s vsync", 256 enable ? "enable" : "disable"); 257 return -EINVAL; 258 } 259 260 if (ctx->vstate.enable == enable) 261 break; 262 ret = hwc_vsync_control(ctx, dpy, enable); 263 if(ret == 0) 264 ctx->vstate.enable = !!enable; 265 ALOGD_IF (VSYNC_DEBUG, "VSYNC state changed to %s", 266 (enable)?"ENABLED":"DISABLED"); 267 break; 268 default: 269 ret = -EINVAL; 270 } 271 return ret; 272} 273 274static int hwc_blank(struct hwc_composer_device_1* dev, int dpy, int blank) 275{ 276 ATRACE_CALL(); 277 hwc_context_t* ctx = (hwc_context_t*)(dev); 278 279 Locker::Autolock _l(ctx->mBlankLock); 280 int ret = 0; 281 ALOGD_IF(BLANK_DEBUG, "%s: %s display: %d", __FUNCTION__, 282 blank==1 ? "Blanking":"Unblanking", dpy); 283 if(blank) { 284 // free up all the overlay pipes in use 285 // when we get a blank for either display 286 // makes sure that all pipes are freed 287 ctx->mOverlay->configBegin(); 288 ctx->mOverlay->configDone(); 289 ctx->mRotMgr->clear(); 290 overlay::Writeback::clear(); 291 } 292 switch(dpy) { 293 case HWC_DISPLAY_PRIMARY: 294 if(blank) { 295 ret = ioctl(ctx->dpyAttr[dpy].fd, FBIOBLANK, 296 FB_BLANK_POWERDOWN); 297 } else { 298 ret = ioctl(ctx->dpyAttr[dpy].fd, FBIOBLANK,FB_BLANK_UNBLANK); 299 } 300 break; 301 case HWC_DISPLAY_EXTERNAL: 302 case HWC_DISPLAY_VIRTUAL: 303 if(blank) { 304 // call external framebuffer commit on blank, 305 // so that any pipe unsets gets committed 306 if (display_commit(ctx, dpy) < 0) { 307 ret = -1; 308 ALOGE("%s:post failed for external display !! ", 309 __FUNCTION__); 310 } 311 } else { 312 } 313 break; 314 default: 315 return -EINVAL; 316 } 317 // Enable HPD here, as during bootup unblank is called 318 // when SF is completely initialized 319 ctx->mExtDisplay->setHPD(1); 320 if(ret == 0){ 321 ctx->dpyAttr[dpy].isActive = !blank; 322 } else { 323 ALOGE("%s: Failed in %s display: %d error:%s", __FUNCTION__, 324 blank==1 ? "blanking":"unblanking", dpy, strerror(errno)); 325 return ret; 326 } 327 328 ALOGD_IF(BLANK_DEBUG, "%s: Done %s display: %d", __FUNCTION__, 329 blank==1 ? "blanking":"unblanking", dpy); 330 return 0; 331} 332 333static int hwc_query(struct hwc_composer_device_1* dev, 334 int param, int* value) 335{ 336 hwc_context_t* ctx = (hwc_context_t*)(dev); 337 int supported = HWC_DISPLAY_PRIMARY_BIT; 338 339 switch (param) { 340 case HWC_BACKGROUND_LAYER_SUPPORTED: 341 // Not supported for now 342 value[0] = 0; 343 break; 344 case HWC_DISPLAY_TYPES_SUPPORTED: 345 if(ctx->mMDP.hasOverlay) 346 supported |= HWC_DISPLAY_EXTERNAL_BIT; 347 value[0] = supported; 348 break; 349 default: 350 return -EINVAL; 351 } 352 return 0; 353 354} 355 356 357static int hwc_set_primary(hwc_context_t *ctx, hwc_display_contents_1_t* list) { 358 ATRACE_CALL(); 359 int ret = 0; 360 const int dpy = HWC_DISPLAY_PRIMARY; 361 362 if (LIKELY(list) && ctx->dpyAttr[dpy].isActive) { 363 uint32_t last = list->numHwLayers - 1; 364 hwc_layer_1_t *fbLayer = &list->hwLayers[last]; 365 int fd = -1; //FenceFD from the Copybit(valid in async mode) 366 bool copybitDone = false; 367 if(ctx->mCopyBit[dpy]) 368 copybitDone = ctx->mCopyBit[dpy]->draw(ctx, list, dpy, &fd); 369 if(list->numHwLayers > 1) 370 hwc_sync(ctx, list, dpy, fd); 371 372 if (!ctx->mMDPComp[dpy]->draw(ctx, list)) { 373 ALOGE("%s: MDPComp draw failed", __FUNCTION__); 374 ret = -1; 375 } 376 377 //TODO We dont check for SKIP flag on this layer because we need PAN 378 //always. Last layer is always FB 379 private_handle_t *hnd = (private_handle_t *)fbLayer->handle; 380 if(copybitDone) { 381 hnd = ctx->mCopyBit[dpy]->getCurrentRenderBuffer(); 382 } 383 384 if(hnd) { 385 if (!ctx->mFBUpdate[dpy]->draw(ctx, hnd)) { 386 ALOGE("%s: FBUpdate draw failed", __FUNCTION__); 387 ret = -1; 388 } 389 } 390 391 if (display_commit(ctx, dpy) < 0) { 392 ALOGE("%s: display commit fail!", __FUNCTION__); 393 ret = -1; 394 } 395 } 396 397 closeAcquireFds(list); 398 return ret; 399} 400 401static int hwc_set_external(hwc_context_t *ctx, 402 hwc_display_contents_1_t* list, int dpy) 403{ 404 ATRACE_CALL(); 405 int ret = 0; 406 Locker::Autolock _l(ctx->mExtLock); 407 408 if (LIKELY(list) && ctx->dpyAttr[dpy].isActive && 409 !ctx->dpyAttr[dpy].isPause && 410 ctx->dpyAttr[dpy].connected) { 411 uint32_t last = list->numHwLayers - 1; 412 hwc_layer_1_t *fbLayer = &list->hwLayers[last]; 413 int fd = -1; //FenceFD from the Copybit(valid in async mode) 414 bool copybitDone = false; 415 if(ctx->mCopyBit[dpy]) 416 copybitDone = ctx->mCopyBit[dpy]->draw(ctx, list, dpy, &fd); 417 418 if(list->numHwLayers > 1) 419 hwc_sync(ctx, list, dpy, fd); 420 421 if (!ctx->mMDPComp[dpy]->draw(ctx, list)) { 422 ALOGE("%s: MDPComp draw failed", __FUNCTION__); 423 ret = -1; 424 } 425 426 private_handle_t *hnd = (private_handle_t *)fbLayer->handle; 427 if(copybitDone) { 428 hnd = ctx->mCopyBit[dpy]->getCurrentRenderBuffer(); 429 } 430 431 if(hnd) { 432 if (!ctx->mFBUpdate[dpy]->draw(ctx, hnd)) { 433 ALOGE("%s: FBUpdate::draw fail!", __FUNCTION__); 434 ret = -1; 435 } 436 } 437 438 if (display_commit(ctx, dpy) < 0) { 439 ALOGE("%s: display commit fail!", __FUNCTION__); 440 ret = -1; 441 } 442 } 443 444 closeAcquireFds(list); 445 return ret; 446} 447 448static int hwc_set_virtual(hwc_context_t *ctx, 449 hwc_display_contents_1_t* list, int dpy) 450{ 451 //XXX: Implement set. 452 closeAcquireFds(list); 453 if (list) { 454 // SF assumes HWC waits for the acquire fence and returns a new fence 455 // that signals when we're done. Since we don't wait, and also don't 456 // touch the buffer, we can just handle the acquire fence back to SF 457 // as the retire fence. 458 list->retireFenceFd = list->outbufAcquireFenceFd; 459 } 460 return 0; 461} 462 463 464static int hwc_set(hwc_composer_device_1 *dev, 465 size_t numDisplays, 466 hwc_display_contents_1_t** displays) 467{ 468 int ret = 0; 469 hwc_context_t* ctx = (hwc_context_t*)(dev); 470 Locker::Autolock _l(ctx->mBlankLock); 471 for (uint32_t i = 0; i < numDisplays; i++) { 472 hwc_display_contents_1_t* list = displays[i]; 473 switch(i) { 474 case HWC_DISPLAY_PRIMARY: 475 ret = hwc_set_primary(ctx, list); 476 break; 477 case HWC_DISPLAY_EXTERNAL: 478 ret = hwc_set_external(ctx, list, i); 479 break; 480 case HWC_DISPLAY_VIRTUAL: 481 ret = hwc_set_virtual(ctx, list, i); 482 break; 483 default: 484 ret = -EINVAL; 485 } 486 } 487 // This is only indicative of how many times SurfaceFlinger posts 488 // frames to the display. 489 CALC_FPS(); 490 MDPComp::resetIdleFallBack(); 491 ctx->mVideoTransFlag = false; 492 return ret; 493} 494 495int hwc_getDisplayConfigs(struct hwc_composer_device_1* dev, int disp, 496 uint32_t* configs, size_t* numConfigs) { 497 int ret = 0; 498 hwc_context_t* ctx = (hwc_context_t*)(dev); 499 //in 1.1 there is no way to choose a config, report as config id # 0 500 //This config is passed to getDisplayAttributes. Ignore for now. 501 switch(disp) { 502 case HWC_DISPLAY_PRIMARY: 503 if(*numConfigs > 0) { 504 configs[0] = 0; 505 *numConfigs = 1; 506 } 507 ret = 0; //NO_ERROR 508 break; 509 case HWC_DISPLAY_EXTERNAL: 510 ret = -1; //Not connected 511 if(ctx->dpyAttr[HWC_DISPLAY_EXTERNAL].connected) { 512 ret = 0; //NO_ERROR 513 if(*numConfigs > 0) { 514 configs[0] = 0; 515 *numConfigs = 1; 516 } 517 } 518 break; 519 } 520 return ret; 521} 522 523int hwc_getDisplayAttributes(struct hwc_composer_device_1* dev, int disp, 524 uint32_t config, const uint32_t* attributes, int32_t* values) { 525 526 hwc_context_t* ctx = (hwc_context_t*)(dev); 527 //If hotpluggable displays are inactive return error 528 if(disp == HWC_DISPLAY_EXTERNAL && !ctx->dpyAttr[disp].connected) { 529 return -1; 530 } 531 532 //From HWComposer 533 static const uint32_t DISPLAY_ATTRIBUTES[] = { 534 HWC_DISPLAY_VSYNC_PERIOD, 535 HWC_DISPLAY_WIDTH, 536 HWC_DISPLAY_HEIGHT, 537 HWC_DISPLAY_DPI_X, 538 HWC_DISPLAY_DPI_Y, 539 HWC_DISPLAY_NO_ATTRIBUTE, 540 }; 541 542 const int NUM_DISPLAY_ATTRIBUTES = (sizeof(DISPLAY_ATTRIBUTES) / 543 sizeof(DISPLAY_ATTRIBUTES)[0]); 544 545 for (size_t i = 0; i < NUM_DISPLAY_ATTRIBUTES - 1; i++) { 546 switch (attributes[i]) { 547 case HWC_DISPLAY_VSYNC_PERIOD: 548 values[i] = ctx->dpyAttr[disp].vsync_period; 549 break; 550 case HWC_DISPLAY_WIDTH: 551 values[i] = ctx->dpyAttr[disp].xres; 552 ALOGD("%s disp = %d, width = %d",__FUNCTION__, disp, 553 ctx->dpyAttr[disp].xres); 554 break; 555 case HWC_DISPLAY_HEIGHT: 556 values[i] = ctx->dpyAttr[disp].yres; 557 ALOGD("%s disp = %d, height = %d",__FUNCTION__, disp, 558 ctx->dpyAttr[disp].yres); 559 break; 560 case HWC_DISPLAY_DPI_X: 561 values[i] = (int32_t) (ctx->dpyAttr[disp].xdpi*1000.0); 562 break; 563 case HWC_DISPLAY_DPI_Y: 564 values[i] = (int32_t) (ctx->dpyAttr[disp].ydpi*1000.0); 565 break; 566 default: 567 ALOGE("Unknown display attribute %d", 568 attributes[i]); 569 return -EINVAL; 570 } 571 } 572 return 0; 573} 574 575void hwc_dump(struct hwc_composer_device_1* dev, char *buff, int buff_len) 576{ 577 hwc_context_t* ctx = (hwc_context_t*)(dev); 578 android::String8 aBuf(""); 579 dumpsys_log(aBuf, "Qualcomm HWC state:\n"); 580 dumpsys_log(aBuf, " MDPVersion=%d\n", ctx->mMDP.version); 581 dumpsys_log(aBuf, " DisplayPanel=%c\n", ctx->mMDP.panel); 582 for(int dpy = 0; dpy < MAX_DISPLAYS; dpy++) { 583 if(ctx->mMDPComp[dpy]) 584 ctx->mMDPComp[dpy]->dump(aBuf); 585 } 586 char ovDump[2048] = {'\0'}; 587 ctx->mOverlay->getDump(ovDump, 2048); 588 dumpsys_log(aBuf, ovDump); 589 ovDump[0] = '\0'; 590 ctx->mRotMgr->getDump(ovDump, 1024); 591 dumpsys_log(aBuf, ovDump); 592 strlcpy(buff, aBuf.string(), buff_len); 593} 594 595static int hwc_device_close(struct hw_device_t *dev) 596{ 597 if(!dev) { 598 ALOGE("%s: NULL device pointer", __FUNCTION__); 599 return -1; 600 } 601 closeContext((hwc_context_t*)dev); 602 free(dev); 603 604 return 0; 605} 606 607static int hwc_device_open(const struct hw_module_t* module, const char* name, 608 struct hw_device_t** device) 609{ 610 int status = -EINVAL; 611 612 if (!strcmp(name, HWC_HARDWARE_COMPOSER)) { 613 struct hwc_context_t *dev; 614 dev = (hwc_context_t*)malloc(sizeof(*dev)); 615 memset(dev, 0, sizeof(*dev)); 616 617 //Initialize hwc context 618 initContext(dev); 619 620 //Setup HWC methods 621 dev->device.common.tag = HARDWARE_DEVICE_TAG; 622 dev->device.common.version = HWC_DEVICE_API_VERSION_1_2; 623 dev->device.common.module = const_cast<hw_module_t*>(module); 624 dev->device.common.close = hwc_device_close; 625 dev->device.prepare = hwc_prepare; 626 dev->device.set = hwc_set; 627 dev->device.eventControl = hwc_eventControl; 628 dev->device.blank = hwc_blank; 629 dev->device.query = hwc_query; 630 dev->device.registerProcs = hwc_registerProcs; 631 dev->device.dump = hwc_dump; 632 dev->device.getDisplayConfigs = hwc_getDisplayConfigs; 633 dev->device.getDisplayAttributes = hwc_getDisplayAttributes; 634 *device = &dev->device.common; 635 status = 0; 636 } 637 return status; 638} 639