| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2023-2024 Intel Corporation |
| */ |
| |
| #include <drm/drm_managed.h> |
| |
| #include "abi/guc_actions_sriov_abi.h" |
| |
| #include "xe_device.h" |
| #include "xe_gt.h" |
| #include "xe_gt_sriov_pf_config.h" |
| #include "xe_gt_sriov_pf_control.h" |
| #include "xe_gt_sriov_pf_helpers.h" |
| #include "xe_gt_sriov_pf_monitor.h" |
| #include "xe_gt_sriov_pf_service.h" |
| #include "xe_gt_sriov_printk.h" |
| #include "xe_guc_ct.h" |
| #include "xe_sriov.h" |
| |
| static const char *control_cmd_to_string(u32 cmd) |
| { |
| switch (cmd) { |
| case GUC_PF_TRIGGER_VF_PAUSE: |
| return "PAUSE"; |
| case GUC_PF_TRIGGER_VF_RESUME: |
| return "RESUME"; |
| case GUC_PF_TRIGGER_VF_STOP: |
| return "STOP"; |
| case GUC_PF_TRIGGER_VF_FLR_START: |
| return "FLR_START"; |
| case GUC_PF_TRIGGER_VF_FLR_FINISH: |
| return "FLR_FINISH"; |
| default: |
| return "<unknown>"; |
| } |
| } |
| |
| static int guc_action_vf_control_cmd(struct xe_guc *guc, u32 vfid, u32 cmd) |
| { |
| u32 request[PF2GUC_VF_CONTROL_REQUEST_MSG_LEN] = { |
| FIELD_PREP(GUC_HXG_MSG_0_ORIGIN, GUC_HXG_ORIGIN_HOST) | |
| FIELD_PREP(GUC_HXG_MSG_0_TYPE, GUC_HXG_TYPE_REQUEST) | |
| FIELD_PREP(GUC_HXG_REQUEST_MSG_0_ACTION, GUC_ACTION_PF2GUC_VF_CONTROL), |
| FIELD_PREP(PF2GUC_VF_CONTROL_REQUEST_MSG_1_VFID, vfid), |
| FIELD_PREP(PF2GUC_VF_CONTROL_REQUEST_MSG_2_COMMAND, cmd), |
| }; |
| int ret; |
| |
| ret = xe_guc_ct_send_block(&guc->ct, request, ARRAY_SIZE(request)); |
| return ret > 0 ? -EPROTO : ret; |
| } |
| |
| static int pf_send_vf_control_cmd(struct xe_gt *gt, unsigned int vfid, u32 cmd) |
| { |
| int err; |
| |
| xe_gt_assert(gt, vfid != PFID); |
| xe_gt_sriov_dbg_verbose(gt, "sending VF%u control command %s\n", |
| vfid, control_cmd_to_string(cmd)); |
| |
| err = guc_action_vf_control_cmd(>->uc.guc, vfid, cmd); |
| if (unlikely(err)) |
| xe_gt_sriov_err(gt, "VF%u control command %s failed (%pe)\n", |
| vfid, control_cmd_to_string(cmd), ERR_PTR(err)); |
| return err; |
| } |
| |
| static int pf_send_vf_pause(struct xe_gt *gt, unsigned int vfid) |
| { |
| return pf_send_vf_control_cmd(gt, vfid, GUC_PF_TRIGGER_VF_PAUSE); |
| } |
| |
| static int pf_send_vf_resume(struct xe_gt *gt, unsigned int vfid) |
| { |
| return pf_send_vf_control_cmd(gt, vfid, GUC_PF_TRIGGER_VF_RESUME); |
| } |
| |
| static int pf_send_vf_stop(struct xe_gt *gt, unsigned int vfid) |
| { |
| return pf_send_vf_control_cmd(gt, vfid, GUC_PF_TRIGGER_VF_STOP); |
| } |
| |
| static int pf_send_vf_flr_start(struct xe_gt *gt, unsigned int vfid) |
| { |
| return pf_send_vf_control_cmd(gt, vfid, GUC_PF_TRIGGER_VF_FLR_START); |
| } |
| |
| static int pf_send_vf_flr_finish(struct xe_gt *gt, unsigned int vfid) |
| { |
| return pf_send_vf_control_cmd(gt, vfid, GUC_PF_TRIGGER_VF_FLR_FINISH); |
| } |
| |
| /** |
| * DOC: The VF state machine |
| * |
| * The simplified VF state machine could be presented as:: |
| * |
| * pause--------------------------o |
| * / | |
| * / v |
| * (READY)<------------------resume-----(PAUSED) |
| * ^ \ / / |
| * | \ / / |
| * | stop---->(STOPPED)<----stop / |
| * | / / |
| * | / / |
| * o--------<-----flr / |
| * \ / |
| * o------<--------------------flr |
| * |
| * Where: |
| * |
| * * READY - represents a state in which VF is fully operable |
| * * PAUSED - represents a state in which VF activity is temporarily suspended |
| * * STOPPED - represents a state in which VF activity is definitely halted |
| * * pause - represents a request to temporarily suspend VF activity |
| * * resume - represents a request to resume VF activity |
| * * stop - represents a request to definitely halt VF activity |
| * * flr - represents a request to perform VF FLR to restore VF activity |
| * |
| * However, each state transition requires additional steps that involves |
| * communication with GuC that might fail or be interrupted by other requests:: |
| * |
| * .................................WIP.... |
| * : : |
| * pause--------------------->PAUSE_WIP----------------------------o |
| * / : / \ : | |
| * / : o----<---stop flr--o : | |
| * / : | \ / | : V |
| * (READY,RESUMED)<--------+------------RESUME_WIP<----+--<-----resume--(PAUSED) |
| * ^ \ \ : | | : / / |
| * | \ \ : | | : / / |
| * | \ \ : | | : / / |
| * | \ \ : o----<----------------------+--<-------stop / |
| * | \ \ : | | : / |
| * | \ \ : V | : / |
| * | \ stop----->STOP_WIP---------flr--->-----o : / |
| * | \ : | | : / |
| * | \ : | V : / |
| * | flr--------+----->----------------->FLR_WIP<-----flr |
| * | : | / ^ : |
| * | : | / | : |
| * o--------<-------:----+-----<----------------o | : |
| * : | | : |
| * :....|...........................|.....: |
| * | | |
| * V | |
| * (STOPPED)--------------------flr |
| * |
| * For details about each internal WIP state machine see: |
| * |
| * * `The VF PAUSE state machine`_ |
| * * `The VF RESUME state machine`_ |
| * * `The VF STOP state machine`_ |
| * * `The VF FLR state machine`_ |
| */ |
| |
| #ifdef CONFIG_DRM_XE_DEBUG_SRIOV |
| static const char *control_bit_to_string(enum xe_gt_sriov_control_bits bit) |
| { |
| switch (bit) { |
| #define CASE2STR(_X) \ |
| case XE_GT_SRIOV_STATE_##_X: return #_X |
| CASE2STR(WIP); |
| CASE2STR(FLR_WIP); |
| CASE2STR(FLR_SEND_START); |
| CASE2STR(FLR_WAIT_GUC); |
| CASE2STR(FLR_GUC_DONE); |
| CASE2STR(FLR_RESET_CONFIG); |
| CASE2STR(FLR_RESET_DATA); |
| CASE2STR(FLR_RESET_MMIO); |
| CASE2STR(FLR_SEND_FINISH); |
| CASE2STR(FLR_FAILED); |
| CASE2STR(PAUSE_WIP); |
| CASE2STR(PAUSE_SEND_PAUSE); |
| CASE2STR(PAUSE_WAIT_GUC); |
| CASE2STR(PAUSE_GUC_DONE); |
| CASE2STR(PAUSE_FAILED); |
| CASE2STR(PAUSED); |
| CASE2STR(RESUME_WIP); |
| CASE2STR(RESUME_SEND_RESUME); |
| CASE2STR(RESUME_FAILED); |
| CASE2STR(RESUMED); |
| CASE2STR(STOP_WIP); |
| CASE2STR(STOP_SEND_STOP); |
| CASE2STR(STOP_FAILED); |
| CASE2STR(STOPPED); |
| CASE2STR(MISMATCH); |
| #undef CASE2STR |
| default: return "?"; |
| } |
| } |
| #endif |
| |
| static unsigned long pf_get_default_timeout(enum xe_gt_sriov_control_bits bit) |
| { |
| switch (bit) { |
| case XE_GT_SRIOV_STATE_FLR_WAIT_GUC: |
| case XE_GT_SRIOV_STATE_PAUSE_WAIT_GUC: |
| return HZ / 2; |
| case XE_GT_SRIOV_STATE_FLR_WIP: |
| case XE_GT_SRIOV_STATE_FLR_RESET_CONFIG: |
| return 5 * HZ; |
| default: |
| return HZ; |
| } |
| } |
| |
| static struct xe_gt_sriov_control_state *pf_pick_vf_control(struct xe_gt *gt, unsigned int vfid) |
| { |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| xe_gt_assert(gt, vfid <= xe_gt_sriov_pf_get_totalvfs(gt)); |
| |
| return >->sriov.pf.vfs[vfid].control; |
| } |
| |
| static unsigned long *pf_peek_vf_state(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_control_state *cs = pf_pick_vf_control(gt, vfid); |
| |
| return &cs->state; |
| } |
| |
| static bool pf_check_vf_state(struct xe_gt *gt, unsigned int vfid, |
| enum xe_gt_sriov_control_bits bit) |
| { |
| return test_bit(bit, pf_peek_vf_state(gt, vfid)); |
| } |
| |
| static void pf_dump_vf_state(struct xe_gt *gt, unsigned int vfid) |
| { |
| unsigned long state = *pf_peek_vf_state(gt, vfid); |
| enum xe_gt_sriov_control_bits bit; |
| |
| if (state) { |
| xe_gt_sriov_dbg_verbose(gt, "VF%u state %#lx%s%*pbl\n", |
| vfid, state, state ? " bits " : "", |
| (int)BITS_PER_LONG, &state); |
| for_each_set_bit(bit, &state, BITS_PER_LONG) |
| xe_gt_sriov_dbg_verbose(gt, "VF%u state %s(%d)\n", |
| vfid, control_bit_to_string(bit), bit); |
| } else { |
| xe_gt_sriov_dbg_verbose(gt, "VF%u state READY\n", vfid); |
| } |
| } |
| |
| static bool pf_expect_vf_state(struct xe_gt *gt, unsigned int vfid, |
| enum xe_gt_sriov_control_bits bit) |
| { |
| bool result = pf_check_vf_state(gt, vfid, bit); |
| |
| if (unlikely(!result)) |
| pf_dump_vf_state(gt, vfid); |
| |
| return result; |
| } |
| |
| static bool pf_expect_vf_not_state(struct xe_gt *gt, unsigned int vfid, |
| enum xe_gt_sriov_control_bits bit) |
| { |
| bool result = !pf_check_vf_state(gt, vfid, bit); |
| |
| if (unlikely(!result)) |
| pf_dump_vf_state(gt, vfid); |
| |
| return result; |
| } |
| |
| static bool pf_enter_vf_state(struct xe_gt *gt, unsigned int vfid, |
| enum xe_gt_sriov_control_bits bit) |
| { |
| if (!test_and_set_bit(bit, pf_peek_vf_state(gt, vfid))) { |
| xe_gt_sriov_dbg_verbose(gt, "VF%u state %s(%d) enter\n", |
| vfid, control_bit_to_string(bit), bit); |
| return true; |
| } |
| return false; |
| } |
| |
| static bool pf_exit_vf_state(struct xe_gt *gt, unsigned int vfid, |
| enum xe_gt_sriov_control_bits bit) |
| { |
| if (test_and_clear_bit(bit, pf_peek_vf_state(gt, vfid))) { |
| xe_gt_sriov_dbg_verbose(gt, "VF%u state %s(%d) exit\n", |
| vfid, control_bit_to_string(bit), bit); |
| return true; |
| } |
| return false; |
| } |
| |
| static void pf_escape_vf_state(struct xe_gt *gt, unsigned int vfid, |
| enum xe_gt_sriov_control_bits bit) |
| { |
| if (pf_exit_vf_state(gt, vfid, bit)) |
| xe_gt_sriov_dbg_verbose(gt, "VF%u state %s(%d) escaped by %ps\n", |
| vfid, control_bit_to_string(bit), bit, |
| __builtin_return_address(0)); |
| } |
| |
| static void pf_enter_vf_mismatch(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_MISMATCH)) { |
| xe_gt_sriov_dbg(gt, "VF%u state mismatch detected by %ps\n", |
| vfid, __builtin_return_address(0)); |
| pf_dump_vf_state(gt, vfid); |
| } |
| } |
| |
| static void pf_exit_vf_mismatch(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_MISMATCH)) |
| xe_gt_sriov_dbg(gt, "VF%u state mismatch cleared by %ps\n", |
| vfid, __builtin_return_address(0)); |
| |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_FAILED); |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_FAILED); |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_FAILED); |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_FAILED); |
| } |
| |
| #define pf_enter_vf_state_machine_bug(gt, vfid) ({ \ |
| pf_enter_vf_mismatch((gt), (vfid)); \ |
| }) |
| |
| static void pf_queue_control_worker(struct xe_gt *gt) |
| { |
| struct xe_device *xe = gt_to_xe(gt); |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(xe)); |
| |
| queue_work(xe->sriov.wq, >->sriov.pf.control.worker); |
| } |
| |
| static void pf_queue_vf(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_pf_control *pfc = >->sriov.pf.control; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| |
| spin_lock(&pfc->lock); |
| list_move_tail(>->sriov.pf.vfs[vfid].control.link, &pfc->list); |
| spin_unlock(&pfc->lock); |
| |
| pf_queue_control_worker(gt); |
| } |
| |
| static void pf_exit_vf_flr_wip(struct xe_gt *gt, unsigned int vfid); |
| static void pf_exit_vf_stop_wip(struct xe_gt *gt, unsigned int vfid); |
| static void pf_exit_vf_pause_wip(struct xe_gt *gt, unsigned int vfid); |
| static void pf_exit_vf_resume_wip(struct xe_gt *gt, unsigned int vfid); |
| |
| static bool pf_enter_vf_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_WIP)) { |
| struct xe_gt_sriov_control_state *cs = pf_pick_vf_control(gt, vfid); |
| |
| reinit_completion(&cs->done); |
| return true; |
| } |
| return false; |
| } |
| |
| static void pf_exit_vf_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_WIP)) { |
| struct xe_gt_sriov_control_state *cs = pf_pick_vf_control(gt, vfid); |
| |
| pf_exit_vf_flr_wip(gt, vfid); |
| pf_exit_vf_stop_wip(gt, vfid); |
| pf_exit_vf_pause_wip(gt, vfid); |
| pf_exit_vf_resume_wip(gt, vfid); |
| |
| complete_all(&cs->done); |
| } |
| } |
| |
| static int pf_wait_vf_wip_done(struct xe_gt *gt, unsigned int vfid, unsigned long timeout) |
| { |
| struct xe_gt_sriov_control_state *cs = pf_pick_vf_control(gt, vfid); |
| |
| return wait_for_completion_timeout(&cs->done, timeout) ? 0 : -ETIMEDOUT; |
| } |
| |
| static void pf_enter_vf_ready(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSED); |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOPPED); |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUMED); |
| pf_exit_vf_mismatch(gt, vfid); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| /** |
| * DOC: The VF PAUSE state machine |
| * |
| * The VF PAUSE state machine looks like:: |
| * |
| * (READY,RESUMED)<-------------<---------------------o---------o |
| * | \ \ |
| * pause \ \ |
| * | \ \ |
| * ....V...........................PAUSE_WIP........ \ \ |
| * : \ : o \ |
| * : \ o------<-----busy : | \ |
| * : \ / / : | | |
| * : PAUSE_SEND_PAUSE ---failed--->----------o--->(PAUSE_FAILED) | |
| * : | \ : | | |
| * : acked rejected---->----------o--->(MISMATCH) / |
| * : | : / |
| * : v : / |
| * : PAUSE_WAIT_GUC : / |
| * : | : / |
| * : done : / |
| * : | : / |
| * : v : / |
| * : PAUSE_GUC_DONE o-----restart |
| * : / : |
| * : / : |
| * :....o..............o...............o...........: |
| * | | | |
| * completed flr stop |
| * | | | |
| * V .....V..... ......V..... |
| * (PAUSED) : FLR_WIP : : STOP_WIP : |
| * :.........: :..........: |
| * |
| * For the full state machine view, see `The VF state machine`_. |
| */ |
| |
| static void pf_exit_vf_pause_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_WIP)) { |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_SEND_PAUSE); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_WAIT_GUC); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_GUC_DONE); |
| } |
| } |
| |
| static void pf_enter_vf_paused(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSED)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUMED); |
| pf_exit_vf_mismatch(gt, vfid); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| static void pf_enter_vf_pause_completed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_paused(gt, vfid); |
| } |
| |
| static void pf_enter_vf_pause_failed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_FAILED); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| static void pf_enter_vf_pause_rejected(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_mismatch(gt, vfid); |
| pf_enter_vf_pause_failed(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_pause_guc_done(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_GUC_DONE)) |
| return false; |
| |
| pf_enter_vf_pause_completed(gt, vfid); |
| return true; |
| } |
| |
| static void pf_enter_vf_pause_guc_done(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_GUC_DONE)) |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static void pf_enter_pause_wait_guc(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_WAIT_GUC)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| } |
| |
| static bool pf_exit_pause_wait_guc(struct xe_gt *gt, unsigned int vfid) |
| { |
| return pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_WAIT_GUC); |
| } |
| |
| static void pf_enter_vf_pause_send_pause(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_SEND_PAUSE)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_pause_send_pause(struct xe_gt *gt, unsigned int vfid) |
| { |
| int err; |
| |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_SEND_PAUSE)) |
| return false; |
| |
| /* GuC may actually send a PAUSE_DONE before we get a RESPONSE */ |
| pf_enter_pause_wait_guc(gt, vfid); |
| |
| err = pf_send_vf_pause(gt, vfid); |
| if (err) { |
| /* send failed, so we shouldn't expect PAUSE_DONE from GuC */ |
| pf_exit_pause_wait_guc(gt, vfid); |
| |
| if (err == -EBUSY) |
| pf_enter_vf_pause_send_pause(gt, vfid); |
| else if (err == -EIO) |
| pf_enter_vf_pause_rejected(gt, vfid); |
| else |
| pf_enter_vf_pause_failed(gt, vfid); |
| } else { |
| /* |
| * we have already moved to WAIT_GUC, maybe even to GUC_DONE |
| * but since GuC didn't complain, we may clear MISMATCH |
| */ |
| pf_exit_vf_mismatch(gt, vfid); |
| } |
| |
| return true; |
| } |
| |
| static bool pf_enter_vf_pause_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_WIP)) { |
| pf_enter_vf_wip(gt, vfid); |
| pf_enter_vf_pause_send_pause(gt, vfid); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_control_pause_vf - Pause a VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function is for PF only. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_control_pause_vf(struct xe_gt *gt, unsigned int vfid) |
| { |
| unsigned long timeout = pf_get_default_timeout(XE_GT_SRIOV_STATE_PAUSE_WIP); |
| int err; |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOPPED)) { |
| xe_gt_sriov_dbg(gt, "VF%u is stopped!\n", vfid); |
| return -EPERM; |
| } |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSED)) { |
| xe_gt_sriov_dbg(gt, "VF%u was already paused!\n", vfid); |
| return -ESTALE; |
| } |
| |
| if (!pf_enter_vf_pause_wip(gt, vfid)) { |
| xe_gt_sriov_dbg(gt, "VF%u pause already in progress!\n", vfid); |
| return -EALREADY; |
| } |
| |
| err = pf_wait_vf_wip_done(gt, vfid, timeout); |
| if (err) { |
| xe_gt_sriov_dbg(gt, "VF%u pause didn't finish in %u ms (%pe)\n", |
| vfid, jiffies_to_msecs(timeout), ERR_PTR(err)); |
| return err; |
| } |
| |
| if (pf_expect_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSED)) { |
| xe_gt_sriov_info(gt, "VF%u paused!\n", vfid); |
| return 0; |
| } |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_FAILED)) { |
| xe_gt_sriov_dbg(gt, "VF%u pause failed!\n", vfid); |
| return -EIO; |
| } |
| |
| xe_gt_sriov_dbg(gt, "VF%u pause was canceled!\n", vfid); |
| return -ECANCELED; |
| } |
| |
| /** |
| * DOC: The VF RESUME state machine |
| * |
| * The VF RESUME state machine looks like:: |
| * |
| * (PAUSED)<-----------------<------------------------o |
| * | \ |
| * resume \ |
| * | \ |
| * ....V............................RESUME_WIP...... \ |
| * : \ : o |
| * : \ o-------<-----busy : | |
| * : \ / / : | |
| * : RESUME_SEND_RESUME ---failed--->--------o--->(RESUME_FAILED) |
| * : / \ : | |
| * : acked rejected---->---------o--->(MISMATCH) |
| * : / : |
| * :....o..............o...............o.....o.....: |
| * | | | \ |
| * completed flr stop restart-->(READY) |
| * | | | |
| * V .....V..... ......V..... |
| * (RESUMED) : FLR_WIP : : STOP_WIP : |
| * :.........: :..........: |
| * |
| * For the full state machine view, see `The VF state machine`_. |
| */ |
| |
| static void pf_exit_vf_resume_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_WIP)) |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_SEND_RESUME); |
| } |
| |
| static void pf_enter_vf_resumed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUMED); |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSED); |
| pf_exit_vf_mismatch(gt, vfid); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| static void pf_enter_vf_resume_completed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_resumed(gt, vfid); |
| } |
| |
| static void pf_enter_vf_resume_failed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_FAILED); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| static void pf_enter_vf_resume_rejected(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_mismatch(gt, vfid); |
| pf_enter_vf_resume_failed(gt, vfid); |
| } |
| |
| static void pf_enter_vf_resume_send_resume(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_SEND_RESUME)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_resume_send_resume(struct xe_gt *gt, unsigned int vfid) |
| { |
| int err; |
| |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_SEND_RESUME)) |
| return false; |
| |
| err = pf_send_vf_resume(gt, vfid); |
| if (err == -EBUSY) |
| pf_enter_vf_resume_send_resume(gt, vfid); |
| else if (err == -EIO) |
| pf_enter_vf_resume_rejected(gt, vfid); |
| else if (err) |
| pf_enter_vf_resume_failed(gt, vfid); |
| else |
| pf_enter_vf_resume_completed(gt, vfid); |
| return true; |
| } |
| |
| static bool pf_enter_vf_resume_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_WIP)) { |
| pf_enter_vf_wip(gt, vfid); |
| pf_enter_vf_resume_send_resume(gt, vfid); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_control_resume_vf - Resume a VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function is for PF only. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_control_resume_vf(struct xe_gt *gt, unsigned int vfid) |
| { |
| unsigned long timeout = pf_get_default_timeout(XE_GT_SRIOV_STATE_RESUME_WIP); |
| int err; |
| |
| if (!pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSED)) { |
| xe_gt_sriov_dbg(gt, "VF%u is not paused!\n", vfid); |
| return -EPERM; |
| } |
| |
| if (!pf_enter_vf_resume_wip(gt, vfid)) { |
| xe_gt_sriov_dbg(gt, "VF%u resume already in progress!\n", vfid); |
| return -EALREADY; |
| } |
| |
| err = pf_wait_vf_wip_done(gt, vfid, timeout); |
| if (err) |
| return err; |
| |
| if (pf_expect_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUMED)) { |
| xe_gt_sriov_info(gt, "VF%u resumed!\n", vfid); |
| return 0; |
| } |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUME_FAILED)) { |
| xe_gt_sriov_dbg(gt, "VF%u resume failed!\n", vfid); |
| return -EIO; |
| } |
| |
| xe_gt_sriov_dbg(gt, "VF%u resume was canceled!\n", vfid); |
| return -ECANCELED; |
| } |
| |
| /** |
| * DOC: The VF STOP state machine |
| * |
| * The VF STOP state machine looks like:: |
| * |
| * (READY,PAUSED,RESUMED)<-------<--------------------o |
| * | \ |
| * stop \ |
| * | \ |
| * ....V..............................STOP_WIP...... \ |
| * : \ : o |
| * : \ o----<----busy : | |
| * : \ / / : | |
| * : STOP_SEND_STOP--------failed--->--------o--->(STOP_FAILED) |
| * : / \ : | |
| * : acked rejected-------->--------o--->(MISMATCH) |
| * : / : |
| * :....o..............o...............o...........: |
| * | | | |
| * completed flr restart |
| * | | | |
| * V .....V..... V |
| * (STOPPED) : FLR_WIP : (READY) |
| * :.........: |
| * |
| * For the full state machine view, see `The VF state machine`_. |
| */ |
| |
| static void pf_exit_vf_stop_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_WIP)) |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_SEND_STOP); |
| } |
| |
| static void pf_enter_vf_stopped(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOPPED)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_RESUMED); |
| pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSED); |
| pf_exit_vf_mismatch(gt, vfid); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| static void pf_enter_vf_stop_completed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_stopped(gt, vfid); |
| } |
| |
| static void pf_enter_vf_stop_failed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_FAILED); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| static void pf_enter_vf_stop_rejected(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_mismatch(gt, vfid); |
| pf_enter_vf_stop_failed(gt, vfid); |
| } |
| |
| static void pf_enter_vf_stop_send_stop(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_SEND_STOP)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_stop_send_stop(struct xe_gt *gt, unsigned int vfid) |
| { |
| int err; |
| |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_SEND_STOP)) |
| return false; |
| |
| err = pf_send_vf_stop(gt, vfid); |
| if (err == -EBUSY) |
| pf_enter_vf_stop_send_stop(gt, vfid); |
| else if (err == -EIO) |
| pf_enter_vf_stop_rejected(gt, vfid); |
| else if (err) |
| pf_enter_vf_stop_failed(gt, vfid); |
| else |
| pf_enter_vf_stop_completed(gt, vfid); |
| return true; |
| } |
| |
| static bool pf_enter_vf_stop_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_WIP)) { |
| pf_enter_vf_wip(gt, vfid); |
| pf_enter_vf_stop_send_stop(gt, vfid); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_control_stop_vf - Stop a VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function is for PF only. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_control_stop_vf(struct xe_gt *gt, unsigned int vfid) |
| { |
| unsigned long timeout = pf_get_default_timeout(XE_GT_SRIOV_STATE_STOP_WIP); |
| int err; |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOPPED)) { |
| xe_gt_sriov_dbg(gt, "VF%u was already stopped!\n", vfid); |
| return -ESTALE; |
| } |
| |
| if (!pf_enter_vf_stop_wip(gt, vfid)) { |
| xe_gt_sriov_dbg(gt, "VF%u stop already in progress!\n", vfid); |
| return -EALREADY; |
| } |
| |
| err = pf_wait_vf_wip_done(gt, vfid, timeout); |
| if (err) |
| return err; |
| |
| if (pf_expect_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOPPED)) { |
| xe_gt_sriov_info(gt, "VF%u stopped!\n", vfid); |
| return 0; |
| } |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_STOP_FAILED)) { |
| xe_gt_sriov_dbg(gt, "VF%u stop failed!\n", vfid); |
| return -EIO; |
| } |
| |
| xe_gt_sriov_dbg(gt, "VF%u stop was canceled!\n", vfid); |
| return -ECANCELED; |
| } |
| |
| /** |
| * DOC: The VF FLR state machine |
| * |
| * The VF FLR state machine looks like:: |
| * |
| * (READY,PAUSED,STOPPED)<------------<--------------o |
| * | \ |
| * flr \ |
| * | \ |
| * ....V..........................FLR_WIP........... \ |
| * : \ : \ |
| * : \ o----<----busy : | |
| * : \ / / : | |
| * : FLR_SEND_START---failed----->-----------o--->(FLR_FAILED)<---o |
| * : | \ : | | |
| * : acked rejected----->-----------o--->(MISMATCH) | |
| * : | : ^ | |
| * : v : | | |
| * : FLR_WAIT_GUC : | | |
| * : | : | | |
| * : done : | | |
| * : | : | | |
| * : v : | | |
| * : FLR_GUC_DONE : | | |
| * : | : | | |
| * : FLR_RESET_CONFIG---failed--->-----------o--------+-----------o |
| * : | : | | |
| * : FLR_RESET_DATA : | | |
| * : | : | | |
| * : FLR_RESET_MMIO : | | |
| * : | : | | |
| * : | o----<----busy : | | |
| * : |/ / : | | |
| * : FLR_SEND_FINISH----failed--->-----------o--------+-----------o |
| * : / \ : | |
| * : acked rejected----->-----------o--------o |
| * : / : |
| * :....o..............................o...........: |
| * | | |
| * completed restart |
| * | / |
| * V / |
| * (READY)<----------<------------o |
| * |
| * For the full state machine view, see `The VF state machine`_. |
| */ |
| |
| static void pf_enter_vf_flr_send_start(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_SEND_START)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static void pf_enter_vf_flr_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_WIP)) { |
| xe_gt_sriov_dbg(gt, "VF%u FLR is already in progress\n", vfid); |
| return; |
| } |
| |
| pf_enter_vf_wip(gt, vfid); |
| pf_enter_vf_flr_send_start(gt, vfid); |
| } |
| |
| static void pf_exit_vf_flr_wip(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_WIP)) { |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_SEND_FINISH); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_MMIO); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_DATA); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_CONFIG); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_GUC_DONE); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_WAIT_GUC); |
| pf_escape_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_SEND_START); |
| } |
| } |
| |
| static void pf_enter_vf_flr_completed(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_ready(gt, vfid); |
| } |
| |
| static void pf_enter_vf_flr_failed(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_FAILED)) |
| xe_gt_sriov_notice(gt, "VF%u FLR failed!\n", vfid); |
| pf_exit_vf_wip(gt, vfid); |
| } |
| |
| static void pf_enter_vf_flr_rejected(struct xe_gt *gt, unsigned int vfid) |
| { |
| pf_enter_vf_mismatch(gt, vfid); |
| pf_enter_vf_flr_failed(gt, vfid); |
| } |
| |
| static void pf_enter_vf_flr_send_finish(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_SEND_FINISH)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_flr_send_finish(struct xe_gt *gt, unsigned int vfid) |
| { |
| int err; |
| |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_SEND_FINISH)) |
| return false; |
| |
| err = pf_send_vf_flr_finish(gt, vfid); |
| if (err == -EBUSY) |
| pf_enter_vf_flr_send_finish(gt, vfid); |
| else if (err == -EIO) |
| pf_enter_vf_flr_rejected(gt, vfid); |
| else if (err) |
| pf_enter_vf_flr_failed(gt, vfid); |
| else |
| pf_enter_vf_flr_completed(gt, vfid); |
| return true; |
| } |
| |
| static void pf_enter_vf_flr_reset_mmio(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_MMIO)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_flr_reset_mmio(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_MMIO)) |
| return false; |
| |
| /* XXX: placeholder */ |
| |
| pf_enter_vf_flr_send_finish(gt, vfid); |
| return true; |
| } |
| |
| static void pf_enter_vf_flr_reset_data(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_DATA)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_flr_reset_data(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_DATA)) |
| return false; |
| |
| xe_gt_sriov_pf_service_reset(gt, vfid); |
| xe_gt_sriov_pf_monitor_flr(gt, vfid); |
| |
| pf_enter_vf_flr_reset_mmio(gt, vfid); |
| return true; |
| } |
| |
| static void pf_enter_vf_flr_reset_config(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_CONFIG)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| |
| pf_queue_vf(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_flr_reset_config(struct xe_gt *gt, unsigned int vfid) |
| { |
| unsigned long timeout = pf_get_default_timeout(XE_GT_SRIOV_STATE_FLR_RESET_CONFIG); |
| int err; |
| |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_RESET_CONFIG)) |
| return false; |
| |
| err = xe_gt_sriov_pf_config_sanitize(gt, vfid, timeout); |
| if (err) |
| pf_enter_vf_flr_failed(gt, vfid); |
| else |
| pf_enter_vf_flr_reset_data(gt, vfid); |
| return true; |
| } |
| |
| static void pf_enter_vf_flr_wait_guc(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_WAIT_GUC)) |
| pf_enter_vf_state_machine_bug(gt, vfid); |
| } |
| |
| static bool pf_exit_vf_flr_wait_guc(struct xe_gt *gt, unsigned int vfid) |
| { |
| return pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_WAIT_GUC); |
| } |
| |
| static bool pf_exit_vf_flr_send_start(struct xe_gt *gt, unsigned int vfid) |
| { |
| int err; |
| |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_SEND_START)) |
| return false; |
| |
| /* GuC may actually send a FLR_DONE before we get a RESPONSE */ |
| pf_enter_vf_flr_wait_guc(gt, vfid); |
| |
| err = pf_send_vf_flr_start(gt, vfid); |
| if (err) { |
| /* send failed, so we shouldn't expect FLR_DONE from GuC */ |
| pf_exit_vf_flr_wait_guc(gt, vfid); |
| |
| if (err == -EBUSY) |
| pf_enter_vf_flr_send_start(gt, vfid); |
| else if (err == -EIO) |
| pf_enter_vf_flr_rejected(gt, vfid); |
| else |
| pf_enter_vf_flr_failed(gt, vfid); |
| } else { |
| /* |
| * we have already moved to WAIT_GUC, maybe even to GUC_DONE |
| * but since GuC didn't complain, we may clear MISMATCH |
| */ |
| pf_exit_vf_mismatch(gt, vfid); |
| } |
| |
| return true; |
| } |
| |
| static bool pf_exit_vf_flr_guc_done(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (!pf_exit_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_GUC_DONE)) |
| return false; |
| |
| pf_enter_vf_flr_reset_config(gt, vfid); |
| return true; |
| } |
| |
| static void pf_enter_vf_flr_guc_done(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_enter_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_GUC_DONE)) |
| pf_queue_vf(gt, vfid); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_control_trigger_flr - Start a VF FLR sequence. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function is for PF only. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_control_trigger_flr(struct xe_gt *gt, unsigned int vfid) |
| { |
| unsigned long timeout = pf_get_default_timeout(XE_GT_SRIOV_STATE_FLR_WIP); |
| int err; |
| |
| pf_enter_vf_flr_wip(gt, vfid); |
| |
| err = pf_wait_vf_wip_done(gt, vfid, timeout); |
| if (err) { |
| xe_gt_sriov_notice(gt, "VF%u FLR didn't finish in %u ms (%pe)\n", |
| vfid, jiffies_to_msecs(timeout), ERR_PTR(err)); |
| return err; |
| } |
| |
| if (!pf_expect_vf_not_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_FAILED)) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| /** |
| * DOC: The VF FLR Flow with GuC |
| * |
| * The VF FLR flow includes several steps:: |
| * |
| * PF GUC PCI |
| * ======================================================== |
| * | | | |
| * (1) | [ ] <----- FLR --| |
| * | [ ] : |
| * (2) [ ] <-------- NOTIFY FLR --[ ] |
| * [ ] | |
| * (3) [ ] | |
| * [ ] | |
| * [ ]-- START FLR ---------> [ ] |
| * | [ ] |
| * (4) | [ ] |
| * | [ ] |
| * [ ] <--------- FLR DONE -- [ ] |
| * [ ] | |
| * (5) [ ] | |
| * [ ] | |
| * [ ]-- FINISH FLR --------> [ ] |
| * | | |
| * |
| * * Step 1: PCI HW generates interrupt to the GuC about VF FLR |
| * * Step 2: GuC FW sends G2H notification to the PF about VF FLR |
| * * Step 2a: on some platforms G2H is only received from root GuC |
| * * Step 3: PF sends H2G request to the GuC to start VF FLR sequence |
| * * Step 3a: on some platforms PF must send H2G to all other GuCs |
| * * Step 4: GuC FW performs VF FLR cleanups and notifies the PF when done |
| * * Step 5: PF performs VF FLR cleanups and notifies the GuC FW when finished |
| */ |
| |
| static bool needs_dispatch_flr(struct xe_device *xe) |
| { |
| return xe->info.platform == XE_PVC; |
| } |
| |
| static void pf_handle_vf_flr(struct xe_gt *gt, u32 vfid) |
| { |
| struct xe_device *xe = gt_to_xe(gt); |
| struct xe_gt *gtit; |
| unsigned int gtid; |
| |
| xe_gt_sriov_info(gt, "VF%u FLR\n", vfid); |
| |
| if (needs_dispatch_flr(xe)) { |
| for_each_gt(gtit, xe, gtid) |
| pf_enter_vf_flr_wip(gtit, vfid); |
| } else { |
| pf_enter_vf_flr_wip(gt, vfid); |
| } |
| } |
| |
| static void pf_handle_vf_flr_done(struct xe_gt *gt, u32 vfid) |
| { |
| if (!pf_exit_vf_flr_wait_guc(gt, vfid)) { |
| xe_gt_sriov_dbg(gt, "Received out of order 'VF%u FLR done'\n", vfid); |
| pf_enter_vf_mismatch(gt, vfid); |
| return; |
| } |
| |
| pf_enter_vf_flr_guc_done(gt, vfid); |
| } |
| |
| static void pf_handle_vf_pause_done(struct xe_gt *gt, u32 vfid) |
| { |
| if (!pf_exit_pause_wait_guc(gt, vfid)) { |
| xe_gt_sriov_dbg(gt, "Received out of order 'VF%u PAUSE done'\n", vfid); |
| pf_enter_vf_mismatch(gt, vfid); |
| return; |
| } |
| |
| pf_enter_vf_pause_guc_done(gt, vfid); |
| } |
| |
| static int pf_handle_vf_event(struct xe_gt *gt, u32 vfid, u32 eventid) |
| { |
| xe_gt_sriov_dbg_verbose(gt, "received VF%u event %#x\n", vfid, eventid); |
| |
| if (vfid > xe_gt_sriov_pf_get_totalvfs(gt)) |
| return -EPROTO; |
| |
| switch (eventid) { |
| case GUC_PF_NOTIFY_VF_FLR: |
| pf_handle_vf_flr(gt, vfid); |
| break; |
| case GUC_PF_NOTIFY_VF_FLR_DONE: |
| pf_handle_vf_flr_done(gt, vfid); |
| break; |
| case GUC_PF_NOTIFY_VF_PAUSE_DONE: |
| pf_handle_vf_pause_done(gt, vfid); |
| break; |
| case GUC_PF_NOTIFY_VF_FIXUP_DONE: |
| break; |
| default: |
| return -ENOPKG; |
| } |
| return 0; |
| } |
| |
| static int pf_handle_pf_event(struct xe_gt *gt, u32 eventid) |
| { |
| switch (eventid) { |
| case GUC_PF_NOTIFY_VF_ENABLE: |
| xe_gt_sriov_dbg_verbose(gt, "VFs %s/%s\n", |
| str_enabled_disabled(true), |
| str_enabled_disabled(false)); |
| break; |
| default: |
| return -ENOPKG; |
| } |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_control_process_guc2pf - Handle VF state notification from GuC. |
| * @gt: the &xe_gt |
| * @msg: the G2H message |
| * @len: the length of the G2H message |
| * |
| * This function is for PF only. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_control_process_guc2pf(struct xe_gt *gt, const u32 *msg, u32 len) |
| { |
| u32 vfid; |
| u32 eventid; |
| |
| xe_gt_assert(gt, len); |
| xe_gt_assert(gt, FIELD_GET(GUC_HXG_MSG_0_ORIGIN, msg[0]) == GUC_HXG_ORIGIN_GUC); |
| xe_gt_assert(gt, FIELD_GET(GUC_HXG_MSG_0_TYPE, msg[0]) == GUC_HXG_TYPE_EVENT); |
| xe_gt_assert(gt, FIELD_GET(GUC_HXG_EVENT_MSG_0_ACTION, msg[0]) == |
| GUC_ACTION_GUC2PF_VF_STATE_NOTIFY); |
| |
| if (unlikely(!xe_device_is_sriov_pf(gt_to_xe(gt)))) |
| return -EPROTO; |
| |
| if (unlikely(FIELD_GET(GUC2PF_VF_STATE_NOTIFY_EVENT_MSG_0_MBZ, msg[0]))) |
| return -EPFNOSUPPORT; |
| |
| if (unlikely(len != GUC2PF_VF_STATE_NOTIFY_EVENT_MSG_LEN)) |
| return -EPROTO; |
| |
| vfid = FIELD_GET(GUC2PF_VF_STATE_NOTIFY_EVENT_MSG_1_VFID, msg[1]); |
| eventid = FIELD_GET(GUC2PF_VF_STATE_NOTIFY_EVENT_MSG_2_EVENT, msg[2]); |
| |
| return vfid ? pf_handle_vf_event(gt, vfid, eventid) : pf_handle_pf_event(gt, eventid); |
| } |
| |
| static bool pf_process_vf_state_machine(struct xe_gt *gt, unsigned int vfid) |
| { |
| if (pf_exit_vf_flr_send_start(gt, vfid)) |
| return true; |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_FLR_WAIT_GUC)) { |
| xe_gt_sriov_dbg_verbose(gt, "VF%u in %s\n", vfid, |
| control_bit_to_string(XE_GT_SRIOV_STATE_FLR_WAIT_GUC)); |
| return false; |
| } |
| |
| if (pf_exit_vf_flr_guc_done(gt, vfid)) |
| return true; |
| |
| if (pf_exit_vf_flr_reset_config(gt, vfid)) |
| return true; |
| |
| if (pf_exit_vf_flr_reset_data(gt, vfid)) |
| return true; |
| |
| if (pf_exit_vf_flr_reset_mmio(gt, vfid)) |
| return true; |
| |
| if (pf_exit_vf_flr_send_finish(gt, vfid)) |
| return true; |
| |
| if (pf_exit_vf_stop_send_stop(gt, vfid)) |
| return true; |
| |
| if (pf_exit_vf_pause_send_pause(gt, vfid)) |
| return true; |
| |
| if (pf_check_vf_state(gt, vfid, XE_GT_SRIOV_STATE_PAUSE_WAIT_GUC)) { |
| xe_gt_sriov_dbg_verbose(gt, "VF%u in %s\n", vfid, |
| control_bit_to_string(XE_GT_SRIOV_STATE_PAUSE_WAIT_GUC)); |
| return true; |
| } |
| |
| if (pf_exit_vf_pause_guc_done(gt, vfid)) |
| return true; |
| |
| if (pf_exit_vf_resume_send_resume(gt, vfid)) |
| return true; |
| |
| return false; |
| } |
| |
| static unsigned int pf_control_state_index(struct xe_gt *gt, |
| struct xe_gt_sriov_control_state *cs) |
| { |
| return container_of(cs, struct xe_gt_sriov_metadata, control) - gt->sriov.pf.vfs; |
| } |
| |
| static void pf_worker_find_work(struct xe_gt *gt) |
| { |
| struct xe_gt_sriov_pf_control *pfc = >->sriov.pf.control; |
| struct xe_gt_sriov_control_state *cs; |
| unsigned int vfid; |
| bool empty; |
| bool more; |
| |
| spin_lock(&pfc->lock); |
| cs = list_first_entry_or_null(&pfc->list, struct xe_gt_sriov_control_state, link); |
| if (cs) |
| list_del_init(&cs->link); |
| empty = list_empty(&pfc->list); |
| spin_unlock(&pfc->lock); |
| |
| if (!cs) |
| return; |
| |
| /* VF metadata structures are indexed by the VFID */ |
| vfid = pf_control_state_index(gt, cs); |
| xe_gt_assert(gt, vfid <= xe_gt_sriov_pf_get_totalvfs(gt)); |
| |
| more = pf_process_vf_state_machine(gt, vfid); |
| if (more) |
| pf_queue_vf(gt, vfid); |
| else if (!empty) |
| pf_queue_control_worker(gt); |
| } |
| |
| static void control_worker_func(struct work_struct *w) |
| { |
| struct xe_gt *gt = container_of(w, struct xe_gt, sriov.pf.control.worker); |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| pf_worker_find_work(gt); |
| } |
| |
| static void pf_stop_worker(struct xe_gt *gt) |
| { |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| cancel_work_sync(>->sriov.pf.control.worker); |
| } |
| |
| static void control_fini_action(struct drm_device *dev, void *data) |
| { |
| struct xe_gt *gt = data; |
| |
| pf_stop_worker(gt); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_control_init() - Initialize PF's control data. |
| * @gt: the &xe_gt |
| * |
| * This function is for PF only. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_control_init(struct xe_gt *gt) |
| { |
| struct xe_device *xe = gt_to_xe(gt); |
| unsigned int n, totalvfs; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(xe)); |
| |
| totalvfs = xe_sriov_pf_get_totalvfs(xe); |
| for (n = 0; n <= totalvfs; n++) { |
| struct xe_gt_sriov_control_state *cs = pf_pick_vf_control(gt, n); |
| |
| init_completion(&cs->done); |
| INIT_LIST_HEAD(&cs->link); |
| } |
| |
| spin_lock_init(>->sriov.pf.control.lock); |
| INIT_LIST_HEAD(>->sriov.pf.control.list); |
| INIT_WORK(>->sriov.pf.control.worker, control_worker_func); |
| |
| return drmm_add_action_or_reset(&xe->drm, control_fini_action, gt); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_control_restart() - Restart SR-IOV control data after a GT reset. |
| * @gt: the &xe_gt |
| * |
| * Any per-VF status maintained by the PF or any ongoing VF control activity |
| * performed by the PF must be reset or cancelled when the GT is reset. |
| * |
| * This function is for PF only. |
| */ |
| void xe_gt_sriov_pf_control_restart(struct xe_gt *gt) |
| { |
| struct xe_device *xe = gt_to_xe(gt); |
| unsigned int n, totalvfs; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(xe)); |
| |
| pf_stop_worker(gt); |
| |
| totalvfs = xe_sriov_pf_get_totalvfs(xe); |
| for (n = 1; n <= totalvfs; n++) |
| pf_enter_vf_ready(gt, n); |
| } |