// SPDX-License-Identifier: MIT /* * Copyright © 2024 Intel Corporation */ #include #include "xe_assert.h" #include "xe_device.h" #include "xe_exec_queue.h" #include "xe_gt.h" #include "xe_hw_engine_group.h" #include "xe_vm.h" static void hw_engine_group_free(struct drm_device *drm, void *arg) { struct xe_hw_engine_group *group = arg; destroy_workqueue(group->resume_wq); kfree(group); } static void hw_engine_group_resume_lr_jobs_func(struct work_struct *w) { struct xe_exec_queue *q; struct xe_hw_engine_group *group = container_of(w, struct xe_hw_engine_group, resume_work); int err; enum xe_hw_engine_group_execution_mode previous_mode; err = xe_hw_engine_group_get_mode(group, EXEC_MODE_LR, &previous_mode); if (err) return; if (previous_mode == EXEC_MODE_LR) goto put; list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) { if (!xe_vm_in_fault_mode(q->vm)) continue; q->ops->resume(q); } put: xe_hw_engine_group_put(group); } static struct xe_hw_engine_group * hw_engine_group_alloc(struct xe_device *xe) { struct xe_hw_engine_group *group; int err; group = kzalloc(sizeof(*group), GFP_KERNEL); if (!group) return ERR_PTR(-ENOMEM); group->resume_wq = alloc_workqueue("xe-resume-lr-jobs-wq", 0, 0); if (!group->resume_wq) return ERR_PTR(-ENOMEM); init_rwsem(&group->mode_sem); INIT_WORK(&group->resume_work, hw_engine_group_resume_lr_jobs_func); INIT_LIST_HEAD(&group->exec_queue_list); err = drmm_add_action_or_reset(&xe->drm, hw_engine_group_free, group); if (err) return ERR_PTR(err); return group; } /** * xe_hw_engine_setup_groups() - Setup the hw engine groups for the gt * @gt: The gt for which groups are setup * * Return: 0 on success, negative error code on error. */ int xe_hw_engine_setup_groups(struct xe_gt *gt) { struct xe_hw_engine *hwe; enum xe_hw_engine_id id; struct xe_hw_engine_group *group_rcs_ccs, *group_bcs, *group_vcs_vecs; struct xe_device *xe = gt_to_xe(gt); int err; group_rcs_ccs = hw_engine_group_alloc(xe); if (IS_ERR(group_rcs_ccs)) { err = PTR_ERR(group_rcs_ccs); goto err_group_rcs_ccs; } group_bcs = hw_engine_group_alloc(xe); if (IS_ERR(group_bcs)) { err = PTR_ERR(group_bcs); goto err_group_bcs; } group_vcs_vecs = hw_engine_group_alloc(xe); if (IS_ERR(group_vcs_vecs)) { err = PTR_ERR(group_vcs_vecs); goto err_group_vcs_vecs; } for_each_hw_engine(hwe, gt, id) { switch (hwe->class) { case XE_ENGINE_CLASS_COPY: hwe->hw_engine_group = group_bcs; break; case XE_ENGINE_CLASS_RENDER: case XE_ENGINE_CLASS_COMPUTE: hwe->hw_engine_group = group_rcs_ccs; break; case XE_ENGINE_CLASS_VIDEO_DECODE: case XE_ENGINE_CLASS_VIDEO_ENHANCE: hwe->hw_engine_group = group_vcs_vecs; break; case XE_ENGINE_CLASS_OTHER: break; default: drm_warn(&xe->drm, "NOT POSSIBLE"); } } return 0; err_group_vcs_vecs: kfree(group_vcs_vecs); err_group_bcs: kfree(group_bcs); err_group_rcs_ccs: kfree(group_rcs_ccs); return err; } /** * xe_hw_engine_group_add_exec_queue() - Add an exec queue to a hw engine group * @group: The hw engine group * @q: The exec_queue * * Return: 0 on success, * -EINTR if the lock could not be acquired */ int xe_hw_engine_group_add_exec_queue(struct xe_hw_engine_group *group, struct xe_exec_queue *q) { int err; struct xe_device *xe = gt_to_xe(q->gt); xe_assert(xe, group); xe_assert(xe, !(q->flags & EXEC_QUEUE_FLAG_VM)); xe_assert(xe, q->vm); if (xe_vm_in_preempt_fence_mode(q->vm)) return 0; err = down_write_killable(&group->mode_sem); if (err) return err; if (xe_vm_in_fault_mode(q->vm) && group->cur_mode == EXEC_MODE_DMA_FENCE) { q->ops->suspend(q); err = q->ops->suspend_wait(q); if (err) goto err_suspend; xe_hw_engine_group_resume_faulting_lr_jobs(group); } list_add(&q->hw_engine_group_link, &group->exec_queue_list); up_write(&group->mode_sem); return 0; err_suspend: up_write(&group->mode_sem); return err; } /** * xe_hw_engine_group_del_exec_queue() - Delete an exec queue from a hw engine group * @group: The hw engine group * @q: The exec_queue */ void xe_hw_engine_group_del_exec_queue(struct xe_hw_engine_group *group, struct xe_exec_queue *q) { struct xe_device *xe = gt_to_xe(q->gt); xe_assert(xe, group); xe_assert(xe, q->vm); down_write(&group->mode_sem); if (!list_empty(&q->hw_engine_group_link)) list_del(&q->hw_engine_group_link); up_write(&group->mode_sem); } /** * xe_hw_engine_group_resume_faulting_lr_jobs() - Asynchronously resume the hw engine group's * faulting LR jobs * @group: The hw engine group */ void xe_hw_engine_group_resume_faulting_lr_jobs(struct xe_hw_engine_group *group) { queue_work(group->resume_wq, &group->resume_work); } /** * xe_hw_engine_group_suspend_faulting_lr_jobs() - Suspend the faulting LR jobs of this group * @group: The hw engine group * * Return: 0 on success, negative error code on error. */ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group *group) { int err; struct xe_exec_queue *q; bool need_resume = false; lockdep_assert_held_write(&group->mode_sem); list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) { if (!xe_vm_in_fault_mode(q->vm)) continue; need_resume = true; q->ops->suspend(q); } list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) { if (!xe_vm_in_fault_mode(q->vm)) continue; err = q->ops->suspend_wait(q); if (err) goto err_suspend; } if (need_resume) xe_hw_engine_group_resume_faulting_lr_jobs(group); return 0; err_suspend: up_write(&group->mode_sem); return err; } /** * xe_hw_engine_group_wait_for_dma_fence_jobs() - Wait for dma fence jobs to complete * @group: The hw engine group * * This function is not meant to be called directly from a user IOCTL as dma_fence_wait() * is not interruptible. * * Return: 0 on success, * -ETIME if waiting for one job failed */ static int xe_hw_engine_group_wait_for_dma_fence_jobs(struct xe_hw_engine_group *group) { long timeout; struct xe_exec_queue *q; struct dma_fence *fence; lockdep_assert_held_write(&group->mode_sem); list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) { if (xe_vm_in_lr_mode(q->vm)) continue; fence = xe_exec_queue_last_fence_get_for_resume(q, q->vm); timeout = dma_fence_wait(fence, false); dma_fence_put(fence); if (timeout < 0) return -ETIME; } return 0; } static int switch_mode(struct xe_hw_engine_group *group) { int err = 0; enum xe_hw_engine_group_execution_mode new_mode; lockdep_assert_held_write(&group->mode_sem); switch (group->cur_mode) { case EXEC_MODE_LR: new_mode = EXEC_MODE_DMA_FENCE; err = xe_hw_engine_group_suspend_faulting_lr_jobs(group); break; case EXEC_MODE_DMA_FENCE: new_mode = EXEC_MODE_LR; err = xe_hw_engine_group_wait_for_dma_fence_jobs(group); break; } if (err) return err; group->cur_mode = new_mode; return 0; } /** * xe_hw_engine_group_get_mode() - Get the group to execute in the new mode * @group: The hw engine group * @new_mode: The new execution mode * @previous_mode: Pointer to the previous mode provided for use by caller * * Return: 0 if successful, -EINTR if locking failed. */ int xe_hw_engine_group_get_mode(struct xe_hw_engine_group *group, enum xe_hw_engine_group_execution_mode new_mode, enum xe_hw_engine_group_execution_mode *previous_mode) __acquires(&group->mode_sem) { int err = down_read_interruptible(&group->mode_sem); if (err) return err; *previous_mode = group->cur_mode; if (new_mode != group->cur_mode) { up_read(&group->mode_sem); err = down_write_killable(&group->mode_sem); if (err) return err; if (new_mode != group->cur_mode) { err = switch_mode(group); if (err) { up_write(&group->mode_sem); return err; } } downgrade_write(&group->mode_sem); } return err; } /** * xe_hw_engine_group_put() - Put the group * @group: The hw engine group */ void xe_hw_engine_group_put(struct xe_hw_engine_group *group) __releases(&group->mode_sem) { up_read(&group->mode_sem); } /** * xe_hw_engine_group_find_exec_mode() - Find the execution mode for this exec queue * @q: The exec_queue */ enum xe_hw_engine_group_execution_mode xe_hw_engine_group_find_exec_mode(struct xe_exec_queue *q) { if (xe_vm_in_fault_mode(q->vm)) return EXEC_MODE_LR; else return EXEC_MODE_DMA_FENCE; }