| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2023-2025 Intel Corporation |
| */ |
| |
| #include "abi/guc_relay_actions_abi.h" |
| |
| #include "xe_device_types.h" |
| #include "xe_sriov.h" |
| #include "xe_sriov_pf_helpers.h" |
| #include "xe_sriov_printk.h" |
| |
| #include "xe_sriov_pf_service.h" |
| #include "xe_sriov_pf_service_types.h" |
| |
| /** |
| * xe_sriov_pf_service_init - Early initialization of the SR-IOV PF service. |
| * @xe: the &xe_device to initialize |
| * |
| * Performs early initialization of the SR-IOV PF service. |
| * |
| * This function can only be called on PF. |
| */ |
| void xe_sriov_pf_service_init(struct xe_device *xe) |
| { |
| BUILD_BUG_ON(!GUC_RELAY_VERSION_BASE_MAJOR && !GUC_RELAY_VERSION_BASE_MINOR); |
| BUILD_BUG_ON(GUC_RELAY_VERSION_BASE_MAJOR > GUC_RELAY_VERSION_LATEST_MAJOR); |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| /* base versions may differ between platforms */ |
| xe->sriov.pf.service.version.base.major = GUC_RELAY_VERSION_BASE_MAJOR; |
| xe->sriov.pf.service.version.base.minor = GUC_RELAY_VERSION_BASE_MINOR; |
| |
| /* latest version is same for all platforms */ |
| xe->sriov.pf.service.version.latest.major = GUC_RELAY_VERSION_LATEST_MAJOR; |
| xe->sriov.pf.service.version.latest.minor = GUC_RELAY_VERSION_LATEST_MINOR; |
| } |
| |
| /* Return: 0 on success or a negative error code on failure. */ |
| static int pf_negotiate_version(struct xe_device *xe, |
| u32 wanted_major, u32 wanted_minor, |
| u32 *major, u32 *minor) |
| { |
| struct xe_sriov_pf_service_version base = xe->sriov.pf.service.version.base; |
| struct xe_sriov_pf_service_version latest = xe->sriov.pf.service.version.latest; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| xe_assert(xe, base.major); |
| xe_assert(xe, base.major <= latest.major); |
| xe_assert(xe, (base.major < latest.major) || (base.minor <= latest.minor)); |
| |
| /* VF doesn't care - return our latest */ |
| if (wanted_major == VF2PF_HANDSHAKE_MAJOR_ANY && |
| wanted_minor == VF2PF_HANDSHAKE_MINOR_ANY) { |
| *major = latest.major; |
| *minor = latest.minor; |
| return 0; |
| } |
| |
| /* VF wants newer than our - return our latest */ |
| if (wanted_major > latest.major) { |
| *major = latest.major; |
| *minor = latest.minor; |
| return 0; |
| } |
| |
| /* VF wants older than min required - reject */ |
| if (wanted_major < base.major || |
| (wanted_major == base.major && wanted_minor < base.minor)) { |
| return -EPERM; |
| } |
| |
| /* previous major - return wanted, as we should still support it */ |
| if (wanted_major < latest.major) { |
| /* XXX: we are not prepared for multi-versions yet */ |
| xe_assert(xe, base.major == latest.major); |
| return -ENOPKG; |
| } |
| |
| /* same major - return common minor */ |
| *major = wanted_major; |
| *minor = min_t(u32, latest.minor, wanted_minor); |
| return 0; |
| } |
| |
| static void pf_connect(struct xe_device *xe, u32 vfid, u32 major, u32 minor) |
| { |
| xe_sriov_pf_assert_vfid(xe, vfid); |
| xe_assert(xe, major || minor); |
| |
| xe->sriov.pf.vfs[vfid].version.major = major; |
| xe->sriov.pf.vfs[vfid].version.minor = minor; |
| } |
| |
| static void pf_disconnect(struct xe_device *xe, u32 vfid) |
| { |
| xe_sriov_pf_assert_vfid(xe, vfid); |
| |
| xe->sriov.pf.vfs[vfid].version.major = 0; |
| xe->sriov.pf.vfs[vfid].version.minor = 0; |
| } |
| |
| /** |
| * xe_sriov_pf_service_is_negotiated - Check if VF has negotiated given ABI version. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * @major: the major version to check |
| * @minor: the minor version to check |
| * |
| * Performs early initialization of the SR-IOV PF service. |
| * |
| * This function can only be called on PF. |
| * |
| * Returns: true if VF can use given ABI version functionality. |
| */ |
| bool xe_sriov_pf_service_is_negotiated(struct xe_device *xe, u32 vfid, u32 major, u32 minor) |
| { |
| xe_sriov_pf_assert_vfid(xe, vfid); |
| |
| return major == xe->sriov.pf.vfs[vfid].version.major && |
| minor <= xe->sriov.pf.vfs[vfid].version.minor; |
| } |
| |
| /** |
| * xe_sriov_pf_service_handshake_vf - Confirm a connection with the VF. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * @wanted_major: the major service version expected by the VF |
| * @wanted_minor: the minor service version expected by the VF |
| * @major: the major service version to be used by the VF |
| * @minor: the minor service version to be used by the VF |
| * |
| * Negotiate a VF/PF ABI version to allow VF use the PF services. |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_sriov_pf_service_handshake_vf(struct xe_device *xe, u32 vfid, |
| u32 wanted_major, u32 wanted_minor, |
| u32 *major, u32 *minor) |
| { |
| int err; |
| |
| xe_sriov_dbg_verbose(xe, "VF%u wants ABI version %u.%u\n", |
| vfid, wanted_major, wanted_minor); |
| |
| err = pf_negotiate_version(xe, wanted_major, wanted_minor, major, minor); |
| |
| if (err < 0) { |
| xe_sriov_notice(xe, "VF%u failed to negotiate ABI %u.%u (%pe)\n", |
| vfid, wanted_major, wanted_minor, ERR_PTR(err)); |
| pf_disconnect(xe, vfid); |
| } else { |
| xe_sriov_dbg(xe, "VF%u negotiated ABI version %u.%u\n", |
| vfid, *major, *minor); |
| pf_connect(xe, vfid, *major, *minor); |
| } |
| |
| return err; |
| } |
| |
| /** |
| * xe_sriov_pf_service_reset_vf - Reset a connection with the VF. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * |
| * Reset a VF driver negotiated VF/PF ABI version. |
| * |
| * After that point, the VF driver will have to perform new version handshake |
| * to continue use of the PF services again. |
| * |
| * This function can only be called on PF. |
| */ |
| void xe_sriov_pf_service_reset_vf(struct xe_device *xe, unsigned int vfid) |
| { |
| pf_disconnect(xe, vfid); |
| } |
| |
| static void print_pf_version(struct drm_printer *p, const char *name, |
| const struct xe_sriov_pf_service_version *version) |
| { |
| drm_printf(p, "%s:\t%u.%u\n", name, version->major, version->minor); |
| } |
| |
| /** |
| * xe_sriov_pf_service_print_versions - Print ABI versions negotiated with VFs. |
| * @xe: the &xe_device |
| * @p: the &drm_printer |
| * |
| * This function is for PF use only. |
| */ |
| void xe_sriov_pf_service_print_versions(struct xe_device *xe, struct drm_printer *p) |
| { |
| unsigned int n, total_vfs = xe_sriov_pf_get_totalvfs(xe); |
| struct xe_sriov_pf_service_version *version; |
| char name[8]; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| print_pf_version(p, "base", &xe->sriov.pf.service.version.base); |
| print_pf_version(p, "latest", &xe->sriov.pf.service.version.latest); |
| |
| for (n = 1; n <= total_vfs; n++) { |
| version = &xe->sriov.pf.vfs[n].version; |
| if (!version->major && !version->minor) |
| continue; |
| |
| print_pf_version(p, xe_sriov_function_name(n, name, sizeof(name)), version); |
| } |
| } |
| |
| #if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST) |
| #include "tests/xe_sriov_pf_service_kunit.c" |
| #endif |