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/fb.h>
  36#include <linux/init.h>
  37#include <linux/screen_info.h>
  38#include <linux/vga_switcheroo.h>
  39#include <linux/console.h>
  40
  41#include <drm/drmP.h>
  42#include <drm/drm_crtc.h>
  43#include <drm/drm_crtc_helper.h>
  44#include <drm/drm_fb_helper.h>
  45
  46#include "nouveau_drm.h"
  47#include "nouveau_gem.h"
  48#include "nouveau_bo.h"
  49#include "nouveau_fbcon.h"
  50#include "nouveau_chan.h"
  51
  52#include "nouveau_crtc.h"
  53
  54MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
  55int nouveau_nofbaccel = 0;
  56module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
  57
  58static void
  59nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
  60{
  61        struct nouveau_fbdev *fbcon = info->par;
  62        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
  63        struct nvif_device *device = &drm->device;
  64        int ret;
  65
  66        if (info->state != FBINFO_STATE_RUNNING)
  67                return;
  68
  69        ret = -ENODEV;
  70        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
  71            mutex_trylock(&drm->client.mutex)) {
  72                if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
  73                        ret = nv04_fbcon_fillrect(info, rect);
  74                else
  75                if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
  76                        ret = nv50_fbcon_fillrect(info, rect);
  77                else
  78                        ret = nvc0_fbcon_fillrect(info, rect);
  79                mutex_unlock(&drm->client.mutex);
  80        }
  81
  82        if (ret == 0)
  83                return;
  84
  85        if (ret != -ENODEV)
  86                nouveau_fbcon_gpu_lockup(info);
  87        cfb_fillrect(info, rect);
  88}
  89
  90static void
  91nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
  92{
  93        struct nouveau_fbdev *fbcon = info->par;
  94        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
  95        struct nvif_device *device = &drm->device;
  96        int ret;
  97
  98        if (info->state != FBINFO_STATE_RUNNING)
  99                return;
 100
 101        ret = -ENODEV;
 102        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
 103            mutex_trylock(&drm->client.mutex)) {
 104                if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
 105                        ret = nv04_fbcon_copyarea(info, image);
 106                else
 107                if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
 108                        ret = nv50_fbcon_copyarea(info, image);
 109                else
 110                        ret = nvc0_fbcon_copyarea(info, image);
 111                mutex_unlock(&drm->client.mutex);
 112        }
 113
 114        if (ret == 0)
 115                return;
 116
 117        if (ret != -ENODEV)
 118                nouveau_fbcon_gpu_lockup(info);
 119        cfb_copyarea(info, image);
 120}
 121
 122static void
 123nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
 124{
 125        struct nouveau_fbdev *fbcon = info->par;
 126        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
 127        struct nvif_device *device = &drm->device;
 128        int ret;
 129
 130        if (info->state != FBINFO_STATE_RUNNING)
 131                return;
 132
 133        ret = -ENODEV;
 134        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
 135            mutex_trylock(&drm->client.mutex)) {
 136                if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
 137                        ret = nv04_fbcon_imageblit(info, image);
 138                else
 139                if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
 140                        ret = nv50_fbcon_imageblit(info, image);
 141                else
 142                        ret = nvc0_fbcon_imageblit(info, image);
 143                mutex_unlock(&drm->client.mutex);
 144        }
 145
 146        if (ret == 0)
 147                return;
 148
 149        if (ret != -ENODEV)
 150                nouveau_fbcon_gpu_lockup(info);
 151        cfb_imageblit(info, image);
 152}
 153
 154static int
 155nouveau_fbcon_sync(struct fb_info *info)
 156{
 157        struct nouveau_fbdev *fbcon = info->par;
 158        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
 159        struct nouveau_channel *chan = drm->channel;
 160        int ret;
 161
 162        if (!chan || !chan->accel_done || in_interrupt() ||
 163            info->state != FBINFO_STATE_RUNNING ||
 164            info->flags & FBINFO_HWACCEL_DISABLED)
 165                return 0;
 166
 167        if (!mutex_trylock(&drm->client.mutex))
 168                return 0;
 169
 170        ret = nouveau_channel_idle(chan);
 171        mutex_unlock(&drm->client.mutex);
 172        if (ret) {
 173                nouveau_fbcon_gpu_lockup(info);
 174                return 0;
 175        }
 176
 177        chan->accel_done = false;
 178        return 0;
 179}
 180
 181static struct fb_ops nouveau_fbcon_ops = {
 182        .owner = THIS_MODULE,
 183        .fb_check_var = drm_fb_helper_check_var,
 184        .fb_set_par = drm_fb_helper_set_par,
 185        .fb_fillrect = nouveau_fbcon_fillrect,
 186        .fb_copyarea = nouveau_fbcon_copyarea,
 187        .fb_imageblit = nouveau_fbcon_imageblit,
 188        .fb_sync = nouveau_fbcon_sync,
 189        .fb_pan_display = drm_fb_helper_pan_display,
 190        .fb_blank = drm_fb_helper_blank,
 191        .fb_setcmap = drm_fb_helper_setcmap,
 192        .fb_debug_enter = drm_fb_helper_debug_enter,
 193        .fb_debug_leave = drm_fb_helper_debug_leave,
 194};
 195
 196static struct fb_ops nouveau_fbcon_sw_ops = {
 197        .owner = THIS_MODULE,
 198        .fb_check_var = drm_fb_helper_check_var,
 199        .fb_set_par = drm_fb_helper_set_par,
 200        .fb_fillrect = cfb_fillrect,
 201        .fb_copyarea = cfb_copyarea,
 202        .fb_imageblit = cfb_imageblit,
 203        .fb_pan_display = drm_fb_helper_pan_display,
 204        .fb_blank = drm_fb_helper_blank,
 205        .fb_setcmap = drm_fb_helper_setcmap,
 206        .fb_debug_enter = drm_fb_helper_debug_enter,
 207        .fb_debug_leave = drm_fb_helper_debug_leave,
 208};
 209
 210void
 211nouveau_fbcon_accel_save_disable(struct drm_device *dev)
 212{
 213        struct nouveau_drm *drm = nouveau_drm(dev);
 214        if (drm->fbcon) {
 215                drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
 216                drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
 217        }
 218}
 219
 220void
 221nouveau_fbcon_accel_restore(struct drm_device *dev)
 222{
 223        struct nouveau_drm *drm = nouveau_drm(dev);
 224        if (drm->fbcon) {
 225                drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
 226        }
 227}
 228
 229static void
 230nouveau_fbcon_accel_fini(struct drm_device *dev)
 231{
 232        struct nouveau_drm *drm = nouveau_drm(dev);
 233        struct nouveau_fbdev *fbcon = drm->fbcon;
 234        if (fbcon && drm->channel) {
 235                console_lock();
 236                fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
 237                console_unlock();
 238                nouveau_channel_idle(drm->channel);
 239                nvif_object_fini(&fbcon->twod);
 240                nvif_object_fini(&fbcon->blit);
 241                nvif_object_fini(&fbcon->gdi);
 242                nvif_object_fini(&fbcon->patt);
 243                nvif_object_fini(&fbcon->rop);
 244                nvif_object_fini(&fbcon->clip);
 245                nvif_object_fini(&fbcon->surf2d);
 246        }
 247}
 248
 249static void
 250nouveau_fbcon_accel_init(struct drm_device *dev)
 251{
 252        struct nouveau_drm *drm = nouveau_drm(dev);
 253        struct nouveau_fbdev *fbcon = drm->fbcon;
 254        struct fb_info *info = fbcon->helper.fbdev;
 255        int ret;
 256
 257        if (drm->device.info.family < NV_DEVICE_INFO_V0_TESLA)
 258                ret = nv04_fbcon_accel_init(info);
 259        else
 260        if (drm->device.info.family < NV_DEVICE_INFO_V0_FERMI)
 261                ret = nv50_fbcon_accel_init(info);
 262        else
 263                ret = nvc0_fbcon_accel_init(info);
 264
 265        if (ret == 0)
 266                info->fbops = &nouveau_fbcon_ops;
 267}
 268
 269static void nouveau_fbcon_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
 270                                    u16 blue, int regno)
 271{
 272        struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
 273
 274        nv_crtc->lut.r[regno] = red;
 275        nv_crtc->lut.g[regno] = green;
 276        nv_crtc->lut.b[regno] = blue;
 277}
 278
 279static void nouveau_fbcon_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
 280                                    u16 *blue, int regno)
 281{
 282        struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
 283
 284        *red = nv_crtc->lut.r[regno];
 285        *green = nv_crtc->lut.g[regno];
 286        *blue = nv_crtc->lut.b[regno];
 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->dev;
 314        struct nouveau_drm *drm = nouveau_drm(dev);
 315        struct nvif_device *device = &drm->device;
 316        struct fb_info *info;
 317        struct drm_framebuffer *fb;
 318        struct nouveau_framebuffer *nouveau_fb;
 319        struct nouveau_channel *chan;
 320        struct nouveau_bo *nvbo;
 321        struct drm_mode_fb_cmd2 mode_cmd;
 322        struct pci_dev *pdev = dev->pdev;
 323        int size, ret;
 324
 325        mode_cmd.width = sizes->surface_width;
 326        mode_cmd.height = sizes->surface_height;
 327
 328        mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
 329        mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
 330
 331        mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
 332                                                          sizes->surface_depth);
 333
 334        size = mode_cmd.pitches[0] * mode_cmd.height;
 335        size = roundup(size, PAGE_SIZE);
 336
 337        ret = nouveau_gem_new(dev, size, 0, NOUVEAU_GEM_DOMAIN_VRAM,
 338                              0, 0x0000, &nvbo);
 339        if (ret) {
 340                NV_ERROR(drm, "failed to allocate framebuffer\n");
 341                goto out;
 342        }
 343
 344        ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_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_bo_vma_add(nvbo, drm->client.vm,
 359                                        &fbcon->nouveau_fb.vma);
 360                if (ret) {
 361                        NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
 362                        chan = NULL;
 363                }
 364        }
 365
 366        mutex_lock(&dev->struct_mutex);
 367
 368        info = framebuffer_alloc(0, &pdev->dev);
 369        if (!info) {
 370                ret = -ENOMEM;
 371                goto out_unlock;
 372        }
 373        info->skip_vt_switch = 1;
 374
 375        ret = fb_alloc_cmap(&info->cmap, 256, 0);
 376        if (ret) {
 377                ret = -ENOMEM;
 378                framebuffer_release(info);
 379                goto out_unlock;
 380        }
 381
 382        info->par = fbcon;
 383
 384        nouveau_framebuffer_init(dev, &fbcon->nouveau_fb, &mode_cmd, nvbo);
 385
 386        nouveau_fb = &fbcon->nouveau_fb;
 387        fb = &nouveau_fb->base;
 388
 389        /* setup helper */
 390        fbcon->helper.fb = fb;
 391        fbcon->helper.fbdev = info;
 392
 393        strcpy(info->fix.id, "nouveaufb");
 394        if (!chan)
 395                info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED;
 396        else
 397                info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
 398                              FBINFO_HWACCEL_FILLRECT |
 399                              FBINFO_HWACCEL_IMAGEBLIT;
 400        info->flags |= FBINFO_CAN_FORCE_OUTPUT;
 401        info->fbops = &nouveau_fbcon_sw_ops;
 402        info->fix.smem_start = nvbo->bo.mem.bus.base +
 403                               nvbo->bo.mem.bus.offset;
 404        info->fix.smem_len = size;
 405
 406        info->screen_base = nvbo_kmap_obj_iovirtual(nouveau_fb->nvbo);
 407        info->screen_size = size;
 408
 409        drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
 410        drm_fb_helper_fill_var(info, &fbcon->helper, sizes->fb_width, sizes->fb_height);
 411
 412        /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
 413
 414        mutex_unlock(&dev->struct_mutex);
 415
 416        if (chan)
 417                nouveau_fbcon_accel_init(dev);
 418        nouveau_fbcon_zfill(dev, fbcon);
 419
 420        /* To allow resizeing without swapping buffers */
 421        NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
 422                nouveau_fb->base.width, nouveau_fb->base.height,
 423                nvbo->bo.offset, nvbo);
 424
 425        vga_switcheroo_client_fb_set(dev->pdev, info);
 426        return 0;
 427
 428out_unlock:
 429        mutex_unlock(&dev->struct_mutex);
 430        if (chan)
 431                nouveau_bo_vma_del(nvbo, &fbcon->nouveau_fb.vma);
 432        nouveau_bo_unmap(nvbo);
 433out_unpin:
 434        nouveau_bo_unpin(nvbo);
 435out_unref:
 436        nouveau_bo_ref(NULL, &nvbo);
 437out:
 438        return ret;
 439}
 440
 441void
 442nouveau_fbcon_output_poll_changed(struct drm_device *dev)
 443{
 444        struct nouveau_drm *drm = nouveau_drm(dev);
 445        if (drm->fbcon)
 446                drm_fb_helper_hotplug_event(&drm->fbcon->helper);
 447}
 448
 449static int
 450nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
 451{
 452        struct nouveau_framebuffer *nouveau_fb = &fbcon->nouveau_fb;
 453        struct fb_info *info;
 454
 455        if (fbcon->helper.fbdev) {
 456                info = fbcon->helper.fbdev;
 457                unregister_framebuffer(info);
 458                if (info->cmap.len)
 459                        fb_dealloc_cmap(&info->cmap);
 460                framebuffer_release(info);
 461        }
 462
 463        if (nouveau_fb->nvbo) {
 464                nouveau_bo_unmap(nouveau_fb->nvbo);
 465                nouveau_bo_vma_del(nouveau_fb->nvbo, &nouveau_fb->vma);
 466                nouveau_bo_unpin(nouveau_fb->nvbo);
 467                drm_gem_object_unreference_unlocked(&nouveau_fb->nvbo->gem);
 468                nouveau_fb->nvbo = NULL;
 469        }
 470        drm_fb_helper_fini(&fbcon->helper);
 471        drm_framebuffer_unregister_private(&nouveau_fb->base);
 472        drm_framebuffer_cleanup(&nouveau_fb->base);
 473        return 0;
 474}
 475
 476void nouveau_fbcon_gpu_lockup(struct fb_info *info)
 477{
 478        struct nouveau_fbdev *fbcon = info->par;
 479        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
 480
 481        NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
 482        info->flags |= FBINFO_HWACCEL_DISABLED;
 483}
 484
 485static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
 486        .gamma_set = nouveau_fbcon_gamma_set,
 487        .gamma_get = nouveau_fbcon_gamma_get,
 488        .fb_probe = nouveau_fbcon_create,
 489};
 490
 491void
 492nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
 493{
 494        struct nouveau_drm *drm = nouveau_drm(dev);
 495        if (drm->fbcon) {
 496                console_lock();
 497                if (state == FBINFO_STATE_RUNNING)
 498                        nouveau_fbcon_accel_restore(dev);
 499                fb_set_suspend(drm->fbcon->helper.fbdev, state);
 500                if (state != FBINFO_STATE_RUNNING)
 501                        nouveau_fbcon_accel_save_disable(dev);
 502                console_unlock();
 503        }
 504}
 505
 506int
 507nouveau_fbcon_init(struct drm_device *dev)
 508{
 509        struct nouveau_drm *drm = nouveau_drm(dev);
 510        struct nouveau_fbdev *fbcon;
 511        int preferred_bpp;
 512        int ret;
 513
 514        if (!dev->mode_config.num_crtc ||
 515            (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
 516                return 0;
 517
 518        fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
 519        if (!fbcon)
 520                return -ENOMEM;
 521
 522        fbcon->dev = dev;
 523        drm->fbcon = fbcon;
 524
 525        drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
 526
 527        ret = drm_fb_helper_init(dev, &fbcon->helper,
 528                                 dev->mode_config.num_crtc, 4);
 529        if (ret)
 530                goto free;
 531
 532        ret = drm_fb_helper_single_add_all_connectors(&fbcon->helper);
 533        if (ret)
 534                goto fini;
 535
 536        if (drm->device.info.ram_size <= 32 * 1024 * 1024)
 537                preferred_bpp = 8;
 538        else
 539        if (drm->device.info.ram_size <= 64 * 1024 * 1024)
 540                preferred_bpp = 16;
 541        else
 542                preferred_bpp = 32;
 543
 544        /* disable all the possible outputs/crtcs before entering KMS mode */
 545        drm_helper_disable_unused_functions(dev);
 546
 547        ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
 548        if (ret)
 549                goto fini;
 550
 551        return 0;
 552
 553fini:
 554        drm_fb_helper_fini(&fbcon->helper);
 555free:
 556        kfree(fbcon);
 557        return ret;
 558}
 559
 560void
 561nouveau_fbcon_fini(struct drm_device *dev)
 562{
 563        struct nouveau_drm *drm = nouveau_drm(dev);
 564
 565        if (!drm->fbcon)
 566                return;
 567
 568        nouveau_fbcon_accel_fini(dev);
 569        nouveau_fbcon_destroy(dev, drm->fbcon);
 570        kfree(drm->fbcon);
 571        drm->fbcon = NULL;
 572}
 573