summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2026-08-14 14:24:11 +1000
committerDave Airlie <airlied@redhat.com>2026-08-14 15:41:40 +1000
commitb2601e783a2e54f6963d65f0d94f96d96c146a3a (patch)
tree470bc9022dd7fc70d2d3662e56f779c634d7e960
parent7581e7c73e8fbea7c885583146fbe09a8462a25d (diff)
parentef526d122b62af5afa437f095aa6661a953676c4 (diff)
downloadlinux-next-b2601e783a2e54f6963d65f0d94f96d96c146a3a.tar.gz
linux-next-b2601e783a2e54f6963d65f0d94f96d96c146a3a.zip
Merge tag 'drm-xe-fixes-2026-08-13' of https://gitlab.freedesktop.org/drm/xe/kernel into drm-fixes
Driver Changes: - Fix DPT Allocation paths (Maarten) - Fixes around UM queue BO (Jia) - Order ring writes before ring tail updates (Matthew Brost) - Add termination on resume for PXP (Daniele) - Document Sentinel and make CTX_TIMESTAMP read TOCTOU-safe (Gajendra) - Fix sync entry leak on OA config emit failure (Linmao Li) - Check managed mutex initilization errors (Linmao Li) - Fix min frequency setting (Vinay) - Fix xe_device_probe error path (Raag) Signed-off-by: Dave Airlie <airlied@redhat.com> From: Thomas Hellstrom <thomas.hellstrom@linux.intel.com> Link: https://patch.msgid.link/an4ZogmPqP2Xtfx3@fedora
-rw-r--r--drivers/gpu/drm/xe/display/xe_fb_pin.c33
-rw-r--r--drivers/gpu/drm/xe/xe_bo.h14
-rw-r--r--drivers/gpu/drm/xe/xe_device.c6
-rw-r--r--drivers/gpu/drm/xe/xe_guc_ads.c93
-rw-r--r--drivers/gpu/drm/xe/xe_guc_ads_types.h5
-rw-r--r--drivers/gpu/drm/xe/xe_guc_pc.c13
-rw-r--r--drivers/gpu/drm/xe/xe_lrc.c29
-rw-r--r--drivers/gpu/drm/xe/xe_lrc.h7
-rw-r--r--drivers/gpu/drm/xe/xe_oa.c13
-rw-r--r--drivers/gpu/drm/xe/xe_pxp.c162
-rw-r--r--drivers/gpu/drm/xe/xe_pxp_types.h27
11 files changed, 279 insertions, 123 deletions
diff --git a/drivers/gpu/drm/xe/display/xe_fb_pin.c b/drivers/gpu/drm/xe/display/xe_fb_pin.c
index 5f4a0cd8deca..73469ea5f333 100644
--- a/drivers/gpu/drm/xe/display/xe_fb_pin.c
+++ b/drivers/gpu/drm/xe/display/xe_fb_pin.c
@@ -164,31 +164,14 @@ static int __xe_pin_fb_vma_dpt(struct drm_gem_object *obj,
dpt_size = ALIGN(intel_rotation_info_size(&view->rotated) * 8,
XE_PAGE_SIZE);
- if (IS_DGFX(xe))
- dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
- dpt_size, ~0ull,
- ttm_bo_type_kernel,
- XE_BO_FLAG_VRAM0 |
- XE_BO_FLAG_GGTT |
- XE_BO_FLAG_PAGETABLE,
- pin_params->alignment, false);
- else
- dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
- dpt_size, ~0ull,
- ttm_bo_type_kernel,
- XE_BO_FLAG_STOLEN |
- XE_BO_FLAG_GGTT |
- XE_BO_FLAG_PAGETABLE,
- pin_params->alignment, false);
- if (IS_ERR(dpt))
- dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
- dpt_size, ~0ull,
- ttm_bo_type_kernel,
- XE_BO_FLAG_SYSTEM |
- XE_BO_FLAG_GGTT |
- XE_BO_FLAG_PAGETABLE |
- XE_BO_FLAG_FORCE_WC,
- pin_params->alignment, false);
+ dpt = xe_bo_create_pin_map_at_novm(xe, tile0,
+ dpt_size, ~0ull,
+ ttm_bo_type_kernel,
+ XE_BO_FLAG_VRAM_IF_DGFX(tile0) |
+ XE_BO_FLAG_GGTT |
+ XE_BO_FLAG_PAGETABLE |
+ XE_BO_FLAG_FORCE_WC,
+ pin_params->alignment, false);
if (IS_ERR(dpt))
return PTR_ERR(dpt);
diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h
index 7ae1d9ac0574..57039cf42ea7 100644
--- a/drivers/gpu/drm/xe/xe_bo.h
+++ b/drivers/gpu/drm/xe/xe_bo.h
@@ -6,6 +6,7 @@
#ifndef _XE_BO_H_
#define _XE_BO_H_
+#include <drm/drm_prime.h>
#include <drm/ttm/ttm_tt.h>
#include "xe_bo_types.h"
@@ -548,6 +549,19 @@ void xe_bo_dev_fini(struct xe_bo_dev *bo_device);
struct sg_table *xe_bo_sg(struct xe_bo *bo);
+/**
+ * xe_bo_sg_is_contiguous() - Check if a BO's DMA address space is contiguous.
+ * @bo: the BO to check (must have a valid sg table, i.e. !xe_bo_is_vram())
+ * @len: required contiguous length in bytes
+ *
+ * Returns true if the first @len bytes of the BO are mapped to a contiguous
+ * DMA address range.
+ */
+static inline bool xe_bo_sg_is_contiguous(struct xe_bo *bo, size_t len)
+{
+ return drm_prime_get_contiguous_size(xe_bo_sg(bo)) >= len;
+}
+
/*
* xe_sg_segment_size() - Provides upper limit for sg segment size.
* @dev: device pointer
diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
index dcb48caa485d..a2b076b1dc60 100644
--- a/drivers/gpu/drm/xe/xe_device.c
+++ b/drivers/gpu/drm/xe/xe_device.c
@@ -1102,7 +1102,11 @@ int xe_device_probe(struct xe_device *xe)
if (err)
goto err_unregister_display;
- return devm_add_action_or_reset(xe->drm.dev, xe_device_sanitize, xe);
+ err = devm_add_action_or_reset(xe->drm.dev, xe_device_sanitize, xe);
+ if (err)
+ goto err_unregister_display;
+
+ return 0;
err_unregister_display:
xe_display_unregister(xe);
diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c
index c98454545a85..886bafd31f45 100644
--- a/drivers/gpu/drm/xe/xe_guc_ads.c
+++ b/drivers/gpu/drm/xe/xe_guc_ads.c
@@ -63,10 +63,14 @@ ads_to_map(struct xe_guc_ads *ads)
/*
* The Additional Data Struct (ADS) has pointers for different buffers used by
- * the GuC. One single gem object contains the ADS struct itself (guc_ads) and
- * all the extra buffers indirectly linked via the ADS struct's entries.
+ * the GuC. One gem object (ads->bo) contains the ADS struct itself (guc_ads)
+ * and most of the extra buffers linked via the ADS struct's entries. The UM
+ * fault queues (PAGE_FAULT, PAGE_FAULT_RESPONSE, ACCESS_COUNTER rings) are
+ * kept in a separate BO (ads->um_queue_bo) so that the full memset of ads->bo
+ * performed on every GT reset does not discard fault descriptors already
+ * written into the rings by the GPU.
*
- * Layout of the ADS blob allocated for the GuC:
+ * Layout of the ADS blob (ads->bo):
*
* +---------------------------------------+ <== base
* | guc_ads |
@@ -98,10 +102,6 @@ ads_to_map(struct xe_guc_ads *ads)
* +---------------------------------------+
* | padding |
* +---------------------------------------+ <== 4K aligned
- * | UM queues |
- * +---------------------------------------+
- * | padding |
- * +---------------------------------------+ <== 4K aligned
* | private data |
* +---------------------------------------+
* | padding |
@@ -155,16 +155,6 @@ static size_t guc_ads_capture_size(struct xe_guc_ads *ads)
return PAGE_ALIGN(ads->capture_size);
}
-static size_t guc_ads_um_queues_size(struct xe_guc_ads *ads)
-{
- struct xe_device *xe = ads_to_xe(ads);
-
- if (!xe->info.has_usm)
- return 0;
-
- return GUC_UM_QUEUE_SIZE * GUC_UM_HW_QUEUE_MAX;
-}
-
static size_t guc_ads_private_data_size(struct xe_guc_ads *ads)
{
return PAGE_ALIGN(ads_to_guc(ads)->fw.private_data_size);
@@ -205,22 +195,12 @@ static size_t guc_ads_capture_offset(struct xe_guc_ads *ads)
return PAGE_ALIGN(offset);
}
-static size_t guc_ads_um_queues_offset(struct xe_guc_ads *ads)
-{
- u32 offset;
-
- offset = guc_ads_capture_offset(ads) +
- guc_ads_capture_size(ads);
-
- return PAGE_ALIGN(offset);
-}
-
static size_t guc_ads_private_data_offset(struct xe_guc_ads *ads)
{
size_t offset;
- offset = guc_ads_um_queues_offset(ads) +
- guc_ads_um_queues_size(ads);
+ offset = guc_ads_capture_offset(ads) +
+ guc_ads_capture_size(ads);
return PAGE_ALIGN(offset);
}
@@ -409,6 +389,49 @@ int xe_guc_ads_init(struct xe_guc_ads *ads)
ads->bo = bo;
+ if (xe->info.has_usm) {
+ /*
+ * Allocate a separate BO for the HW fault ring (UM queues).
+ *
+ * Round the size up to the next power of two so that on iGPU
+ * (system memory, no IOMMU) the TTM pool issues a single
+ * alloc_pages(order=N) call, maximising the chance of getting
+ * a physically contiguous block. GuC requires contiguous DPA.
+ */
+ size_t um_size = IS_DGFX(xe) ?
+ GUC_UM_QUEUE_SIZE * GUC_UM_HW_QUEUE_MAX :
+ roundup_pow_of_two(GUC_UM_QUEUE_SIZE *
+ GUC_UM_HW_QUEUE_MAX);
+
+ u32 um_flags = XE_BO_FLAG_VRAM_IF_DGFX(tile) |
+ XE_BO_FLAG_GGTT |
+ XE_BO_FLAG_GGTT_INVALIDATE |
+ XE_BO_FLAG_PINNED_NORESTORE |
+ XE_BO_FLAG_NEEDS_UC;
+
+ bo = xe_managed_bo_create_pin_map(xe, tile, um_size, um_flags);
+ if (IS_ERR(bo))
+ return PTR_ERR(bo);
+
+ /*
+ * On pre-Xe3p platforms, GAM (not GuC) accesses the UM queue
+ * ring via base_dpa, which must be a contiguous DMA address
+ * range. Verify that the allocated pages are contiguous in
+ * DMA address space.
+ */
+ if (!xe_bo_is_vram(bo) &&
+ !xe_guc_using_main_gamctrl_queues(ads_to_guc(ads)) &&
+ unlikely(!xe_bo_sg_is_contiguous(bo,
+ GUC_UM_QUEUE_SIZE *
+ GUC_UM_HW_QUEUE_MAX))) {
+ drm_err(&xe->drm,
+ "UM fault queue memory is not contiguous in DMA address space; GAM requires contiguous DPA\n");
+ return -ENOMEM;
+ }
+
+ ads->um_queue_bo = bo;
+ }
+
return 0;
}
ALLOW_ERROR_INJECTION(xe_guc_ads_init, ERRNO); /* See xe_pci_probe() */
@@ -820,7 +843,7 @@ static void guc_mmio_reg_state_init(struct xe_guc_ads *ads)
static void guc_um_init_params(struct xe_guc_ads *ads)
{
- u32 um_queue_offset = guc_ads_um_queues_offset(ads);
+ struct xe_bo *um_bo = ads->um_queue_bo;
struct xe_guc *guc = ads_to_guc(ads);
struct xe_device *xe = ads_to_xe(ads);
u64 base_dpa;
@@ -830,8 +853,14 @@ static void guc_um_init_params(struct xe_guc_ads *ads)
with_dpa = !xe_guc_using_main_gamctrl_queues(guc);
- base_ggtt = xe_bo_ggtt_addr(ads->bo) + um_queue_offset;
- base_dpa = xe_bo_main_addr(ads->bo, PAGE_SIZE) + um_queue_offset;
+ if (um_bo) {
+ /* All USM platforms: UM queues in dedicated um_queue_bo */
+ base_ggtt = xe_bo_ggtt_addr(um_bo);
+ base_dpa = xe_bo_main_addr(um_bo, PAGE_SIZE);
+ } else {
+ /* Platform does not support USM: no UM queues, nothing to do */
+ return;
+ }
for (i = 0; i < GUC_UM_HW_QUEUE_MAX; ++i) {
/*
diff --git a/drivers/gpu/drm/xe/xe_guc_ads_types.h b/drivers/gpu/drm/xe/xe_guc_ads_types.h
index 48a8e092023f..845c1fbd93a4 100644
--- a/drivers/gpu/drm/xe/xe_guc_ads_types.h
+++ b/drivers/gpu/drm/xe/xe_guc_ads_types.h
@@ -16,6 +16,11 @@ struct xe_bo;
struct xe_guc_ads {
/** @bo: Xe BO for GuC ads blob */
struct xe_bo *bo;
+ /**
+ * @um_queue_bo: Dedicated BO for the HW fault ring (UM queues).
+ * NULL if the platform does not support USM.
+ */
+ struct xe_bo *um_queue_bo;
/** @golden_lrc_size: golden LRC size */
size_t golden_lrc_size;
/** @regset_size: size of register set passed to GuC for save/restore */
diff --git a/drivers/gpu/drm/xe/xe_guc_pc.c b/drivers/gpu/drm/xe/xe_guc_pc.c
index 59f2fa79ad42..7cf8f4858598 100644
--- a/drivers/gpu/drm/xe/xe_guc_pc.c
+++ b/drivers/gpu/drm/xe/xe_guc_pc.c
@@ -911,6 +911,7 @@ static bool pc_needs_min_freq_change(struct xe_guc_pc *pc)
static int pc_adjust_freq_bounds(struct xe_guc_pc *pc)
{
int ret;
+ u32 min_freq;
lockdep_assert_held(&pc->freq_lock);
@@ -933,8 +934,14 @@ static int pc_adjust_freq_bounds(struct xe_guc_pc *pc)
* Same thing happens for Server platforms where min is listed as
* RPMax
*/
- if (pc_get_min_freq(pc) > pc->rp0_freq)
+ min_freq = pc_get_min_freq(pc);
+ if (min_freq > pc->rp0_freq) {
ret = pc_set_min_freq(pc, pc->rp0_freq);
+ if (ret)
+ goto out;
+
+ min_freq = pc->rp0_freq;
+ }
/*
* Setting GT RP min frequency to 1.2GHz by default for
@@ -947,8 +954,8 @@ static int pc_adjust_freq_bounds(struct xe_guc_pc *pc)
* we aren't expecting high power output across board
*
*/
- if (pc_needs_min_freq_change(pc))
- ret = pc_set_min_freq(pc, max(BMG_MIN_FREQ, pc_get_min_freq(pc)));
+ if (pc_needs_min_freq_change(pc) && min_freq < BMG_MIN_FREQ)
+ ret = pc_set_min_freq(pc, BMG_MIN_FREQ);
out:
return ret;
diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c
index a4292a11391d..9f8217ff1904 100644
--- a/drivers/gpu/drm/xe/xe_lrc.c
+++ b/drivers/gpu/drm/xe/xe_lrc.c
@@ -1096,7 +1096,7 @@ static void xe_lrc_finish(struct xe_lrc *lrc)
* on until it is scheduled, we also read the ENGINE_ID MMIO in the WA BB and
* store it in the PPHSWP.
*/
-#define CONTEXT_ACTIVE 1ULL
+#define CONTEXT_ACTIVE XE_LRC_CTX_TIMESTAMP_ACTIVE
static ssize_t setup_utilization_wa(struct xe_lrc *lrc,
struct xe_hw_engine *hwe,
u32 *batch,
@@ -1849,6 +1849,13 @@ void xe_lrc_write_ring(struct xe_lrc *lrc, const void *data, size_t size)
__xe_lrc_write_ring(lrc, ring, &noop, sizeof(noop));
}
+
+ /*
+ * The ring and the LRC context image are both WC, so the ring tail
+ * update which publishes these writes can become visible to the device
+ * first. Ensure the ring contents are visible before returning.
+ */
+ xe_device_wmb(xe);
}
u64 xe_lrc_descriptor(struct xe_lrc *lrc)
@@ -2720,21 +2727,27 @@ static u64 xe_lrc_update_multi_queue_timestamp(struct xe_lrc *lrc, u64 *old_ts)
static u64 xe_lrc_context_timestamp(struct xe_lrc *lrc)
{
u64 reg_ts, new_ts = lrc->ctx_timestamp;
+ u64 stored;
/* CTX_TIMESTAMP mmio read is invalid on VF, so return the LRC value */
if (IS_SRIOV_VF(lrc_to_xe(lrc)))
return xe_lrc_ctx_timestamp(lrc);
- if (context_active(lrc) &&
- !get_ctx_timestamp(lrc, xe_lrc_engine_id(lrc), &reg_ts))
+ /* Safely read CTX_TIMESTAMP: Avoid TOCTOU on LRC-stored CONTEXT_ACTIVE sentinel */
+ stored = xe_lrc_ctx_timestamp(lrc);
+ if (stored != CONTEXT_ACTIVE)
+ return stored;
+
+ /* Context is active: read the live timestamp from the engine's MMIO register */
+ if (!get_ctx_timestamp(lrc, xe_lrc_engine_id(lrc), &reg_ts))
new_ts = reg_ts;
- /*
- * If context swicthed out while we were here, just return the latest
- * LRC CTX TIMESTAMP value.
+ /* If the context switched out prefer using the value
+ * from context-save over the stale MMIO read.
*/
- if (!context_active(lrc))
- return xe_lrc_ctx_timestamp(lrc);
+ stored = xe_lrc_ctx_timestamp(lrc);
+ if (stored != CONTEXT_ACTIVE)
+ return stored;
return new_ts;
}
diff --git a/drivers/gpu/drm/xe/xe_lrc.h b/drivers/gpu/drm/xe/xe_lrc.h
index 0a3a611391ee..7be5e3da8bc8 100644
--- a/drivers/gpu/drm/xe/xe_lrc.h
+++ b/drivers/gpu/drm/xe/xe_lrc.h
@@ -9,6 +9,13 @@
#include "xe_lrc_types.h"
+/*
+ * Sentinel value stored in lrc->ctx_timestamp while a context is starting.
+ * The hardware hasn't yet written the real CTX_TIMESTAMP, so this is not a
+ * valid elapsed-time sample and must not be used as one.
+ */
+#define XE_LRC_CTX_TIMESTAMP_ACTIVE 1ULL
+
struct drm_printer;
struct xe_bb;
struct xe_device;
diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c
index 2dce6a47202c..18d990c5d4ec 100644
--- a/drivers/gpu/drm/xe/xe_oa.c
+++ b/drivers/gpu/drm/xe/xe_oa.c
@@ -1594,6 +1594,10 @@ static long xe_oa_config_locked(struct xe_oa_stream *stream, u64 arg)
config = xchg(&stream->oa_config, config);
drm_dbg(&stream->oa->xe->drm, "changed to oa config uuid=%s\n",
stream->oa_config->uuid);
+ } else {
+ while (param.num_syncs--)
+ xe_sync_entry_cleanup(&param.syncs[param.num_syncs]);
+ kfree(param.syncs);
}
err_config_put:
@@ -2713,9 +2717,7 @@ static int xe_oa_init_gt(struct xe_gt *gt)
__xe_oa_init_oa_units(gt);
- drmm_mutex_init(&gt_to_xe(gt)->drm, &gt->oa.gt_lock);
-
- return 0;
+ return drmm_mutex_init(&gt_to_xe(gt)->drm, &gt->oa.gt_lock);
}
static void xe_oa_print_gt_oa_units(struct xe_gt *gt)
@@ -2855,7 +2857,10 @@ int xe_oa_init(struct xe_device *xe)
oa->xe = xe;
oa->oa_formats = oa_formats;
- drmm_mutex_init(&oa->xe->drm, &oa->metrics_lock);
+ ret = drmm_mutex_init(&oa->xe->drm, &oa->metrics_lock);
+ if (ret)
+ goto exit;
+
idr_init_base(&oa->metrics_idr, 1);
ret = xe_oa_init_oa_units(oa);
diff --git a/drivers/gpu/drm/xe/xe_pxp.c b/drivers/gpu/drm/xe/xe_pxp.c
index 968b7e70b3f9..d17ab6e59df5 100644
--- a/drivers/gpu/drm/xe/xe_pxp.c
+++ b/drivers/gpu/drm/xe/xe_pxp.c
@@ -8,6 +8,8 @@
#include <drm/drm_managed.h>
#include <uapi/drm/xe_drm.h>
+#include <linux/device.h>
+
#include "xe_bo.h"
#include "xe_bo_types.h"
#include "xe_device_types.h"
@@ -164,16 +166,9 @@ static void mark_termination_in_progress(struct xe_pxp *pxp)
pxp->status = XE_PXP_TERMINATION_IN_PROGRESS;
}
-static void pxp_terminate(struct xe_pxp *pxp)
+static bool pxp_prep_for_termination(struct xe_pxp *pxp)
{
- int ret = 0;
- struct xe_device *xe = pxp->xe;
-
- if (!wait_for_completion_timeout(&pxp->activation,
- msecs_to_jiffies(PXP_ACTIVATION_TIMEOUT_MS)))
- drm_err(&xe->drm, "failed to wait for PXP start before termination\n");
-
- mutex_lock(&pxp->mutex);
+ lockdep_assert_held(&pxp->mutex);
if (pxp->status == XE_PXP_ACTIVE)
pxp->key_instance++;
@@ -182,10 +177,8 @@ static void pxp_terminate(struct xe_pxp *pxp)
* we'll mark the status as needing termination on resume, so no need to
* emit a termination now.
*/
- if (pxp->status == XE_PXP_SUSPENDED) {
- mutex_unlock(&pxp->mutex);
- return;
- }
+ if (pxp->status == XE_PXP_SUSPENDED)
+ return false;
/*
* If we have a termination already in progress, we need to wait for
@@ -195,15 +188,44 @@ static void pxp_terminate(struct xe_pxp *pxp)
*/
if (pxp->status == XE_PXP_TERMINATION_IN_PROGRESS) {
pxp->status = XE_PXP_NEEDS_ADDITIONAL_TERMINATION;
- mutex_unlock(&pxp->mutex);
- return;
+ return false;
}
mark_termination_in_progress(pxp);
- mutex_unlock(&pxp->mutex);
+ return true;
+}
+
+static void pxp_terminate(struct xe_pxp *pxp, bool hw_only)
+{
+ struct xe_device *xe = pxp->xe;
+ int ret = 0;
- pxp_invalidate_queues(pxp);
+ if (!wait_for_completion_timeout(&pxp->activation,
+ msecs_to_jiffies(PXP_ACTIVATION_TIMEOUT_MS)))
+ drm_err(&xe->drm, "failed to wait for PXP start before termination\n");
+
+ if (!hw_only) {
+ bool prep_ok;
+
+ mutex_lock(&pxp->mutex);
+
+ prep_ok = pxp_prep_for_termination(pxp);
+
+ mutex_unlock(&pxp->mutex);
+
+ if (!prep_ok)
+ return;
+
+ pxp_invalidate_queues(pxp);
+ } else {
+ /*
+ * The caller of the HW-only termination should have already
+ * called pxp_prep_for_termination and marked the termination as
+ * in progress.
+ */
+ xe_assert(xe, !completion_done(&pxp->termination));
+ }
ret = pxp_terminate_hw(pxp);
if (ret) {
@@ -249,33 +271,46 @@ static void pxp_terminate_complete(struct xe_pxp *pxp)
mutex_unlock(&pxp->mutex);
}
-static void pxp_irq_work(struct work_struct *work)
+static void pxp_events_work(struct work_struct *work)
{
- struct xe_pxp *pxp = container_of(work, typeof(*pxp), irq.work);
+ struct xe_pxp *pxp = container_of(work, typeof(*pxp), events.work);
struct xe_device *xe = pxp->xe;
+ bool hw_only = false;
u32 events = 0;
- spin_lock_irq(&xe->irq.lock);
- events = pxp->irq.events;
- pxp->irq.events = 0;
- spin_unlock_irq(&xe->irq.lock);
+ events = atomic_xchg(&pxp->events.pending, 0);
if (!events)
return;
/*
- * If we're processing a termination irq while suspending then don't
- * bother, we're going to re-init everything on resume anyway.
+ * If the termination request comes from an irq while we're suspending,
+ * then we can defer it to the resume path instead of waking the device
+ * up.
+ * In the case of the termination on resume the pm reference is taken
+ * in xe_pxp_pm_resume() and released here.
+ * Note that we do not expect both events to be set at the same time,
+ * but if it does happen due to a spurious interrupt we want to behave
+ * as if the only request we got was the one from the resume path; this
+ * is because the termination prep has already been done in
+ * xe_pxp_pm_resume() and it is impossible for any PXP operations to
+ * occur between the prep and the termination completion, so there is no
+ * need for a new SW prep.
*/
- if ((events & PXP_TERMINATION_REQUEST) && !xe_pm_runtime_get_if_active(xe))
+ if (events & PXP_TERMINATION_REQUEST_ON_RESUME) {
+ events &= ~PXP_TERMINATION_REQUEST_IRQ;
+ hw_only = true;
+ }
+
+ if ((events & PXP_TERMINATION_REQUEST_IRQ) && !xe_pm_runtime_get_if_active(xe))
return;
if (events & PXP_TERMINATION_REQUEST) {
- events &= ~PXP_TERMINATION_COMPLETE;
- pxp_terminate(pxp);
+ events &= ~PXP_TERMINATION_COMPLETE_IRQ;
+ pxp_terminate(pxp, hw_only);
}
- if (events & PXP_TERMINATION_COMPLETE)
+ if (events & PXP_TERMINATION_COMPLETE_IRQ)
pxp_terminate_complete(pxp);
if (events & PXP_TERMINATION_REQUEST)
@@ -296,20 +331,18 @@ void xe_pxp_irq_handler(struct xe_device *xe, u16 iir)
return;
}
- lockdep_assert_held(&xe->irq.lock);
-
if (unlikely(!iir))
return;
if (iir & (KCR_PXP_STATE_TERMINATED_INTERRUPT |
KCR_APP_TERMINATED_PER_FW_REQ_INTERRUPT))
- pxp->irq.events |= PXP_TERMINATION_REQUEST;
+ atomic_or(PXP_TERMINATION_REQUEST_IRQ, &pxp->events.pending);
if (iir & KCR_PXP_STATE_RESET_COMPLETE_INTERRUPT)
- pxp->irq.events |= PXP_TERMINATION_COMPLETE;
+ atomic_or(PXP_TERMINATION_COMPLETE_IRQ, &pxp->events.pending);
- if (pxp->irq.events)
- queue_work(pxp->irq.wq, &pxp->irq.work);
+ if (atomic_read(&pxp->events.pending))
+ queue_work(pxp->events.wq, &pxp->events.work);
}
static int kcr_pxp_set_status(const struct xe_pxp *pxp, bool enable)
@@ -340,7 +373,7 @@ static void pxp_fini(void *arg)
{
struct xe_pxp *pxp = arg;
- destroy_workqueue(pxp->irq.wq);
+ destroy_workqueue(pxp->events.wq);
xe_pxp_destroy_execution_resources(pxp);
/* no need to explicitly disable KCR since we're going to do an FLR */
@@ -402,7 +435,7 @@ int xe_pxp_init(struct xe_device *xe)
INIT_LIST_HEAD(&pxp->queues.list);
spin_lock_init(&pxp->queues.lock);
- INIT_WORK(&pxp->irq.work, pxp_irq_work);
+ INIT_WORK(&pxp->events.work, pxp_events_work);
pxp->xe = xe;
pxp->gt = gt;
@@ -421,8 +454,8 @@ int xe_pxp_init(struct xe_device *xe)
mutex_init(&pxp->mutex);
- pxp->irq.wq = alloc_ordered_workqueue("pxp-wq", 0);
- if (!pxp->irq.wq) {
+ pxp->events.wq = alloc_ordered_workqueue("pxp-wq", 0);
+ if (!pxp->events.wq) {
err = -ENOMEM;
goto out_free;
}
@@ -442,7 +475,7 @@ int xe_pxp_init(struct xe_device *xe)
out_kcr_disable:
kcr_pxp_disable(pxp);
out_wq:
- destroy_workqueue(pxp->irq.wq);
+ destroy_workqueue(pxp->events.wq);
out_free:
drmm_kfree(&xe->drm, pxp);
out:
@@ -889,6 +922,7 @@ wait_for_activation:
fallthrough;
case XE_PXP_ACTIVE:
pxp->key_instance++;
+ pxp->needs_termination_on_resume = true;
needs_queue_inval = true;
break;
}
@@ -924,6 +958,7 @@ wait_for_activation:
*/
void xe_pxp_pm_resume(struct xe_pxp *pxp)
{
+ bool has_pm = false;
int err;
if (!xe_pxp_is_enabled(pxp))
@@ -931,14 +966,57 @@ void xe_pxp_pm_resume(struct xe_pxp *pxp)
err = kcr_pxp_enable(pxp);
+ /*
+ * We want to avoid the device runtime suspending before we're done with
+ * the termination queued below, so we need a runtime PM reference; we
+ * can't call the rpm functions from within the PXP lock, so we take the
+ * ref here. Note that we don't want the rpm resume code to actually run
+ * here as that would call back into this function, but as long as we
+ * don't enable DPM_FLAG_SMART_SUSPEND (which we currently do not) we're
+ * guaranteed to not be runtime suspended at this point, so we can
+ * safely use the get_noresume variant.
+ */
+ if (pxp->needs_termination_on_resume) {
+ has_pm = true;
+
+ xe_assert(pxp->xe, !dev_pm_smart_suspend(pxp->xe->drm.dev));
+ xe_pm_runtime_get_noresume(pxp->xe);
+ }
+
mutex_lock(&pxp->mutex);
xe_assert(pxp->xe, pxp->status == XE_PXP_SUSPENDED);
- if (err)
+ if (err) {
pxp->status = XE_PXP_ERROR;
- else
+ } else {
pxp->status = XE_PXP_NEEDS_TERMINATION;
+ if (pxp->needs_termination_on_resume) {
+ pxp->needs_termination_on_resume = false;
+
+ /*
+ * We can't call pxp_terminate_hw directly from here
+ * because we're not allowed to do allocations within
+ * the rpm resume call, so we defer the termination to
+ * the worker that we use for the termination irqs.
+ * However, we do not want any PXP ops to go through
+ * between the suspend completing and the worker
+ * starting, so we need to do the termination prep
+ * immediately, which will mark the termination as in
+ * progress and stall PXP ops.
+ */
+ if (pxp_prep_for_termination(pxp)) {
+ has_pm = false; /* move PM ref ownership to worker */
+
+ atomic_or(PXP_TERMINATION_REQUEST_ON_RESUME, &pxp->events.pending);
+ queue_work(pxp->events.wq, &pxp->events.work);
+ }
+ }
+ }
+
mutex_unlock(&pxp->mutex);
+
+ if (has_pm)
+ xe_pm_runtime_put(pxp->xe);
}
diff --git a/drivers/gpu/drm/xe/xe_pxp_types.h b/drivers/gpu/drm/xe/xe_pxp_types.h
index ec86306e16f4..8132a9750b6e 100644
--- a/drivers/gpu/drm/xe/xe_pxp_types.h
+++ b/drivers/gpu/drm/xe/xe_pxp_types.h
@@ -85,17 +85,20 @@ struct xe_pxp {
/** @gsc_res: kernel-owned objects for PXP submissions to the GSCCS */
struct xe_pxp_gsc_client_resources gsc_res;
- /** @irq: wrapper for the worker and queue used for PXP irq support */
+ /** @events: wrapper for the worker and queue used for PXP event handling */
struct {
- /** @irq.work: worker that manages irq events. */
+ /** @events.work: worker that manages termination events. */
struct work_struct work;
- /** @irq.wq: workqueue on which to queue the irq work. */
+ /** @events.wq: workqueue on which to queue the work. */
struct workqueue_struct *wq;
- /** @irq.events: pending events, protected with xe->irq.lock. */
- u32 events;
-#define PXP_TERMINATION_REQUEST BIT(0)
-#define PXP_TERMINATION_COMPLETE BIT(1)
- } irq;
+ /** @events.pending: pending events */
+ atomic_t pending;
+#define PXP_TERMINATION_REQUEST_IRQ BIT(0)
+#define PXP_TERMINATION_REQUEST_ON_RESUME BIT(1)
+#define PXP_TERMINATION_REQUEST (PXP_TERMINATION_REQUEST_IRQ | \
+ PXP_TERMINATION_REQUEST_ON_RESUME)
+#define PXP_TERMINATION_COMPLETE_IRQ BIT(2)
+ } events;
/** @mutex: protects the pxp status and the queue list */
struct mutex mutex;
@@ -130,6 +133,14 @@ struct xe_pxp {
* suspend cycles.
*/
u32 last_suspend_key_instance;
+ /**
+ * @needs_termination_on_resume: indicates if PXP termination is needed
+ * on resume. This is set if PXP was active when we suspend and it is
+ * cleared when we queue the termination on resume. Since the suspend
+ * and resume calls cannot execute at the same time, this variable does
+ * not need to be protected by the PXP lock.
+ */
+ bool needs_termination_on_resume;
};
#endif /* _XE_PXP_TYPES_H_ */