+u32 gm45_get_vblank_counter(struct drm_device *dev, int pipe)
+{
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+ int reg = pipe ? PIPEB_FRMCOUNT_GM45 : PIPEA_FRMCOUNT_GM45;
+
+ if (!i915_pipe_enabled(dev, pipe)) {
+ DRM_DEBUG_DRIVER("trying to get vblank count for disabled "
+ "pipe %d\n", pipe);
+ return 0;
+ }
+
+ return I915_READ(reg);
+}
+
+/*
+ * Handle hotplug events outside the interrupt handler proper.
+ */
+static void i915_hotplug_work_func(struct work_struct *work)
+{
+ drm_i915_private_t *dev_priv = container_of(work, drm_i915_private_t,
+ hotplug_work);
+ struct drm_device *dev = dev_priv->dev;
+ struct drm_mode_config *mode_config = &dev->mode_config;
+ struct drm_connector *connector;
+
+ if (mode_config->num_connector) {
+ list_for_each_entry(connector, &mode_config->connector_list, head) {
+ struct intel_output *intel_output = to_intel_output(connector);
+
+ if (intel_output->hot_plug)
+ (*intel_output->hot_plug) (intel_output);
+ }
+ }
+ /* Just fire off a uevent and let userspace tell us what to do */
+ drm_sysfs_hotplug_event(dev);
+}
+
+static void i915_handle_rps_change(struct drm_device *dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ u32 busy_up, busy_down, max_avg, min_avg;
+ u16 rgvswctl;
+ u8 new_delay = dev_priv->cur_delay;
+
+ I915_WRITE(MEMINTRSTS, I915_READ(MEMINTRSTS) & ~MEMINT_EVAL_CHG);
+ busy_up = I915_READ(RCPREVBSYTUPAVG);
+ busy_down = I915_READ(RCPREVBSYTDNAVG);
+ max_avg = I915_READ(RCBMAXAVG);
+ min_avg = I915_READ(RCBMINAVG);
+
+ /* Handle RCS change request from hw */
+ if (busy_up > max_avg) {
+ if (dev_priv->cur_delay != dev_priv->max_delay)
+ new_delay = dev_priv->cur_delay - 1;
+ if (new_delay < dev_priv->max_delay)
+ new_delay = dev_priv->max_delay;
+ } else if (busy_down < min_avg) {
+ if (dev_priv->cur_delay != dev_priv->min_delay)
+ new_delay = dev_priv->cur_delay + 1;
+ if (new_delay > dev_priv->min_delay)
+ new_delay = dev_priv->min_delay;
+ }
+
+ DRM_DEBUG("rps change requested: %d -> %d\n",
+ dev_priv->cur_delay, new_delay);
+
+ rgvswctl = I915_READ(MEMSWCTL);
+ if (rgvswctl & MEMCTL_CMD_STS) {
+ DRM_ERROR("gpu busy, RCS change rejected\n");
+ return; /* still busy with another command */
+ }
+
+ /* Program the new state */
+ rgvswctl = (MEMCTL_CMD_CHFREQ << MEMCTL_CMD_SHIFT) |
+ (new_delay << MEMCTL_FREQ_SHIFT) | MEMCTL_SFCAVM;
+ I915_WRITE(MEMSWCTL, rgvswctl);
+ POSTING_READ(MEMSWCTL);
+
+ rgvswctl |= MEMCTL_CMD_STS;
+ I915_WRITE(MEMSWCTL, rgvswctl);
+
+ dev_priv->cur_delay = new_delay;
+
+ DRM_DEBUG("rps changed\n");
+
+ return;
+}
+
+irqreturn_t ironlake_irq_handler(struct drm_device *dev)
+{
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+ int ret = IRQ_NONE;
+ u32 de_iir, gt_iir, de_ier, pch_iir;
+ struct drm_i915_master_private *master_priv;
+
+ /* disable master interrupt before clearing iir */
+ de_ier = I915_READ(DEIER);
+ I915_WRITE(DEIER, de_ier & ~DE_MASTER_IRQ_CONTROL);
+ (void)I915_READ(DEIER);
+
+ de_iir = I915_READ(DEIIR);
+ gt_iir = I915_READ(GTIIR);
+ pch_iir = I915_READ(SDEIIR);
+
+ if (de_iir == 0 && gt_iir == 0 && pch_iir == 0)
+ goto done;
+
+ ret = IRQ_HANDLED;
+
+ if (dev->primary->master) {
+ master_priv = dev->primary->master->driver_priv;
+ if (master_priv->sarea_priv)
+ master_priv->sarea_priv->last_dispatch =
+ READ_BREADCRUMB(dev_priv);
+ }
+
+ if (gt_iir & GT_USER_INTERRUPT) {
+ u32 seqno = i915_get_gem_seqno(dev);
+ dev_priv->mm.irq_gem_seqno = seqno;
+ trace_i915_gem_request_complete(dev, seqno);
+ DRM_WAKEUP(&dev_priv->irq_queue);
+ dev_priv->hangcheck_count = 0;
+ mod_timer(&dev_priv->hangcheck_timer, jiffies + DRM_I915_HANGCHECK_PERIOD);
+ }
+
+ if (de_iir & DE_GSE)
+ ironlake_opregion_gse_intr(dev);
+
+ if (de_iir & DE_PLANEA_FLIP_DONE) {
+ intel_prepare_page_flip(dev, 0);
+ intel_finish_page_flip(dev, 0);
+ }
+
+ if (de_iir & DE_PLANEB_FLIP_DONE) {
+ intel_prepare_page_flip(dev, 1);
+ intel_finish_page_flip(dev, 1);
+ }
+
+ if (de_iir & DE_PIPEA_VBLANK)
+ drm_handle_vblank(dev, 0);
+
+ if (de_iir & DE_PIPEB_VBLANK)
+ drm_handle_vblank(dev, 1);
+
+ /* check event from PCH */
+ if ((de_iir & DE_PCH_EVENT) &&
+ (pch_iir & SDE_HOTPLUG_MASK)) {
+ queue_work(dev_priv->wq, &dev_priv->hotplug_work);
+ }
+
+ if (de_iir & DE_PCU_EVENT) {
+ I915_WRITE(MEMINTRSTS, I915_READ(MEMINTRSTS));
+ i915_handle_rps_change(dev);
+ }
+
+ /* should clear PCH hotplug event before clear CPU irq */
+ I915_WRITE(SDEIIR, pch_iir);
+ I915_WRITE(GTIIR, gt_iir);
+ I915_WRITE(DEIIR, de_iir);
+
+done:
+ I915_WRITE(DEIER, de_ier);
+ (void)I915_READ(DEIER);
+
+ return ret;
+}
+
+/**
+ * i915_error_work_func - do process context error handling work
+ * @work: work struct
+ *
+ * Fire an error uevent so userspace can see that a hang or error
+ * was detected.
+ */
+static void i915_error_work_func(struct work_struct *work)
+{
+ drm_i915_private_t *dev_priv = container_of(work, drm_i915_private_t,
+ error_work);
+ struct drm_device *dev = dev_priv->dev;
+ char *error_event[] = { "ERROR=1", NULL };
+ char *reset_event[] = { "RESET=1", NULL };
+ char *reset_done_event[] = { "ERROR=0", NULL };
+
+ DRM_DEBUG_DRIVER("generating error event\n");
+ kobject_uevent_env(&dev->primary->kdev.kobj, KOBJ_CHANGE, error_event);
+
+ if (atomic_read(&dev_priv->mm.wedged)) {
+ if (IS_I965G(dev)) {
+ DRM_DEBUG_DRIVER("resetting chip\n");
+ kobject_uevent_env(&dev->primary->kdev.kobj, KOBJ_CHANGE, reset_event);
+ if (!i965_reset(dev, GDRST_RENDER)) {
+ atomic_set(&dev_priv->mm.wedged, 0);
+ kobject_uevent_env(&dev->primary->kdev.kobj, KOBJ_CHANGE, reset_done_event);
+ }
+ } else {
+ DRM_DEBUG_DRIVER("reboot required\n");
+ }
+ }
+}
+
+static struct drm_i915_error_object *
+i915_error_object_create(struct drm_device *dev,
+ struct drm_gem_object *src)
+{
+ struct drm_i915_error_object *dst;
+ struct drm_i915_gem_object *src_priv;
+ int page, page_count;
+
+ if (src == NULL)
+ return NULL;
+
+ src_priv = to_intel_bo(src);
+ if (src_priv->pages == NULL)
+ return NULL;
+
+ page_count = src->size / PAGE_SIZE;
+
+ dst = kmalloc(sizeof(*dst) + page_count * sizeof (u32 *), GFP_ATOMIC);
+ if (dst == NULL)
+ return NULL;
+
+ for (page = 0; page < page_count; page++) {
+ void *s, *d = kmalloc(PAGE_SIZE, GFP_ATOMIC);
+ if (d == NULL)
+ goto unwind;
+ s = kmap_atomic(src_priv->pages[page], KM_USER0);
+ memcpy(d, s, PAGE_SIZE);
+ kunmap_atomic(s, KM_USER0);
+ dst->pages[page] = d;
+ }
+ dst->page_count = page_count;
+ dst->gtt_offset = src_priv->gtt_offset;
+
+ return dst;
+
+unwind:
+ while (page--)
+ kfree(dst->pages[page]);
+ kfree(dst);
+ return NULL;
+}
+
+static void
+i915_error_object_free(struct drm_i915_error_object *obj)
+{
+ int page;
+
+ if (obj == NULL)
+ return;
+
+ for (page = 0; page < obj->page_count; page++)
+ kfree(obj->pages[page]);
+
+ kfree(obj);
+}
+
+static void
+i915_error_state_free(struct drm_device *dev,
+ struct drm_i915_error_state *error)
+{
+ i915_error_object_free(error->batchbuffer[0]);
+ i915_error_object_free(error->batchbuffer[1]);
+ i915_error_object_free(error->ringbuffer);
+ kfree(error->active_bo);
+ kfree(error);
+}
+
+static u32
+i915_get_bbaddr(struct drm_device *dev, u32 *ring)
+{
+ u32 cmd;
+
+ if (IS_I830(dev) || IS_845G(dev))
+ cmd = MI_BATCH_BUFFER;
+ else if (IS_I965G(dev))
+ cmd = (MI_BATCH_BUFFER_START | (2 << 6) |
+ MI_BATCH_NON_SECURE_I965);
+ else
+ cmd = (MI_BATCH_BUFFER_START | (2 << 6));
+
+ return ring[0] == cmd ? ring[1] : 0;
+}
+
+static u32
+i915_ringbuffer_last_batch(struct drm_device *dev)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ u32 head, bbaddr;
+ u32 *ring;
+
+ /* Locate the current position in the ringbuffer and walk back
+ * to find the most recently dispatched batch buffer.
+ */
+ bbaddr = 0;
+ head = I915_READ(PRB0_HEAD) & HEAD_ADDR;
+ ring = (u32 *)(dev_priv->ring.virtual_start + head);
+
+ while (--ring >= (u32 *)dev_priv->ring.virtual_start) {
+ bbaddr = i915_get_bbaddr(dev, ring);
+ if (bbaddr)
+ break;
+ }
+
+ if (bbaddr == 0) {
+ ring = (u32 *)(dev_priv->ring.virtual_start + dev_priv->ring.Size);
+ while (--ring >= (u32 *)dev_priv->ring.virtual_start) {
+ bbaddr = i915_get_bbaddr(dev, ring);
+ if (bbaddr)
+ break;
+ }
+ }
+
+ return bbaddr;
+}
+
+/**
+ * i915_capture_error_state - capture an error record for later analysis
+ * @dev: drm device
+ *
+ * Should be called when an error is detected (either a hang or an error
+ * interrupt) to capture error state from the time of the error. Fills
+ * out a structure which becomes available in debugfs for user level tools
+ * to pick up.
+ */
+static void i915_capture_error_state(struct drm_device *dev)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ struct drm_i915_gem_object *obj_priv;
+ struct drm_i915_error_state *error;
+ struct drm_gem_object *batchbuffer[2];
+ unsigned long flags;
+ u32 bbaddr;
+ int count;
+
+ spin_lock_irqsave(&dev_priv->error_lock, flags);
+ error = dev_priv->first_error;
+ spin_unlock_irqrestore(&dev_priv->error_lock, flags);
+ if (error)
+ return;
+
+ error = kmalloc(sizeof(*error), GFP_ATOMIC);
+ if (!error) {
+ DRM_DEBUG_DRIVER("out of memory, not capturing error state\n");
+ return;
+ }
+
+ error->seqno = i915_get_gem_seqno(dev);
+ error->eir = I915_READ(EIR);
+ error->pgtbl_er = I915_READ(PGTBL_ER);
+ error->pipeastat = I915_READ(PIPEASTAT);
+ error->pipebstat = I915_READ(PIPEBSTAT);
+ error->instpm = I915_READ(INSTPM);
+ if (!IS_I965G(dev)) {
+ error->ipeir = I915_READ(IPEIR);
+ error->ipehr = I915_READ(IPEHR);
+ error->instdone = I915_READ(INSTDONE);
+ error->acthd = I915_READ(ACTHD);
+ error->bbaddr = 0;
+ } else {
+ error->ipeir = I915_READ(IPEIR_I965);
+ error->ipehr = I915_READ(IPEHR_I965);
+ error->instdone = I915_READ(INSTDONE_I965);
+ error->instps = I915_READ(INSTPS);
+ error->instdone1 = I915_READ(INSTDONE1);
+ error->acthd = I915_READ(ACTHD_I965);
+ error->bbaddr = I915_READ64(BB_ADDR);
+ }
+
+ bbaddr = i915_ringbuffer_last_batch(dev);
+
+ /* Grab the current batchbuffer, most likely to have crashed. */
+ batchbuffer[0] = NULL;
+ batchbuffer[1] = NULL;
+ count = 0;
+ list_for_each_entry(obj_priv, &dev_priv->mm.active_list, list) {
+ struct drm_gem_object *obj = obj_priv->obj;
+
+ if (batchbuffer[0] == NULL &&
+ bbaddr >= obj_priv->gtt_offset &&
+ bbaddr < obj_priv->gtt_offset + obj->size)
+ batchbuffer[0] = obj;
+
+ if (batchbuffer[1] == NULL &&
+ error->acthd >= obj_priv->gtt_offset &&
+ error->acthd < obj_priv->gtt_offset + obj->size &&
+ batchbuffer[0] != obj)
+ batchbuffer[1] = obj;
+
+ count++;
+ }
+
+ /* We need to copy these to an anonymous buffer as the simplest
+ * method to avoid being overwritten by userpace.
+ */
+ error->batchbuffer[0] = i915_error_object_create(dev, batchbuffer[0]);
+ error->batchbuffer[1] = i915_error_object_create(dev, batchbuffer[1]);
+
+ /* Record the ringbuffer */
+ error->ringbuffer = i915_error_object_create(dev, dev_priv->ring.ring_obj);
+
+ /* Record buffers on the active list. */
+ error->active_bo = NULL;
+ error->active_bo_count = 0;
+
+ if (count)
+ error->active_bo = kmalloc(sizeof(*error->active_bo)*count,
+ GFP_ATOMIC);
+
+ if (error->active_bo) {
+ int i = 0;
+ list_for_each_entry(obj_priv, &dev_priv->mm.active_list, list) {
+ struct drm_gem_object *obj = obj_priv->obj;
+
+ error->active_bo[i].size = obj->size;
+ error->active_bo[i].name = obj->name;
+ error->active_bo[i].seqno = obj_priv->last_rendering_seqno;
+ error->active_bo[i].gtt_offset = obj_priv->gtt_offset;
+ error->active_bo[i].read_domains = obj->read_domains;
+ error->active_bo[i].write_domain = obj->write_domain;
+ error->active_bo[i].fence_reg = obj_priv->fence_reg;
+ error->active_bo[i].pinned = 0;
+ if (obj_priv->pin_count > 0)
+ error->active_bo[i].pinned = 1;
+ if (obj_priv->user_pin_count > 0)
+ error->active_bo[i].pinned = -1;
+ error->active_bo[i].tiling = obj_priv->tiling_mode;
+ error->active_bo[i].dirty = obj_priv->dirty;
+ error->active_bo[i].purgeable = obj_priv->madv != I915_MADV_WILLNEED;
+
+ if (++i == count)
+ break;
+ }
+ error->active_bo_count = i;
+ }
+
+ do_gettimeofday(&error->time);
+
+ spin_lock_irqsave(&dev_priv->error_lock, flags);
+ if (dev_priv->first_error == NULL) {
+ dev_priv->first_error = error;
+ error = NULL;
+ }
+ spin_unlock_irqrestore(&dev_priv->error_lock, flags);
+
+ if (error)
+ i915_error_state_free(dev, error);
+}
+
+void i915_destroy_error_state(struct drm_device *dev)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ struct drm_i915_error_state *error;
+
+ spin_lock(&dev_priv->error_lock);
+ error = dev_priv->first_error;
+ dev_priv->first_error = NULL;
+ spin_unlock(&dev_priv->error_lock);
+
+ if (error)
+ i915_error_state_free(dev, error);
+}
+
+/**
+ * i915_handle_error - handle an error interrupt
+ * @dev: drm device
+ *
+ * Do some basic checking of regsiter state at error interrupt time and
+ * dump it to the syslog. Also call i915_capture_error_state() to make
+ * sure we get a record and make it available in debugfs. Fire a uevent
+ * so userspace knows something bad happened (should trigger collection
+ * of a ring dump etc.).
+ */
+static void i915_handle_error(struct drm_device *dev, bool wedged)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ u32 eir = I915_READ(EIR);
+ u32 pipea_stats = I915_READ(PIPEASTAT);
+ u32 pipeb_stats = I915_READ(PIPEBSTAT);
+
+ i915_capture_error_state(dev);
+
+ printk(KERN_ERR "render error detected, EIR: 0x%08x\n",
+ eir);
+
+ if (IS_G4X(dev)) {
+ if (eir & (GM45_ERROR_MEM_PRIV | GM45_ERROR_CP_PRIV)) {
+ u32 ipeir = I915_READ(IPEIR_I965);
+
+ printk(KERN_ERR " IPEIR: 0x%08x\n",
+ I915_READ(IPEIR_I965));
+ printk(KERN_ERR " IPEHR: 0x%08x\n",
+ I915_READ(IPEHR_I965));
+ printk(KERN_ERR " INSTDONE: 0x%08x\n",
+ I915_READ(INSTDONE_I965));
+ printk(KERN_ERR " INSTPS: 0x%08x\n",
+ I915_READ(INSTPS));
+ printk(KERN_ERR " INSTDONE1: 0x%08x\n",
+ I915_READ(INSTDONE1));
+ printk(KERN_ERR " ACTHD: 0x%08x\n",
+ I915_READ(ACTHD_I965));
+ I915_WRITE(IPEIR_I965, ipeir);
+ (void)I915_READ(IPEIR_I965);
+ }
+ if (eir & GM45_ERROR_PAGE_TABLE) {
+ u32 pgtbl_err = I915_READ(PGTBL_ER);
+ printk(KERN_ERR "page table error\n");
+ printk(KERN_ERR " PGTBL_ER: 0x%08x\n",
+ pgtbl_err);
+ I915_WRITE(PGTBL_ER, pgtbl_err);
+ (void)I915_READ(PGTBL_ER);
+ }
+ }
+
+ if (IS_I9XX(dev)) {
+ if (eir & I915_ERROR_PAGE_TABLE) {
+ u32 pgtbl_err = I915_READ(PGTBL_ER);
+ printk(KERN_ERR "page table error\n");
+ printk(KERN_ERR " PGTBL_ER: 0x%08x\n",
+ pgtbl_err);
+ I915_WRITE(PGTBL_ER, pgtbl_err);
+ (void)I915_READ(PGTBL_ER);
+ }
+ }
+
+ if (eir & I915_ERROR_MEMORY_REFRESH) {
+ printk(KERN_ERR "memory refresh error\n");
+ printk(KERN_ERR "PIPEASTAT: 0x%08x\n",
+ pipea_stats);
+ printk(KERN_ERR "PIPEBSTAT: 0x%08x\n",
+ pipeb_stats);
+ /* pipestat has already been acked */
+ }
+ if (eir & I915_ERROR_INSTRUCTION) {
+ printk(KERN_ERR "instruction error\n");
+ printk(KERN_ERR " INSTPM: 0x%08x\n",
+ I915_READ(INSTPM));
+ if (!IS_I965G(dev)) {
+ u32 ipeir = I915_READ(IPEIR);
+
+ printk(KERN_ERR " IPEIR: 0x%08x\n",
+ I915_READ(IPEIR));
+ printk(KERN_ERR " IPEHR: 0x%08x\n",
+ I915_READ(IPEHR));
+ printk(KERN_ERR " INSTDONE: 0x%08x\n",
+ I915_READ(INSTDONE));
+ printk(KERN_ERR " ACTHD: 0x%08x\n",
+ I915_READ(ACTHD));
+ I915_WRITE(IPEIR, ipeir);
+ (void)I915_READ(IPEIR);
+ } else {
+ u32 ipeir = I915_READ(IPEIR_I965);
+
+ printk(KERN_ERR " IPEIR: 0x%08x\n",
+ I915_READ(IPEIR_I965));
+ printk(KERN_ERR " IPEHR: 0x%08x\n",
+ I915_READ(IPEHR_I965));
+ printk(KERN_ERR " INSTDONE: 0x%08x\n",
+ I915_READ(INSTDONE_I965));
+ printk(KERN_ERR " INSTPS: 0x%08x\n",
+ I915_READ(INSTPS));
+ printk(KERN_ERR " INSTDONE1: 0x%08x\n",
+ I915_READ(INSTDONE1));
+ printk(KERN_ERR " ACTHD: 0x%08x\n",
+ I915_READ(ACTHD_I965));
+ I915_WRITE(IPEIR_I965, ipeir);
+ (void)I915_READ(IPEIR_I965);
+ }
+ }
+
+ I915_WRITE(EIR, eir);
+ (void)I915_READ(EIR);
+ eir = I915_READ(EIR);
+ if (eir) {
+ /*
+ * some errors might have become stuck,
+ * mask them.
+ */
+ DRM_ERROR("EIR stuck: 0x%08x, masking\n", eir);
+ I915_WRITE(EMR, I915_READ(EMR) | eir);
+ I915_WRITE(IIR, I915_RENDER_COMMAND_PARSER_ERROR_INTERRUPT);
+ }
+
+ if (wedged) {
+ atomic_set(&dev_priv->mm.wedged, 1);
+
+ /*
+ * Wakeup waiting processes so they don't hang
+ */
+ DRM_WAKEUP(&dev_priv->irq_queue);
+ }
+
+ queue_work(dev_priv->wq, &dev_priv->error_work);
+}
+