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