linux/drivers/gpu/drm/nouveau/nouveau_fbcon.c
<<
>>
Prefs
   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/drmP.h>
  41#include <drm/drm_crtc.h>
  42#include <drm/drm_crtc_helper.h>
  43#include <drm/drm_fb_helper.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                return ret;
 194        return 0;
 195}
 196
 197static int
 198nouveau_fbcon_release(struct fb_info *info, int user)
 199{
 200        struct nouveau_fbdev *fbcon = info->par;
 201        struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
 202        pm_runtime_put(drm->dev->dev);
 203        return 0;
 204}
 205
 206static struct fb_ops nouveau_fbcon_ops = {
 207        .owner = THIS_MODULE,
 208        DRM_FB_HELPER_DEFAULT_OPS,
 209        .fb_open = nouveau_fbcon_open,
 210        .fb_release = nouveau_fbcon_release,
 211        .fb_fillrect = nouveau_fbcon_fillrect,
 212        .fb_copyarea = nouveau_fbcon_copyarea,
 213        .fb_imageblit = nouveau_fbcon_imageblit,
 214        .fb_sync = nouveau_fbcon_sync,
 215};
 216
 217static struct fb_ops nouveau_fbcon_sw_ops = {
 218        .owner = THIS_MODULE,
 219        DRM_FB_HELPER_DEFAULT_OPS,
 220        .fb_open = nouveau_fbcon_open,
 221        .fb_release = nouveau_fbcon_release,
 222        .fb_fillrect = drm_fb_helper_cfb_fillrect,
 223        .fb_copyarea = drm_fb_helper_cfb_copyarea,
 224        .fb_imageblit = drm_fb_helper_cfb_imageblit,
 225};
 226
 227void
 228nouveau_fbcon_accel_save_disable(struct drm_device *dev)
 229{
 230        struct nouveau_drm *drm = nouveau_drm(dev);
 231        if (drm->fbcon && drm->fbcon->helper.fbdev) {
 232                drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
 233                drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
 234        }
 235}
 236
 237void
 238nouveau_fbcon_accel_restore(struct drm_device *dev)
 239{
 240        struct nouveau_drm *drm = nouveau_drm(dev);
 241        if (drm->fbcon && drm->fbcon->helper.fbdev) {
 242                drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
 243        }
 244}
 245
 246static void
 247nouveau_fbcon_accel_fini(struct drm_device *dev)
 248{
 249        struct nouveau_drm *drm = nouveau_drm(dev);
 250        struct nouveau_fbdev *fbcon = drm->fbcon;
 251        if (fbcon && drm->channel) {
 252                console_lock();
 253                if (fbcon->helper.fbdev)
 254                        fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
 255                console_unlock();
 256                nouveau_channel_idle(drm->channel);
 257                nvif_object_fini(&fbcon->twod);
 258                nvif_object_fini(&fbcon->blit);
 259                nvif_object_fini(&fbcon->gdi);
 260                nvif_object_fini(&fbcon->patt);
 261                nvif_object_fini(&fbcon->rop);
 262                nvif_object_fini(&fbcon->clip);
 263                nvif_object_fini(&fbcon->surf2d);
 264        }
 265}
 266
 267static void
 268nouveau_fbcon_accel_init(struct drm_device *dev)
 269{
 270        struct nouveau_drm *drm = nouveau_drm(dev);
 271        struct nouveau_fbdev *fbcon = drm->fbcon;
 272        struct fb_info *info = fbcon->helper.fbdev;
 273        int ret;
 274
 275        if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA)
 276                ret = nv04_fbcon_accel_init(info);
 277        else
 278        if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
 279                ret = nv50_fbcon_accel_init(info);
 280        else
 281                ret = nvc0_fbcon_accel_init(info);
 282
 283        if (ret == 0)
 284                info->fbops = &nouveau_fbcon_ops;
 285}
 286
 287static void
 288nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
 289{
 290        struct fb_info *info = fbcon->helper.fbdev;
 291        struct fb_fillrect rect;
 292
 293        /* Clear the entire fbcon.  The drm will program every connector
 294         * with it's preferred mode.  If the sizes differ, one display will
 295         * quite likely have garbage around the console.
 296         */
 297        rect.dx = rect.dy = 0;
 298        rect.width = info->var.xres_virtual;
 299        rect.height = info->var.yres_virtual;
 300        rect.color = 0;
 301        rect.rop = ROP_COPY;
 302        info->fbops->fb_fillrect(info, &rect);
 303}
 304
 305static int
 306nouveau_fbcon_create(struct drm_fb_helper *helper,
 307                     struct drm_fb_helper_surface_size *sizes)
 308{
 309        struct nouveau_fbdev *fbcon =
 310                container_of(helper, struct nouveau_fbdev, helper);
 311        struct drm_device *dev = fbcon->helper.dev;
 312        struct nouveau_drm *drm = nouveau_drm(dev);
 313        struct nvif_device *device = &drm->client.device;
 314        struct fb_info *info;
 315        struct nouveau_framebuffer *fb;
 316        struct nouveau_channel *chan;
 317        struct nouveau_bo *nvbo;
 318        struct drm_mode_fb_cmd2 mode_cmd;
 319        int ret;
 320
 321        mode_cmd.width = sizes->surface_width;
 322        mode_cmd.height = sizes->surface_height;
 323
 324        mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
 325        mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
 326
 327        mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
 328                                                          sizes->surface_depth);
 329
 330        ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] *
 331                              mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM,
 332                              0, 0x0000, &nvbo);
 333        if (ret) {
 334                NV_ERROR(drm, "failed to allocate framebuffer\n");
 335                goto out;
 336        }
 337
 338        ret = nouveau_framebuffer_new(dev, &mode_cmd, nvbo, &fb);
 339        if (ret)
 340                goto out_unref;
 341
 342        ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM, false);
 343        if (ret) {
 344                NV_ERROR(drm, "failed to pin fb: %d\n", ret);
 345                goto out_unref;
 346        }
 347
 348        ret = nouveau_bo_map(nvbo);
 349        if (ret) {
 350                NV_ERROR(drm, "failed to map fb: %d\n", ret);
 351                goto out_unpin;
 352        }
 353
 354        chan = nouveau_nofbaccel ? NULL : drm->channel;
 355        if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
 356                ret = nouveau_vma_new(nvbo, &drm->client.vmm, &fb->vma);
 357                if (ret) {
 358                        NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
 359                        chan = NULL;
 360                }
 361        }
 362
 363        info = drm_fb_helper_alloc_fbi(helper);
 364        if (IS_ERR(info)) {
 365                ret = PTR_ERR(info);
 366                goto out_unlock;
 367        }
 368        info->skip_vt_switch = 1;
 369
 370        info->par = fbcon;
 371
 372        /* setup helper */
 373        fbcon->helper.fb = &fb->base;
 374
 375        strcpy(info->fix.id, "nouveaufb");
 376        if (!chan)
 377                info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED;
 378        else
 379                info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
 380                              FBINFO_HWACCEL_FILLRECT |
 381                              FBINFO_HWACCEL_IMAGEBLIT;
 382        info->flags |= FBINFO_CAN_FORCE_OUTPUT;
 383        info->fbops = &nouveau_fbcon_sw_ops;
 384        info->fix.smem_start = fb->nvbo->bo.mem.bus.base +
 385                               fb->nvbo->bo.mem.bus.offset;
 386        info->fix.smem_len = fb->nvbo->bo.mem.num_pages << PAGE_SHIFT;
 387
 388        info->screen_base = nvbo_kmap_obj_iovirtual(fb->nvbo);
 389        info->screen_size = fb->nvbo->bo.mem.num_pages << PAGE_SHIFT;
 390
 391        drm_fb_helper_fill_fix(info, fb->base.pitches[0],
 392                               fb->base.format->depth);
 393        drm_fb_helper_fill_var(info, &fbcon->helper, sizes->fb_width, sizes->fb_height);
 394
 395        /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
 396
 397        if (chan)
 398                nouveau_fbcon_accel_init(dev);
 399        nouveau_fbcon_zfill(dev, fbcon);
 400
 401        /* To allow resizeing without swapping buffers */
 402        NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
 403                fb->base.width, fb->base.height, fb->nvbo->bo.offset, nvbo);
 404
 405        vga_switcheroo_client_fb_set(dev->pdev, info);
 406        return 0;
 407
 408out_unlock:
 409        if (chan)
 410                nouveau_vma_del(&fb->vma);
 411        nouveau_bo_unmap(fb->nvbo);
 412out_unpin:
 413        nouveau_bo_unpin(fb->nvbo);
 414out_unref:
 415        nouveau_bo_ref(NULL, &fb->nvbo);
 416out:
 417        return ret;
 418}
 419
 420static int
 421nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
 422{
 423        struct nouveau_framebuffer *nouveau_fb = nouveau_framebuffer(fbcon->helper.fb);
 424
 425        drm_fb_helper_unregister_fbi(&fbcon->helper);
 426        drm_fb_helper_fini(&fbcon->helper);
 427
 428        if (nouveau_fb && nouveau_fb->nvbo) {
 429                nouveau_vma_del(&nouveau_fb->vma);
 430                nouveau_bo_unmap(nouveau_fb->nvbo);
 431                nouveau_bo_unpin(nouveau_fb->nvbo);
 432                drm_framebuffer_unreference(&nouveau_fb->base);
 433        }
 434
 435        return 0;
 436}
 437
 438void nouveau_fbcon_gpu_lockup(struct fb_info *info)
 439{
 440        struct nouveau_fbdev *fbcon = info->par;
 441        struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
 442
 443        NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
 444        info->flags |= FBINFO_HWACCEL_DISABLED;
 445}
 446
 447static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
 448        .fb_probe = nouveau_fbcon_create,
 449};
 450
 451static void
 452nouveau_fbcon_set_suspend_work(struct work_struct *work)
 453{
 454        struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work);
 455        int state = READ_ONCE(drm->fbcon_new_state);
 456
 457        if (state == FBINFO_STATE_RUNNING)
 458                pm_runtime_get_sync(drm->dev->dev);
 459
 460        console_lock();
 461        if (state == FBINFO_STATE_RUNNING)
 462                nouveau_fbcon_accel_restore(drm->dev);
 463        drm_fb_helper_set_suspend(&drm->fbcon->helper, state);
 464        if (state != FBINFO_STATE_RUNNING)
 465                nouveau_fbcon_accel_save_disable(drm->dev);
 466        console_unlock();
 467
 468        if (state == FBINFO_STATE_RUNNING) {
 469                pm_runtime_mark_last_busy(drm->dev->dev);
 470                pm_runtime_put_sync(drm->dev->dev);
 471        }
 472}
 473
 474void
 475nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
 476{
 477        struct nouveau_drm *drm = nouveau_drm(dev);
 478
 479        if (!drm->fbcon)
 480                return;
 481
 482        drm->fbcon_new_state = state;
 483        /* Since runtime resume can happen as a result of a sysfs operation,
 484         * it's possible we already have the console locked. So handle fbcon
 485         * init/deinit from a seperate work thread
 486         */
 487        schedule_work(&drm->fbcon_work);
 488}
 489
 490int
 491nouveau_fbcon_init(struct drm_device *dev)
 492{
 493        struct nouveau_drm *drm = nouveau_drm(dev);
 494        struct nouveau_fbdev *fbcon;
 495        int preferred_bpp = nouveau_fbcon_bpp;
 496        int ret;
 497
 498        if (!dev->mode_config.num_crtc ||
 499            (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
 500                return 0;
 501
 502        fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
 503        if (!fbcon)
 504                return -ENOMEM;
 505
 506        drm->fbcon = fbcon;
 507        INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
 508
 509        drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
 510
 511        ret = drm_fb_helper_init(dev, &fbcon->helper, 4);
 512        if (ret)
 513                goto free;
 514
 515        ret = drm_fb_helper_single_add_all_connectors(&fbcon->helper);
 516        if (ret)
 517                goto fini;
 518
 519        if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) {
 520                if (drm->client.device.info.ram_size <= 32 * 1024 * 1024)
 521                        preferred_bpp = 8;
 522                else
 523                if (drm->client.device.info.ram_size <= 64 * 1024 * 1024)
 524                        preferred_bpp = 16;
 525                else
 526                        preferred_bpp = 32;
 527        }
 528
 529        /* disable all the possible outputs/crtcs before entering KMS mode */
 530        if (!drm_drv_uses_atomic_modeset(dev))
 531                drm_helper_disable_unused_functions(dev);
 532
 533        ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
 534        if (ret)
 535                goto fini;
 536
 537        if (fbcon->helper.fbdev)
 538                fbcon->helper.fbdev->pixmap.buf_align = 4;
 539        return 0;
 540
 541fini:
 542        drm_fb_helper_fini(&fbcon->helper);
 543free:
 544        kfree(fbcon);
 545        return ret;
 546}
 547
 548void
 549nouveau_fbcon_fini(struct drm_device *dev)
 550{
 551        struct nouveau_drm *drm = nouveau_drm(dev);
 552
 553        if (!drm->fbcon)
 554                return;
 555
 556        nouveau_fbcon_accel_fini(dev);
 557        nouveau_fbcon_destroy(dev, drm->fbcon);
 558        kfree(drm->fbcon);
 559        drm->fbcon = NULL;
 560}
 561