drm/nouveau: add per-channel mutex, use to lock access to drm's channel
authorBen Skeggs <bskeggs@redhat.com>
Tue, 5 Oct 2010 06:53:48 +0000 (16:53 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Fri, 3 Dec 2010 05:05:10 +0000 (15:05 +1000)
This fixes a race condition between fbcon acceleration and TTM buffer
moves.  To reproduce:

- start X
- switch to vt and "while (true); do dmesg; done"
- switch to another vt and "sleep 2 && cat /path/to/debugfs/dri/0/evict_vram"
- switch back to vt running dmesg

We don't make use of this on any other channel yet, they're currently
protected by drm_global_mutex.  This will change in the near future.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nouveau_bo.c
drivers/gpu/drm/nouveau/nouveau_channel.c
drivers/gpu/drm/nouveau/nouveau_drv.h
drivers/gpu/drm/nouveau/nouveau_fbcon.c

index c41e1c200ef5f2143ea670b45eb3f1d0633864e2..d8817b4bb1894c04fb48bb74524f437212436683 100644 (file)
@@ -683,17 +683,24 @@ nouveau_bo_move_m2mf(struct ttm_buffer_object *bo, int evict, bool intr,
        int ret;
 
        chan = nvbo->channel;
-       if (!chan || nvbo->no_vm)
+       if (!chan || nvbo->no_vm) {
                chan = dev_priv->channel;
+               mutex_lock(&chan->mutex);
+       }
 
        if (dev_priv->card_type < NV_50)
                ret = nv04_bo_move_m2mf(chan, bo, &bo->mem, new_mem);
        else
                ret = nv50_bo_move_m2mf(chan, bo, &bo->mem, new_mem);
-       if (ret)
-               return ret;
+       if (ret == 0) {
+               ret = nouveau_bo_move_accel_cleanup(chan, nvbo, evict,
+                                                   no_wait_reserve,
+                                                   no_wait_gpu, new_mem);
+       }
 
-       return nouveau_bo_move_accel_cleanup(chan, nvbo, evict, no_wait_reserve, no_wait_gpu, new_mem);
+       if (chan == dev_priv->channel)
+               mutex_unlock(&chan->mutex);
+       return ret;
 }
 
 static int
index 373950e3481474e72cdc19c7fb858adf3cd8190c..8636478c477a492c7c90d60e035a12fba34e13c2 100644 (file)
@@ -145,6 +145,7 @@ nouveau_channel_alloc(struct drm_device *dev, struct nouveau_channel **chan_ret,
        chan->file_priv = file_priv;
        chan->vram_handle = vram_handle;
        chan->gart_handle = tt_handle;
+       mutex_init(&chan->mutex);
 
        NV_INFO(dev, "Allocating FIFO number %d\n", channel);
 
index 1c7db64c03bf03b45070939f16bc5a0092820ba0..04bc56cf4f1ad2459a7450cfb0d91b2070cc02b7 100644 (file)
@@ -166,6 +166,8 @@ struct nouveau_channel {
        struct drm_device *dev;
        int id;
 
+       struct mutex mutex;
+
        /* owner of this fifo */
        struct drm_file *file_priv;
        /* mapping of the fifo itself */
index 22e83adcc930a26b73e9f6abc83d349adf61247e..bc30dbe11d0002d2e0e4626c41efd443b86a801c 100644 (file)
@@ -62,11 +62,13 @@ nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
 
        ret = -ENODEV;
        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED)) {
+               mutex_lock(&dev_priv->channel->mutex);
                if (dev_priv->card_type < NV_50)
                        ret = nv04_fbcon_fillrect(info, rect);
                else
                if (dev_priv->card_type < NV_C0)
                        ret = nv50_fbcon_fillrect(info, rect);
+               mutex_unlock(&dev_priv->channel->mutex);
        }
 
        if (ret == 0)
@@ -90,11 +92,13 @@ nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
 
        ret = -ENODEV;
        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED)) {
+               mutex_lock(&dev_priv->channel->mutex);
                if (dev_priv->card_type < NV_50)
                        ret = nv04_fbcon_copyarea(info, image);
                else
                if (dev_priv->card_type < NV_C0)
                        ret = nv50_fbcon_copyarea(info, image);
+               mutex_unlock(&dev_priv->channel->mutex);
        }
 
        if (ret == 0)
@@ -118,11 +122,13 @@ nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
 
        ret = -ENODEV;
        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED)) {
+               mutex_lock(&dev_priv->channel->mutex);
                if (dev_priv->card_type < NV_50)
                        ret = nv04_fbcon_imageblit(info, image);
                else
                if (dev_priv->card_type < NV_C0)
                        ret = nv50_fbcon_imageblit(info, image);
+               mutex_unlock(&dev_priv->channel->mutex);
        }
 
        if (ret == 0)
@@ -142,12 +148,15 @@ nouveau_fbcon_sync(struct fb_info *info)
        struct nouveau_channel *chan = dev_priv->channel;
        int ret, i;
 
-       if (!chan || !chan->accel_done ||
+       if (!chan || !chan->accel_done || in_interrupt() ||
            info->state != FBINFO_STATE_RUNNING ||
            info->flags & FBINFO_HWACCEL_DISABLED)
                return 0;
 
-       if (RING_SPACE(chan, 4)) {
+       mutex_lock(&chan->mutex);
+       ret = RING_SPACE(chan, 4);
+       if (ret) {
+               mutex_unlock(&chan->mutex);
                nouveau_fbcon_gpu_lockup(info);
                return 0;
        }
@@ -158,6 +167,7 @@ nouveau_fbcon_sync(struct fb_info *info)
        OUT_RING(chan, 0);
        nouveau_bo_wr32(chan->notifier_bo, chan->m2mf_ntfy + 3, 0xffffffff);
        FIRE_RING(chan);
+       mutex_unlock(&chan->mutex);
 
        ret = -EBUSY;
        for (i = 0; i < 100000; i++) {
@@ -353,6 +363,8 @@ nouveau_fbcon_create(struct nouveau_fbdev *nfbdev,
        info->pixmap.flags = FB_PIXMAP_SYSTEM;
        info->pixmap.scan_align = 1;
 
+       mutex_unlock(&dev->struct_mutex);
+
        if (dev_priv->channel && !nouveau_nofbaccel) {
                ret = -ENODEV;
                if (dev_priv->card_type < NV_50)
@@ -373,7 +385,6 @@ nouveau_fbcon_create(struct nouveau_fbdev *nfbdev,
                                                nouveau_fb->base.height,
                                                nvbo->bo.offset, nvbo);
 
-       mutex_unlock(&dev->struct_mutex);
        vga_switcheroo_client_fb_set(dev->pdev, info);
        return 0;