1/* $NetBSD: nouveau_display.c,v 1.6 2024/04/16 14:34:02 riastradh Exp $ */ 2 3/* 4 * Copyright (C) 2008 Maarten Maathuis. 5 * All Rights Reserved. 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining 8 * a copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice (including the 16 * next paragraph) shall be included in all copies or substantial 17 * portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE 23 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 * 27 */ 28 29#include <sys/cdefs.h> 30__KERNEL_RCSID(0, "$NetBSD: nouveau_display.c,v 1.6 2024/04/16 14:34:02 riastradh Exp $"); 31 32#include <acpi/video.h> 33 34#include <drm/drm_atomic.h> 35#include <drm/drm_atomic_helper.h> 36#include <drm/drm_crtc_helper.h> 37#include <drm/drm_fb_helper.h> 38#include <drm/drm_fourcc.h> 39#include <drm/drm_probe_helper.h> 40#include <drm/drm_vblank.h> 41 42#include "nouveau_fbcon.h" 43#include "nouveau_crtc.h" 44#include "nouveau_gem.h" 45#include "nouveau_connector.h" 46#include "nv50_display.h" 47 48#include <nvif/class.h> 49#include <nvif/cl0046.h> 50#include <nvif/event.h> 51 52#ifdef __NetBSD__ 53/* Used only for runtime power management, not in NetBSD for now. */ 54#undef CONFIG_ACPI 55#endif 56 57static int 58nouveau_display_vblank_handler(struct nvif_notify *notify) 59{ 60 struct nouveau_crtc *nv_crtc = 61 container_of(notify, typeof(*nv_crtc), vblank); 62 drm_crtc_handle_vblank(&nv_crtc->base); 63 return NVIF_NOTIFY_KEEP; 64} 65 66int 67nouveau_display_vblank_enable(struct drm_device *dev, unsigned int pipe) 68{ 69 struct drm_crtc *crtc; 70 struct nouveau_crtc *nv_crtc; 71 72 crtc = drm_crtc_from_index(dev, pipe); 73 if (!crtc) 74 return -EINVAL; 75 76 nv_crtc = nouveau_crtc(crtc); 77 nvif_notify_get(&nv_crtc->vblank); 78 79 return 0; 80} 81 82void 83nouveau_display_vblank_disable(struct drm_device *dev, unsigned int pipe) 84{ 85 struct drm_crtc *crtc; 86 struct nouveau_crtc *nv_crtc; 87 88 crtc = drm_crtc_from_index(dev, pipe); 89 if (!crtc) 90 return; 91 92 nv_crtc = nouveau_crtc(crtc); 93 nvif_notify_put(&nv_crtc->vblank); 94} 95 96static inline int 97calc(int blanks, int blanke, int total, int line) 98{ 99 if (blanke >= blanks) { 100 if (line >= blanks) 101 line -= total; 102 } else { 103 if (line >= blanks) 104 line -= total; 105 line -= blanke + 1; 106 } 107 return line; 108} 109 110static bool 111nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos, 112 ktime_t *stime, ktime_t *etime) 113{ 114 struct { 115 struct nv04_disp_mthd_v0 base; 116 struct nv04_disp_scanoutpos_v0 scan; 117 } args = { 118 .base.method = NV04_DISP_SCANOUTPOS, 119 .base.head = nouveau_crtc(crtc)->index, 120 }; 121 struct nouveau_display *disp = nouveau_display(crtc->dev); 122 struct drm_vblank_crtc *vblank = &crtc->dev->vblank[drm_crtc_index(crtc)]; 123 int retry = 20; 124 bool ret = false; 125 126 do { 127 ret = nvif_mthd(&disp->disp.object, 0, &args, sizeof(args)); 128 if (ret != 0) 129 return false; 130 131 if (args.scan.vline) { 132 ret = true; 133 break; 134 } 135 136 if (retry) ndelay(vblank->linedur_ns); 137 } while (retry--); 138 139 *hpos = args.scan.hline; 140 *vpos = calc(args.scan.vblanks, args.scan.vblanke, 141 args.scan.vtotal, args.scan.vline); 142 if (stime) *stime = ns_to_ktime(args.scan.time[0]); 143 if (etime) *etime = ns_to_ktime(args.scan.time[1]); 144 145 return ret; 146} 147 148bool 149nouveau_display_scanoutpos(struct drm_device *dev, unsigned int pipe, 150 bool in_vblank_irq, int *vpos, int *hpos, 151 ktime_t *stime, ktime_t *etime, 152 const struct drm_display_mode *mode) 153{ 154 struct drm_crtc *crtc; 155 156 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { 157 if (nouveau_crtc(crtc)->index == pipe) { 158 return nouveau_display_scanoutpos_head(crtc, vpos, hpos, 159 stime, etime); 160 } 161 } 162 163 return false; 164} 165 166static void 167nouveau_display_vblank_fini(struct drm_device *dev) 168{ 169 struct drm_crtc *crtc; 170 171 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { 172 struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); 173 nvif_notify_fini(&nv_crtc->vblank); 174 } 175} 176 177static int 178nouveau_display_vblank_init(struct drm_device *dev) 179{ 180 struct nouveau_display *disp = nouveau_display(dev); 181 struct drm_crtc *crtc; 182 int ret; 183 184 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { 185 struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); 186 ret = nvif_notify_init(&disp->disp.object, 187 nouveau_display_vblank_handler, false, 188 NV04_DISP_NTFY_VBLANK, 189 &(struct nvif_notify_head_req_v0) { 190 .head = nv_crtc->index, 191 }, 192 sizeof(struct nvif_notify_head_req_v0), 193 sizeof(struct nvif_notify_head_rep_v0), 194 &nv_crtc->vblank); 195 if (ret) { 196 nouveau_display_vblank_fini(dev); 197 return ret; 198 } 199 } 200 201 ret = drm_vblank_init(dev, dev->mode_config.num_crtc); 202 if (ret) { 203 nouveau_display_vblank_fini(dev); 204 return ret; 205 } 206 207 return 0; 208} 209 210static void 211nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb) 212{ 213 struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb); 214 215 if (fb->nvbo) 216 drm_gem_object_put_unlocked(&fb->nvbo->bo.base); 217 218 drm_framebuffer_cleanup(drm_fb); 219 kfree(fb); 220} 221 222static int 223nouveau_user_framebuffer_create_handle(struct drm_framebuffer *drm_fb, 224 struct drm_file *file_priv, 225 unsigned int *handle) 226{ 227 struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb); 228 229 return drm_gem_handle_create(file_priv, &fb->nvbo->bo.base, handle); 230} 231 232static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = { 233 .destroy = nouveau_user_framebuffer_destroy, 234 .create_handle = nouveau_user_framebuffer_create_handle, 235}; 236 237int 238nouveau_framebuffer_new(struct drm_device *dev, 239 const struct drm_mode_fb_cmd2 *mode_cmd, 240 struct nouveau_bo *nvbo, 241 struct nouveau_framebuffer **pfb) 242{ 243 struct nouveau_drm *drm = nouveau_drm(dev); 244 struct nouveau_framebuffer *fb; 245 int ret; 246 247 /* YUV overlays have special requirements pre-NV50 */ 248 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA && 249 250 (mode_cmd->pixel_format == DRM_FORMAT_YUYV || 251 mode_cmd->pixel_format == DRM_FORMAT_UYVY || 252 mode_cmd->pixel_format == DRM_FORMAT_NV12 || 253 mode_cmd->pixel_format == DRM_FORMAT_NV21) && 254 (mode_cmd->pitches[0] & 0x3f || /* align 64 */ 255 mode_cmd->pitches[0] >= 0x10000 || /* at most 64k pitch */ 256 (mode_cmd->pitches[1] && /* pitches for planes must match */ 257 mode_cmd->pitches[0] != mode_cmd->pitches[1]))) { 258 struct drm_format_name_buf format_name; 259 DRM_DEBUG_KMS("Unsuitable framebuffer: format: %s; pitches: 0x%x\n 0x%x\n", 260 drm_get_format_name(mode_cmd->pixel_format, 261 &format_name), 262 mode_cmd->pitches[0], 263 mode_cmd->pitches[1]); 264 return -EINVAL; 265 } 266 267 if (!(fb = *pfb = kzalloc(sizeof(*fb), GFP_KERNEL))) 268 return -ENOMEM; 269 270 drm_helper_mode_fill_fb_struct(dev, &fb->base, mode_cmd); 271 fb->nvbo = nvbo; 272 273 ret = drm_framebuffer_init(dev, &fb->base, &nouveau_framebuffer_funcs); 274 if (ret) 275 kfree(fb); 276 return ret; 277} 278 279struct drm_framebuffer * 280nouveau_user_framebuffer_create(struct drm_device *dev, 281 struct drm_file *file_priv, 282 const struct drm_mode_fb_cmd2 *mode_cmd) 283{ 284 struct nouveau_framebuffer *fb; 285 struct nouveau_bo *nvbo; 286 struct drm_gem_object *gem; 287 int ret; 288 289 gem = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]); 290 if (!gem) 291 return ERR_PTR(-ENOENT); 292 nvbo = nouveau_gem_object(gem); 293 294 ret = nouveau_framebuffer_new(dev, mode_cmd, nvbo, &fb); 295 if (ret == 0) 296 return &fb->base; 297 298 drm_gem_object_put_unlocked(gem); 299 return ERR_PTR(ret); 300} 301 302static const struct drm_mode_config_funcs nouveau_mode_config_funcs = { 303 .fb_create = nouveau_user_framebuffer_create, 304 .output_poll_changed = nouveau_fbcon_output_poll_changed, 305}; 306 307 308struct nouveau_drm_prop_enum_list { 309 u8 gen_mask; 310 int type; 311 const char *name; 312}; 313 314static struct nouveau_drm_prop_enum_list underscan[] = { 315 { 6, UNDERSCAN_AUTO, "auto" }, 316 { 6, UNDERSCAN_OFF, "off" }, 317 { 6, UNDERSCAN_ON, "on" }, 318 {} 319}; 320 321static struct nouveau_drm_prop_enum_list dither_mode[] = { 322 { 7, DITHERING_MODE_AUTO, "auto" }, 323 { 7, DITHERING_MODE_OFF, "off" }, 324 { 1, DITHERING_MODE_ON, "on" }, 325 { 6, DITHERING_MODE_STATIC2X2, "static 2x2" }, 326 { 6, DITHERING_MODE_DYNAMIC2X2, "dynamic 2x2" }, 327 { 4, DITHERING_MODE_TEMPORAL, "temporal" }, 328 {} 329}; 330 331static struct nouveau_drm_prop_enum_list dither_depth[] = { 332 { 6, DITHERING_DEPTH_AUTO, "auto" }, 333 { 6, DITHERING_DEPTH_6BPC, "6 bpc" }, 334 { 6, DITHERING_DEPTH_8BPC, "8 bpc" }, 335 {} 336}; 337 338#define PROP_ENUM(p,gen,n,list) do { \ 339 struct nouveau_drm_prop_enum_list *l = (list); \ 340 int c = 0; \ 341 while (l->gen_mask) { \ 342 if (l->gen_mask & (1 << (gen))) \ 343 c++; \ 344 l++; \ 345 } \ 346 if (c) { \ 347 p = drm_property_create(dev, DRM_MODE_PROP_ENUM, n, c); \ 348 l = (list); \ 349 while (p && l->gen_mask) { \ 350 if (l->gen_mask & (1 << (gen))) { \ 351 drm_property_add_enum(p, l->type, l->name); \ 352 } \ 353 l++; \ 354 } \ 355 } \ 356} while(0) 357 358static void 359nouveau_display_hpd_work(struct work_struct *work) 360{ 361 struct nouveau_drm *drm = container_of(work, typeof(*drm), hpd_work); 362 363 pm_runtime_get_sync(drm->dev->dev); 364 365 drm_helper_hpd_irq_event(drm->dev); 366 367 pm_runtime_mark_last_busy(drm->dev->dev); 368 pm_runtime_put_sync(drm->dev->dev); 369} 370 371#ifdef CONFIG_ACPI 372 373static int 374nouveau_display_acpi_ntfy(struct notifier_block *nb, unsigned long val, 375 void *data) 376{ 377 struct nouveau_drm *drm = container_of(nb, typeof(*drm), acpi_nb); 378 struct acpi_bus_event *info = data; 379 int ret; 380 381 if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) { 382 if (info->type == ACPI_VIDEO_NOTIFY_PROBE) { 383 ret = pm_runtime_get(drm->dev->dev); 384 if (ret == 1 || ret == -EACCES) { 385 /* If the GPU is already awake, or in a state 386 * where we can't wake it up, it can handle 387 * it's own hotplug events. 388 */ 389 pm_runtime_put_autosuspend(drm->dev->dev); 390 } else if (ret == 0) { 391 /* This may be the only indication we receive 392 * of a connector hotplug on a runtime 393 * suspended GPU, schedule hpd_work to check. 394 */ 395 NV_DEBUG(drm, "ACPI requested connector reprobe\n"); 396 schedule_work(&drm->hpd_work); 397 pm_runtime_put_noidle(drm->dev->dev); 398 } else { 399 NV_WARN(drm, "Dropped ACPI reprobe event due to RPM error: %d\n", 400 ret); 401 } 402 403 /* acpi-video should not generate keypresses for this */ 404 return NOTIFY_BAD; 405 } 406 } 407 408 return NOTIFY_DONE; 409} 410#endif 411 412int 413nouveau_display_init(struct drm_device *dev, bool resume, bool runtime) 414{ 415 struct nouveau_display *disp = nouveau_display(dev); 416 struct drm_connector *connector; 417 struct drm_connector_list_iter conn_iter; 418 int ret; 419 420 /* 421 * Enable hotplug interrupts (done as early as possible, since we need 422 * them for MST) 423 */ 424 drm_connector_list_iter_begin(dev, &conn_iter); 425 nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) { 426 struct nouveau_connector *conn = nouveau_connector(connector); 427 nvif_notify_get(&conn->hpd); 428 } 429 drm_connector_list_iter_end(&conn_iter); 430 431 ret = disp->init(dev, resume, runtime); 432 if (ret) 433 return ret; 434 435 /* enable connector detection and polling for connectors without HPD 436 * support 437 */ 438 drm_kms_helper_poll_enable(dev); 439 440 return ret; 441} 442 443void 444nouveau_display_fini(struct drm_device *dev, bool suspend, bool runtime) 445{ 446 struct nouveau_display *disp = nouveau_display(dev); 447 struct nouveau_drm *drm = nouveau_drm(dev); 448 struct drm_connector *connector; 449 struct drm_connector_list_iter conn_iter; 450 451 if (!suspend) { 452 if (drm_drv_uses_atomic_modeset(dev)) 453 drm_atomic_helper_shutdown(dev); 454 else 455 drm_helper_force_disable_all(dev); 456 } 457 458 /* disable hotplug interrupts */ 459 drm_connector_list_iter_begin(dev, &conn_iter); 460 nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) { 461 struct nouveau_connector *conn = nouveau_connector(connector); 462 nvif_notify_put(&conn->hpd); 463 } 464 drm_connector_list_iter_end(&conn_iter); 465 466 if (!runtime) 467 cancel_work_sync(&drm->hpd_work); 468 469 drm_kms_helper_poll_disable(dev); 470 disp->fini(dev, suspend); 471} 472 473static void 474nouveau_display_create_properties(struct drm_device *dev) 475{ 476 struct nouveau_display *disp = nouveau_display(dev); 477 int gen; 478 479 if (disp->disp.object.oclass < NV50_DISP) 480 gen = 0; 481 else 482 if (disp->disp.object.oclass < GF110_DISP) 483 gen = 1; 484 else 485 gen = 2; 486 487 PROP_ENUM(disp->dithering_mode, gen, "dithering mode", dither_mode); 488 PROP_ENUM(disp->dithering_depth, gen, "dithering depth", dither_depth); 489 PROP_ENUM(disp->underscan_property, gen, "underscan", underscan); 490 491 disp->underscan_hborder_property = 492 drm_property_create_range(dev, 0, "underscan hborder", 0, 128); 493 494 disp->underscan_vborder_property = 495 drm_property_create_range(dev, 0, "underscan vborder", 0, 128); 496 497 if (gen < 1) 498 return; 499 500 /* -90..+90 */ 501 disp->vibrant_hue_property = 502 drm_property_create_range(dev, 0, "vibrant hue", 0, 180); 503 504 /* -100..+100 */ 505 disp->color_vibrance_property = 506 drm_property_create_range(dev, 0, "color vibrance", 0, 200); 507} 508 509int 510nouveau_display_create(struct drm_device *dev) 511{ 512 struct nouveau_drm *drm = nouveau_drm(dev); 513 struct nvkm_device *device = nvxx_device(&drm->client.device); 514 struct nouveau_display *disp; 515 int ret; 516 517 disp = drm->display = kzalloc(sizeof(*disp), GFP_KERNEL); 518 if (!disp) 519 return -ENOMEM; 520 521 drm_mode_config_init(dev); 522 drm_mode_create_scaling_mode_property(dev); 523 drm_mode_create_dvi_i_properties(dev); 524 525 dev->mode_config.funcs = &nouveau_mode_config_funcs; 526 dev->mode_config.fb_base = device->func->resource_addr(device, 1); 527 528 dev->mode_config.min_width = 0; 529 dev->mode_config.min_height = 0; 530 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_CELSIUS) { 531 dev->mode_config.max_width = 2048; 532 dev->mode_config.max_height = 2048; 533 } else 534 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) { 535 dev->mode_config.max_width = 4096; 536 dev->mode_config.max_height = 4096; 537 } else 538 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI) { 539 dev->mode_config.max_width = 8192; 540 dev->mode_config.max_height = 8192; 541 } else { 542 dev->mode_config.max_width = 16384; 543 dev->mode_config.max_height = 16384; 544 } 545 546 dev->mode_config.preferred_depth = 24; 547 dev->mode_config.prefer_shadow = 1; 548 549 if (drm->client.device.info.chipset < 0x11) 550 dev->mode_config.async_page_flip = false; 551 else 552 dev->mode_config.async_page_flip = true; 553 554 drm_kms_helper_poll_init(dev); 555 drm_kms_helper_poll_disable(dev); 556 557 if (nouveau_modeset != 2 && drm->vbios.dcb.entries) { 558 ret = nvif_disp_ctor(&drm->client.device, 0, &disp->disp); 559 if (ret == 0) { 560 nouveau_display_create_properties(dev); 561 if (disp->disp.object.oclass < NV50_DISP) 562 ret = nv04_display_create(dev); 563 else 564 ret = nv50_display_create(dev); 565 } 566 } else { 567 ret = 0; 568 } 569 570 if (ret) 571 goto disp_create_err; 572 573 drm_mode_config_reset(dev); 574 575 if (dev->mode_config.num_crtc) { 576 ret = nouveau_display_vblank_init(dev); 577 if (ret) 578 goto vblank_err; 579 } 580 581 INIT_WORK(&drm->hpd_work, nouveau_display_hpd_work); 582#ifdef CONFIG_ACPI 583 drm->acpi_nb.notifier_call = nouveau_display_acpi_ntfy; 584 register_acpi_notifier(&drm->acpi_nb); 585#endif 586 587 return 0; 588 589vblank_err: 590 disp->dtor(dev); 591disp_create_err: 592 drm_kms_helper_poll_fini(dev); 593 drm_mode_config_cleanup(dev); 594 return ret; 595} 596 597void 598nouveau_display_destroy(struct drm_device *dev) 599{ 600 struct nouveau_display *disp = nouveau_display(dev); 601 602#ifdef CONFIG_ACPI 603 unregister_acpi_notifier(&nouveau_drm(dev)->acpi_nb); 604#endif 605 nouveau_display_vblank_fini(dev); 606 607 drm_kms_helper_poll_fini(dev); 608 drm_mode_config_cleanup(dev); 609 610 if (disp->dtor) 611 disp->dtor(dev); 612 613 nvif_disp_dtor(&disp->disp); 614 615 nouveau_drm(dev)->display = NULL; 616 kfree(disp); 617} 618 619int 620nouveau_display_suspend(struct drm_device *dev, bool runtime) 621{ 622 struct nouveau_display *disp = nouveau_display(dev); 623 624 if (drm_drv_uses_atomic_modeset(dev)) { 625 if (!runtime) { 626 disp->suspend = drm_atomic_helper_suspend(dev); 627 if (IS_ERR(disp->suspend)) { 628 int ret = PTR_ERR(disp->suspend); 629 disp->suspend = NULL; 630 return ret; 631 } 632 } 633 } 634 635 nouveau_display_fini(dev, true, runtime); 636 return 0; 637} 638 639void 640nouveau_display_resume(struct drm_device *dev, bool runtime) 641{ 642 struct nouveau_display *disp = nouveau_display(dev); 643 644 nouveau_display_init(dev, true, runtime); 645 646 if (drm_drv_uses_atomic_modeset(dev)) { 647 if (disp->suspend) { 648 drm_atomic_helper_resume(dev, disp->suspend); 649 disp->suspend = NULL; 650 } 651 return; 652 } 653} 654 655int 656nouveau_display_dumb_create(struct drm_file *file_priv, struct drm_device *dev, 657 struct drm_mode_create_dumb *args) 658{ 659 struct nouveau_cli *cli = nouveau_cli(file_priv); 660 struct nouveau_bo *bo; 661 uint32_t domain; 662 int ret; 663 664 args->pitch = roundup(args->width * (args->bpp / 8), 256); 665 args->size = args->pitch * args->height; 666 args->size = roundup(args->size, PAGE_SIZE); 667 668 /* Use VRAM if there is any ; otherwise fallback to system memory */ 669 if (nouveau_drm(dev)->client.device.info.ram_size != 0) 670 domain = NOUVEAU_GEM_DOMAIN_VRAM; 671 else 672 domain = NOUVEAU_GEM_DOMAIN_GART; 673 674 ret = nouveau_gem_new(cli, args->size, 0, domain, 0, 0, &bo); 675 if (ret) 676 return ret; 677 678 ret = drm_gem_handle_create(file_priv, &bo->bo.base, &args->handle); 679 drm_gem_object_put_unlocked(&bo->bo.base); 680 return ret; 681} 682 683int 684nouveau_display_dumb_map_offset(struct drm_file *file_priv, 685 struct drm_device *dev, 686 uint32_t handle, uint64_t *poffset) 687{ 688 struct drm_gem_object *gem; 689 690 gem = drm_gem_object_lookup(file_priv, handle); 691 if (gem) { 692 struct nouveau_bo *bo = nouveau_gem_object(gem); 693 *poffset = drm_vma_node_offset_addr(&bo->bo.base.vma_node); 694 drm_gem_object_put_unlocked(gem); 695 return 0; 696 } 697 698 return -ENOENT; 699} 700