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