| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2025 Intel Corporation |
| */ |
| |
| #include <drm/drm_managed.h> |
| |
| #include "xe_device.h" |
| #include "xe_gt_sriov_pf_control.h" |
| #include "xe_gt_sriov_pf_migration.h" |
| #include "xe_pm.h" |
| #include "xe_sriov.h" |
| #include "xe_sriov_packet.h" |
| #include "xe_sriov_packet_types.h" |
| #include "xe_sriov_pf_helpers.h" |
| #include "xe_sriov_pf_migration.h" |
| #include "xe_sriov_printk.h" |
| |
| static struct xe_sriov_migration_state *pf_pick_migration(struct xe_device *xe, unsigned int vfid) |
| { |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| xe_assert(xe, vfid <= xe_sriov_pf_get_totalvfs(xe)); |
| |
| return &xe->sriov.pf.vfs[vfid].migration; |
| } |
| |
| /** |
| * xe_sriov_pf_migration_waitqueue() - Get waitqueue for migration. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * |
| * Return: pointer to the migration waitqueue. |
| */ |
| wait_queue_head_t *xe_sriov_pf_migration_waitqueue(struct xe_device *xe, unsigned int vfid) |
| { |
| return &pf_pick_migration(xe, vfid)->wq; |
| } |
| |
| /** |
| * xe_sriov_pf_migration_supported() - Check if SR-IOV VF migration is supported by the device |
| * @xe: the &xe_device |
| * |
| * Return: true if migration is supported, false otherwise |
| */ |
| bool xe_sriov_pf_migration_supported(struct xe_device *xe) |
| { |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| return IS_ENABLED(CONFIG_DRM_XE_DEBUG) || !xe->sriov.pf.migration.disabled; |
| } |
| |
| /** |
| * xe_sriov_pf_migration_disable() - Turn off SR-IOV VF migration support on PF. |
| * @xe: the &xe_device instance. |
| * @fmt: format string for the log message, to be combined with following VAs. |
| */ |
| void xe_sriov_pf_migration_disable(struct xe_device *xe, const char *fmt, ...) |
| { |
| struct va_format vaf; |
| va_list va_args; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| va_start(va_args, fmt); |
| vaf.fmt = fmt; |
| vaf.va = &va_args; |
| xe_sriov_notice(xe, "migration %s: %pV\n", |
| IS_ENABLED(CONFIG_DRM_XE_DEBUG) ? |
| "missing prerequisite" : "disabled", |
| &vaf); |
| va_end(va_args); |
| |
| xe->sriov.pf.migration.disabled = true; |
| } |
| |
| static void pf_migration_check_support(struct xe_device *xe) |
| { |
| if (!xe_device_has_memirq(xe)) |
| xe_sriov_pf_migration_disable(xe, "requires memory-based IRQ support"); |
| } |
| |
| static void pf_migration_cleanup(void *arg) |
| { |
| struct xe_sriov_migration_state *migration = arg; |
| |
| xe_sriov_packet_free(migration->pending); |
| xe_sriov_packet_free(migration->trailer); |
| xe_sriov_packet_free(migration->descriptor); |
| } |
| |
| /** |
| * xe_sriov_pf_migration_init() - Initialize support for SR-IOV VF migration. |
| * @xe: the &xe_device |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_sriov_pf_migration_init(struct xe_device *xe) |
| { |
| unsigned int n, totalvfs; |
| int err; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| pf_migration_check_support(xe); |
| |
| if (!xe_sriov_pf_migration_supported(xe)) |
| return 0; |
| |
| totalvfs = xe_sriov_pf_get_totalvfs(xe); |
| for (n = 1; n <= totalvfs; n++) { |
| struct xe_sriov_migration_state *migration = pf_pick_migration(xe, n); |
| |
| err = drmm_mutex_init(&xe->drm, &migration->lock); |
| if (err) |
| return err; |
| |
| init_waitqueue_head(&migration->wq); |
| |
| err = devm_add_action_or_reset(xe->drm.dev, pf_migration_cleanup, migration); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static bool pf_migration_data_ready(struct xe_device *xe, unsigned int vfid) |
| { |
| struct xe_gt *gt; |
| u8 gt_id; |
| |
| for_each_gt(gt, xe, gt_id) { |
| if (xe_gt_sriov_pf_control_check_save_failed(gt, vfid) || |
| xe_gt_sriov_pf_control_check_save_data_done(gt, vfid) || |
| !xe_gt_sriov_pf_migration_ring_empty(gt, vfid)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static struct xe_sriov_packet * |
| pf_migration_consume(struct xe_device *xe, unsigned int vfid) |
| { |
| struct xe_sriov_packet *data; |
| bool more_data = false; |
| struct xe_gt *gt; |
| u8 gt_id; |
| |
| for_each_gt(gt, xe, gt_id) { |
| data = xe_gt_sriov_pf_migration_save_consume(gt, vfid); |
| if (data && PTR_ERR(data) != EAGAIN) |
| return data; |
| if (PTR_ERR(data) == -EAGAIN) |
| more_data = true; |
| } |
| |
| if (!more_data) |
| return NULL; |
| |
| return ERR_PTR(-EAGAIN); |
| } |
| |
| /** |
| * xe_sriov_pf_migration_save_consume() - Consume a VF migration data packet from the device. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * |
| * Called by the save migration data consumer (userspace) when |
| * processing migration data. |
| * If there is no migration data to process, wait until more data is available. |
| * |
| * Return: Pointer to &xe_sriov_packet on success, |
| * NULL if ring is empty and no more migration data is expected, |
| * ERR_PTR value in case of error. |
| */ |
| struct xe_sriov_packet * |
| xe_sriov_pf_migration_save_consume(struct xe_device *xe, unsigned int vfid) |
| { |
| struct xe_sriov_migration_state *migration = pf_pick_migration(xe, vfid); |
| struct xe_sriov_packet *data; |
| int ret; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| for (;;) { |
| data = pf_migration_consume(xe, vfid); |
| if (PTR_ERR(data) != -EAGAIN) |
| break; |
| |
| ret = wait_event_interruptible(migration->wq, |
| pf_migration_data_ready(xe, vfid)); |
| if (ret) |
| return ERR_PTR(ret); |
| } |
| |
| return data; |
| } |
| |
| static int pf_handle_descriptor(struct xe_device *xe, unsigned int vfid, |
| struct xe_sriov_packet *data) |
| { |
| int ret; |
| |
| if (data->hdr.tile_id != 0 || data->hdr.gt_id != 0) |
| return -EINVAL; |
| |
| ret = xe_sriov_packet_process_descriptor(xe, vfid, data); |
| if (ret) |
| return ret; |
| |
| xe_sriov_packet_free(data); |
| |
| return 0; |
| } |
| |
| static int pf_handle_trailer(struct xe_device *xe, unsigned int vfid, |
| struct xe_sriov_packet *data) |
| { |
| struct xe_gt *gt; |
| u8 gt_id; |
| |
| if (data->hdr.tile_id != 0 || data->hdr.gt_id != 0) |
| return -EINVAL; |
| if (data->hdr.offset != 0 || data->hdr.size != 0 || data->buff || data->bo) |
| return -EINVAL; |
| |
| xe_sriov_packet_free(data); |
| |
| for_each_gt(gt, xe, gt_id) |
| xe_gt_sriov_pf_control_restore_data_done(gt, vfid); |
| |
| return 0; |
| } |
| |
| /** |
| * xe_sriov_pf_migration_restore_produce() - Produce a VF migration data packet to the device. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * @data: Pointer to &xe_sriov_packet |
| * |
| * Called by the restore migration data producer (userspace) when processing |
| * migration data. |
| * If the underlying data structure is full, wait until there is space. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_sriov_pf_migration_restore_produce(struct xe_device *xe, unsigned int vfid, |
| struct xe_sriov_packet *data) |
| { |
| struct xe_gt *gt; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| if (data->hdr.type == XE_SRIOV_PACKET_TYPE_DESCRIPTOR) |
| return pf_handle_descriptor(xe, vfid, data); |
| if (data->hdr.type == XE_SRIOV_PACKET_TYPE_TRAILER) |
| return pf_handle_trailer(xe, vfid, data); |
| |
| gt = xe_device_get_gt(xe, data->hdr.gt_id); |
| if (!gt || data->hdr.tile_id != gt->tile->id || data->hdr.type == 0) { |
| xe_sriov_err_ratelimited(xe, "Received invalid restore packet for VF%u (type:%u, tile:%u, GT:%u)\n", |
| vfid, data->hdr.type, data->hdr.tile_id, data->hdr.gt_id); |
| return -EINVAL; |
| } |
| |
| return xe_gt_sriov_pf_migration_restore_produce(gt, vfid, data); |
| } |
| |
| /** |
| * xe_sriov_pf_migration_read() - Read migration data from the device. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * @buf: start address of userspace buffer |
| * @len: requested read size from userspace |
| * |
| * Return: number of bytes that has been successfully read, |
| * 0 if no more migration data is available, |
| * -errno on failure. |
| */ |
| ssize_t xe_sriov_pf_migration_read(struct xe_device *xe, unsigned int vfid, |
| char __user *buf, size_t len) |
| { |
| struct xe_sriov_migration_state *migration = pf_pick_migration(xe, vfid); |
| ssize_t ret, consumed = 0; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| scoped_cond_guard(mutex_intr, return -EINTR, &migration->lock) { |
| while (consumed < len) { |
| ret = xe_sriov_packet_read_single(xe, vfid, buf, len - consumed); |
| if (ret == -ENODATA) |
| break; |
| if (ret < 0) |
| return ret; |
| |
| consumed += ret; |
| buf += ret; |
| } |
| } |
| |
| return consumed; |
| } |
| |
| /** |
| * xe_sriov_pf_migration_write() - Write migration data to the device. |
| * @xe: the &xe_device |
| * @vfid: the VF identifier |
| * @buf: start address of userspace buffer |
| * @len: requested write size from userspace |
| * |
| * Return: number of bytes that has been successfully written, |
| * -errno on failure. |
| */ |
| ssize_t xe_sriov_pf_migration_write(struct xe_device *xe, unsigned int vfid, |
| const char __user *buf, size_t len) |
| { |
| struct xe_sriov_migration_state *migration = pf_pick_migration(xe, vfid); |
| ssize_t ret, produced = 0; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| scoped_cond_guard(mutex_intr, return -EINTR, &migration->lock) { |
| while (produced < len) { |
| ret = xe_sriov_packet_write_single(xe, vfid, buf, len - produced); |
| if (ret < 0) |
| return ret; |
| |
| produced += ret; |
| buf += ret; |
| } |
| } |
| |
| return produced; |
| } |
| |
| /** |
| * xe_sriov_pf_migration_size() - Total size of migration data from all components within a device |
| * @xe: the &xe_device |
| * @vfid: the VF identifier (can't be 0) |
| * |
| * This function is for PF only. |
| * |
| * Return: total migration data size in bytes or a negative error code on failure. |
| */ |
| ssize_t xe_sriov_pf_migration_size(struct xe_device *xe, unsigned int vfid) |
| { |
| size_t size = 0; |
| struct xe_gt *gt; |
| ssize_t ret; |
| u8 gt_id; |
| |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| xe_assert(xe, vfid); |
| |
| for_each_gt(gt, xe, gt_id) { |
| ret = xe_gt_sriov_pf_migration_size(gt, vfid); |
| if (ret < 0) |
| return ret; |
| |
| size += ret; |
| } |
| |
| return size; |
| } |