cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

nouveau_fbcon.c (16323B)


      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/drm_crtc.h>
     41#include <drm/drm_crtc_helper.h>
     42#include <drm/drm_fb_helper.h>
     43#include <drm/drm_fourcc.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		pm_runtime_put(drm->dev->dev);
    194		return ret;
    195	}
    196	return 0;
    197}
    198
    199static int
    200nouveau_fbcon_release(struct fb_info *info, int user)
    201{
    202	struct nouveau_fbdev *fbcon = info->par;
    203	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
    204	pm_runtime_put(drm->dev->dev);
    205	return 0;
    206}
    207
    208static const struct fb_ops nouveau_fbcon_ops = {
    209	.owner = THIS_MODULE,
    210	DRM_FB_HELPER_DEFAULT_OPS,
    211	.fb_open = nouveau_fbcon_open,
    212	.fb_release = nouveau_fbcon_release,
    213	.fb_fillrect = nouveau_fbcon_fillrect,
    214	.fb_copyarea = nouveau_fbcon_copyarea,
    215	.fb_imageblit = nouveau_fbcon_imageblit,
    216	.fb_sync = nouveau_fbcon_sync,
    217};
    218
    219static const struct fb_ops nouveau_fbcon_sw_ops = {
    220	.owner = THIS_MODULE,
    221	DRM_FB_HELPER_DEFAULT_OPS,
    222	.fb_open = nouveau_fbcon_open,
    223	.fb_release = nouveau_fbcon_release,
    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};
    228
    229void
    230nouveau_fbcon_accel_save_disable(struct drm_device *dev)
    231{
    232	struct nouveau_drm *drm = nouveau_drm(dev);
    233	if (drm->fbcon && drm->fbcon->helper.fbdev) {
    234		drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
    235		drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
    236	}
    237}
    238
    239void
    240nouveau_fbcon_accel_restore(struct drm_device *dev)
    241{
    242	struct nouveau_drm *drm = nouveau_drm(dev);
    243	if (drm->fbcon && drm->fbcon->helper.fbdev) {
    244		drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
    245	}
    246}
    247
    248static void
    249nouveau_fbcon_accel_fini(struct drm_device *dev)
    250{
    251	struct nouveau_drm *drm = nouveau_drm(dev);
    252	struct nouveau_fbdev *fbcon = drm->fbcon;
    253	if (fbcon && drm->channel) {
    254		console_lock();
    255		if (fbcon->helper.fbdev)
    256			fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
    257		console_unlock();
    258		nouveau_channel_idle(drm->channel);
    259		nvif_object_dtor(&fbcon->twod);
    260		nvif_object_dtor(&fbcon->blit);
    261		nvif_object_dtor(&fbcon->gdi);
    262		nvif_object_dtor(&fbcon->patt);
    263		nvif_object_dtor(&fbcon->rop);
    264		nvif_object_dtor(&fbcon->clip);
    265		nvif_object_dtor(&fbcon->surf2d);
    266	}
    267}
    268
    269static void
    270nouveau_fbcon_accel_init(struct drm_device *dev)
    271{
    272	struct nouveau_drm *drm = nouveau_drm(dev);
    273	struct nouveau_fbdev *fbcon = drm->fbcon;
    274	struct fb_info *info = fbcon->helper.fbdev;
    275	int ret;
    276
    277	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA)
    278		ret = nv04_fbcon_accel_init(info);
    279	else
    280	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
    281		ret = nv50_fbcon_accel_init(info);
    282	else
    283		ret = nvc0_fbcon_accel_init(info);
    284
    285	if (ret == 0)
    286		info->fbops = &nouveau_fbcon_ops;
    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->helper.dev;
    314	struct nouveau_drm *drm = nouveau_drm(dev);
    315	struct nvif_device *device = &drm->client.device;
    316	struct fb_info *info;
    317	struct drm_framebuffer *fb;
    318	struct nouveau_channel *chan;
    319	struct nouveau_bo *nvbo;
    320	struct drm_mode_fb_cmd2 mode_cmd = {};
    321	int ret;
    322
    323	mode_cmd.width = sizes->surface_width;
    324	mode_cmd.height = sizes->surface_height;
    325
    326	mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
    327	mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
    328
    329	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
    330							  sizes->surface_depth);
    331
    332	ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] *
    333			      mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM,
    334			      0, 0x0000, &nvbo);
    335	if (ret) {
    336		NV_ERROR(drm, "failed to allocate framebuffer\n");
    337		goto out;
    338	}
    339
    340	ret = nouveau_framebuffer_new(dev, &mode_cmd, &nvbo->bo.base, &fb);
    341	if (ret)
    342		goto out_unref;
    343
    344	ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_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_vma_new(nvbo, chan->vmm, &fbcon->vma);
    359		if (ret) {
    360			NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
    361			chan = NULL;
    362		}
    363	}
    364
    365	info = drm_fb_helper_alloc_fbi(helper);
    366	if (IS_ERR(info)) {
    367		ret = PTR_ERR(info);
    368		goto out_unlock;
    369	}
    370
    371	/* setup helper */
    372	fbcon->helper.fb = fb;
    373
    374	if (!chan)
    375		info->flags = FBINFO_HWACCEL_DISABLED;
    376	else
    377		info->flags = FBINFO_HWACCEL_COPYAREA |
    378			      FBINFO_HWACCEL_FILLRECT |
    379			      FBINFO_HWACCEL_IMAGEBLIT;
    380	info->fbops = &nouveau_fbcon_sw_ops;
    381	info->fix.smem_start = nvbo->bo.resource->bus.offset;
    382	info->fix.smem_len = nvbo->bo.base.size;
    383
    384	info->screen_base = nvbo_kmap_obj_iovirtual(nvbo);
    385	info->screen_size = nvbo->bo.base.size;
    386
    387	drm_fb_helper_fill_info(info, &fbcon->helper, sizes);
    388
    389	/* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
    390
    391	if (chan)
    392		nouveau_fbcon_accel_init(dev);
    393	nouveau_fbcon_zfill(dev, fbcon);
    394
    395	/* To allow resizeing without swapping buffers */
    396	NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
    397		fb->width, fb->height, nvbo->offset, nvbo);
    398
    399	if (dev_is_pci(dev->dev))
    400		vga_switcheroo_client_fb_set(to_pci_dev(dev->dev), info);
    401
    402	return 0;
    403
    404out_unlock:
    405	if (chan)
    406		nouveau_vma_del(&fbcon->vma);
    407	nouveau_bo_unmap(nvbo);
    408out_unpin:
    409	nouveau_bo_unpin(nvbo);
    410out_unref:
    411	nouveau_bo_ref(NULL, &nvbo);
    412out:
    413	return ret;
    414}
    415
    416static int
    417nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
    418{
    419	struct drm_framebuffer *fb = fbcon->helper.fb;
    420	struct nouveau_bo *nvbo;
    421
    422	drm_fb_helper_unregister_fbi(&fbcon->helper);
    423	drm_fb_helper_fini(&fbcon->helper);
    424
    425	if (fb && fb->obj[0]) {
    426		nvbo = nouveau_gem_object(fb->obj[0]);
    427		nouveau_vma_del(&fbcon->vma);
    428		nouveau_bo_unmap(nvbo);
    429		nouveau_bo_unpin(nvbo);
    430		drm_framebuffer_put(fb);
    431	}
    432
    433	return 0;
    434}
    435
    436void nouveau_fbcon_gpu_lockup(struct fb_info *info)
    437{
    438	struct nouveau_fbdev *fbcon = info->par;
    439	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
    440
    441	NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
    442	info->flags |= FBINFO_HWACCEL_DISABLED;
    443}
    444
    445static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
    446	.fb_probe = nouveau_fbcon_create,
    447};
    448
    449static void
    450nouveau_fbcon_set_suspend_work(struct work_struct *work)
    451{
    452	struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work);
    453	int state = READ_ONCE(drm->fbcon_new_state);
    454
    455	if (state == FBINFO_STATE_RUNNING)
    456		pm_runtime_get_sync(drm->dev->dev);
    457
    458	console_lock();
    459	if (state == FBINFO_STATE_RUNNING)
    460		nouveau_fbcon_accel_restore(drm->dev);
    461	drm_fb_helper_set_suspend(&drm->fbcon->helper, state);
    462	if (state != FBINFO_STATE_RUNNING)
    463		nouveau_fbcon_accel_save_disable(drm->dev);
    464	console_unlock();
    465
    466	if (state == FBINFO_STATE_RUNNING) {
    467		nouveau_fbcon_hotplug_resume(drm->fbcon);
    468		pm_runtime_mark_last_busy(drm->dev->dev);
    469		pm_runtime_put_sync(drm->dev->dev);
    470	}
    471}
    472
    473void
    474nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
    475{
    476	struct nouveau_drm *drm = nouveau_drm(dev);
    477
    478	if (!drm->fbcon)
    479		return;
    480
    481	drm->fbcon_new_state = state;
    482	/* Since runtime resume can happen as a result of a sysfs operation,
    483	 * it's possible we already have the console locked. So handle fbcon
    484	 * init/deinit from a seperate work thread
    485	 */
    486	schedule_work(&drm->fbcon_work);
    487}
    488
    489void
    490nouveau_fbcon_output_poll_changed(struct drm_device *dev)
    491{
    492	struct nouveau_drm *drm = nouveau_drm(dev);
    493	struct nouveau_fbdev *fbcon = drm->fbcon;
    494	int ret;
    495
    496	if (!fbcon)
    497		return;
    498
    499	mutex_lock(&fbcon->hotplug_lock);
    500
    501	ret = pm_runtime_get(dev->dev);
    502	if (ret == 1 || ret == -EACCES) {
    503		drm_fb_helper_hotplug_event(&fbcon->helper);
    504
    505		pm_runtime_mark_last_busy(dev->dev);
    506		pm_runtime_put_autosuspend(dev->dev);
    507	} else if (ret == 0) {
    508		/* If the GPU was already in the process of suspending before
    509		 * this event happened, then we can't block here as we'll
    510		 * deadlock the runtime pmops since they wait for us to
    511		 * finish. So, just defer this event for when we runtime
    512		 * resume again. It will be handled by fbcon_work.
    513		 */
    514		NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n");
    515		fbcon->hotplug_waiting = true;
    516		pm_runtime_put_noidle(drm->dev->dev);
    517	} else {
    518		DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n",
    519			 ret);
    520	}
    521
    522	mutex_unlock(&fbcon->hotplug_lock);
    523}
    524
    525void
    526nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon)
    527{
    528	struct nouveau_drm *drm;
    529
    530	if (!fbcon)
    531		return;
    532	drm = nouveau_drm(fbcon->helper.dev);
    533
    534	mutex_lock(&fbcon->hotplug_lock);
    535	if (fbcon->hotplug_waiting) {
    536		fbcon->hotplug_waiting = false;
    537
    538		NV_DEBUG(drm, "Handling deferred fbcon HPD events\n");
    539		drm_fb_helper_hotplug_event(&fbcon->helper);
    540	}
    541	mutex_unlock(&fbcon->hotplug_lock);
    542}
    543
    544int
    545nouveau_fbcon_init(struct drm_device *dev)
    546{
    547	struct nouveau_drm *drm = nouveau_drm(dev);
    548	struct nouveau_fbdev *fbcon;
    549	int preferred_bpp = nouveau_fbcon_bpp;
    550	int ret;
    551
    552	if (!dev->mode_config.num_crtc ||
    553	    (to_pci_dev(dev->dev)->class >> 8) != PCI_CLASS_DISPLAY_VGA)
    554		return 0;
    555
    556	fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
    557	if (!fbcon)
    558		return -ENOMEM;
    559
    560	drm->fbcon = fbcon;
    561	INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
    562	mutex_init(&fbcon->hotplug_lock);
    563
    564	drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
    565
    566	ret = drm_fb_helper_init(dev, &fbcon->helper);
    567	if (ret)
    568		goto free;
    569
    570	if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) {
    571		if (drm->client.device.info.ram_size <= 32 * 1024 * 1024)
    572			preferred_bpp = 8;
    573		else
    574		if (drm->client.device.info.ram_size <= 64 * 1024 * 1024)
    575			preferred_bpp = 16;
    576		else
    577			preferred_bpp = 32;
    578	}
    579
    580	/* disable all the possible outputs/crtcs before entering KMS mode */
    581	if (!drm_drv_uses_atomic_modeset(dev))
    582		drm_helper_disable_unused_functions(dev);
    583
    584	ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
    585	if (ret)
    586		goto fini;
    587
    588	if (fbcon->helper.fbdev)
    589		fbcon->helper.fbdev->pixmap.buf_align = 4;
    590	return 0;
    591
    592fini:
    593	drm_fb_helper_fini(&fbcon->helper);
    594free:
    595	kfree(fbcon);
    596	drm->fbcon = NULL;
    597	return ret;
    598}
    599
    600void
    601nouveau_fbcon_fini(struct drm_device *dev)
    602{
    603	struct nouveau_drm *drm = nouveau_drm(dev);
    604
    605	if (!drm->fbcon)
    606		return;
    607
    608	nouveau_fbcon_accel_fini(dev);
    609	nouveau_fbcon_destroy(dev, drm->fbcon);
    610	kfree(drm->fbcon);
    611	drm->fbcon = NULL;
    612}