GuC WOPCM registers are write-once registers. Current driver code accesses these registers without checking the accessibility to these registers which will lead to unpredictable driver behaviors if these registers were touch by other components (such as faulty BIOS code). This patch moves the GuC WOPCM registers updating code into intel_guc_wopcm.c and adds check before and after the update to GuC WOPCM registers so that we can make sure the driver is in a known state before and after writing to these write-once registers. v6: - Made sure module reloading won't bug the kernel while doing locking status checking v7: - Fixed patch format issues v8: - Fixed coding style issue on register lock bit macro definition (Sagar) v9: - Avoided to use redundant !! to cast uint to bool (Chris) - Return error code instead of GEM_BUG_ON for locked with invalid register values case (Sagar) - Updated guc_wopcm_hw_init to use guc_wopcm as first parameter (Michal) - Added code to set and validate the HuC_LOADING_AGENT_GUC bit in GuC WOPCM offset register based on the presence of HuC firmware (Michal) - Use bit fields instead of macros for GuC WOPCM flags (Michal) v10: - Refined variable names, removed reduntant comments (Joonas) - Introduced lockable_reg to handle the write once register write and propagate the write error to caller (Joonas) - Used lockable_reg abstraction to avoid locking bit check on generic i915_reg_t (Michal) - Added log message for error paths (Michal) - Removed hw_updated flag and only relies on real hardware status Cc: Michal Wajdeczko <michal.wajdeczko@xxxxxxxxx> Cc: Chris Wilson <chris@xxxxxxxxxxxxxxxxxx> Cc: Joonas Lahtinen <joonas.lahtinen@xxxxxxxxxxxxxxx> Signed-off-by: Jackie Li <yaodong.li@xxxxxxxxx> --- drivers/gpu/drm/i915/intel_guc_reg.h | 2 + drivers/gpu/drm/i915/intel_guc_wopcm.c | 151 ++++++++++++++++++++++++++++++++- drivers/gpu/drm/i915/intel_guc_wopcm.h | 10 ++- drivers/gpu/drm/i915/intel_uc.c | 9 +- 4 files changed, 162 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_guc_reg.h b/drivers/gpu/drm/i915/intel_guc_reg.h index c482df5..a550078 100644 --- a/drivers/gpu/drm/i915/intel_guc_reg.h +++ b/drivers/gpu/drm/i915/intel_guc_reg.h @@ -68,6 +68,8 @@ #define DMA_GUC_WOPCM_OFFSET _MMIO(0xc340) #define HUC_LOADING_AGENT_VCR (0<<1) #define HUC_LOADING_AGENT_GUC (1<<1) +#define GUC_WOPCM_OFFSET_SHIFT 14 +#define GUC_WOPCM_OFFSET_MASK (0x3ffff << GUC_WOPCM_OFFSET_SHIFT) #define GUC_MAX_IDLE_COUNT _MMIO(0xC3E4) #define HUC_STATUS2 _MMIO(0xD3B0) diff --git a/drivers/gpu/drm/i915/intel_guc_wopcm.c b/drivers/gpu/drm/i915/intel_guc_wopcm.c index 0194266..4043798 100644 --- a/drivers/gpu/drm/i915/intel_guc_wopcm.c +++ b/drivers/gpu/drm/i915/intel_guc_wopcm.c @@ -83,6 +83,12 @@ void intel_guc_wopcm_init_early(struct intel_guc_wopcm *guc_wopcm) guc_wopcm->top = WOPCM_DEFAULT_SIZE; } +static inline +struct intel_guc *guc_wopcm_to_guc(struct intel_guc_wopcm *guc_wopcm) +{ + return container_of(guc_wopcm, struct intel_guc, wopcm); +} + /** * intel_guc_wopcm_init() - Initialize the GuC WOPCM. * @guc_wopcm: pointer to intel_guc_wopcm. @@ -101,13 +107,13 @@ void intel_guc_wopcm_init_early(struct intel_guc_wopcm *guc_wopcm) int intel_guc_wopcm_init(struct intel_guc_wopcm *guc_wopcm, u32 guc_fw_size, u32 huc_fw_size) { - struct intel_guc *guc = - container_of(guc_wopcm, struct intel_guc, wopcm); + struct intel_guc *guc = guc_wopcm_to_guc(guc_wopcm); u32 reserved = context_reserved_size(guc); u32 offset, size, top; int err; GEM_BUG_ON(!guc_fw_size); + GEM_BUG_ON(guc->wopcm.valid); offset = huc_fw_size + WOPCM_RESERVED_SIZE; @@ -138,6 +144,7 @@ int intel_guc_wopcm_init(struct intel_guc_wopcm *guc_wopcm, u32 guc_fw_size, guc->wopcm.offset = offset; guc->wopcm.size = size; guc->wopcm.top = top; + guc->wopcm.load_huc_fw = huc_fw_size > 0; err = guc_wopcm_size_check(guc, huc_fw_size); if (err) { @@ -152,3 +159,143 @@ int intel_guc_wopcm_init(struct intel_guc_wopcm *guc_wopcm, u32 guc_fw_size, return 0; } + +struct lockable_reg { + const char *name; + struct intel_guc *guc; + i915_reg_t reg; + u32 reg_val; + u32 lock_bit; + bool (*validate)(struct lockable_reg *lreg, u32 reg_val, u32 new_val); +}; + +static inline u32 lockable_reg_read(struct lockable_reg *lreg) +{ + struct drm_i915_private *dev_priv = guc_to_i915(lreg->guc); + + lreg->reg_val = I915_READ(lreg->reg); + + return lreg->reg_val; +} + +static inline bool lockable_reg_validate(struct lockable_reg *lreg, u32 new_val) +{ + GEM_BUG_ON(!lreg->validate); + + return lreg->validate(lreg, lreg->reg_val, new_val); +} + +static inline bool lockable_reg_locked(struct lockable_reg *lreg) +{ + u32 reg_val = lockable_reg_read(lreg); + + return reg_val & lreg->lock_bit; +} + +static inline bool lockable_reg_locked_and_valid(struct lockable_reg *lreg, + u32 new_val) +{ + if (lockable_reg_locked(lreg)) { + if (lockable_reg_validate(lreg, new_val)) + return true; + + return false; + } + + return false; +} + +static inline bool lockable_reg_write(struct lockable_reg *lreg, u32 val) +{ + struct drm_i915_private *dev_priv = guc_to_i915(lreg->guc); + + /* + * Write-once register was locked which may happen with either a faulty + * BIOS code or driver module reloading. We should still return success + * for the write if the register was locked with a valid value. + */ + if (lockable_reg_locked(lreg)) { + if (lockable_reg_validate(lreg, val)) + goto out; + + DRM_DEBUG_DRIVER("Register %s was locked with invalid value\n", + lreg->name); + + return false; + } + + I915_WRITE(lreg->reg, val); + + if (!lockable_reg_locked_and_valid(lreg, val)) { + DRM_DEBUG_DRIVER("Failed to lock Register %s\n", lreg->name); + return false; + } + +out: + DRM_DEBUG_DRIVER("Write-once register %s locked with valid value %#x\n", + lreg->name, lreg->reg_val); + + return true; +} + +static inline bool size_reg_value_validate(struct lockable_reg *lreg, + u32 reg_val, u32 new_val) +{ + u32 reg_size = reg_val & GUC_WOPCM_SIZE_MASK; + u32 target_size = new_val & GUC_WOPCM_SIZE_MASK; + + return reg_size == target_size; +} + +static inline bool offset_reg_value_validate(struct lockable_reg *lreg, + u32 reg_val, u32 new_val) +{ + u32 mask = GUC_WOPCM_OFFSET_MASK | HUC_LOADING_AGENT_GUC; + u32 reg_offset = reg_val & mask; + u32 target_offset = new_val & mask; + + return reg_offset == target_offset; +} + +#define DEFINE_LOCKABLE_REG(var, rg, lb, func) \ + struct lockable_reg var = { \ + .name = #rg, \ + .guc = guc_wopcm_to_guc(guc_wopcm), \ + .reg = rg, \ + .reg_val = 0, \ + .lock_bit = lb, \ + .validate = func, \ + } + +/** + * intel_guc_wopcm_init_hw() - Setup GuC WOPCM registers. + * @guc_wopcm: pointer to intel_guc_wopcm. + * + * Setup the GuC WOPCM size and offset registers with the stored values. It will + * also check the registers locking status to determine whether these registers + * are unlocked and can be updated. + * + * Return: 0 on success. -EIO if registers were locked with incorrect values. + */ +int intel_guc_wopcm_init_hw(struct intel_guc_wopcm *guc_wopcm) +{ + DEFINE_LOCKABLE_REG(guc_wopcm_size_reg, GUC_WOPCM_SIZE, BIT(0), + size_reg_value_validate); + DEFINE_LOCKABLE_REG(guc_wopcm_offset_reg, DMA_GUC_WOPCM_OFFSET, BIT(0), + offset_reg_value_validate); + u32 reg_val; + + GEM_BUG_ON(!guc_wopcm->valid); + + reg_val = guc_wopcm->size; + if (!lockable_reg_write(&guc_wopcm_size_reg, reg_val)) + return -EIO; + + reg_val = guc_wopcm->offset; + if (guc_wopcm->load_huc_fw) + reg_val |= HUC_LOADING_AGENT_GUC; + if (!lockable_reg_write(&guc_wopcm_offset_reg, reg_val)) + return -EIO; + + return 0; +} diff --git a/drivers/gpu/drm/i915/intel_guc_wopcm.h b/drivers/gpu/drm/i915/intel_guc_wopcm.h index 13fcab6..89dd44c 100644 --- a/drivers/gpu/drm/i915/intel_guc_wopcm.h +++ b/drivers/gpu/drm/i915/intel_guc_wopcm.h @@ -66,7 +66,8 @@ struct intel_guc; * @offset: GuC WOPCM offset from the WOPCM base. * @size: size of GuC WOPCM for GuC firmware. * @top: start of the non-GuC WOPCM memory. - * @valid: whether this structure contains valid (1-valid, 0-invalid) info. + * @valid: whether the values in this struct are valid. + * @load_huc_fw: whether need to configure GuC to load HuC firmware. * * We simply use this structure to track the GuC use of WOPCM. The layout of * WOPCM would be defined by writing to GuC WOPCM offset and size registers. @@ -75,11 +76,14 @@ struct intel_guc_wopcm { u32 offset; u32 size; u32 top; - u32 valid; + + /* GuC WOPCM flags below. */ + u32 valid:1; + u32 load_huc_fw:1; }; void intel_guc_wopcm_init_early(struct intel_guc_wopcm *guc_wopcm); int intel_guc_wopcm_init(struct intel_guc_wopcm *guc_wopcm, u32 guc_size, u32 huc_size); - +int intel_guc_wopcm_init_hw(struct intel_guc_wopcm *guc_wopcm); #endif diff --git a/drivers/gpu/drm/i915/intel_uc.c b/drivers/gpu/drm/i915/intel_uc.c index c842f36..8938096 100644 --- a/drivers/gpu/drm/i915/intel_uc.c +++ b/drivers/gpu/drm/i915/intel_uc.c @@ -343,14 +343,13 @@ int intel_uc_init_hw(struct drm_i915_private *dev_priv) GEM_BUG_ON(!HAS_GUC(dev_priv)); + ret = intel_guc_wopcm_init_hw(&guc->wopcm); + if (ret) + goto err_out; + guc_disable_communication(guc); gen9_reset_guc_interrupts(dev_priv); - /* init WOPCM */ - I915_WRITE(GUC_WOPCM_SIZE, guc->wopcm.size); - I915_WRITE(DMA_GUC_WOPCM_OFFSET, - guc->wopcm.offset | HUC_LOADING_AGENT_GUC); - /* WaEnableuKernelHeaderValidFix:skl */ /* WaEnableGuCBootHashCheckNotSet:skl,bxt,kbl */ if (IS_GEN9(dev_priv)) -- 2.7.4 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx