Commit abf7b30d authored by Thomas Zimmermann's avatar Thomas Zimmermann Committed by Gerd Hoffmann

drm/cirrus: Use drm_framebuffer_put to avoid kernel oops in clean-up

In the Cirrus driver, the regular clean-up code also performs the clean-up
of a failed initialization. If the fbdev's framebuffer was not initialized,
the clean-up will fail within drm_framebuffer_unregister_private. Booting
with cirrus.bpp=16 triggers this bug.

The framebuffer is currently stored directly within struct cirrus_fbdev. To
fix the bug, we turn it into a pointer that is only set for initialized
framebuffers. The fbdev's clean-up code skips uninitialized framebuffers.

The memory for struct drm_framebuffer is allocated dynamically. This requires
additional error handling within cirrusfb_create. The framebuffer clean-up is
now performed by drm_framebuffer_put, which also frees the data strcuture's
memory.

Link: https://bugzilla.suse.com/show_bug.cgi?id=1101822Signed-off-by: default avatarThomas Zimmermann <tzimmermann@suse.de>
Link: http://patchwork.freedesktop.org/patch/msgid/20180720112743.27159-1-tzimmermann@suse.deSigned-off-by: default avatarGerd Hoffmann <kraxel@redhat.com>
parent f82aab2d
...@@ -146,7 +146,7 @@ struct cirrus_device { ...@@ -146,7 +146,7 @@ struct cirrus_device {
struct cirrus_fbdev { struct cirrus_fbdev {
struct drm_fb_helper helper; struct drm_fb_helper helper;
struct drm_framebuffer gfb; struct drm_framebuffer *gfb;
void *sysram; void *sysram;
int size; int size;
int x1, y1, x2, y2; /* dirty rect */ int x1, y1, x2, y2; /* dirty rect */
......
...@@ -22,14 +22,14 @@ static void cirrus_dirty_update(struct cirrus_fbdev *afbdev, ...@@ -22,14 +22,14 @@ static void cirrus_dirty_update(struct cirrus_fbdev *afbdev,
struct drm_gem_object *obj; struct drm_gem_object *obj;
struct cirrus_bo *bo; struct cirrus_bo *bo;
int src_offset, dst_offset; int src_offset, dst_offset;
int bpp = afbdev->gfb.format->cpp[0]; int bpp = afbdev->gfb->format->cpp[0];
int ret = -EBUSY; int ret = -EBUSY;
bool unmap = false; bool unmap = false;
bool store_for_later = false; bool store_for_later = false;
int x2, y2; int x2, y2;
unsigned long flags; unsigned long flags;
obj = afbdev->gfb.obj[0]; obj = afbdev->gfb->obj[0];
bo = gem_to_cirrus_bo(obj); bo = gem_to_cirrus_bo(obj);
/* /*
...@@ -82,7 +82,7 @@ static void cirrus_dirty_update(struct cirrus_fbdev *afbdev, ...@@ -82,7 +82,7 @@ static void cirrus_dirty_update(struct cirrus_fbdev *afbdev,
} }
for (i = y; i < y + height; i++) { for (i = y; i < y + height; i++) {
/* assume equal stride for now */ /* assume equal stride for now */
src_offset = dst_offset = i * afbdev->gfb.pitches[0] + (x * bpp); src_offset = dst_offset = i * afbdev->gfb->pitches[0] + (x * bpp);
memcpy_toio(bo->kmap.virtual + src_offset, afbdev->sysram + src_offset, width * bpp); memcpy_toio(bo->kmap.virtual + src_offset, afbdev->sysram + src_offset, width * bpp);
} }
...@@ -192,23 +192,26 @@ static int cirrusfb_create(struct drm_fb_helper *helper, ...@@ -192,23 +192,26 @@ static int cirrusfb_create(struct drm_fb_helper *helper,
return -ENOMEM; return -ENOMEM;
info = drm_fb_helper_alloc_fbi(helper); info = drm_fb_helper_alloc_fbi(helper);
if (IS_ERR(info)) if (IS_ERR(info)) {
return PTR_ERR(info); ret = PTR_ERR(info);
goto err_vfree;
}
info->par = gfbdev; info->par = gfbdev;
ret = cirrus_framebuffer_init(cdev->dev, &gfbdev->gfb, &mode_cmd, gobj); fb = kzalloc(sizeof(*fb), GFP_KERNEL);
if (!fb) {
ret = -ENOMEM;
goto err_drm_gem_object_put_unlocked;
}
ret = cirrus_framebuffer_init(cdev->dev, fb, &mode_cmd, gobj);
if (ret) if (ret)
return ret; goto err_kfree;
gfbdev->sysram = sysram; gfbdev->sysram = sysram;
gfbdev->size = size; gfbdev->size = size;
gfbdev->gfb = fb;
fb = &gfbdev->gfb;
if (!fb) {
DRM_INFO("fb is NULL\n");
return -EINVAL;
}
/* setup helper */ /* setup helper */
gfbdev->helper.fb = fb; gfbdev->helper.fb = fb;
...@@ -241,24 +244,27 @@ static int cirrusfb_create(struct drm_fb_helper *helper, ...@@ -241,24 +244,27 @@ static int cirrusfb_create(struct drm_fb_helper *helper,
DRM_INFO(" pitch is %d\n", fb->pitches[0]); DRM_INFO(" pitch is %d\n", fb->pitches[0]);
return 0; return 0;
err_kfree:
kfree(fb);
err_drm_gem_object_put_unlocked:
drm_gem_object_put_unlocked(gobj);
err_vfree:
vfree(sysram);
return ret;
} }
static int cirrus_fbdev_destroy(struct drm_device *dev, static int cirrus_fbdev_destroy(struct drm_device *dev,
struct cirrus_fbdev *gfbdev) struct cirrus_fbdev *gfbdev)
{ {
struct drm_framebuffer *gfb = &gfbdev->gfb; struct drm_framebuffer *gfb = gfbdev->gfb;
drm_fb_helper_unregister_fbi(&gfbdev->helper); drm_fb_helper_unregister_fbi(&gfbdev->helper);
if (gfb->obj[0]) {
drm_gem_object_put_unlocked(gfb->obj[0]);
gfb->obj[0] = NULL;
}
vfree(gfbdev->sysram); vfree(gfbdev->sysram);
drm_fb_helper_fini(&gfbdev->helper); drm_fb_helper_fini(&gfbdev->helper);
drm_framebuffer_unregister_private(gfb); if (gfb)
drm_framebuffer_cleanup(gfb); drm_framebuffer_put(gfb);
return 0; return 0;
} }
......
...@@ -127,7 +127,7 @@ static int cirrus_crtc_do_set_base(struct drm_crtc *crtc, ...@@ -127,7 +127,7 @@ static int cirrus_crtc_do_set_base(struct drm_crtc *crtc,
return ret; return ret;
} }
if (&cdev->mode_info.gfbdev->gfb == crtc->primary->fb) { if (cdev->mode_info.gfbdev->gfb == crtc->primary->fb) {
/* if pushing console in kmap it */ /* if pushing console in kmap it */
ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap); ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap);
if (ret) if (ret)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment