On 04/05, Robert Bragg wrote: > Enables access to OA unit metrics for BDW, CHV, SKL and BXT which all > share (more-or-less) the same OA unit design. > > Of particular note in comparison to Haswell: some OA unit HW config > state has become per-context state and as a consequence it is somewhat > more complicated to manage synchronous state changes from the cpu while > there's no guarantee of what context (if any) is currently actively > running on the gpu. > > The periodic sampling frequency which can be particularly useful for > system-wide analysis (as opposed to command stream synchronised > MI_REPORT_PERF_COUNT commands) is perhaps the most surprising state to > have become per-context save and restored (while the OABUFFER > destination is still a shared, system-wide resource). > > This support for gen8+ takes care to consider a number of timing > challenges involved in synchronously updating per-context state > primarily by programming all config state from the cpu and updating all > current and saved contexts synchronously while the OA unit is still > disabled. > > The driver intentionally avoids depending on command streamer > programming to update OA state considering the lack of synchronization > between the automatic loading of OACTXCONTROL state (that includes the > periodic sampling state and enable state) on context restore and the > parsing of any general purpose BB the driver can control. I.e. this > implementation is careful to avoid the possibility of a context restore > temporarily enabling any out-of-date periodic sampling state. In > addition to the risk of transiently-out-of-date state being loaded > automatically; there are also internal HW latencies involved in the > loading of MUX configurations which would be difficult to account for > from the command streamer (and we only want to enable the unit when once > the MUX configuration is complete). > > Since the Gen8+ OA unit design no longer supports clock gating the unit > off for a single given context (which effectively stopped any progress > of counters while any other context was running) and instead supports > tagging OA reports with a context ID for filtering on the CPU, it means > we can no longer hide the system-wide progress of counters from a > non-privileged application only interested in metrics for its own > context. Although we could theoretically try and subtract the progress > of other contexts before forwarding reports via read() we aren't in a > position to filter reports captured via MI_REPORT_PERF_COUNT commands. > As a result, for Gen8+, we always require the > dev.i915.perf_stream_paranoid to be unset for any access to OA metrics > if not root. > > Signed-off-by: Robert Bragg <robert@xxxxxxxxxxxxx> > --- > drivers/gpu/drm/i915/i915_drv.h | 45 +- > drivers/gpu/drm/i915/i915_gem_context.h | 1 + > drivers/gpu/drm/i915/i915_perf.c | 938 +++++++++++++++++++++++++++++--- > drivers/gpu/drm/i915/i915_reg.h | 22 + > drivers/gpu/drm/i915/intel_lrc.c | 5 + > include/uapi/drm/i915_drm.h | 19 +- > 6 files changed, 937 insertions(+), 93 deletions(-) > > diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h > index 9c37b73ac7ac..3a22b6fd0ee6 100644 > --- a/drivers/gpu/drm/i915/i915_drv.h > +++ b/drivers/gpu/drm/i915/i915_drv.h > @@ -2067,9 +2067,17 @@ struct i915_oa_ops { > void (*init_oa_buffer)(struct drm_i915_private *dev_priv); > > /** > - * @enable_metric_set: Applies any MUX configuration to set up the > - * Boolean and Custom (B/C) counters that are part of the counter > - * reports being sampled. May apply system constraints such as > + * @select_metric_set: The auto generated code that checks whether a > + * requested OA config is applicable to the system and if so sets up > + * the mux, oa and flex eu register config pointers according to the > + * current dev_priv->perf.oa.metrics_set. > + */ > + int (*select_metric_set)(struct drm_i915_private *dev_priv); > + > + /** > + * @enable_metric_set: Selects and applies any MUX configuration to set > + * up the Boolean and Custom (B/C) counters that are part of the > + * counter reports being sampled. May apply system constraints such as > * disabling EU clock gating as required. > */ > int (*enable_metric_set)(struct drm_i915_private *dev_priv); > @@ -2100,20 +2108,13 @@ struct i915_oa_ops { > size_t *offset); > > /** > - * @oa_buffer_check: Check for OA buffer data + update tail > - * > - * This is either called via fops or the poll check hrtimer (atomic > - * ctx) without any locks taken. > + * @oa_hw_tail_read: read the OA tail pointer register > * > - * It's safe to read OA config state here unlocked, assuming that this > - * is only called while the stream is enabled, while the global OA > - * configuration can't be modified. > - * > - * Efficiency is more important than avoiding some false positives > - * here, which will be handled gracefully - likely resulting in an > - * %EAGAIN error for userspace. > + * In particular this enables us to share all the fiddly code for > + * handling the OA unit tail pointer race that affects multiple > + * generations. > */ > - bool (*oa_buffer_check)(struct drm_i915_private *dev_priv); > + u32 (*oa_hw_tail_read)(struct drm_i915_private *dev_priv); > }; > > struct intel_cdclk_state { > @@ -2475,6 +2476,7 @@ struct drm_i915_private { > struct { > struct i915_vma *vma; > u8 *vaddr; > + u32 last_ctx_id; > int format; > int format_size; > > @@ -2544,6 +2546,14 @@ struct drm_i915_private { > } oa_buffer; > > u32 gen7_latched_oastatus1; > + u32 ctx_oactxctrl_offset; > + u32 ctx_flexeu0_offset; > + > + /* The RPT_ID/reason field for Gen8+ includes a bit > + * to determine if the CTX ID in the report is valid > + * but the specific bit differs between Gen 8 and 9 > + */ > + u32 gen8_valid_ctx_bit; > > struct i915_oa_ops ops; > const struct i915_oa_format *oa_formats; > @@ -2854,6 +2864,8 @@ intel_info(const struct drm_i915_private *dev_priv) > #define IS_KBL_ULX(dev_priv) (INTEL_DEVID(dev_priv) == 0x590E || \ > INTEL_DEVID(dev_priv) == 0x5915 || \ > INTEL_DEVID(dev_priv) == 0x591E) > +#define IS_SKL_GT2(dev_priv) (IS_SKYLAKE(dev_priv) && \ > + (INTEL_DEVID(dev_priv) & 0x00F0) == 0x0010) > #define IS_SKL_GT3(dev_priv) (IS_SKYLAKE(dev_priv) && \ > (INTEL_DEVID(dev_priv) & 0x00F0) == 0x0020) > #define IS_SKL_GT4(dev_priv) (IS_SKYLAKE(dev_priv) && \ > @@ -3605,6 +3617,9 @@ i915_gem_context_lookup_timeline(struct i915_gem_context *ctx, > > int i915_perf_open_ioctl(struct drm_device *dev, void *data, > struct drm_file *file); > +void i915_oa_update_reg_state(struct intel_engine_cs *engine, > + struct i915_gem_context *ctx, > + uint32_t *reg_state); > > /* i915_gem_evict.c */ > int __must_check i915_gem_evict_something(struct i915_address_space *vm, > diff --git a/drivers/gpu/drm/i915/i915_gem_context.h b/drivers/gpu/drm/i915/i915_gem_context.h > index 4af2ab94558b..3f4ce73bea43 100644 > --- a/drivers/gpu/drm/i915/i915_gem_context.h > +++ b/drivers/gpu/drm/i915/i915_gem_context.h > @@ -151,6 +151,7 @@ struct i915_gem_context { > u64 lrc_desc; > int pin_count; > bool initialised; > + atomic_t oa_state_dirty; > } engine[I915_NUM_ENGINES]; > > /** ring_size: size for allocating the per-engine ring buffer */ > diff --git a/drivers/gpu/drm/i915/i915_perf.c b/drivers/gpu/drm/i915/i915_perf.c > index 3277a52ce98e..98eb6415b63a 100644 > --- a/drivers/gpu/drm/i915/i915_perf.c > +++ b/drivers/gpu/drm/i915/i915_perf.c > @@ -196,6 +196,12 @@ > > #include "i915_drv.h" > #include "i915_oa_hsw.h" > +#include "i915_oa_bdw.h" > +#include "i915_oa_chv.h" > +#include "i915_oa_sklgt2.h" > +#include "i915_oa_sklgt3.h" > +#include "i915_oa_sklgt4.h" > +#include "i915_oa_bxt.h" > > /* HW requires this to be a power of two, between 128k and 16M, though driver > * is currently generally designed assuming the largest 16M size is used such > @@ -215,7 +221,7 @@ > * > * Although this can be observed explicitly while copying reports to userspace > * by checking for a zeroed report-id field in tail reports, we want to account > - * for this earlier, as part of the _oa_buffer_check to avoid lots of redundant > + * for this earlier, as part of the oa_buffer_check to avoid lots of redundant > * read() attempts. > * > * In effect we define a tail pointer for reading that lags the real tail > @@ -237,7 +243,7 @@ > * indicates that an updated tail pointer is needed. > * > * Most of the implementation details for this workaround are in > - * gen7_oa_buffer_check_unlocked() and gen7_appand_oa_reports() > + * oa_buffer_check_unlocked() and _append_oa_reports() > * > * Note for posterity: previously the driver used to define an effective tail > * pointer that lagged the real pointer by a 'tail margin' measured in bytes > @@ -272,6 +278,13 @@ static u32 i915_perf_stream_paranoid = true; > > #define INVALID_CTX_ID 0xffffffff > > +/* On Gen8+ automatically triggered OA reports include a 'reason' field... */ > +#define OAREPORT_REASON_MASK 0x3f > +#define OAREPORT_REASON_SHIFT 19 > +#define OAREPORT_REASON_TIMER (1<<0) > +#define OAREPORT_REASON_CTX_SWITCH (1<<3) > +#define OAREPORT_REASON_CLK_RATIO (1<<5) > + > > /* For sysctl proc_dointvec_minmax of i915_oa_max_sample_rate > * > @@ -303,6 +316,13 @@ static struct i915_oa_format hsw_oa_formats[I915_OA_FORMAT_MAX] = { > [I915_OA_FORMAT_C4_B8] = { 7, 64 }, > }; > > +static struct i915_oa_format gen8_plus_oa_formats[I915_OA_FORMAT_MAX] = { > + [I915_OA_FORMAT_A12] = { 0, 64 }, > + [I915_OA_FORMAT_A12_B8_C8] = { 2, 128 }, > + [I915_OA_FORMAT_A32u40_A4u32_B8_C8] = { 5, 256 }, > + [I915_OA_FORMAT_C4_B8] = { 7, 64 }, > +}; > + > #define SAMPLE_OA_REPORT (1<<0) > > /** > @@ -332,8 +352,20 @@ struct perf_open_properties { > int oa_period_exponent; > }; > > +static u32 gen8_oa_hw_tail_read(struct drm_i915_private *dev_priv) > +{ > + return I915_READ(GEN8_OATAILPTR) & GEN8_OATAILPTR_MASK; > +} > + > +static u32 gen7_oa_hw_tail_read(struct drm_i915_private *dev_priv) > +{ > + u32 oastatus1 = I915_READ(GEN7_OASTATUS1); > + > + return oastatus1 & GEN7_OASTATUS1_TAIL_MASK; > +} > + > /** > - * gen7_oa_buffer_check_unlocked - check for data and update tail ptr state > + * oa_buffer_check_unlocked - check for data and update tail ptr state > * @dev_priv: i915 device instance > * > * This is either called via fops (for blocking reads in user ctx) or the poll > @@ -356,12 +388,11 @@ struct perf_open_properties { > * > * Returns: %true if the OA buffer contains data, else %false > */ > -static bool gen7_oa_buffer_check_unlocked(struct drm_i915_private *dev_priv) > +static bool oa_buffer_check_unlocked(struct drm_i915_private *dev_priv) > { > int report_size = dev_priv->perf.oa.oa_buffer.format_size; > unsigned long flags; > unsigned int aged_idx; > - u32 oastatus1; > u32 head, hw_tail, aged_tail, aging_tail; > u64 now; > > @@ -381,8 +412,7 @@ static bool gen7_oa_buffer_check_unlocked(struct drm_i915_private *dev_priv) > aged_tail = dev_priv->perf.oa.oa_buffer.tails[aged_idx].offset; > aging_tail = dev_priv->perf.oa.oa_buffer.tails[!aged_idx].offset; > > - oastatus1 = I915_READ(GEN7_OASTATUS1); > - hw_tail = oastatus1 & GEN7_OASTATUS1_TAIL_MASK; > + hw_tail = dev_priv->perf.oa.ops.oa_hw_tail_read(dev_priv); > > /* The tail pointer increases in 64 byte increments, > * not in report_size steps... > @@ -404,6 +434,7 @@ static bool gen7_oa_buffer_check_unlocked(struct drm_i915_private *dev_priv) > if (aging_tail != INVALID_TAIL_PTR && > ((now - dev_priv->perf.oa.oa_buffer.aging_timestamp) > > OA_TAIL_MARGIN_NSEC)) { > + > aged_idx ^= 1; > dev_priv->perf.oa.oa_buffer.aged_tail_idx = aged_idx; > > @@ -553,6 +584,284 @@ static int append_oa_sample(struct i915_perf_stream *stream, > * > * Returns: 0 on success, negative error code on failure. > */ > +static int gen8_append_oa_reports(struct i915_perf_stream *stream, > + char __user *buf, > + size_t count, > + size_t *offset) > +{ > + struct drm_i915_private *dev_priv = stream->dev_priv; > + int report_size = dev_priv->perf.oa.oa_buffer.format_size; > + u8 *oa_buf_base = dev_priv->perf.oa.oa_buffer.vaddr; > + u32 gtt_offset = i915_ggtt_offset(dev_priv->perf.oa.oa_buffer.vma); > + u32 mask = (OA_BUFFER_SIZE - 1); > + size_t start_offset = *offset; > + unsigned long flags; > + unsigned int aged_tail_idx; > + u32 head, tail; > + u32 taken; > + int ret = 0; > + > + if (WARN_ON(!stream->enabled)) > + return -EIO; > + > + spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); > + > + head = dev_priv->perf.oa.oa_buffer.head; > + aged_tail_idx = dev_priv->perf.oa.oa_buffer.aged_tail_idx; > + tail = dev_priv->perf.oa.oa_buffer.tails[aged_tail_idx].offset; > + > + spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); > + > + /* An invalid tail pointer here means we're still waiting for the poll > + * hrtimer callback to give us a pointer > + */ > + if (tail == INVALID_TAIL_PTR) > + return -EAGAIN; > + > + /* NB: oa_buffer.head/tail include the gtt_offset which we don't want > + * while indexing relative to oa_buf_base. > + */ > + head -= gtt_offset; > + tail -= gtt_offset; > + > + /* An out of bounds or misaligned head or tail pointer implies a driver > + * bug since we validate + align the tail pointers we read from the > + * hardware and we are in full control of the head pointer which should > + * only be incremented by multiples of the report size (notably also > + * all a power of two). > + */ > + if (WARN_ONCE(head > OA_BUFFER_SIZE || head % report_size || > + tail > OA_BUFFER_SIZE || tail % report_size, > + "Inconsistent OA buffer pointers: head = %u, tail = %u\n", > + head, tail)) > + return -EIO; > + > + > + for (/* none */; > + (taken = OA_TAKEN(tail, head)); > + head = (head + report_size) & mask) { > + u8 *report = oa_buf_base + head; > + u32 *report32 = (void *)report; > + u32 ctx_id; > + u32 reason; > + > + /* All the report sizes factor neatly into the buffer > + * size so we never expect to see a report split > + * between the beginning and end of the buffer. > + * > + * Given the initial alignment check a misalignment > + * here would imply a driver bug that would result > + * in an overrun. > + */ > + if (WARN_ON((OA_BUFFER_SIZE - head) < report_size)) { > + DRM_ERROR("Spurious OA head ptr: non-integral report offset\n"); > + break; > + } > + > + /* The reason field includes flags identifying what > + * triggered this specific report (mostly timer > + * triggered or e.g. due to a context switch). > + * > + * This field is never expected to be zero so we can > + * check that the report isn't invalid before copying > + * it to userspace... > + */ > + reason = ((report32[0] >> OAREPORT_REASON_SHIFT) & > + OAREPORT_REASON_MASK); > + if (reason == 0) { > + if (__ratelimit(&dev_priv->perf.oa.spurious_report_rs)) > + DRM_NOTE("Skipping spurious, invalid OA report\n"); > + continue; > + } > + > + /* XXX: Just keep the lower 21 bits for now since I'm not > + * entirely sure if the HW touches any of the higher bits in > + * this field > + */ > + ctx_id = report32[2] & 0x1fffff; > + > + /* Squash whatever is in the CTX_ID field if it's > + * marked as invalid to be sure we avoid > + * false-positive, single-context filtering below... > + */ > + if (!(report32[0] & dev_priv->perf.oa.gen8_valid_ctx_bit)) > + ctx_id = report32[2] = INVALID_CTX_ID; > + > + /* NB: For Gen 8 the OA unit no longer supports clock gating > + * off for a specific context and the kernel can't securely > + * stop the counters from updating as system-wide / global > + * values. > + * > + * Automatic reports now include a context ID so reports can be > + * filtered on the cpu but it's not worth trying to > + * automatically subtract/hide counter progress for other > + * contexts while filtering since we can't stop userspace > + * issuing MI_REPORT_PERF_COUNT commands which would still > + * provide a side-band view of the real values. > + * > + * To allow userspace (such as Mesa/GL_INTEL_performance_query) > + * to normalize counters for a single filtered context then it > + * needs be forwarded bookend context-switch reports so that it > + * can track switches in between MI_REPORT_PERF_COUNT commands > + * and can itself subtract/ignore the progress of counters > + * associated with other contexts. Note that the hardware > + * automatically triggers reports when switching to a new > + * context which are tagged with the ID of the newly active > + * context. To avoid the complexity (and likely fragility) of > + * reading ahead while parsing reports to try and minimize > + * forwarding redundant context switch reports (i.e. between > + * other, unrelated contexts) we simply elect to forward them > + * all. > + * > + * We don't rely solely on the reason field to identify context > + * switches since it's not-uncommon for periodic samples to > + * identify a switch before any 'context switch' report. > + */ > + if (!dev_priv->perf.oa.exclusive_stream->ctx || > + dev_priv->perf.oa.specific_ctx_id == ctx_id || > + (dev_priv->perf.oa.oa_buffer.last_ctx_id == > + dev_priv->perf.oa.specific_ctx_id) || > + reason & OAREPORT_REASON_CTX_SWITCH) { > + > + /* While filtering for a single context we avoid > + * leaking the IDs of other contexts. > + */ > + if (dev_priv->perf.oa.exclusive_stream->ctx && > + dev_priv->perf.oa.specific_ctx_id != ctx_id) { > + report32[2] = INVALID_CTX_ID; > + } > + > + ret = append_oa_sample(stream, buf, count, offset, > + report); > + if (ret) > + break; > + > + dev_priv->perf.oa.oa_buffer.last_ctx_id = ctx_id; > + } > + > + /* The above reason field sanity check is based on > + * the assumption that the OA buffer is initially > + * zeroed and we reset the field after copying so the > + * check is still meaningful once old reports start > + * being overwritten. > + */ > + report32[0] = 0; > + } > + > + if (start_offset != *offset) { > + spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); > + > + /* We removed the gtt_offset for the copy loop above, indexing > + * relative to oa_buf_base so put back here... > + */ > + head += gtt_offset; > + > + I915_WRITE(GEN8_OAHEADPTR, head & GEN8_OAHEADPTR_MASK); > + dev_priv->perf.oa.oa_buffer.head = head; > + > + spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); > + } > + > + return ret; > +} > + > +/** > + * gen8_oa_read - copy status records then buffered OA reports > + * @stream: An i915-perf stream opened for OA metrics > + * @buf: destination buffer given by userspace > + * @count: the number of bytes userspace wants to read > + * @offset: (inout): the current position for writing into @buf > + * > + * Checks OA unit status registers and if necessary appends corresponding > + * status records for userspace (such as for a buffer full condition) and then > + * initiate appending any buffered OA reports. > + * > + * Updates @offset according to the number of bytes successfully copied into > + * the userspace buffer. > + * > + * NB: some data may be successfully copied to the userspace buffer > + * even if an error is returned, and this is reflected in the > + * updated @offset. > + * > + * Returns: zero on success or a negative error code > + */ > +static int gen8_oa_read(struct i915_perf_stream *stream, > + char __user *buf, > + size_t count, > + size_t *offset) > +{ > + struct drm_i915_private *dev_priv = stream->dev_priv; > + u32 oastatus; > + int ret; > + > + if (WARN_ON(!dev_priv->perf.oa.oa_buffer.vaddr)) > + return -EIO; > + > + oastatus = I915_READ(GEN8_OASTATUS); > + > + /* We treat OABUFFER_OVERFLOW as a significant error: > + * > + * Although theoretically we could handle this more gracefully > + * sometimes, some Gens don't correctly suppress certain > + * automatically triggered reports in this condition and so we > + * have to assume that old reports are now being trampled > + * over. > + * > + * Considering how we don't currently give userspace control > + * over the OA buffer size and always configure a large 16MB > + * buffer, then a buffer overflow does anyway likely indicate > + * that something has gone quite badly wrong. > + */ > + if (oastatus & GEN8_OASTATUS_OABUFFER_OVERFLOW) { > + ret = append_oa_status(stream, buf, count, offset, > + DRM_I915_PERF_RECORD_OA_BUFFER_LOST); > + if (ret) > + return ret; > + > + DRM_DEBUG("OA buffer overflow (exponent = %d): force restart\n", > + dev_priv->perf.oa.period_exponent); > + > + dev_priv->perf.oa.ops.oa_disable(dev_priv); > + dev_priv->perf.oa.ops.oa_enable(dev_priv); > + > + /* Note: .oa_enable() is expected to re-init the oabuffer > + * and reset GEN8_OASTATUS for us > + */ > + oastatus = I915_READ(GEN8_OASTATUS); > + } > + > + if (oastatus & GEN8_OASTATUS_REPORT_LOST) { > + ret = append_oa_status(stream, buf, count, offset, > + DRM_I915_PERF_RECORD_OA_REPORT_LOST); if ret != 0 shouldn't we bail? > + if (ret == 0) { > + I915_WRITE(GEN8_OASTATUS, > + oastatus & ~GEN8_OASTATUS_REPORT_LOST); > + } > + } > + > + return gen8_append_oa_reports(stream, buf, count, offset); > +} <SNIP> > +/* > + * From Broadwell PRM, 3D-Media-GPGPU -> Register State Context > + * > + * MMIO reads or writes to any of the registers listed in the > + * “Register State Context image” subsections through HOST/IA > + * MMIO interface for debug purposes must follow the steps below: > + * > + * - SW should set the Force Wakeup bit to prevent GT from entering C6. > + * - Write 0x2050[31:0] = 0x00010001 (disable sequence). > + * - Disable IDLE messaging in CS (Write 0x2050[31:0] = 0x00010001). > + * - BDW: Poll/Wait for register bits of 0x22AC[6:0] turn to 0x30 value. > + * - SKL+: Poll/Wait for register bits of 0x22A4[6:0] turn to 0x30 value. > + * - Read/Write to desired MMIO registers. > + * - Enable IDLE messaging in CS (Write 0x2050[31:0] = 0x00010000). > + * - Force Wakeup bit should be reset to enable C6 entry. > + * > + * XXX: don't nest or overlap calls to this API, it has no ref > + * counting to track how many entities require the RCS to be > + * blocked from being idle. > + */ > +static int gen8_begin_ctx_mmio(struct drm_i915_private *dev_priv) > +{ > + i915_reg_t fsm_reg = dev_priv->info.gen > 8 ? INTEL_GEN? > + GEN9_RCS_FE_FSM2 : GEN6_RCS_PWR_FSM; > + int ret = 0; > + > + /* There's only no active context while idle in execlist mode > + * (though we shouldn't be using this in any other case) > + */ So there may not be an active context? What happens if we do the mmio and there is no active context? > + if (WARN_ON(!i915.enable_execlists)) > + return -EINVAL; > + > + intel_uncore_forcewake_get(dev_priv, FORCEWAKE_RENDER); > + > + I915_WRITE(GEN6_RC_SLEEP_PSMI_CONTROL, > + _MASKED_BIT_ENABLE(GEN6_PSMI_SLEEP_MSG_DISABLE)); > + > + /* Note: we don't currently have a good handle on the maximum > + * latency for this wake up so while we only need to hold rcs > + * busy from process context we at least keep the waiting > + * interruptible... > + */ > + ret = wait_for((I915_READ(fsm_reg) & 0x3f) == 0x30, 50); intel_wait_for_register(dev_priv, fsm_reg, 0x3f, 0x30, 50), and maybe a DRM_ERROR timeout message? _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx