In current code, we only compare the locked WOPCM register values with the calculated values. However, we can continue loading GuC/HuC firmware if the locked (or partially locked) values were valid for current GuC/HuC firmware sizes. This patch added a new code path to verify whether the locked register values can be used for GuC/HuC firmware loading, it will recalculate the verify the new values if these registers were partially locked, so that we won't fail the GuC/HuC firmware loading even if the locked register values are different from the calculated ones. v2: - Update WOPCM register only if it's not locked v4: - Fixed typo in code comments (Michal) - Refined function names and parameters (Michal) - Avoided duplicated function calls (Michal) - Refined register updating ordering (Michal) Signed-off-by: Jackie Li <yaodong.li@xxxxxxxxx> Cc: Michal Wajdeczko <michal.wajdeczko@xxxxxxxxx> Cc: Sagar Arun Kamble <sagar.a.kamble@xxxxxxxxx> Cc: Michal Winiarski <michal.winiarski@xxxxxxxxx> Cc: John Spotswood <john.a.spotswood@xxxxxxxxx> Cc: Joonas Lahtinen <joonas.lahtinen@xxxxxxxxxxxxxxx> Reviewed-by: John Spotswood <john.a.spotswood@xxxxxxxxx> --- drivers/gpu/drm/i915/intel_wopcm.c | 251 ++++++++++++++++++++++++++++--------- 1 file changed, 194 insertions(+), 57 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_wopcm.c b/drivers/gpu/drm/i915/intel_wopcm.c index b1c08ca..8fdcc48 100644 --- a/drivers/gpu/drm/i915/intel_wopcm.c +++ b/drivers/gpu/drm/i915/intel_wopcm.c @@ -51,6 +51,8 @@ /* 8KB from GUC_WOPCM_RESERVED is reserved for GuC stack. */ #define GUC_WOPCM_STACK_RESERVED (8 * 1024) +/* GuC WOPCM Size value needs to be aligned to 4KB. */ +#define GUC_WOPCM_SIZE_ALIGNMENT (1UL << GUC_WOPCM_SIZE_SHIFT) /* GuC WOPCM Offset value needs to be aligned to 16KB. */ #define GUC_WOPCM_OFFSET_ALIGNMENT (1UL << GUC_WOPCM_OFFSET_SHIFT) @@ -86,60 +88,104 @@ static inline u32 context_reserved_size(struct drm_i915_private *i915) return 0; } -static inline int gen9_check_dword_gap(u32 guc_wopcm_base, u32 guc_wopcm_size) +static inline int gen9_check_dword_gap(struct intel_wopcm *wopcm) { - u32 offset; + u32 gen9_min_guc_wopcm_size = wopcm->guc.base + GEN9_GUC_WOPCM_OFFSET + + sizeof(u32); /* * GuC WOPCM size shall be at least a dword larger than the offset from * WOPCM base (GuC WOPCM offset from WOPCM base + GEN9_GUC_WOPCM_OFFSET) * due to hardware limitation on Gen9. */ - offset = guc_wopcm_base + GEN9_GUC_WOPCM_OFFSET; - if (offset > guc_wopcm_size || - (guc_wopcm_size - offset) < sizeof(u32)) { + if (wopcm->guc.size < gen9_min_guc_wopcm_size) { DRM_ERROR("GuC WOPCM size %uKiB is too small. %uKiB needed.\n", - guc_wopcm_size / 1024, - (u32)(offset + sizeof(u32)) / 1024); + wopcm->guc.size / 1024, + gen9_min_guc_wopcm_size / 1024); return -E2BIG; } return 0; } -static inline int gen9_check_huc_fw_fits(u32 guc_wopcm_size, u32 huc_fw_size) +static inline int gen9_check_huc_fw_fits(struct intel_wopcm *wopcm, + u32 huc_fw_size) { + u32 available_guc_wopcm = wopcm->guc.size - GUC_WOPCM_RESERVED; + /* * On Gen9 & CNL A0, hardware requires the total available GuC WOPCM * size to be larger than or equal to HuC firmware size. Otherwise, * firmware uploading would fail. */ - if (huc_fw_size > guc_wopcm_size - GUC_WOPCM_RESERVED) { + if (huc_fw_size > available_guc_wopcm) { DRM_ERROR("HuC FW (%uKiB) won't fit in GuC WOPCM (%uKiB).\n", - huc_fw_size / 1024, - (guc_wopcm_size - GUC_WOPCM_RESERVED) / 1024); + huc_fw_size / 1024, available_guc_wopcm / 1024); return -E2BIG; } return 0; } -static inline int check_hw_restriction(struct drm_i915_private *i915, - u32 guc_wopcm_base, u32 guc_wopcm_size, +static inline int check_hw_restriction(struct intel_wopcm *wopcm, u32 huc_fw_size) { + struct drm_i915_private *i915 = wopcm_to_i915(wopcm); int err = 0; if (IS_GEN9(i915)) - err = gen9_check_dword_gap(guc_wopcm_base, guc_wopcm_size); + err = gen9_check_dword_gap(wopcm); if (!err && (IS_GEN9(i915) || IS_CNL_REVID(i915, CNL_REVID_A0, CNL_REVID_A0))) - err = gen9_check_huc_fw_fits(guc_wopcm_size, huc_fw_size); + err = gen9_check_huc_fw_fits(wopcm, huc_fw_size); return err; } +static inline u32 fw_usable_wopcm_size(struct intel_wopcm *wopcm) +{ + struct drm_i915_private *i915 = wopcm_to_i915(wopcm); + u32 ctx_rsvd_size = context_reserved_size(i915); + + return wopcm->size - ctx_rsvd_size; +} + +static inline u32 min_guc_wopcm_size(u32 guc_fw_size) +{ + return ALIGN(guc_fw_size + GUC_WOPCM_RESERVED + + GUC_WOPCM_STACK_RESERVED, GUC_WOPCM_SIZE_ALIGNMENT); +} + +static inline u32 min_guc_wopcm_base(u32 huc_fw_size) +{ + return ALIGN(huc_fw_size + WOPCM_RESERVED_SIZE, + GUC_WOPCM_OFFSET_ALIGNMENT); +} + +static inline u32 max_guc_wopcm_size(u32 max_available_wopcm_size, + u32 min_guc_wopcm_base) +{ + if (max_available_wopcm_size > min_guc_wopcm_base) + return (max_available_wopcm_size - min_guc_wopcm_base) & + GUC_WOPCM_SIZE_MASK; + return 0; +} + +static inline int verify_calculated_values(struct intel_wopcm *wopcm, + u32 guc_fw_size, u32 huc_fw_size) +{ + u32 min_size = min_guc_wopcm_size(guc_fw_size); + + if (wopcm->guc.size < min_size) { + DRM_ERROR("Need %uKiB WOPCM for GuC FW, %uKiB available.\n", + min_size, wopcm->guc.size / 1024); + return -E2BIG; + } + + return check_hw_restriction(wopcm, huc_fw_size); +} + /** * intel_wopcm_init() - Initialize the WOPCM structure. * @wopcm: pointer to intel_wopcm. @@ -150,66 +196,135 @@ static inline int check_hw_restriction(struct drm_i915_private *i915, * size. It will fail the WOPCM init if any of these checks were failed, so that * the following GuC firmware uploading would be aborted. * - * Return: 0 on success, non-zero error code on failure. + * Return: 0 on success. Minus error code if registers were locked with + * incorrect values. -EIO if registers failed to lock with correct values. */ int intel_wopcm_init(struct intel_wopcm *wopcm) { struct drm_i915_private *i915 = wopcm_to_i915(wopcm); u32 guc_fw_size = intel_uc_fw_get_upload_size(&i915->guc.fw); u32 huc_fw_size = intel_uc_fw_get_upload_size(&i915->huc.fw); - u32 ctx_rsvd = context_reserved_size(i915); - u32 guc_wopcm_base; - u32 guc_wopcm_size; - u32 guc_wopcm_rsvd; + u32 fw_usable_size = fw_usable_wopcm_size(wopcm); int err; GEM_BUG_ON(!wopcm->size); - if (guc_fw_size >= wopcm->size) { + if (guc_fw_size >= fw_usable_size) { DRM_ERROR("GuC FW (%uKiB) is too big to fit in WOPCM.", guc_fw_size / 1024); return -E2BIG; } - if (huc_fw_size >= wopcm->size) { + if (huc_fw_size >= fw_usable_size) { DRM_ERROR("HuC FW (%uKiB) is too big to fit in WOPCM.", huc_fw_size / 1024); return -E2BIG; } - guc_wopcm_base = ALIGN(huc_fw_size + WOPCM_RESERVED_SIZE, - GUC_WOPCM_OFFSET_ALIGNMENT); - if ((guc_wopcm_base + ctx_rsvd) >= wopcm->size) { - DRM_ERROR("GuC WOPCM base (%uKiB) is too big.\n", - guc_wopcm_base / 1024); - return -E2BIG; - } - - guc_wopcm_size = wopcm->size - guc_wopcm_base - ctx_rsvd; - guc_wopcm_size &= GUC_WOPCM_SIZE_MASK; + wopcm->guc.base = min_guc_wopcm_base(huc_fw_size); + wopcm->guc.size = max_guc_wopcm_size(fw_usable_size, wopcm->guc.base); DRM_DEBUG_DRIVER("Calculated GuC WOPCM Region: [%uKiB, %uKiB)\n", - guc_wopcm_base / 1024, guc_wopcm_size / 1024); + wopcm->guc.base / 1024, + (wopcm->guc.base + wopcm->guc.size) / 1024); - guc_wopcm_rsvd = GUC_WOPCM_RESERVED + GUC_WOPCM_STACK_RESERVED; - if ((guc_fw_size + guc_wopcm_rsvd) > guc_wopcm_size) { - DRM_ERROR("Need %uKiB WOPCM for GuC, %uKiB available.\n", - (guc_fw_size + guc_wopcm_rsvd) / 1024, - guc_wopcm_size / 1024); - return -E2BIG; - } - - err = check_hw_restriction(i915, guc_wopcm_base, guc_wopcm_size, - huc_fw_size); + err = verify_calculated_values(wopcm, guc_fw_size, huc_fw_size); if (err) return err; - wopcm->guc.base = guc_wopcm_base; - wopcm->guc.size = guc_wopcm_size; - return 0; } +static inline int verify_locked_values(struct intel_wopcm *wopcm) +{ + struct drm_i915_private *i915 = wopcm_to_i915(wopcm); + u32 guc_fw_size = intel_uc_fw_get_upload_size(&i915->guc.fw); + u32 huc_fw_size = intel_uc_fw_get_upload_size(&i915->huc.fw); + u32 fw_usable_size = fw_usable_wopcm_size(wopcm); + u32 min_base = min_guc_wopcm_base(huc_fw_size); + + /* + * Locked GuC WOPCM base and size could be any values which are large + * enough to lead to a wrap around after add operations. + */ + if (wopcm->guc.base >= fw_usable_size) { + DRM_ERROR("Locked base value %uKiB >= WOPCM size %uKiB.\n", + wopcm->guc.base / 1024, + wopcm->size / 1024); + return -E2BIG; + } + + if (wopcm->guc.size >= fw_usable_size) { + DRM_ERROR("Locked size value %uKiB >= WOPCM size %uKiB.\n", + wopcm->guc.size / 1024, + wopcm->size / 1024); + return -E2BIG; + } + + if (wopcm->guc.base + wopcm->guc.size > fw_usable_size) { + DRM_ERROR("Need %uKiB WOPCM for firmware, %uKiB available.\n", + (wopcm->guc.base + wopcm->guc.size) / 1024, + fw_usable_size / 1024); + return -E2BIG; + } + + if (wopcm->guc.base < min_base) { + DRM_ERROR("Need %uKiB WOPCM for HuC FW, %uKiB available.\n", + min_base / 1024, + wopcm->guc.base / 1024); + return -E2BIG; + } + + return verify_calculated_values(wopcm, guc_fw_size, huc_fw_size); +} + +/* + * Check WOPCM write-once register status. if locked with different values + * from the calculated ones, then this function will try to recalculate the + * values when possible (partially locked) and verify the recalculated value. + */ +static inline int check_locked_values(struct intel_wopcm *wopcm, + bool *update_size_reg, + bool *update_offset_reg) +{ + struct drm_i915_private *dev_priv = wopcm_to_i915(wopcm); + u32 huc_fw_size = intel_uc_fw_get_upload_size(&dev_priv->huc.fw); + u32 fw_usable_size = fw_usable_wopcm_size(wopcm); + u32 offset_val = I915_READ(DMA_GUC_WOPCM_OFFSET); + u32 size_val = I915_READ(GUC_WOPCM_SIZE); + bool offset_reg_locked = offset_val & GUC_WOPCM_OFFSET_VALID; + bool size_reg_locked = size_val & GUC_WOPCM_SIZE_LOCKED; + + *update_size_reg = !size_reg_locked; + *update_offset_reg = !offset_reg_locked; + + if (!offset_reg_locked && !size_reg_locked) + return 0; + + if (wopcm->guc.base == (offset_val & GUC_WOPCM_OFFSET_MASK) && + wopcm->guc.size == (size_val & GUC_WOPCM_SIZE_MASK)) + return 0; + + if (USES_HUC(dev_priv) && offset_reg_locked && + !(offset_val & HUC_LOADING_AGENT_GUC)) { + DRM_ERROR("HUC_LOADING_AGENT_GUC isn't locked for USES_HUC.\n"); + return -EIO; + } + + if (!offset_reg_locked) + wopcm->guc.base = min_guc_wopcm_base(huc_fw_size); + + if (!size_reg_locked) + wopcm->guc.size = max_guc_wopcm_size(fw_usable_size, + wopcm->guc.base); + + DRM_DEBUG_DRIVER("Recalculated GuC WOPCM Region: [%uKiB, %uKiB)\n", + wopcm->guc.base / 1024, + (wopcm->guc.base + wopcm->guc.size) / 1024); + + return verify_locked_values(wopcm); +} + static inline int write_and_verify(struct drm_i915_private *dev_priv, i915_reg_t reg, u32 val, u32 mask, u32 locked_bit) @@ -238,6 +353,8 @@ static inline int write_and_verify(struct drm_i915_private *dev_priv, int intel_wopcm_init_hw(struct intel_wopcm *wopcm) { struct drm_i915_private *dev_priv = wopcm_to_i915(wopcm); + bool update_size_reg; + bool update_offset_reg; int err; if (!USES_GUC(dev_priv)) @@ -247,19 +364,38 @@ int intel_wopcm_init_hw(struct intel_wopcm *wopcm) GEM_BUG_ON(!wopcm->guc.size); GEM_BUG_ON(!wopcm->guc.base); - err = write_and_verify(dev_priv, GUC_WOPCM_SIZE, wopcm->guc.size, - GUC_WOPCM_SIZE_MASK | GUC_WOPCM_SIZE_LOCKED, - GUC_WOPCM_SIZE_LOCKED); - if (err) + err = check_locked_values(wopcm, &update_size_reg, &update_offset_reg); + if (err) { + DRM_ERROR("WOPCM registers are locked with invalid values.\n"); goto err_out; + } - err = write_and_verify(dev_priv, DMA_GUC_WOPCM_OFFSET, - wopcm->guc.base | HUC_LOADING_AGENT_GUC, - GUC_WOPCM_OFFSET_MASK | HUC_LOADING_AGENT_GUC | - GUC_WOPCM_OFFSET_VALID, - GUC_WOPCM_OFFSET_VALID); - if (err) - goto err_out; + if (update_offset_reg) { + err = write_and_verify(dev_priv, DMA_GUC_WOPCM_OFFSET, + wopcm->guc.base | HUC_LOADING_AGENT_GUC, + GUC_WOPCM_OFFSET_MASK | + HUC_LOADING_AGENT_GUC | + GUC_WOPCM_OFFSET_VALID, + GUC_WOPCM_OFFSET_VALID); + if (err) { + DRM_ERROR("Failed to GuC WOPCM offset with %#x.\n", + wopcm->guc.base); + goto err_out; + } + } + + if (update_size_reg) { + err = write_and_verify(dev_priv, GUC_WOPCM_SIZE, + wopcm->guc.size, + GUC_WOPCM_SIZE_MASK | + GUC_WOPCM_SIZE_LOCKED, + GUC_WOPCM_SIZE_LOCKED); + if (err) { + DRM_ERROR("Failed to GuC WOPCM size with %#x.\n", + wopcm->guc.size); + goto err_out; + } + } return 0; @@ -268,6 +404,7 @@ int intel_wopcm_init_hw(struct intel_wopcm *wopcm) DRM_ERROR("DMA_GUC_WOPCM_OFFSET=%#x\n", I915_READ(DMA_GUC_WOPCM_OFFSET)); DRM_ERROR("GUC_WOPCM_SIZE=%#x\n", I915_READ(GUC_WOPCM_SIZE)); + DRM_ERROR("A system reboot is required.\n"); return err; } -- 2.7.4 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx