| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2023-2024 Intel Corporation |
| */ |
| |
| #include "abi/guc_actions_sriov_abi.h" |
| |
| #include "xe_device.h" |
| #include "xe_gt.h" |
| #include "xe_gt_sriov_pf_control.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; |
| |
| /* XXX those two commands are now sent from the G2H handler */ |
| if (cmd == GUC_PF_TRIGGER_VF_FLR_START || cmd == GUC_PF_TRIGGER_VF_FLR_FINISH) |
| return xe_guc_ct_send_g2h_handler(&guc->ct, request, ARRAY_SIZE(request)); |
| |
| 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); |
| |
| 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); |
| } |
| |
| /** |
| * 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) |
| { |
| return pf_send_vf_pause(gt, vfid); |
| } |
| |
| /** |
| * 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) |
| { |
| return pf_send_vf_resume(gt, vfid); |
| } |
| |
| /** |
| * 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) |
| { |
| return pf_send_vf_stop(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) |
| { |
| int err; |
| |
| /* XXX pf_send_vf_flr_start() expects ct->lock */ |
| mutex_lock(>->uc.guc.ct.lock); |
| err = pf_send_vf_flr_start(gt, vfid); |
| mutex_unlock(>->uc.guc.ct.lock); |
| |
| return err; |
| } |
| |
| /** |
| * DOC: The VF FLR Flow with GuC |
| * |
| * 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_send_vf_flr_start(gtit, vfid); |
| } else { |
| pf_send_vf_flr_start(gt, vfid); |
| } |
| } |
| |
| static void pf_handle_vf_flr_done(struct xe_gt *gt, u32 vfid) |
| { |
| pf_send_vf_flr_finish(gt, vfid); |
| } |
| |
| static int pf_handle_vf_event(struct xe_gt *gt, u32 vfid, u32 eventid) |
| { |
| 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: |
| 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); |
| } |