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.mem.bus.offset; 382 info->fix.smem_len = nvbo->bo.mem.num_pages << PAGE_SHIFT; 383 384 info->screen_base = nvbo_kmap_obj_iovirtual(nvbo); 385 info->screen_size = nvbo->bo.mem.num_pages << PAGE_SHIFT; 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 vga_switcheroo_client_fb_set(dev->pdev, info); 400 return 0; 401 402out_unlock: 403 if (chan) 404 nouveau_vma_del(&fbcon->vma); 405 nouveau_bo_unmap(nvbo); 406out_unpin: 407 nouveau_bo_unpin(nvbo); 408out_unref: 409 nouveau_bo_ref(NULL, &nvbo); 410out: 411 return ret; 412} 413 414static int 415nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon) 416{ 417 struct drm_framebuffer *fb = fbcon->helper.fb; 418 struct nouveau_bo *nvbo; 419 420 drm_fb_helper_unregister_fbi(&fbcon->helper); 421 drm_fb_helper_fini(&fbcon->helper); 422 423 if (fb && fb->obj[0]) { 424 nvbo = nouveau_gem_object(fb->obj[0]); 425 nouveau_vma_del(&fbcon->vma); 426 nouveau_bo_unmap(nvbo); 427 nouveau_bo_unpin(nvbo); 428 drm_framebuffer_put(fb); 429 } 430 431 return 0; 432} 433 434void nouveau_fbcon_gpu_lockup(struct fb_info *info) 435{ 436 struct nouveau_fbdev *fbcon = info->par; 437 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 438 439 NV_ERROR(drm, "GPU lockup - switching to software fbcon\n"); 440 info->flags |= FBINFO_HWACCEL_DISABLED; 441} 442 443static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = { 444 .fb_probe = nouveau_fbcon_create, 445}; 446 447static void 448nouveau_fbcon_set_suspend_work(struct work_struct *work) 449{ 450 struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work); 451 int state = READ_ONCE(drm->fbcon_new_state); 452 453 if (state == FBINFO_STATE_RUNNING) 454 pm_runtime_get_sync(drm->dev->dev); 455 456 console_lock(); 457 if (state == FBINFO_STATE_RUNNING) 458 nouveau_fbcon_accel_restore(drm->dev); 459 drm_fb_helper_set_suspend(&drm->fbcon->helper, state); 460 if (state != FBINFO_STATE_RUNNING) 461 nouveau_fbcon_accel_save_disable(drm->dev); 462 console_unlock(); 463 464 if (state == FBINFO_STATE_RUNNING) { 465 nouveau_fbcon_hotplug_resume(drm->fbcon); 466 pm_runtime_mark_last_busy(drm->dev->dev); 467 pm_runtime_put_autosuspend(drm->dev->dev); 468 } 469} 470 471void 472nouveau_fbcon_set_suspend(struct drm_device *dev, int state) 473{ 474 struct nouveau_drm *drm = nouveau_drm(dev); 475 476 if (!drm->fbcon) 477 return; 478 479 drm->fbcon_new_state = state; 480 /* Since runtime resume can happen as a result of a sysfs operation, 481 * it's possible we already have the console locked. So handle fbcon 482 * init/deinit from a seperate work thread 483 */ 484 schedule_work(&drm->fbcon_work); 485} 486 487void 488nouveau_fbcon_output_poll_changed(struct drm_device *dev) 489{ 490 struct nouveau_drm *drm = nouveau_drm(dev); 491 struct nouveau_fbdev *fbcon = drm->fbcon; 492 int ret; 493 494 if (!fbcon) 495 return; 496 497 mutex_lock(&fbcon->hotplug_lock); 498 499 ret = pm_runtime_get(dev->dev); 500 if (ret == 1 || ret == -EACCES) { 501 drm_fb_helper_hotplug_event(&fbcon->helper); 502 503 pm_runtime_mark_last_busy(dev->dev); 504 pm_runtime_put_autosuspend(dev->dev); 505 } else if (ret == 0) { 506 /* If the GPU was already in the process of suspending before 507 * this event happened, then we can't block here as we'll 508 * deadlock the runtime pmops since they wait for us to 509 * finish. So, just defer this event for when we runtime 510 * resume again. It will be handled by fbcon_work. 511 */ 512 NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n"); 513 fbcon->hotplug_waiting = true; 514 pm_runtime_put_noidle(drm->dev->dev); 515 } else { 516 DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n", 517 ret); 518 } 519 520 mutex_unlock(&fbcon->hotplug_lock); 521} 522 523void 524nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon) 525{ 526 struct nouveau_drm *drm; 527 528 if (!fbcon) 529 return; 530 drm = nouveau_drm(fbcon->helper.dev); 531 532 mutex_lock(&fbcon->hotplug_lock); 533 if (fbcon->hotplug_waiting) { 534 fbcon->hotplug_waiting = false; 535 536 NV_DEBUG(drm, "Handling deferred fbcon HPD events\n"); 537 drm_fb_helper_hotplug_event(&fbcon->helper); 538 } 539 mutex_unlock(&fbcon->hotplug_lock); 540} 541 542int 543nouveau_fbcon_init(struct drm_device *dev) 544{ 545 struct nouveau_drm *drm = nouveau_drm(dev); 546 struct nouveau_fbdev *fbcon; 547 int preferred_bpp = nouveau_fbcon_bpp; 548 int ret; 549 550 if (!dev->mode_config.num_crtc || 551 (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA) 552 return 0; 553 554 fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL); 555 if (!fbcon) 556 return -ENOMEM; 557 558 drm->fbcon = fbcon; 559 INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work); 560 mutex_init(&fbcon->hotplug_lock); 561 562 drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs); 563 564 ret = drm_fb_helper_init(dev, &fbcon->helper); 565 if (ret) 566 goto free; 567 568 if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) { 569 if (drm->client.device.info.ram_size <= 32 * 1024 * 1024) 570 preferred_bpp = 8; 571 else 572 if (drm->client.device.info.ram_size <= 64 * 1024 * 1024) 573 preferred_bpp = 16; 574 else 575 preferred_bpp = 32; 576 } 577 578 /* disable all the possible outputs/crtcs before entering KMS mode */ 579 if (!drm_drv_uses_atomic_modeset(dev)) 580 drm_helper_disable_unused_functions(dev); 581 582 ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp); 583 if (ret) 584 goto fini; 585 586 if (fbcon->helper.fbdev) 587 fbcon->helper.fbdev->pixmap.buf_align = 4; 588 return 0; 589 590fini: 591 drm_fb_helper_fini(&fbcon->helper); 592free: 593 kfree(fbcon); 594 drm->fbcon = NULL; 595 return ret; 596} 597 598void 599nouveau_fbcon_fini(struct drm_device *dev) 600{ 601 struct nouveau_drm *drm = nouveau_drm(dev); 602 603 if (!drm->fbcon) 604 return; 605 606 nouveau_fbcon_accel_fini(dev); 607 nouveau_fbcon_destroy(dev, drm->fbcon); 608 kfree(drm->fbcon); 609 drm->fbcon = NULL; 610} 611