0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027 #include <linux/module.h>
0028 #include <linux/kernel.h>
0029 #include <linux/errno.h>
0030 #include <linux/string.h>
0031 #include <linux/mm.h>
0032 #include <linux/tty.h>
0033 #include <linux/sysrq.h>
0034 #include <linux/delay.h>
0035 #include <linux/init.h>
0036 #include <linux/screen_info.h>
0037 #include <linux/vga_switcheroo.h>
0038 #include <linux/console.h>
0039
0040 #include <drm/drm_crtc.h>
0041 #include <drm/drm_crtc_helper.h>
0042 #include <drm/drm_probe_helper.h>
0043 #include <drm/drm_fb_helper.h>
0044 #include <drm/drm_fourcc.h>
0045 #include <drm/drm_atomic.h>
0046
0047 #include "nouveau_drv.h"
0048 #include "nouveau_gem.h"
0049 #include "nouveau_bo.h"
0050 #include "nouveau_fbcon.h"
0051 #include "nouveau_chan.h"
0052 #include "nouveau_vmm.h"
0053
0054 #include "nouveau_crtc.h"
0055
0056 MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
0057 int nouveau_nofbaccel = 0;
0058 module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
0059
0060 MODULE_PARM_DESC(fbcon_bpp, "fbcon bits-per-pixel (default: auto)");
0061 static int nouveau_fbcon_bpp;
0062 module_param_named(fbcon_bpp, nouveau_fbcon_bpp, int, 0400);
0063
0064 static void
0065 nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
0066 {
0067 struct nouveau_fbdev *fbcon = info->par;
0068 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
0069 struct nvif_device *device = &drm->client.device;
0070 int ret;
0071
0072 if (info->state != FBINFO_STATE_RUNNING)
0073 return;
0074
0075 ret = -ENODEV;
0076 if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
0077 mutex_trylock(&drm->client.mutex)) {
0078 if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
0079 ret = nv04_fbcon_fillrect(info, rect);
0080 else
0081 if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
0082 ret = nv50_fbcon_fillrect(info, rect);
0083 else
0084 ret = nvc0_fbcon_fillrect(info, rect);
0085 mutex_unlock(&drm->client.mutex);
0086 }
0087
0088 if (ret == 0)
0089 return;
0090
0091 if (ret != -ENODEV)
0092 nouveau_fbcon_gpu_lockup(info);
0093 drm_fb_helper_cfb_fillrect(info, rect);
0094 }
0095
0096 static void
0097 nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
0098 {
0099 struct nouveau_fbdev *fbcon = info->par;
0100 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
0101 struct nvif_device *device = &drm->client.device;
0102 int ret;
0103
0104 if (info->state != FBINFO_STATE_RUNNING)
0105 return;
0106
0107 ret = -ENODEV;
0108 if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
0109 mutex_trylock(&drm->client.mutex)) {
0110 if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
0111 ret = nv04_fbcon_copyarea(info, image);
0112 else
0113 if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
0114 ret = nv50_fbcon_copyarea(info, image);
0115 else
0116 ret = nvc0_fbcon_copyarea(info, image);
0117 mutex_unlock(&drm->client.mutex);
0118 }
0119
0120 if (ret == 0)
0121 return;
0122
0123 if (ret != -ENODEV)
0124 nouveau_fbcon_gpu_lockup(info);
0125 drm_fb_helper_cfb_copyarea(info, image);
0126 }
0127
0128 static void
0129 nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
0130 {
0131 struct nouveau_fbdev *fbcon = info->par;
0132 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
0133 struct nvif_device *device = &drm->client.device;
0134 int ret;
0135
0136 if (info->state != FBINFO_STATE_RUNNING)
0137 return;
0138
0139 ret = -ENODEV;
0140 if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
0141 mutex_trylock(&drm->client.mutex)) {
0142 if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
0143 ret = nv04_fbcon_imageblit(info, image);
0144 else
0145 if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
0146 ret = nv50_fbcon_imageblit(info, image);
0147 else
0148 ret = nvc0_fbcon_imageblit(info, image);
0149 mutex_unlock(&drm->client.mutex);
0150 }
0151
0152 if (ret == 0)
0153 return;
0154
0155 if (ret != -ENODEV)
0156 nouveau_fbcon_gpu_lockup(info);
0157 drm_fb_helper_cfb_imageblit(info, image);
0158 }
0159
0160 static int
0161 nouveau_fbcon_sync(struct fb_info *info)
0162 {
0163 struct nouveau_fbdev *fbcon = info->par;
0164 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
0165 struct nouveau_channel *chan = drm->channel;
0166 int ret;
0167
0168 if (!chan || !chan->accel_done || in_interrupt() ||
0169 info->state != FBINFO_STATE_RUNNING ||
0170 info->flags & FBINFO_HWACCEL_DISABLED)
0171 return 0;
0172
0173 if (!mutex_trylock(&drm->client.mutex))
0174 return 0;
0175
0176 ret = nouveau_channel_idle(chan);
0177 mutex_unlock(&drm->client.mutex);
0178 if (ret) {
0179 nouveau_fbcon_gpu_lockup(info);
0180 return 0;
0181 }
0182
0183 chan->accel_done = false;
0184 return 0;
0185 }
0186
0187 static int
0188 nouveau_fbcon_open(struct fb_info *info, int user)
0189 {
0190 struct nouveau_fbdev *fbcon = info->par;
0191 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
0192 int ret = pm_runtime_get_sync(drm->dev->dev);
0193 if (ret < 0 && ret != -EACCES) {
0194 pm_runtime_put(drm->dev->dev);
0195 return ret;
0196 }
0197 return 0;
0198 }
0199
0200 static int
0201 nouveau_fbcon_release(struct fb_info *info, int user)
0202 {
0203 struct nouveau_fbdev *fbcon = info->par;
0204 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
0205 pm_runtime_put(drm->dev->dev);
0206 return 0;
0207 }
0208
0209 static const struct fb_ops nouveau_fbcon_ops = {
0210 .owner = THIS_MODULE,
0211 DRM_FB_HELPER_DEFAULT_OPS,
0212 .fb_open = nouveau_fbcon_open,
0213 .fb_release = nouveau_fbcon_release,
0214 .fb_fillrect = nouveau_fbcon_fillrect,
0215 .fb_copyarea = nouveau_fbcon_copyarea,
0216 .fb_imageblit = nouveau_fbcon_imageblit,
0217 .fb_sync = nouveau_fbcon_sync,
0218 };
0219
0220 static const struct fb_ops nouveau_fbcon_sw_ops = {
0221 .owner = THIS_MODULE,
0222 DRM_FB_HELPER_DEFAULT_OPS,
0223 .fb_open = nouveau_fbcon_open,
0224 .fb_release = nouveau_fbcon_release,
0225 .fb_fillrect = drm_fb_helper_cfb_fillrect,
0226 .fb_copyarea = drm_fb_helper_cfb_copyarea,
0227 .fb_imageblit = drm_fb_helper_cfb_imageblit,
0228 };
0229
0230 void
0231 nouveau_fbcon_accel_save_disable(struct drm_device *dev)
0232 {
0233 struct nouveau_drm *drm = nouveau_drm(dev);
0234 if (drm->fbcon && drm->fbcon->helper.fbdev) {
0235 drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
0236 drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
0237 }
0238 }
0239
0240 void
0241 nouveau_fbcon_accel_restore(struct drm_device *dev)
0242 {
0243 struct nouveau_drm *drm = nouveau_drm(dev);
0244 if (drm->fbcon && drm->fbcon->helper.fbdev) {
0245 drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
0246 }
0247 }
0248
0249 static void
0250 nouveau_fbcon_accel_fini(struct drm_device *dev)
0251 {
0252 struct nouveau_drm *drm = nouveau_drm(dev);
0253 struct nouveau_fbdev *fbcon = drm->fbcon;
0254 if (fbcon && drm->channel) {
0255 console_lock();
0256 if (fbcon->helper.fbdev)
0257 fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
0258 console_unlock();
0259 nouveau_channel_idle(drm->channel);
0260 nvif_object_dtor(&fbcon->twod);
0261 nvif_object_dtor(&fbcon->blit);
0262 nvif_object_dtor(&fbcon->gdi);
0263 nvif_object_dtor(&fbcon->patt);
0264 nvif_object_dtor(&fbcon->rop);
0265 nvif_object_dtor(&fbcon->clip);
0266 nvif_object_dtor(&fbcon->surf2d);
0267 }
0268 }
0269
0270 static void
0271 nouveau_fbcon_accel_init(struct drm_device *dev)
0272 {
0273 struct nouveau_drm *drm = nouveau_drm(dev);
0274 struct nouveau_fbdev *fbcon = drm->fbcon;
0275 struct fb_info *info = fbcon->helper.fbdev;
0276 int ret;
0277
0278 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA)
0279 ret = nv04_fbcon_accel_init(info);
0280 else
0281 if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
0282 ret = nv50_fbcon_accel_init(info);
0283 else
0284 ret = nvc0_fbcon_accel_init(info);
0285
0286 if (ret == 0)
0287 info->fbops = &nouveau_fbcon_ops;
0288 }
0289
0290 static void
0291 nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
0292 {
0293 struct fb_info *info = fbcon->helper.fbdev;
0294 struct fb_fillrect rect;
0295
0296
0297
0298
0299
0300 rect.dx = rect.dy = 0;
0301 rect.width = info->var.xres_virtual;
0302 rect.height = info->var.yres_virtual;
0303 rect.color = 0;
0304 rect.rop = ROP_COPY;
0305 info->fbops->fb_fillrect(info, &rect);
0306 }
0307
0308 static int
0309 nouveau_fbcon_create(struct drm_fb_helper *helper,
0310 struct drm_fb_helper_surface_size *sizes)
0311 {
0312 struct nouveau_fbdev *fbcon =
0313 container_of(helper, struct nouveau_fbdev, helper);
0314 struct drm_device *dev = fbcon->helper.dev;
0315 struct nouveau_drm *drm = nouveau_drm(dev);
0316 struct nvif_device *device = &drm->client.device;
0317 struct fb_info *info;
0318 struct drm_framebuffer *fb;
0319 struct nouveau_channel *chan;
0320 struct nouveau_bo *nvbo;
0321 struct drm_mode_fb_cmd2 mode_cmd = {};
0322 int ret;
0323
0324 mode_cmd.width = sizes->surface_width;
0325 mode_cmd.height = sizes->surface_height;
0326
0327 mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
0328 mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
0329
0330 mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
0331 sizes->surface_depth);
0332
0333 ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] *
0334 mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM,
0335 0, 0x0000, &nvbo);
0336 if (ret) {
0337 NV_ERROR(drm, "failed to allocate framebuffer\n");
0338 goto out;
0339 }
0340
0341 ret = nouveau_framebuffer_new(dev, &mode_cmd, &nvbo->bo.base, &fb);
0342 if (ret)
0343 goto out_unref;
0344
0345 ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, false);
0346 if (ret) {
0347 NV_ERROR(drm, "failed to pin fb: %d\n", ret);
0348 goto out_unref;
0349 }
0350
0351 ret = nouveau_bo_map(nvbo);
0352 if (ret) {
0353 NV_ERROR(drm, "failed to map fb: %d\n", ret);
0354 goto out_unpin;
0355 }
0356
0357 chan = nouveau_nofbaccel ? NULL : drm->channel;
0358 if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
0359 ret = nouveau_vma_new(nvbo, chan->vmm, &fbcon->vma);
0360 if (ret) {
0361 NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
0362 chan = NULL;
0363 }
0364 }
0365
0366 info = drm_fb_helper_alloc_fbi(helper);
0367 if (IS_ERR(info)) {
0368 ret = PTR_ERR(info);
0369 goto out_unlock;
0370 }
0371
0372
0373 fbcon->helper.fb = fb;
0374
0375 if (!chan)
0376 info->flags = FBINFO_HWACCEL_DISABLED;
0377 else
0378 info->flags = FBINFO_HWACCEL_COPYAREA |
0379 FBINFO_HWACCEL_FILLRECT |
0380 FBINFO_HWACCEL_IMAGEBLIT;
0381 info->fbops = &nouveau_fbcon_sw_ops;
0382 info->fix.smem_start = nvbo->bo.resource->bus.offset;
0383 info->fix.smem_len = nvbo->bo.base.size;
0384
0385 info->screen_base = nvbo_kmap_obj_iovirtual(nvbo);
0386 info->screen_size = nvbo->bo.base.size;
0387
0388 drm_fb_helper_fill_info(info, &fbcon->helper, sizes);
0389
0390
0391
0392 if (chan)
0393 nouveau_fbcon_accel_init(dev);
0394 nouveau_fbcon_zfill(dev, fbcon);
0395
0396
0397 NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
0398 fb->width, fb->height, nvbo->offset, nvbo);
0399
0400 if (dev_is_pci(dev->dev))
0401 vga_switcheroo_client_fb_set(to_pci_dev(dev->dev), info);
0402
0403 return 0;
0404
0405 out_unlock:
0406 if (chan)
0407 nouveau_vma_del(&fbcon->vma);
0408 nouveau_bo_unmap(nvbo);
0409 out_unpin:
0410 nouveau_bo_unpin(nvbo);
0411 out_unref:
0412 nouveau_bo_ref(NULL, &nvbo);
0413 out:
0414 return ret;
0415 }
0416
0417 static int
0418 nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
0419 {
0420 struct drm_framebuffer *fb = fbcon->helper.fb;
0421 struct nouveau_bo *nvbo;
0422
0423 drm_fb_helper_unregister_fbi(&fbcon->helper);
0424 drm_fb_helper_fini(&fbcon->helper);
0425
0426 if (fb && fb->obj[0]) {
0427 nvbo = nouveau_gem_object(fb->obj[0]);
0428 nouveau_vma_del(&fbcon->vma);
0429 nouveau_bo_unmap(nvbo);
0430 nouveau_bo_unpin(nvbo);
0431 drm_framebuffer_put(fb);
0432 }
0433
0434 return 0;
0435 }
0436
0437 void nouveau_fbcon_gpu_lockup(struct fb_info *info)
0438 {
0439 struct nouveau_fbdev *fbcon = info->par;
0440 struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
0441
0442 NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
0443 info->flags |= FBINFO_HWACCEL_DISABLED;
0444 }
0445
0446 static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
0447 .fb_probe = nouveau_fbcon_create,
0448 };
0449
0450 static void
0451 nouveau_fbcon_set_suspend_work(struct work_struct *work)
0452 {
0453 struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work);
0454 int state = READ_ONCE(drm->fbcon_new_state);
0455
0456 if (state == FBINFO_STATE_RUNNING)
0457 pm_runtime_get_sync(drm->dev->dev);
0458
0459 console_lock();
0460 if (state == FBINFO_STATE_RUNNING)
0461 nouveau_fbcon_accel_restore(drm->dev);
0462 drm_fb_helper_set_suspend(&drm->fbcon->helper, state);
0463 if (state != FBINFO_STATE_RUNNING)
0464 nouveau_fbcon_accel_save_disable(drm->dev);
0465 console_unlock();
0466
0467 if (state == FBINFO_STATE_RUNNING) {
0468 nouveau_fbcon_hotplug_resume(drm->fbcon);
0469 pm_runtime_mark_last_busy(drm->dev->dev);
0470 pm_runtime_put_autosuspend(drm->dev->dev);
0471 }
0472 }
0473
0474 void
0475 nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
0476 {
0477 struct nouveau_drm *drm = nouveau_drm(dev);
0478
0479 if (!drm->fbcon)
0480 return;
0481
0482 drm->fbcon_new_state = state;
0483
0484
0485
0486
0487 schedule_work(&drm->fbcon_work);
0488 }
0489
0490 void
0491 nouveau_fbcon_output_poll_changed(struct drm_device *dev)
0492 {
0493 struct nouveau_drm *drm = nouveau_drm(dev);
0494 struct nouveau_fbdev *fbcon = drm->fbcon;
0495 int ret;
0496
0497 if (!fbcon)
0498 return;
0499
0500 mutex_lock(&fbcon->hotplug_lock);
0501
0502 ret = pm_runtime_get(dev->dev);
0503 if (ret == 1 || ret == -EACCES) {
0504 drm_fb_helper_hotplug_event(&fbcon->helper);
0505
0506 pm_runtime_mark_last_busy(dev->dev);
0507 pm_runtime_put_autosuspend(dev->dev);
0508 } else if (ret == 0) {
0509
0510
0511
0512
0513
0514
0515 NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n");
0516 fbcon->hotplug_waiting = true;
0517 pm_runtime_put_noidle(drm->dev->dev);
0518 } else {
0519 DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n",
0520 ret);
0521 }
0522
0523 mutex_unlock(&fbcon->hotplug_lock);
0524 }
0525
0526 void
0527 nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon)
0528 {
0529 struct nouveau_drm *drm;
0530
0531 if (!fbcon)
0532 return;
0533 drm = nouveau_drm(fbcon->helper.dev);
0534
0535 mutex_lock(&fbcon->hotplug_lock);
0536 if (fbcon->hotplug_waiting) {
0537 fbcon->hotplug_waiting = false;
0538
0539 NV_DEBUG(drm, "Handling deferred fbcon HPD events\n");
0540 drm_fb_helper_hotplug_event(&fbcon->helper);
0541 }
0542 mutex_unlock(&fbcon->hotplug_lock);
0543 }
0544
0545 int
0546 nouveau_fbcon_init(struct drm_device *dev)
0547 {
0548 struct nouveau_drm *drm = nouveau_drm(dev);
0549 struct nouveau_fbdev *fbcon;
0550 int preferred_bpp = nouveau_fbcon_bpp;
0551 int ret;
0552
0553 if (!dev->mode_config.num_crtc ||
0554 (to_pci_dev(dev->dev)->class >> 8) != PCI_CLASS_DISPLAY_VGA)
0555 return 0;
0556
0557 fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
0558 if (!fbcon)
0559 return -ENOMEM;
0560
0561 drm->fbcon = fbcon;
0562 INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
0563 mutex_init(&fbcon->hotplug_lock);
0564
0565 drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
0566
0567 ret = drm_fb_helper_init(dev, &fbcon->helper);
0568 if (ret)
0569 goto free;
0570
0571 if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) {
0572 if (drm->client.device.info.ram_size <= 32 * 1024 * 1024)
0573 preferred_bpp = 8;
0574 else
0575 if (drm->client.device.info.ram_size <= 64 * 1024 * 1024)
0576 preferred_bpp = 16;
0577 else
0578 preferred_bpp = 32;
0579 }
0580
0581
0582 if (!drm_drv_uses_atomic_modeset(dev))
0583 drm_helper_disable_unused_functions(dev);
0584
0585 ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
0586 if (ret)
0587 goto fini;
0588
0589 if (fbcon->helper.fbdev)
0590 fbcon->helper.fbdev->pixmap.buf_align = 4;
0591 return 0;
0592
0593 fini:
0594 drm_fb_helper_fini(&fbcon->helper);
0595 free:
0596 kfree(fbcon);
0597 drm->fbcon = NULL;
0598 return ret;
0599 }
0600
0601 void
0602 nouveau_fbcon_fini(struct drm_device *dev)
0603 {
0604 struct nouveau_drm *drm = nouveau_drm(dev);
0605
0606 if (!drm->fbcon)
0607 return;
0608
0609 drm_kms_helper_poll_fini(dev);
0610 nouveau_fbcon_accel_fini(dev);
0611 nouveau_fbcon_destroy(dev, drm->fbcon);
0612 kfree(drm->fbcon);
0613 drm->fbcon = NULL;
0614 }