Skip to content

Commit

Permalink
drm/i915: Implement GPU reset on i965
Browse files Browse the repository at this point in the history
This patch puts in place the machinery to attempt to reset the GPU. This
will be used when attempting to recover from a GPU hang.

Signed-off-by: Owain G. Ainsworth <oga@openbsd.org>
Signed-off-by: Ben Gamari <bgamari.foss@gmail.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
  • Loading branch information
Ben Gamari authored and Jesse Barnes committed Sep 17, 2009
1 parent f65d942 commit 11ed50e
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 0 deletions.
8 changes: 8 additions & 0 deletions drivers/gpu/drm/i915/i915_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,9 @@ static int i915_load_modeset_init(struct drm_device *dev,
drm_mm_init(&dev_priv->vram, 0, prealloc_size);
DRM_INFO("set up %ldM of stolen space\n", prealloc_size / (1024*1024));

/* We're off and running w/KMS */
dev_priv->mm.suspended = 0;

/* Let GEM Manage from end of prealloc space to end of aperture.
*
* However, leave one page at the end still bound to the scratch page.
Expand All @@ -1184,7 +1187,9 @@ static int i915_load_modeset_init(struct drm_device *dev,
*/
i915_gem_do_init(dev, prealloc_size, agp_size - 4096);

mutex_lock(&dev->struct_mutex);
ret = i915_gem_init_ringbuffer(dev);
mutex_unlock(&dev->struct_mutex);
if (ret)
goto out;

Expand Down Expand Up @@ -1433,6 +1438,9 @@ int i915_driver_load(struct drm_device *dev, unsigned long flags)
return ret;
}

/* Start out suspended */
dev_priv->mm.suspended = 1;

if (drm_core_check_feature(dev, DRIVER_MODESET)) {
ret = i915_load_modeset_init(dev, prealloc_start,
prealloc_size, agp_size);
Expand Down
124 changes: 124 additions & 0 deletions drivers/gpu/drm/i915/i915_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,130 @@ static int i915_resume(struct drm_device *dev)
return ret;
}

/**
* i965_reset - reset chip after a hang
* @dev: drm device to reset
* @flags: reset domains
*
* Reset the chip. Useful if a hang is detected. Returns zero on successful
* reset or otherwise an error code.
*
* Procedure is fairly simple:
* - reset the chip using the reset reg
* - re-init context state
* - re-init hardware status page
* - re-init ring buffer
* - re-init interrupt state
* - re-init display
*/
int i965_reset(struct drm_device *dev, u8 flags)
{
drm_i915_private_t *dev_priv = dev->dev_private;
unsigned long timeout;
u8 gdrst;
/*
* We really should only reset the display subsystem if we actually
* need to
*/
bool need_display = true;

mutex_lock(&dev->struct_mutex);

/*
* Clear request list
*/
i915_gem_retire_requests(dev);

if (need_display)
i915_save_display(dev);

if (IS_I965G(dev) || IS_G4X(dev)) {
/*
* Set the domains we want to reset, then the reset bit (bit 0).
* Clear the reset bit after a while and wait for hardware status
* bit (bit 1) to be set
*/
pci_read_config_byte(dev->pdev, GDRST, &gdrst);
pci_write_config_byte(dev->pdev, GDRST, gdrst | flags | ((flags == GDRST_FULL) ? 0x1 : 0x0));
udelay(50);
pci_write_config_byte(dev->pdev, GDRST, gdrst & 0xfe);

/* ...we don't want to loop forever though, 500ms should be plenty */
timeout = jiffies + msecs_to_jiffies(500);
do {
udelay(100);
pci_read_config_byte(dev->pdev, GDRST, &gdrst);
} while ((gdrst & 0x1) && time_after(timeout, jiffies));

if (gdrst & 0x1) {
WARN(true, "i915: Failed to reset chip\n");
mutex_unlock(&dev->struct_mutex);
return -EIO;
}
} else {
DRM_ERROR("Error occurred. Don't know how to reset this chip.\n");
return -ENODEV;
}

/* Ok, now get things going again... */

/*
* Everything depends on having the GTT running, so we need to start
* there. Fortunately we don't need to do this unless we reset the
* chip at a PCI level.
*
* Next we need to restore the context, but we don't use those
* yet either...
*
* Ring buffer needs to be re-initialized in the KMS case, or if X
* was running at the time of the reset (i.e. we weren't VT
* switched away).
*/
if (drm_core_check_feature(dev, DRIVER_MODESET) ||
!dev_priv->mm.suspended) {
drm_i915_ring_buffer_t *ring = &dev_priv->ring;
struct drm_gem_object *obj = ring->ring_obj;
struct drm_i915_gem_object *obj_priv = obj->driver_private;
dev_priv->mm.suspended = 0;

/* Stop the ring if it's running. */
I915_WRITE(PRB0_CTL, 0);
I915_WRITE(PRB0_TAIL, 0);
I915_WRITE(PRB0_HEAD, 0);

/* Initialize the ring. */
I915_WRITE(PRB0_START, obj_priv->gtt_offset);
I915_WRITE(PRB0_CTL,
((obj->size - 4096) & RING_NR_PAGES) |
RING_NO_REPORT |
RING_VALID);
if (!drm_core_check_feature(dev, DRIVER_MODESET))
i915_kernel_lost_context(dev);
else {
ring->head = I915_READ(PRB0_HEAD) & HEAD_ADDR;
ring->tail = I915_READ(PRB0_TAIL) & TAIL_ADDR;
ring->space = ring->head - (ring->tail + 8);
if (ring->space < 0)
ring->space += ring->Size;
}

mutex_unlock(&dev->struct_mutex);
drm_irq_uninstall(dev);
drm_irq_install(dev);
mutex_lock(&dev->struct_mutex);
}

/*
* Display needs restore too...
*/
if (need_display)
i915_restore_display(dev);

mutex_unlock(&dev->struct_mutex);
return 0;
}


static int __devinit
i915_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/i915/i915_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ extern long i915_compat_ioctl(struct file *filp, unsigned int cmd,
extern int i915_emit_box(struct drm_device *dev,
struct drm_clip_rect *boxes,
int i, int DR1, int DR4);
extern int i965_reset(struct drm_device *dev, u8 flags);

/* i915_irq.c */
void i915_hangcheck_elapsed(unsigned long data);
Expand Down
8 changes: 8 additions & 0 deletions drivers/gpu/drm/i915/i915_irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,14 @@ static void i915_handle_error(struct drm_device *dev)
I915_WRITE(IIR, I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT);
}

if (dev_priv->mm.wedged) {
/*
* Wakeup waiting processes so they don't hang
*/
printk("i915: Waking up sleeping processes\n");
DRM_WAKEUP(&dev_priv->irq_queue);
}

queue_work(dev_priv->wq, &dev_priv->error_work);
}

Expand Down
4 changes: 4 additions & 0 deletions drivers/gpu/drm/i915/i915_reg.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
#define I915_GC_RENDER_CLOCK_200_MHZ (1 << 0)
#define I915_GC_RENDER_CLOCK_333_MHZ (4 << 0)
#define LBB 0xf4
#define GDRST 0xc0
#define GDRST_FULL (0<<2)
#define GDRST_RENDER (1<<2)
#define GDRST_MEDIA (3<<2)

/* VGA stuff */

Expand Down

0 comments on commit 11ed50e

Please sign in to comment.