vblank.c revision 4e185dc0
1/* 2 * Copyright © 2013 Keith Packard 3 * 4 * Permission to use, copy, modify, distribute, and sell this software and its 5 * documentation for any purpose is hereby granted without fee, provided that 6 * the above copyright notice appear in all copies and that both that copyright 7 * notice and this permission notice appear in supporting documentation, and 8 * that the name of the copyright holders not be used in advertising or 9 * publicity pertaining to distribution of the software without specific, 10 * written prior permission. The copyright holders make no representations 11 * about the suitability of this software for any purpose. It is provided "as 12 * is" without express or implied warranty. 13 * 14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 15 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO 16 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR 17 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 18 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 19 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 20 * OF THIS SOFTWARE. 21 */ 22 23/** @file vblank.c 24 * 25 * Support for tracking the DRM's vblank events. 26 */ 27 28#ifdef HAVE_DIX_CONFIG_H 29#include "dix-config.h" 30#endif 31 32#include <unistd.h> 33#include <xf86.h> 34#include <xf86Crtc.h> 35#include "driver.h" 36#include "drmmode_display.h" 37 38/** 39 * Tracking for outstanding events queued to the kernel. 40 * 41 * Each list entry is a struct ms_drm_queue, which has a uint32_t 42 * value generated from drm_seq that identifies the event and a 43 * reference back to the crtc/screen associated with the event. It's 44 * done this way rather than in the screen because we want to be able 45 * to drain the list of event handlers that should be called at server 46 * regen time, even though we don't close the drm fd and have no way 47 * to actually drain the kernel events. 48 */ 49static struct xorg_list ms_drm_queue; 50static uint32_t ms_drm_seq; 51 52static void ms_box_intersect(BoxPtr dest, BoxPtr a, BoxPtr b) 53{ 54 dest->x1 = a->x1 > b->x1 ? a->x1 : b->x1; 55 dest->x2 = a->x2 < b->x2 ? a->x2 : b->x2; 56 if (dest->x1 >= dest->x2) { 57 dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0; 58 return; 59 } 60 61 dest->y1 = a->y1 > b->y1 ? a->y1 : b->y1; 62 dest->y2 = a->y2 < b->y2 ? a->y2 : b->y2; 63 if (dest->y1 >= dest->y2) 64 dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0; 65} 66 67static void ms_crtc_box(xf86CrtcPtr crtc, BoxPtr crtc_box) 68{ 69 if (crtc->enabled) { 70 crtc_box->x1 = crtc->x; 71 crtc_box->x2 = 72 crtc->x + xf86ModeWidth(&crtc->mode, crtc->rotation); 73 crtc_box->y1 = crtc->y; 74 crtc_box->y2 = 75 crtc->y + xf86ModeHeight(&crtc->mode, crtc->rotation); 76 } else 77 crtc_box->x1 = crtc_box->x2 = crtc_box->y1 = crtc_box->y2 = 0; 78} 79 80static void ms_randr_crtc_box(RRCrtcPtr crtc, BoxPtr crtc_box) 81{ 82 if (crtc->mode) { 83 crtc_box->x1 = crtc->x; 84 crtc_box->y1 = crtc->y; 85 switch (crtc->rotation) { 86 case RR_Rotate_0: 87 case RR_Rotate_180: 88 default: 89 crtc_box->x2 = crtc->x + crtc->mode->mode.width; 90 crtc_box->y2 = crtc->y + crtc->mode->mode.height; 91 break; 92 case RR_Rotate_90: 93 case RR_Rotate_270: 94 crtc_box->x2 = crtc->x + crtc->mode->mode.height; 95 crtc_box->y2 = crtc->y + crtc->mode->mode.width; 96 break; 97 } 98 } else 99 crtc_box->x1 = crtc_box->x2 = crtc_box->y1 = crtc_box->y2 = 0; 100} 101 102static int ms_box_area(BoxPtr box) 103{ 104 return (int)(box->x2 - box->x1) * (int)(box->y2 - box->y1); 105} 106 107Bool 108ms_crtc_on(xf86CrtcPtr crtc) 109{ 110 drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; 111 112 return crtc->enabled && drmmode_crtc->dpms_mode == DPMSModeOn; 113} 114 115/* 116 * Return the first output which is connected to an active CRTC on this screen. 117 * 118 * RRFirstOutput() will return an output from a slave screen if it is primary, 119 * which is not the behavior that ms_covering_crtc() wants. 120 */ 121 122static RROutputPtr ms_first_output(ScreenPtr pScreen) 123{ 124 rrScrPriv(pScreen); 125 RROutputPtr output; 126 int i, j; 127 128 if (!pScrPriv) 129 return NULL; 130 131 if (pScrPriv->primaryOutput && pScrPriv->primaryOutput->crtc && 132 (pScrPriv->primaryOutput->pScreen == pScreen)) { 133 return pScrPriv->primaryOutput; 134 } 135 136 for (i = 0; i < pScrPriv->numCrtcs; i++) { 137 RRCrtcPtr crtc = pScrPriv->crtcs[i]; 138 139 for (j = 0; j < pScrPriv->numOutputs; j++) { 140 output = pScrPriv->outputs[j]; 141 if (output->crtc == crtc) 142 return output; 143 } 144 } 145 return NULL; 146} 147 148/* 149 * Return the crtc covering 'box'. If two crtcs cover a portion of 150 * 'box', then prefer the crtc with greater coverage. 151 */ 152 153static xf86CrtcPtr 154ms_covering_xf86_crtc(ScreenPtr pScreen, BoxPtr box, Bool screen_is_ms) 155{ 156 ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); 157 xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); 158 xf86CrtcPtr crtc, best_crtc; 159 int coverage, best_coverage; 160 int c; 161 BoxRec crtc_box, cover_box; 162 Bool crtc_on; 163 164 best_crtc = NULL; 165 best_coverage = 0; 166 167 if (!xf86_config) 168 return NULL; 169 170 for (c = 0; c < xf86_config->num_crtc; c++) { 171 crtc = xf86_config->crtc[c]; 172 173 if (screen_is_ms) 174 crtc_on = ms_crtc_on(crtc); 175 else 176 crtc_on = crtc->enabled; 177 178 /* If the CRTC is off, treat it as not covering */ 179 if (!crtc_on) 180 continue; 181 182 ms_crtc_box(crtc, &crtc_box); 183 ms_box_intersect(&cover_box, &crtc_box, box); 184 coverage = ms_box_area(&cover_box); 185 if (coverage > best_coverage) { 186 best_crtc = crtc; 187 best_coverage = coverage; 188 } 189 } 190 191 /* Fallback to primary crtc for drawable's on slave outputs */ 192 if (best_crtc == NULL && !pScreen->isGPU) { 193 RROutputPtr primary_output = NULL; 194 ScreenPtr slave; 195 196 if (dixPrivateKeyRegistered(rrPrivKey)) 197 primary_output = ms_first_output(scrn->pScreen); 198 if (!primary_output || !primary_output->crtc) 199 return NULL; 200 201 crtc = primary_output->crtc->devPrivate; 202 if (!ms_crtc_on(crtc)) 203 return NULL; 204 205 xorg_list_for_each_entry(slave, &pScreen->slave_list, slave_head) { 206 if (!slave->is_output_slave) 207 continue; 208 209 if (ms_covering_xf86_crtc(slave, box, FALSE)) { 210 /* The drawable is on a slave output, return primary crtc */ 211 return crtc; 212 } 213 } 214 } 215 216 return best_crtc; 217} 218 219static RRCrtcPtr 220ms_covering_randr_crtc(ScreenPtr pScreen, BoxPtr box, Bool screen_is_ms) 221{ 222 ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); 223 rrScrPrivPtr pScrPriv = rrGetScrPriv(pScreen); 224 RRCrtcPtr crtc, best_crtc; 225 int coverage, best_coverage; 226 int c; 227 BoxRec crtc_box, cover_box; 228 Bool crtc_on; 229 230 best_crtc = NULL; 231 best_coverage = 0; 232 233 if (!pScrPriv) 234 return NULL; 235 236 for (c = 0; c < pScrPriv->numCrtcs; c++) { 237 crtc = pScrPriv->crtcs[c]; 238 239 if (screen_is_ms) { 240 crtc_on = ms_crtc_on((xf86CrtcPtr) crtc->devPrivate); 241 } else { 242 crtc_on = !!crtc->mode; 243 } 244 245 /* If the CRTC is off, treat it as not covering */ 246 if (!crtc_on) 247 continue; 248 249 ms_randr_crtc_box(crtc, &crtc_box); 250 ms_box_intersect(&cover_box, &crtc_box, box); 251 coverage = ms_box_area(&cover_box); 252 if (coverage > best_coverage) { 253 best_crtc = crtc; 254 best_coverage = coverage; 255 } 256 } 257 258 /* Fallback to primary crtc for drawable's on slave outputs */ 259 if (best_crtc == NULL && !pScreen->isGPU) { 260 RROutputPtr primary_output = NULL; 261 ScreenPtr slave; 262 263 if (dixPrivateKeyRegistered(rrPrivKey)) 264 primary_output = ms_first_output(scrn->pScreen); 265 if (!primary_output || !primary_output->crtc) 266 return NULL; 267 268 crtc = primary_output->crtc; 269 if (!ms_crtc_on((xf86CrtcPtr) crtc->devPrivate)) 270 return NULL; 271 272 xorg_list_for_each_entry(slave, &pScreen->slave_list, slave_head) { 273 if (!slave->is_output_slave) 274 continue; 275 276 if (ms_covering_randr_crtc(slave, box, FALSE)) { 277 /* The drawable is on a slave output, return primary crtc */ 278 return crtc; 279 } 280 } 281 } 282 283 return best_crtc; 284} 285 286xf86CrtcPtr 287ms_dri2_crtc_covering_drawable(DrawablePtr pDraw) 288{ 289 ScreenPtr pScreen = pDraw->pScreen; 290 BoxRec box; 291 292 box.x1 = pDraw->x; 293 box.y1 = pDraw->y; 294 box.x2 = box.x1 + pDraw->width; 295 box.y2 = box.y1 + pDraw->height; 296 297 return ms_covering_xf86_crtc(pScreen, &box, TRUE); 298} 299 300RRCrtcPtr 301ms_randr_crtc_covering_drawable(DrawablePtr pDraw) 302{ 303 ScreenPtr pScreen = pDraw->pScreen; 304 BoxRec box; 305 306 box.x1 = pDraw->x; 307 box.y1 = pDraw->y; 308 box.x2 = box.x1 + pDraw->width; 309 box.y2 = box.y1 + pDraw->height; 310 311 return ms_covering_randr_crtc(pScreen, &box, TRUE); 312} 313 314static Bool 315ms_get_kernel_ust_msc(xf86CrtcPtr crtc, 316 uint64_t *msc, uint64_t *ust) 317{ 318 ScreenPtr screen = crtc->randr_crtc->pScreen; 319 ScrnInfoPtr scrn = xf86ScreenToScrn(screen); 320 modesettingPtr ms = modesettingPTR(scrn); 321 drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; 322 drmVBlank vbl; 323 int ret; 324 325 if (ms->has_queue_sequence || !ms->tried_queue_sequence) { 326 uint64_t ns; 327 ms->tried_queue_sequence = TRUE; 328 329 ret = drmCrtcGetSequence(ms->fd, drmmode_crtc->mode_crtc->crtc_id, 330 msc, &ns); 331 if (ret != -1 || (errno != ENOTTY && errno != EINVAL)) { 332 ms->has_queue_sequence = TRUE; 333 if (ret == 0) 334 *ust = ns / 1000; 335 return ret == 0; 336 } 337 } 338 /* Get current count */ 339 vbl.request.type = DRM_VBLANK_RELATIVE | drmmode_crtc->vblank_pipe; 340 vbl.request.sequence = 0; 341 vbl.request.signal = 0; 342 ret = drmWaitVBlank(ms->fd, &vbl); 343 if (ret) { 344 *msc = 0; 345 *ust = 0; 346 return FALSE; 347 } else { 348 *msc = vbl.reply.sequence; 349 *ust = (CARD64) vbl.reply.tval_sec * 1000000 + vbl.reply.tval_usec; 350 return TRUE; 351 } 352} 353 354Bool 355ms_queue_vblank(xf86CrtcPtr crtc, ms_queue_flag flags, 356 uint64_t msc, uint64_t *msc_queued, uint32_t seq) 357{ 358 ScreenPtr screen = crtc->randr_crtc->pScreen; 359 ScrnInfoPtr scrn = xf86ScreenToScrn(screen); 360 modesettingPtr ms = modesettingPTR(scrn); 361 drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; 362 drmVBlank vbl; 363 int ret; 364 365 for (;;) { 366 /* Queue an event at the specified sequence */ 367 if (ms->has_queue_sequence || !ms->tried_queue_sequence) { 368 uint32_t drm_flags = 0; 369 uint64_t kernel_queued; 370 371 ms->tried_queue_sequence = TRUE; 372 373 if (flags & MS_QUEUE_RELATIVE) 374 drm_flags |= DRM_CRTC_SEQUENCE_RELATIVE; 375 if (flags & MS_QUEUE_NEXT_ON_MISS) 376 drm_flags |= DRM_CRTC_SEQUENCE_NEXT_ON_MISS; 377 378 ret = drmCrtcQueueSequence(ms->fd, drmmode_crtc->mode_crtc->crtc_id, 379 drm_flags, msc, &kernel_queued, seq); 380 if (ret == 0) { 381 if (msc_queued) 382 *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, kernel_queued, TRUE); 383 ms->has_queue_sequence = TRUE; 384 return TRUE; 385 } 386 387 if (ret != -1 || (errno != ENOTTY && errno != EINVAL)) { 388 ms->has_queue_sequence = TRUE; 389 goto check; 390 } 391 } 392 vbl.request.type = DRM_VBLANK_EVENT | drmmode_crtc->vblank_pipe; 393 if (flags & MS_QUEUE_RELATIVE) 394 vbl.request.type |= DRM_VBLANK_RELATIVE; 395 else 396 vbl.request.type |= DRM_VBLANK_ABSOLUTE; 397 if (flags & MS_QUEUE_NEXT_ON_MISS) 398 vbl.request.type |= DRM_VBLANK_NEXTONMISS; 399 400 vbl.request.sequence = msc; 401 vbl.request.signal = seq; 402 ret = drmWaitVBlank(ms->fd, &vbl); 403 if (ret == 0) { 404 if (msc_queued) 405 *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence, FALSE); 406 return TRUE; 407 } 408 check: 409 if (errno != EBUSY) { 410 ms_drm_abort_seq(scrn, seq); 411 return FALSE; 412 } 413 ms_flush_drm_events(screen); 414 } 415} 416 417/** 418 * Convert a 32-bit or 64-bit kernel MSC sequence number to a 64-bit local 419 * sequence number, adding in the high 32 bits, and dealing with 32-bit 420 * wrapping if needed. 421 */ 422uint64_t 423ms_kernel_msc_to_crtc_msc(xf86CrtcPtr crtc, uint64_t sequence, Bool is64bit) 424{ 425 drmmode_crtc_private_rec *drmmode_crtc = crtc->driver_private; 426 427 if (!is64bit) { 428 /* sequence is provided as a 32 bit value from one of the 32 bit apis, 429 * e.g., drmWaitVBlank(), classic vblank events, or pageflip events. 430 * 431 * Track and handle 32-Bit wrapping, somewhat robust against occasional 432 * out-of-order not always monotonically increasing sequence values. 433 */ 434 if ((int64_t) sequence < ((int64_t) drmmode_crtc->msc_prev - 0x40000000)) 435 drmmode_crtc->msc_high += 0x100000000L; 436 437 if ((int64_t) sequence > ((int64_t) drmmode_crtc->msc_prev + 0x40000000)) 438 drmmode_crtc->msc_high -= 0x100000000L; 439 440 drmmode_crtc->msc_prev = sequence; 441 442 return drmmode_crtc->msc_high + sequence; 443 } 444 445 /* True 64-Bit sequence from Linux 4.15+ 64-Bit drmCrtcGetSequence / 446 * drmCrtcQueueSequence apis and events. Pass through sequence unmodified, 447 * but update the 32-bit tracking variables with reliable ground truth. 448 * 449 * With 64-Bit api in use, the only !is64bit input is from pageflip events, 450 * and any pageflip event is usually preceeded by some is64bit input from 451 * swap scheduling, so this should provide reliable mapping for pageflip 452 * events based on true 64-bit input as baseline as well. 453 */ 454 drmmode_crtc->msc_prev = sequence; 455 drmmode_crtc->msc_high = sequence & 0xffffffff00000000; 456 457 return sequence; 458} 459 460int 461ms_get_crtc_ust_msc(xf86CrtcPtr crtc, uint64_t *ust, uint64_t *msc) 462{ 463 ScreenPtr screen = crtc->randr_crtc->pScreen; 464 ScrnInfoPtr scrn = xf86ScreenToScrn(screen); 465 modesettingPtr ms = modesettingPTR(scrn); 466 uint64_t kernel_msc; 467 468 if (!ms_get_kernel_ust_msc(crtc, &kernel_msc, ust)) 469 return BadMatch; 470 *msc = ms_kernel_msc_to_crtc_msc(crtc, kernel_msc, ms->has_queue_sequence); 471 472 return Success; 473} 474 475/** 476 * Check for pending DRM events and process them. 477 */ 478static void 479ms_drm_socket_handler(int fd, int ready, void *data) 480{ 481 ScreenPtr screen = data; 482 ScrnInfoPtr scrn = xf86ScreenToScrn(screen); 483 modesettingPtr ms = modesettingPTR(scrn); 484 485 if (data == NULL) 486 return; 487 488 drmHandleEvent(fd, &ms->event_context); 489} 490 491/* 492 * Enqueue a potential drm response; when the associated response 493 * appears, we've got data to pass to the handler from here 494 */ 495uint32_t 496ms_drm_queue_alloc(xf86CrtcPtr crtc, 497 void *data, 498 ms_drm_handler_proc handler, 499 ms_drm_abort_proc abort) 500{ 501 ScreenPtr screen = crtc->randr_crtc->pScreen; 502 ScrnInfoPtr scrn = xf86ScreenToScrn(screen); 503 struct ms_drm_queue *q; 504 505 q = calloc(1, sizeof(struct ms_drm_queue)); 506 507 if (!q) 508 return 0; 509 if (!ms_drm_seq) 510 ++ms_drm_seq; 511 q->seq = ms_drm_seq++; 512 q->scrn = scrn; 513 q->crtc = crtc; 514 q->data = data; 515 q->handler = handler; 516 q->abort = abort; 517 518 xorg_list_add(&q->list, &ms_drm_queue); 519 520 return q->seq; 521} 522 523/** 524 * Abort one queued DRM entry, removing it 525 * from the list, calling the abort function and 526 * freeing the memory 527 */ 528static void 529ms_drm_abort_one(struct ms_drm_queue *q) 530{ 531 xorg_list_del(&q->list); 532 q->abort(q->data); 533 free(q); 534} 535 536/** 537 * Abort all queued entries on a specific scrn, used 538 * when resetting the X server 539 */ 540static void 541ms_drm_abort_scrn(ScrnInfoPtr scrn) 542{ 543 struct ms_drm_queue *q, *tmp; 544 545 xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { 546 if (q->scrn == scrn) 547 ms_drm_abort_one(q); 548 } 549} 550 551/** 552 * Abort by drm queue sequence number. 553 */ 554void 555ms_drm_abort_seq(ScrnInfoPtr scrn, uint32_t seq) 556{ 557 struct ms_drm_queue *q, *tmp; 558 559 xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { 560 if (q->seq == seq) { 561 ms_drm_abort_one(q); 562 break; 563 } 564 } 565} 566 567/* 568 * Externally usable abort function that uses a callback to match a single 569 * queued entry to abort 570 */ 571void 572ms_drm_abort(ScrnInfoPtr scrn, Bool (*match)(void *data, void *match_data), 573 void *match_data) 574{ 575 struct ms_drm_queue *q; 576 577 xorg_list_for_each_entry(q, &ms_drm_queue, list) { 578 if (match(q->data, match_data)) { 579 ms_drm_abort_one(q); 580 break; 581 } 582 } 583} 584 585/* 586 * General DRM kernel handler. Looks for the matching sequence number in the 587 * drm event queue and calls the handler for it. 588 */ 589static void 590ms_drm_sequence_handler(int fd, uint64_t frame, uint64_t ns, Bool is64bit, uint64_t user_data) 591{ 592 struct ms_drm_queue *q, *tmp; 593 uint32_t seq = (uint32_t) user_data; 594 595 xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { 596 if (q->seq == seq) { 597 uint64_t msc; 598 599 msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame, is64bit); 600 xorg_list_del(&q->list); 601 q->handler(msc, ns / 1000, q->data); 602 free(q); 603 break; 604 } 605 } 606} 607 608static void 609ms_drm_sequence_handler_64bit(int fd, uint64_t frame, uint64_t ns, uint64_t user_data) 610{ 611 /* frame is true 64 bit wrapped into 64 bit */ 612 ms_drm_sequence_handler(fd, frame, ns, TRUE, user_data); 613} 614 615static void 616ms_drm_handler(int fd, uint32_t frame, uint32_t sec, uint32_t usec, 617 void *user_ptr) 618{ 619 /* frame is 32 bit wrapped into 64 bit */ 620 ms_drm_sequence_handler(fd, frame, ((uint64_t) sec * 1000000 + usec) * 1000, 621 FALSE, (uint32_t) (uintptr_t) user_ptr); 622} 623 624Bool 625ms_vblank_screen_init(ScreenPtr screen) 626{ 627 ScrnInfoPtr scrn = xf86ScreenToScrn(screen); 628 modesettingPtr ms = modesettingPTR(scrn); 629 modesettingEntPtr ms_ent = ms_ent_priv(scrn); 630 xorg_list_init(&ms_drm_queue); 631 632 ms->event_context.version = 4; 633 ms->event_context.vblank_handler = ms_drm_handler; 634 ms->event_context.page_flip_handler = ms_drm_handler; 635 ms->event_context.sequence_handler = ms_drm_sequence_handler_64bit; 636 637 /* We need to re-register the DRM fd for the synchronisation 638 * feedback on every server generation, so perform the 639 * registration within ScreenInit and not PreInit. 640 */ 641 if (ms_ent->fd_wakeup_registered != serverGeneration) { 642 SetNotifyFd(ms->fd, ms_drm_socket_handler, X_NOTIFY_READ, screen); 643 ms_ent->fd_wakeup_registered = serverGeneration; 644 ms_ent->fd_wakeup_ref = 1; 645 } else 646 ms_ent->fd_wakeup_ref++; 647 648 return TRUE; 649} 650 651void 652ms_vblank_close_screen(ScreenPtr screen) 653{ 654 ScrnInfoPtr scrn = xf86ScreenToScrn(screen); 655 modesettingPtr ms = modesettingPTR(scrn); 656 modesettingEntPtr ms_ent = ms_ent_priv(scrn); 657 658 ms_drm_abort_scrn(scrn); 659 660 if (ms_ent->fd_wakeup_registered == serverGeneration && 661 !--ms_ent->fd_wakeup_ref) { 662 RemoveNotifyFd(ms->fd); 663 } 664} 665