// SPDX-License-Identifier: MIT /* * Copyright © 2024 Intel Corporation */ #include #include "abi/guc_actions_sriov_abi.h" #include "xe_bo.h" #include "xe_gt_sriov_pf_helpers.h" #include "xe_gt_sriov_pf_migration.h" #include "xe_gt_sriov_printk.h" #include "xe_guc.h" #include "xe_guc_ct.h" #include "xe_sriov.h" /* Return: number of dwords saved/restored/required or a negative error code on failure */ static int guc_action_vf_save_restore(struct xe_guc *guc, u32 vfid, u32 opcode, u64 addr, u32 ndwords) { u32 request[PF2GUC_SAVE_RESTORE_VF_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_SAVE_RESTORE_VF) | FIELD_PREP(PF2GUC_SAVE_RESTORE_VF_REQUEST_MSG_0_OPCODE, opcode), FIELD_PREP(PF2GUC_SAVE_RESTORE_VF_REQUEST_MSG_1_VFID, vfid), FIELD_PREP(PF2GUC_SAVE_RESTORE_VF_REQUEST_MSG_2_ADDR_LO, lower_32_bits(addr)), FIELD_PREP(PF2GUC_SAVE_RESTORE_VF_REQUEST_MSG_3_ADDR_HI, upper_32_bits(addr)), FIELD_PREP(PF2GUC_SAVE_RESTORE_VF_REQUEST_MSG_4_SIZE, ndwords), }; return xe_guc_ct_send_block(&guc->ct, request, ARRAY_SIZE(request)); } /* Return: size of the state in dwords or a negative error code on failure */ static int pf_send_guc_query_vf_state_size(struct xe_gt *gt, unsigned int vfid) { int ret; ret = guc_action_vf_save_restore(>->uc.guc, vfid, GUC_PF_OPCODE_VF_SAVE, 0, 0); return ret ?: -ENODATA; } /* Return: number of state dwords saved or a negative error code on failure */ static int pf_send_guc_save_vf_state(struct xe_gt *gt, unsigned int vfid, void *buff, size_t size) { const int ndwords = size / sizeof(u32); struct xe_tile *tile = gt_to_tile(gt); struct xe_device *xe = tile_to_xe(tile); struct xe_guc *guc = >->uc.guc; struct xe_bo *bo; int ret; xe_gt_assert(gt, size % sizeof(u32) == 0); xe_gt_assert(gt, size == ndwords * sizeof(u32)); bo = xe_bo_create_pin_map(xe, tile, NULL, ALIGN(size, PAGE_SIZE), ttm_bo_type_kernel, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_GGTT | XE_BO_FLAG_GGTT_INVALIDATE); if (IS_ERR(bo)) return PTR_ERR(bo); ret = guc_action_vf_save_restore(guc, vfid, GUC_PF_OPCODE_VF_SAVE, xe_bo_ggtt_addr(bo), ndwords); if (!ret) ret = -ENODATA; else if (ret > ndwords) ret = -EPROTO; else if (ret > 0) xe_map_memcpy_from(xe, buff, &bo->vmap, 0, ret * sizeof(u32)); xe_bo_unpin_map_no_vm(bo); return ret; } /* Return: number of state dwords restored or a negative error code on failure */ static int pf_send_guc_restore_vf_state(struct xe_gt *gt, unsigned int vfid, const void *buff, size_t size) { const int ndwords = size / sizeof(u32); struct xe_tile *tile = gt_to_tile(gt); struct xe_device *xe = tile_to_xe(tile); struct xe_guc *guc = >->uc.guc; struct xe_bo *bo; int ret; xe_gt_assert(gt, size % sizeof(u32) == 0); xe_gt_assert(gt, size == ndwords * sizeof(u32)); bo = xe_bo_create_pin_map(xe, tile, NULL, ALIGN(size, PAGE_SIZE), ttm_bo_type_kernel, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_GGTT | XE_BO_FLAG_GGTT_INVALIDATE); if (IS_ERR(bo)) return PTR_ERR(bo); xe_map_memcpy_to(xe, &bo->vmap, 0, buff, size); ret = guc_action_vf_save_restore(guc, vfid, GUC_PF_OPCODE_VF_RESTORE, xe_bo_ggtt_addr(bo), ndwords); if (!ret) ret = -ENODATA; else if (ret > ndwords) ret = -EPROTO; xe_bo_unpin_map_no_vm(bo); return ret; } static bool pf_migration_supported(struct xe_gt *gt) { xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); return gt->sriov.pf.migration.supported; } static struct mutex *pf_migration_mutex(struct xe_gt *gt) { xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); return >->sriov.pf.migration.snapshot_lock; } static struct xe_gt_sriov_state_snapshot *pf_pick_vf_snapshot(struct xe_gt *gt, unsigned int vfid) { xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); lockdep_assert_held(pf_migration_mutex(gt)); return >->sriov.pf.vfs[vfid].snapshot; } static unsigned int pf_snapshot_index(struct xe_gt *gt, struct xe_gt_sriov_state_snapshot *snapshot) { return container_of(snapshot, struct xe_gt_sriov_metadata, snapshot) - gt->sriov.pf.vfs; } static void pf_free_guc_state(struct xe_gt *gt, struct xe_gt_sriov_state_snapshot *snapshot) { struct xe_device *xe = gt_to_xe(gt); drmm_kfree(&xe->drm, snapshot->guc.buff); snapshot->guc.buff = NULL; snapshot->guc.size = 0; } static int pf_alloc_guc_state(struct xe_gt *gt, struct xe_gt_sriov_state_snapshot *snapshot, size_t size) { struct xe_device *xe = gt_to_xe(gt); void *p; pf_free_guc_state(gt, snapshot); if (!size) return -ENODATA; if (size % sizeof(u32)) return -EINVAL; if (size > SZ_2M) return -EFBIG; p = drmm_kzalloc(&xe->drm, size, GFP_KERNEL); if (!p) return -ENOMEM; snapshot->guc.buff = p; snapshot->guc.size = size; return 0; } static void pf_dump_guc_state(struct xe_gt *gt, struct xe_gt_sriov_state_snapshot *snapshot) { if (IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV)) { unsigned int vfid __maybe_unused = pf_snapshot_index(gt, snapshot); xe_gt_sriov_dbg_verbose(gt, "VF%u GuC state is %zu dwords:\n", vfid, snapshot->guc.size / sizeof(u32)); print_hex_dump_bytes("state: ", DUMP_PREFIX_OFFSET, snapshot->guc.buff, min(SZ_64, snapshot->guc.size)); } } static int pf_save_vf_guc_state(struct xe_gt *gt, unsigned int vfid) { struct xe_gt_sriov_state_snapshot *snapshot = pf_pick_vf_snapshot(gt, vfid); size_t size; int ret; ret = pf_send_guc_query_vf_state_size(gt, vfid); if (ret < 0) goto fail; size = ret * sizeof(u32); xe_gt_sriov_dbg_verbose(gt, "VF%u state size is %d dwords (%zu bytes)\n", vfid, ret, size); ret = pf_alloc_guc_state(gt, snapshot, size); if (ret < 0) goto fail; ret = pf_send_guc_save_vf_state(gt, vfid, snapshot->guc.buff, size); if (ret < 0) goto fail; size = ret * sizeof(u32); xe_gt_assert(gt, size); xe_gt_assert(gt, size <= snapshot->guc.size); snapshot->guc.size = size; pf_dump_guc_state(gt, snapshot); return 0; fail: xe_gt_sriov_dbg(gt, "Unable to save VF%u state (%pe)\n", vfid, ERR_PTR(ret)); pf_free_guc_state(gt, snapshot); return ret; } /** * xe_gt_sriov_pf_migration_save_guc_state() - Take a GuC VF state snapshot. * @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_migration_save_guc_state(struct xe_gt *gt, unsigned int vfid) { int err; xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); xe_gt_assert(gt, vfid != PFID); xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); if (!pf_migration_supported(gt)) return -ENOPKG; mutex_lock(pf_migration_mutex(gt)); err = pf_save_vf_guc_state(gt, vfid); mutex_unlock(pf_migration_mutex(gt)); return err; } static int pf_restore_vf_guc_state(struct xe_gt *gt, unsigned int vfid) { struct xe_gt_sriov_state_snapshot *snapshot = pf_pick_vf_snapshot(gt, vfid); int ret; if (!snapshot->guc.size) return -ENODATA; xe_gt_sriov_dbg_verbose(gt, "restoring %zu dwords of VF%u GuC state\n", snapshot->guc.size / sizeof(u32), vfid); ret = pf_send_guc_restore_vf_state(gt, vfid, snapshot->guc.buff, snapshot->guc.size); if (ret < 0) goto fail; xe_gt_sriov_dbg_verbose(gt, "restored %d dwords of VF%u GuC state\n", ret, vfid); return 0; fail: xe_gt_sriov_dbg(gt, "Failed to restore VF%u GuC state (%pe)\n", vfid, ERR_PTR(ret)); return ret; } /** * xe_gt_sriov_pf_migration_restore_guc_state() - Restore a GuC VF state. * @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_migration_restore_guc_state(struct xe_gt *gt, unsigned int vfid) { int ret; xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); xe_gt_assert(gt, vfid != PFID); xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); if (!pf_migration_supported(gt)) return -ENOPKG; mutex_lock(pf_migration_mutex(gt)); ret = pf_restore_vf_guc_state(gt, vfid); mutex_unlock(pf_migration_mutex(gt)); return ret; } #ifdef CONFIG_DEBUG_FS /** * xe_gt_sriov_pf_migration_read_guc_state() - Read a GuC VF state. * @gt: the &xe_gt * @vfid: the VF identifier * @buf: the user space buffer to read to * @count: the maximum number of bytes to read * @pos: the current position in the buffer * * This function is for PF only. * * This function reads up to @count bytes from the saved VF GuC state buffer * at offset @pos into the user space address starting at @buf. * * Return: the number of bytes read or a negative error code on failure. */ ssize_t xe_gt_sriov_pf_migration_read_guc_state(struct xe_gt *gt, unsigned int vfid, char __user *buf, size_t count, loff_t *pos) { struct xe_gt_sriov_state_snapshot *snapshot; ssize_t ret; xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); xe_gt_assert(gt, vfid != PFID); xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); if (!pf_migration_supported(gt)) return -ENOPKG; mutex_lock(pf_migration_mutex(gt)); snapshot = pf_pick_vf_snapshot(gt, vfid); if (snapshot->guc.size) ret = simple_read_from_buffer(buf, count, pos, snapshot->guc.buff, snapshot->guc.size); else ret = -ENODATA; mutex_unlock(pf_migration_mutex(gt)); return ret; } /** * xe_gt_sriov_pf_migration_write_guc_state() - Write a GuC VF state. * @gt: the &xe_gt * @vfid: the VF identifier * @buf: the user space buffer with GuC VF state * @size: the size of GuC VF state (in bytes) * * This function is for PF only. * * This function reads @size bytes of the VF GuC state stored at user space * address @buf and writes it into a internal VF state buffer. * * Return: the number of bytes used or a negative error code on failure. */ ssize_t xe_gt_sriov_pf_migration_write_guc_state(struct xe_gt *gt, unsigned int vfid, const char __user *buf, size_t size) { struct xe_gt_sriov_state_snapshot *snapshot; loff_t pos = 0; ssize_t ret; xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); xe_gt_assert(gt, vfid != PFID); xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); if (!pf_migration_supported(gt)) return -ENOPKG; mutex_lock(pf_migration_mutex(gt)); snapshot = pf_pick_vf_snapshot(gt, vfid); ret = pf_alloc_guc_state(gt, snapshot, size); if (!ret) { ret = simple_write_to_buffer(snapshot->guc.buff, size, &pos, buf, size); if (ret < 0) pf_free_guc_state(gt, snapshot); else pf_dump_guc_state(gt, snapshot); } mutex_unlock(pf_migration_mutex(gt)); return ret; } #endif /* CONFIG_DEBUG_FS */ static bool pf_check_migration_support(struct xe_gt *gt) { /* GuC 70.25 with save/restore v2 is required */ xe_gt_assert(gt, GUC_FIRMWARE_VER(>->uc.guc) >= MAKE_GUC_VER(70, 25, 0)); /* XXX: for now this is for feature enabling only */ return IS_ENABLED(CONFIG_DRM_XE_DEBUG); } /** * xe_gt_sriov_pf_migration_init() - Initialize support for VF migration. * @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_migration_init(struct xe_gt *gt) { struct xe_device *xe = gt_to_xe(gt); int err; xe_gt_assert(gt, IS_SRIOV_PF(xe)); gt->sriov.pf.migration.supported = pf_check_migration_support(gt); if (!pf_migration_supported(gt)) return 0; err = drmm_mutex_init(&xe->drm, >->sriov.pf.migration.snapshot_lock); if (err) return err; return 0; }