nouveau_fbcon.c (16323B)
1/* 2 * Copyright © 2007 David Airlie 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 * Authors: 24 * David Airlie 25 */ 26 27#include <linux/module.h> 28#include <linux/kernel.h> 29#include <linux/errno.h> 30#include <linux/string.h> 31#include <linux/mm.h> 32#include <linux/tty.h> 33#include <linux/sysrq.h> 34#include <linux/delay.h> 35#include <linux/init.h> 36#include <linux/screen_info.h> 37#include <linux/vga_switcheroo.h> 38#include <linux/console.h> 39 40#include <drm/drm_crtc.h> 41#include <drm/drm_crtc_helper.h> 42#include <drm/drm_fb_helper.h> 43#include <drm/drm_fourcc.h> 44#include <drm/drm_atomic.h> 45 46#include "nouveau_drv.h" 47#include "nouveau_gem.h" 48#include "nouveau_bo.h" 49#include "nouveau_fbcon.h" 50#include "nouveau_chan.h" 51#include "nouveau_vmm.h" 52 53#include "nouveau_crtc.h" 54 55MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration"); 56int nouveau_nofbaccel = 0; 57module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400); 58 59MODULE_PARM_DESC(fbcon_bpp, "fbcon bits-per-pixel (default: auto)"); 60static int nouveau_fbcon_bpp; 61module_param_named(fbcon_bpp, nouveau_fbcon_bpp, int, 0400); 62 63static void 64nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect) 65{ 66 struct nouveau_fbdev *fbcon = info->par; 67 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 68 struct nvif_device *device = &drm->client.device; 69 int ret; 70 71 if (info->state != FBINFO_STATE_RUNNING) 72 return; 73 74 ret = -ENODEV; 75 if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && 76 mutex_trylock(&drm->client.mutex)) { 77 if (device->info.family < NV_DEVICE_INFO_V0_TESLA) 78 ret = nv04_fbcon_fillrect(info, rect); 79 else 80 if (device->info.family < NV_DEVICE_INFO_V0_FERMI) 81 ret = nv50_fbcon_fillrect(info, rect); 82 else 83 ret = nvc0_fbcon_fillrect(info, rect); 84 mutex_unlock(&drm->client.mutex); 85 } 86 87 if (ret == 0) 88 return; 89 90 if (ret != -ENODEV) 91 nouveau_fbcon_gpu_lockup(info); 92 drm_fb_helper_cfb_fillrect(info, rect); 93} 94 95static void 96nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image) 97{ 98 struct nouveau_fbdev *fbcon = info->par; 99 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 100 struct nvif_device *device = &drm->client.device; 101 int ret; 102 103 if (info->state != FBINFO_STATE_RUNNING) 104 return; 105 106 ret = -ENODEV; 107 if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && 108 mutex_trylock(&drm->client.mutex)) { 109 if (device->info.family < NV_DEVICE_INFO_V0_TESLA) 110 ret = nv04_fbcon_copyarea(info, image); 111 else 112 if (device->info.family < NV_DEVICE_INFO_V0_FERMI) 113 ret = nv50_fbcon_copyarea(info, image); 114 else 115 ret = nvc0_fbcon_copyarea(info, image); 116 mutex_unlock(&drm->client.mutex); 117 } 118 119 if (ret == 0) 120 return; 121 122 if (ret != -ENODEV) 123 nouveau_fbcon_gpu_lockup(info); 124 drm_fb_helper_cfb_copyarea(info, image); 125} 126 127static void 128nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image) 129{ 130 struct nouveau_fbdev *fbcon = info->par; 131 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 132 struct nvif_device *device = &drm->client.device; 133 int ret; 134 135 if (info->state != FBINFO_STATE_RUNNING) 136 return; 137 138 ret = -ENODEV; 139 if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && 140 mutex_trylock(&drm->client.mutex)) { 141 if (device->info.family < NV_DEVICE_INFO_V0_TESLA) 142 ret = nv04_fbcon_imageblit(info, image); 143 else 144 if (device->info.family < NV_DEVICE_INFO_V0_FERMI) 145 ret = nv50_fbcon_imageblit(info, image); 146 else 147 ret = nvc0_fbcon_imageblit(info, image); 148 mutex_unlock(&drm->client.mutex); 149 } 150 151 if (ret == 0) 152 return; 153 154 if (ret != -ENODEV) 155 nouveau_fbcon_gpu_lockup(info); 156 drm_fb_helper_cfb_imageblit(info, image); 157} 158 159static int 160nouveau_fbcon_sync(struct fb_info *info) 161{ 162 struct nouveau_fbdev *fbcon = info->par; 163 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 164 struct nouveau_channel *chan = drm->channel; 165 int ret; 166 167 if (!chan || !chan->accel_done || in_interrupt() || 168 info->state != FBINFO_STATE_RUNNING || 169 info->flags & FBINFO_HWACCEL_DISABLED) 170 return 0; 171 172 if (!mutex_trylock(&drm->client.mutex)) 173 return 0; 174 175 ret = nouveau_channel_idle(chan); 176 mutex_unlock(&drm->client.mutex); 177 if (ret) { 178 nouveau_fbcon_gpu_lockup(info); 179 return 0; 180 } 181 182 chan->accel_done = false; 183 return 0; 184} 185 186static int 187nouveau_fbcon_open(struct fb_info *info, int user) 188{ 189 struct nouveau_fbdev *fbcon = info->par; 190 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 191 int ret = pm_runtime_get_sync(drm->dev->dev); 192 if (ret < 0 && ret != -EACCES) { 193 pm_runtime_put(drm->dev->dev); 194 return ret; 195 } 196 return 0; 197} 198 199static int 200nouveau_fbcon_release(struct fb_info *info, int user) 201{ 202 struct nouveau_fbdev *fbcon = info->par; 203 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 204 pm_runtime_put(drm->dev->dev); 205 return 0; 206} 207 208static const struct fb_ops nouveau_fbcon_ops = { 209 .owner = THIS_MODULE, 210 DRM_FB_HELPER_DEFAULT_OPS, 211 .fb_open = nouveau_fbcon_open, 212 .fb_release = nouveau_fbcon_release, 213 .fb_fillrect = nouveau_fbcon_fillrect, 214 .fb_copyarea = nouveau_fbcon_copyarea, 215 .fb_imageblit = nouveau_fbcon_imageblit, 216 .fb_sync = nouveau_fbcon_sync, 217}; 218 219static const struct fb_ops nouveau_fbcon_sw_ops = { 220 .owner = THIS_MODULE, 221 DRM_FB_HELPER_DEFAULT_OPS, 222 .fb_open = nouveau_fbcon_open, 223 .fb_release = nouveau_fbcon_release, 224 .fb_fillrect = drm_fb_helper_cfb_fillrect, 225 .fb_copyarea = drm_fb_helper_cfb_copyarea, 226 .fb_imageblit = drm_fb_helper_cfb_imageblit, 227}; 228 229void 230nouveau_fbcon_accel_save_disable(struct drm_device *dev) 231{ 232 struct nouveau_drm *drm = nouveau_drm(dev); 233 if (drm->fbcon && drm->fbcon->helper.fbdev) { 234 drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags; 235 drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED; 236 } 237} 238 239void 240nouveau_fbcon_accel_restore(struct drm_device *dev) 241{ 242 struct nouveau_drm *drm = nouveau_drm(dev); 243 if (drm->fbcon && drm->fbcon->helper.fbdev) { 244 drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags; 245 } 246} 247 248static void 249nouveau_fbcon_accel_fini(struct drm_device *dev) 250{ 251 struct nouveau_drm *drm = nouveau_drm(dev); 252 struct nouveau_fbdev *fbcon = drm->fbcon; 253 if (fbcon && drm->channel) { 254 console_lock(); 255 if (fbcon->helper.fbdev) 256 fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED; 257 console_unlock(); 258 nouveau_channel_idle(drm->channel); 259 nvif_object_dtor(&fbcon->twod); 260 nvif_object_dtor(&fbcon->blit); 261 nvif_object_dtor(&fbcon->gdi); 262 nvif_object_dtor(&fbcon->patt); 263 nvif_object_dtor(&fbcon->rop); 264 nvif_object_dtor(&fbcon->clip); 265 nvif_object_dtor(&fbcon->surf2d); 266 } 267} 268 269static void 270nouveau_fbcon_accel_init(struct drm_device *dev) 271{ 272 struct nouveau_drm *drm = nouveau_drm(dev); 273 struct nouveau_fbdev *fbcon = drm->fbcon; 274 struct fb_info *info = fbcon->helper.fbdev; 275 int ret; 276 277 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) 278 ret = nv04_fbcon_accel_init(info); 279 else 280 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI) 281 ret = nv50_fbcon_accel_init(info); 282 else 283 ret = nvc0_fbcon_accel_init(info); 284 285 if (ret == 0) 286 info->fbops = &nouveau_fbcon_ops; 287} 288 289static void 290nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon) 291{ 292 struct fb_info *info = fbcon->helper.fbdev; 293 struct fb_fillrect rect; 294 295 /* Clear the entire fbcon. The drm will program every connector 296 * with it's preferred mode. If the sizes differ, one display will 297 * quite likely have garbage around the console. 298 */ 299 rect.dx = rect.dy = 0; 300 rect.width = info->var.xres_virtual; 301 rect.height = info->var.yres_virtual; 302 rect.color = 0; 303 rect.rop = ROP_COPY; 304 info->fbops->fb_fillrect(info, &rect); 305} 306 307static int 308nouveau_fbcon_create(struct drm_fb_helper *helper, 309 struct drm_fb_helper_surface_size *sizes) 310{ 311 struct nouveau_fbdev *fbcon = 312 container_of(helper, struct nouveau_fbdev, helper); 313 struct drm_device *dev = fbcon->helper.dev; 314 struct nouveau_drm *drm = nouveau_drm(dev); 315 struct nvif_device *device = &drm->client.device; 316 struct fb_info *info; 317 struct drm_framebuffer *fb; 318 struct nouveau_channel *chan; 319 struct nouveau_bo *nvbo; 320 struct drm_mode_fb_cmd2 mode_cmd = {}; 321 int ret; 322 323 mode_cmd.width = sizes->surface_width; 324 mode_cmd.height = sizes->surface_height; 325 326 mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3); 327 mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256); 328 329 mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, 330 sizes->surface_depth); 331 332 ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] * 333 mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM, 334 0, 0x0000, &nvbo); 335 if (ret) { 336 NV_ERROR(drm, "failed to allocate framebuffer\n"); 337 goto out; 338 } 339 340 ret = nouveau_framebuffer_new(dev, &mode_cmd, &nvbo->bo.base, &fb); 341 if (ret) 342 goto out_unref; 343 344 ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, false); 345 if (ret) { 346 NV_ERROR(drm, "failed to pin fb: %d\n", ret); 347 goto out_unref; 348 } 349 350 ret = nouveau_bo_map(nvbo); 351 if (ret) { 352 NV_ERROR(drm, "failed to map fb: %d\n", ret); 353 goto out_unpin; 354 } 355 356 chan = nouveau_nofbaccel ? NULL : drm->channel; 357 if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) { 358 ret = nouveau_vma_new(nvbo, chan->vmm, &fbcon->vma); 359 if (ret) { 360 NV_ERROR(drm, "failed to map fb into chan: %d\n", ret); 361 chan = NULL; 362 } 363 } 364 365 info = drm_fb_helper_alloc_fbi(helper); 366 if (IS_ERR(info)) { 367 ret = PTR_ERR(info); 368 goto out_unlock; 369 } 370 371 /* setup helper */ 372 fbcon->helper.fb = fb; 373 374 if (!chan) 375 info->flags = FBINFO_HWACCEL_DISABLED; 376 else 377 info->flags = FBINFO_HWACCEL_COPYAREA | 378 FBINFO_HWACCEL_FILLRECT | 379 FBINFO_HWACCEL_IMAGEBLIT; 380 info->fbops = &nouveau_fbcon_sw_ops; 381 info->fix.smem_start = nvbo->bo.resource->bus.offset; 382 info->fix.smem_len = nvbo->bo.base.size; 383 384 info->screen_base = nvbo_kmap_obj_iovirtual(nvbo); 385 info->screen_size = nvbo->bo.base.size; 386 387 drm_fb_helper_fill_info(info, &fbcon->helper, sizes); 388 389 /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ 390 391 if (chan) 392 nouveau_fbcon_accel_init(dev); 393 nouveau_fbcon_zfill(dev, fbcon); 394 395 /* To allow resizeing without swapping buffers */ 396 NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n", 397 fb->width, fb->height, nvbo->offset, nvbo); 398 399 if (dev_is_pci(dev->dev)) 400 vga_switcheroo_client_fb_set(to_pci_dev(dev->dev), info); 401 402 return 0; 403 404out_unlock: 405 if (chan) 406 nouveau_vma_del(&fbcon->vma); 407 nouveau_bo_unmap(nvbo); 408out_unpin: 409 nouveau_bo_unpin(nvbo); 410out_unref: 411 nouveau_bo_ref(NULL, &nvbo); 412out: 413 return ret; 414} 415 416static int 417nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon) 418{ 419 struct drm_framebuffer *fb = fbcon->helper.fb; 420 struct nouveau_bo *nvbo; 421 422 drm_fb_helper_unregister_fbi(&fbcon->helper); 423 drm_fb_helper_fini(&fbcon->helper); 424 425 if (fb && fb->obj[0]) { 426 nvbo = nouveau_gem_object(fb->obj[0]); 427 nouveau_vma_del(&fbcon->vma); 428 nouveau_bo_unmap(nvbo); 429 nouveau_bo_unpin(nvbo); 430 drm_framebuffer_put(fb); 431 } 432 433 return 0; 434} 435 436void nouveau_fbcon_gpu_lockup(struct fb_info *info) 437{ 438 struct nouveau_fbdev *fbcon = info->par; 439 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 440 441 NV_ERROR(drm, "GPU lockup - switching to software fbcon\n"); 442 info->flags |= FBINFO_HWACCEL_DISABLED; 443} 444 445static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = { 446 .fb_probe = nouveau_fbcon_create, 447}; 448 449static void 450nouveau_fbcon_set_suspend_work(struct work_struct *work) 451{ 452 struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work); 453 int state = READ_ONCE(drm->fbcon_new_state); 454 455 if (state == FBINFO_STATE_RUNNING) 456 pm_runtime_get_sync(drm->dev->dev); 457 458 console_lock(); 459 if (state == FBINFO_STATE_RUNNING) 460 nouveau_fbcon_accel_restore(drm->dev); 461 drm_fb_helper_set_suspend(&drm->fbcon->helper, state); 462 if (state != FBINFO_STATE_RUNNING) 463 nouveau_fbcon_accel_save_disable(drm->dev); 464 console_unlock(); 465 466 if (state == FBINFO_STATE_RUNNING) { 467 nouveau_fbcon_hotplug_resume(drm->fbcon); 468 pm_runtime_mark_last_busy(drm->dev->dev); 469 pm_runtime_put_sync(drm->dev->dev); 470 } 471} 472 473void 474nouveau_fbcon_set_suspend(struct drm_device *dev, int state) 475{ 476 struct nouveau_drm *drm = nouveau_drm(dev); 477 478 if (!drm->fbcon) 479 return; 480 481 drm->fbcon_new_state = state; 482 /* Since runtime resume can happen as a result of a sysfs operation, 483 * it's possible we already have the console locked. So handle fbcon 484 * init/deinit from a seperate work thread 485 */ 486 schedule_work(&drm->fbcon_work); 487} 488 489void 490nouveau_fbcon_output_poll_changed(struct drm_device *dev) 491{ 492 struct nouveau_drm *drm = nouveau_drm(dev); 493 struct nouveau_fbdev *fbcon = drm->fbcon; 494 int ret; 495 496 if (!fbcon) 497 return; 498 499 mutex_lock(&fbcon->hotplug_lock); 500 501 ret = pm_runtime_get(dev->dev); 502 if (ret == 1 || ret == -EACCES) { 503 drm_fb_helper_hotplug_event(&fbcon->helper); 504 505 pm_runtime_mark_last_busy(dev->dev); 506 pm_runtime_put_autosuspend(dev->dev); 507 } else if (ret == 0) { 508 /* If the GPU was already in the process of suspending before 509 * this event happened, then we can't block here as we'll 510 * deadlock the runtime pmops since they wait for us to 511 * finish. So, just defer this event for when we runtime 512 * resume again. It will be handled by fbcon_work. 513 */ 514 NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n"); 515 fbcon->hotplug_waiting = true; 516 pm_runtime_put_noidle(drm->dev->dev); 517 } else { 518 DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n", 519 ret); 520 } 521 522 mutex_unlock(&fbcon->hotplug_lock); 523} 524 525void 526nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon) 527{ 528 struct nouveau_drm *drm; 529 530 if (!fbcon) 531 return; 532 drm = nouveau_drm(fbcon->helper.dev); 533 534 mutex_lock(&fbcon->hotplug_lock); 535 if (fbcon->hotplug_waiting) { 536 fbcon->hotplug_waiting = false; 537 538 NV_DEBUG(drm, "Handling deferred fbcon HPD events\n"); 539 drm_fb_helper_hotplug_event(&fbcon->helper); 540 } 541 mutex_unlock(&fbcon->hotplug_lock); 542} 543 544int 545nouveau_fbcon_init(struct drm_device *dev) 546{ 547 struct nouveau_drm *drm = nouveau_drm(dev); 548 struct nouveau_fbdev *fbcon; 549 int preferred_bpp = nouveau_fbcon_bpp; 550 int ret; 551 552 if (!dev->mode_config.num_crtc || 553 (to_pci_dev(dev->dev)->class >> 8) != PCI_CLASS_DISPLAY_VGA) 554 return 0; 555 556 fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL); 557 if (!fbcon) 558 return -ENOMEM; 559 560 drm->fbcon = fbcon; 561 INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work); 562 mutex_init(&fbcon->hotplug_lock); 563 564 drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs); 565 566 ret = drm_fb_helper_init(dev, &fbcon->helper); 567 if (ret) 568 goto free; 569 570 if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) { 571 if (drm->client.device.info.ram_size <= 32 * 1024 * 1024) 572 preferred_bpp = 8; 573 else 574 if (drm->client.device.info.ram_size <= 64 * 1024 * 1024) 575 preferred_bpp = 16; 576 else 577 preferred_bpp = 32; 578 } 579 580 /* disable all the possible outputs/crtcs before entering KMS mode */ 581 if (!drm_drv_uses_atomic_modeset(dev)) 582 drm_helper_disable_unused_functions(dev); 583 584 ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp); 585 if (ret) 586 goto fini; 587 588 if (fbcon->helper.fbdev) 589 fbcon->helper.fbdev->pixmap.buf_align = 4; 590 return 0; 591 592fini: 593 drm_fb_helper_fini(&fbcon->helper); 594free: 595 kfree(fbcon); 596 drm->fbcon = NULL; 597 return ret; 598} 599 600void 601nouveau_fbcon_fini(struct drm_device *dev) 602{ 603 struct nouveau_drm *drm = nouveau_drm(dev); 604 605 if (!drm->fbcon) 606 return; 607 608 nouveau_fbcon_accel_fini(dev); 609 nouveau_fbcon_destroy(dev, drm->fbcon); 610 kfree(drm->fbcon); 611 drm->fbcon = NULL; 612}