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)
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRskUM7w1oG5rx2IZO4FpNVCsYGvwUCan4Y0QAKCRC4FpNVCsYG
 vw4gAQD/56NgUmv0Rx3BsDCQhjCMb99DtTGbnEvhehmb6nJ0gQD/eBPPDmobVJYZ
 P0pGk6B014FrmH+PDjD9Vo2EbswLFQE=
 =ez0c
 -----END PGP SIGNATURE-----

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
This commit is contained in:
Dave Airlie 2026-08-14 14:24:11 +10:00
commit b2601e783a
11 changed files with 279 additions and 123 deletions

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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) {
/*

View File

@ -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 */

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
pxp_invalidate_queues(pxp);
static void pxp_terminate(struct xe_pxp *pxp, bool hw_only)
{
struct xe_device *xe = pxp->xe;
int ret = 0;
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 @@ int xe_pxp_pm_suspend(struct xe_pxp *pxp)
fallthrough;
case XE_PXP_ACTIVE:
pxp->key_instance++;
pxp->needs_termination_on_resume = true;
needs_queue_inval = true;
break;
}
@ -924,6 +958,7 @@ int xe_pxp_pm_suspend(struct xe_pxp *pxp)
*/
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);
}

View File

@ -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_ */