mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 14:53:58 -04:00
When an error is returned from xe_sriov_pf_migration_restore_produce(), the data pointer is not set to NULL, which can trigger use-after-free in subsequent .write() calls. Set the pointer to NULL upon error to fix the problem. Fixes:1ed30397c0("drm/xe/pf: Add support for encap/decap of bitstream to/from packet") Reported-by: Sebastian Österlund <sebastian.osterlund@intel.com> Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/7230 Reviewed-by: Shuicheng Lin <shuicheng.lin@intel.com> Link: https://patch.msgid.link/20260217154118.176902-1-michal.winiarski@intel.com Signed-off-by: Michał Winiarski <michal.winiarski@intel.com> (cherry picked from commit4f53d8c6d2) Signed-off-by: Rodrigo Vivi <rodrigo.vivi@intel.com>
522 lines
12 KiB
C
522 lines
12 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2025 Intel Corporation
|
|
*/
|
|
|
|
#include "xe_bo.h"
|
|
#include "xe_device.h"
|
|
#include "xe_guc_klv_helpers.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 mutex *pf_migration_mutex(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.lock;
|
|
}
|
|
|
|
static struct xe_sriov_packet **pf_pick_pending(struct xe_device *xe, unsigned int vfid)
|
|
{
|
|
xe_assert(xe, IS_SRIOV_PF(xe));
|
|
xe_assert(xe, vfid <= xe_sriov_pf_get_totalvfs(xe));
|
|
lockdep_assert_held(pf_migration_mutex(xe, vfid));
|
|
|
|
return &xe->sriov.pf.vfs[vfid].migration.pending;
|
|
}
|
|
|
|
static struct xe_sriov_packet **
|
|
pf_pick_descriptor(struct xe_device *xe, unsigned int vfid)
|
|
{
|
|
xe_assert(xe, IS_SRIOV_PF(xe));
|
|
xe_assert(xe, vfid <= xe_sriov_pf_get_totalvfs(xe));
|
|
lockdep_assert_held(pf_migration_mutex(xe, vfid));
|
|
|
|
return &xe->sriov.pf.vfs[vfid].migration.descriptor;
|
|
}
|
|
|
|
static struct xe_sriov_packet **pf_pick_trailer(struct xe_device *xe, unsigned int vfid)
|
|
{
|
|
xe_assert(xe, IS_SRIOV_PF(xe));
|
|
xe_assert(xe, vfid <= xe_sriov_pf_get_totalvfs(xe));
|
|
lockdep_assert_held(pf_migration_mutex(xe, vfid));
|
|
|
|
return &xe->sriov.pf.vfs[vfid].migration.trailer;
|
|
}
|
|
|
|
static struct xe_sriov_packet **pf_pick_read_packet(struct xe_device *xe,
|
|
unsigned int vfid)
|
|
{
|
|
struct xe_sriov_packet **data;
|
|
|
|
data = pf_pick_descriptor(xe, vfid);
|
|
if (*data)
|
|
return data;
|
|
|
|
data = pf_pick_pending(xe, vfid);
|
|
if (!*data)
|
|
*data = xe_sriov_pf_migration_save_consume(xe, vfid);
|
|
if (*data)
|
|
return data;
|
|
|
|
data = pf_pick_trailer(xe, vfid);
|
|
if (*data)
|
|
return data;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool pkt_needs_bo(struct xe_sriov_packet *data)
|
|
{
|
|
return data->hdr.type == XE_SRIOV_PACKET_TYPE_VRAM;
|
|
}
|
|
|
|
/**
|
|
* xe_sriov_packet_alloc() - Allocate migration data packet
|
|
* @xe: the &xe_device
|
|
*
|
|
* Only allocates the "outer" structure, without initializing the migration
|
|
* data backing storage.
|
|
*
|
|
* Return: Pointer to &xe_sriov_packet on success,
|
|
* NULL in case of error.
|
|
*/
|
|
struct xe_sriov_packet *xe_sriov_packet_alloc(struct xe_device *xe)
|
|
{
|
|
struct xe_sriov_packet *data;
|
|
|
|
data = kzalloc_obj(*data);
|
|
if (!data)
|
|
return NULL;
|
|
|
|
data->xe = xe;
|
|
data->hdr_remaining = sizeof(data->hdr);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* xe_sriov_packet_free() - Free migration data packet.
|
|
* @data: the &xe_sriov_packet
|
|
*/
|
|
void xe_sriov_packet_free(struct xe_sriov_packet *data)
|
|
{
|
|
if (IS_ERR_OR_NULL(data))
|
|
return;
|
|
|
|
if (pkt_needs_bo(data))
|
|
xe_bo_unpin_map_no_vm(data->bo);
|
|
else
|
|
kvfree(data->buff);
|
|
|
|
kfree(data);
|
|
}
|
|
|
|
static int pkt_init(struct xe_sriov_packet *data)
|
|
{
|
|
struct xe_gt *gt = xe_device_get_gt(data->xe, data->hdr.gt_id);
|
|
|
|
if (!gt)
|
|
return -EINVAL;
|
|
|
|
if (data->hdr.size == 0)
|
|
return 0;
|
|
|
|
if (pkt_needs_bo(data)) {
|
|
struct xe_bo *bo;
|
|
|
|
bo = xe_bo_create_pin_map_novm(data->xe, gt->tile, PAGE_ALIGN(data->hdr.size),
|
|
ttm_bo_type_kernel,
|
|
XE_BO_FLAG_SYSTEM | XE_BO_FLAG_PINNED, false);
|
|
if (IS_ERR(bo))
|
|
return PTR_ERR(bo);
|
|
|
|
data->bo = bo;
|
|
data->vaddr = bo->vmap.vaddr;
|
|
} else {
|
|
void *buff = kvzalloc(data->hdr.size, GFP_KERNEL);
|
|
|
|
if (!buff)
|
|
return -ENOMEM;
|
|
|
|
data->buff = buff;
|
|
data->vaddr = buff;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define XE_SRIOV_PACKET_SUPPORTED_VERSION 1
|
|
|
|
/**
|
|
* xe_sriov_packet_init() - Initialize migration packet header and backing storage.
|
|
* @data: the &xe_sriov_packet
|
|
* @tile_id: tile identifier
|
|
* @gt_id: GT identifier
|
|
* @type: &xe_sriov_packet_type
|
|
* @offset: offset of data packet payload (within wider resource)
|
|
* @size: size of data packet payload
|
|
*
|
|
* Return: 0 on success or a negative error code on failure.
|
|
*/
|
|
int xe_sriov_packet_init(struct xe_sriov_packet *data, u8 tile_id, u8 gt_id,
|
|
enum xe_sriov_packet_type type, loff_t offset, size_t size)
|
|
{
|
|
data->hdr.version = XE_SRIOV_PACKET_SUPPORTED_VERSION;
|
|
data->hdr.type = type;
|
|
data->hdr.tile_id = tile_id;
|
|
data->hdr.gt_id = gt_id;
|
|
data->hdr.offset = offset;
|
|
data->hdr.size = size;
|
|
data->remaining = size;
|
|
|
|
return pkt_init(data);
|
|
}
|
|
|
|
/**
|
|
* xe_sriov_packet_init_from_hdr() - Initialize migration packet backing storage based on header.
|
|
* @data: the &xe_sriov_packet
|
|
*
|
|
* Header data is expected to be filled prior to calling this function.
|
|
*
|
|
* Return: 0 on success or a negative error code on failure.
|
|
*/
|
|
int xe_sriov_packet_init_from_hdr(struct xe_sriov_packet *data)
|
|
{
|
|
xe_assert(data->xe, !data->hdr_remaining);
|
|
|
|
if (data->hdr.version != XE_SRIOV_PACKET_SUPPORTED_VERSION)
|
|
return -EINVAL;
|
|
|
|
data->remaining = data->hdr.size;
|
|
|
|
return pkt_init(data);
|
|
}
|
|
|
|
static ssize_t pkt_hdr_read(struct xe_sriov_packet *data,
|
|
char __user *buf, size_t len)
|
|
{
|
|
loff_t offset = sizeof(data->hdr) - data->hdr_remaining;
|
|
|
|
if (!data->hdr_remaining)
|
|
return -EINVAL;
|
|
|
|
if (len > data->hdr_remaining)
|
|
len = data->hdr_remaining;
|
|
|
|
if (copy_to_user(buf, (void *)&data->hdr + offset, len))
|
|
return -EFAULT;
|
|
|
|
data->hdr_remaining -= len;
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t pkt_data_read(struct xe_sriov_packet *data,
|
|
char __user *buf, size_t len)
|
|
{
|
|
if (len > data->remaining)
|
|
len = data->remaining;
|
|
|
|
if (copy_to_user(buf, data->vaddr + (data->hdr.size - data->remaining), len))
|
|
return -EFAULT;
|
|
|
|
data->remaining -= len;
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t pkt_read_single(struct xe_sriov_packet **data,
|
|
unsigned int vfid, char __user *buf, size_t len)
|
|
{
|
|
ssize_t copied = 0;
|
|
|
|
if ((*data)->hdr_remaining)
|
|
copied = pkt_hdr_read(*data, buf, len);
|
|
else
|
|
copied = pkt_data_read(*data, buf, len);
|
|
|
|
if ((*data)->remaining == 0 && (*data)->hdr_remaining == 0) {
|
|
xe_sriov_packet_free(*data);
|
|
*data = NULL;
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
/**
|
|
* xe_sriov_packet_read_single() - Read migration data from a single packet.
|
|
* @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_packet_read_single(struct xe_device *xe, unsigned int vfid,
|
|
char __user *buf, size_t len)
|
|
{
|
|
struct xe_sriov_packet **data = pf_pick_read_packet(xe, vfid);
|
|
|
|
if (!data)
|
|
return -ENODATA;
|
|
if (IS_ERR(*data))
|
|
return PTR_ERR(*data);
|
|
|
|
return pkt_read_single(data, vfid, buf, len);
|
|
}
|
|
|
|
static ssize_t pkt_hdr_write(struct xe_sriov_packet *data,
|
|
const char __user *buf, size_t len)
|
|
{
|
|
loff_t offset = sizeof(data->hdr) - data->hdr_remaining;
|
|
int ret;
|
|
|
|
if (len > data->hdr_remaining)
|
|
len = data->hdr_remaining;
|
|
|
|
if (copy_from_user((void *)&data->hdr + offset, buf, len))
|
|
return -EFAULT;
|
|
|
|
data->hdr_remaining -= len;
|
|
|
|
if (!data->hdr_remaining) {
|
|
ret = xe_sriov_packet_init_from_hdr(data);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t pkt_data_write(struct xe_sriov_packet *data,
|
|
const char __user *buf, size_t len)
|
|
{
|
|
if (len > data->remaining)
|
|
len = data->remaining;
|
|
|
|
if (copy_from_user(data->vaddr + (data->hdr.size - data->remaining), buf, len))
|
|
return -EFAULT;
|
|
|
|
data->remaining -= len;
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* xe_sriov_packet_write_single() - Write migration data to a single packet.
|
|
* @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_packet_write_single(struct xe_device *xe, unsigned int vfid,
|
|
const char __user *buf, size_t len)
|
|
{
|
|
struct xe_sriov_packet **data = pf_pick_pending(xe, vfid);
|
|
int ret;
|
|
ssize_t copied;
|
|
|
|
if (IS_ERR_OR_NULL(*data)) {
|
|
*data = xe_sriov_packet_alloc(xe);
|
|
if (!*data)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if ((*data)->hdr_remaining)
|
|
copied = pkt_hdr_write(*data, buf, len);
|
|
else
|
|
copied = pkt_data_write(*data, buf, len);
|
|
|
|
if ((*data)->hdr_remaining == 0 && (*data)->remaining == 0) {
|
|
ret = xe_sriov_pf_migration_restore_produce(xe, vfid, *data);
|
|
if (ret) {
|
|
xe_sriov_packet_free(*data);
|
|
*data = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
*data = NULL;
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
#define MIGRATION_KLV_DEVICE_DEVID_KEY 0xf001u
|
|
#define MIGRATION_KLV_DEVICE_DEVID_LEN 1u
|
|
#define MIGRATION_KLV_DEVICE_REVID_KEY 0xf002u
|
|
#define MIGRATION_KLV_DEVICE_REVID_LEN 1u
|
|
|
|
#define MIGRATION_DESCRIPTOR_DWORDS (GUC_KLV_LEN_MIN + MIGRATION_KLV_DEVICE_DEVID_LEN + \
|
|
GUC_KLV_LEN_MIN + MIGRATION_KLV_DEVICE_REVID_LEN)
|
|
static int pf_descriptor_init(struct xe_device *xe, unsigned int vfid)
|
|
{
|
|
struct xe_sriov_packet **desc = pf_pick_descriptor(xe, vfid);
|
|
struct xe_sriov_packet *data;
|
|
unsigned int len = 0;
|
|
u32 *klvs;
|
|
int ret;
|
|
|
|
data = xe_sriov_packet_alloc(xe);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ret = xe_sriov_packet_init(data, 0, 0, XE_SRIOV_PACKET_TYPE_DESCRIPTOR,
|
|
0, MIGRATION_DESCRIPTOR_DWORDS * sizeof(u32));
|
|
if (ret) {
|
|
xe_sriov_packet_free(data);
|
|
return ret;
|
|
}
|
|
|
|
klvs = data->vaddr;
|
|
klvs[len++] = PREP_GUC_KLV_CONST(MIGRATION_KLV_DEVICE_DEVID_KEY,
|
|
MIGRATION_KLV_DEVICE_DEVID_LEN);
|
|
klvs[len++] = xe->info.devid;
|
|
klvs[len++] = PREP_GUC_KLV_CONST(MIGRATION_KLV_DEVICE_REVID_KEY,
|
|
MIGRATION_KLV_DEVICE_REVID_LEN);
|
|
klvs[len++] = xe->info.revid;
|
|
|
|
xe_assert(xe, len == MIGRATION_DESCRIPTOR_DWORDS);
|
|
|
|
*desc = data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* xe_sriov_packet_process_descriptor() - Process migration data descriptor packet.
|
|
* @xe: the &xe_device
|
|
* @vfid: the VF identifier
|
|
* @data: the &xe_sriov_packet containing the descriptor
|
|
*
|
|
* The descriptor uses the same KLV format as GuC, and contains metadata used for
|
|
* checking migration data compatibility.
|
|
*
|
|
* Return: 0 on success, -errno on failure.
|
|
*/
|
|
int xe_sriov_packet_process_descriptor(struct xe_device *xe, unsigned int vfid,
|
|
struct xe_sriov_packet *data)
|
|
{
|
|
u32 num_dwords = data->hdr.size / sizeof(u32);
|
|
u32 *klvs = data->vaddr;
|
|
|
|
xe_assert(xe, data->hdr.type == XE_SRIOV_PACKET_TYPE_DESCRIPTOR);
|
|
|
|
if (data->hdr.size % sizeof(u32)) {
|
|
xe_sriov_warn(xe, "Aborting migration, descriptor not in KLV format (size=%llu)\n",
|
|
data->hdr.size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
while (num_dwords >= GUC_KLV_LEN_MIN) {
|
|
u32 key = FIELD_GET(GUC_KLV_0_KEY, klvs[0]);
|
|
u32 len = FIELD_GET(GUC_KLV_0_LEN, klvs[0]);
|
|
|
|
klvs += GUC_KLV_LEN_MIN;
|
|
num_dwords -= GUC_KLV_LEN_MIN;
|
|
|
|
if (len > num_dwords) {
|
|
xe_sriov_warn(xe, "Aborting migration, truncated KLV %#x, len %u\n",
|
|
key, len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (key) {
|
|
case MIGRATION_KLV_DEVICE_DEVID_KEY:
|
|
if (*klvs != xe->info.devid) {
|
|
xe_sriov_warn(xe,
|
|
"Aborting migration, devid mismatch %#06x!=%#06x\n",
|
|
*klvs, xe->info.devid);
|
|
return -ENODEV;
|
|
}
|
|
break;
|
|
case MIGRATION_KLV_DEVICE_REVID_KEY:
|
|
if (*klvs != xe->info.revid) {
|
|
xe_sriov_warn(xe,
|
|
"Aborting migration, revid mismatch %#06x!=%#06x\n",
|
|
*klvs, xe->info.revid);
|
|
return -ENODEV;
|
|
}
|
|
break;
|
|
default:
|
|
xe_sriov_dbg(xe,
|
|
"Skipping unknown migration KLV %#x, len=%u\n",
|
|
key, len);
|
|
print_hex_dump_bytes("desc: ", DUMP_PREFIX_OFFSET, klvs,
|
|
min(SZ_64, len * sizeof(u32)));
|
|
break;
|
|
}
|
|
|
|
klvs += len;
|
|
num_dwords -= len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pf_pending_init(struct xe_device *xe, unsigned int vfid)
|
|
{
|
|
struct xe_sriov_packet **data = pf_pick_pending(xe, vfid);
|
|
|
|
*data = NULL;
|
|
}
|
|
|
|
#define MIGRATION_TRAILER_SIZE 0
|
|
static int pf_trailer_init(struct xe_device *xe, unsigned int vfid)
|
|
{
|
|
struct xe_sriov_packet **trailer = pf_pick_trailer(xe, vfid);
|
|
struct xe_sriov_packet *data;
|
|
int ret;
|
|
|
|
data = xe_sriov_packet_alloc(xe);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ret = xe_sriov_packet_init(data, 0, 0, XE_SRIOV_PACKET_TYPE_TRAILER,
|
|
0, MIGRATION_TRAILER_SIZE);
|
|
if (ret) {
|
|
xe_sriov_packet_free(data);
|
|
return ret;
|
|
}
|
|
|
|
*trailer = data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* xe_sriov_packet_save_init() - Initialize the pending save migration packets.
|
|
* @xe: the &xe_device
|
|
* @vfid: the VF identifier
|
|
*
|
|
* Return: 0 on success, -errno on failure.
|
|
*/
|
|
int xe_sriov_packet_save_init(struct xe_device *xe, unsigned int vfid)
|
|
{
|
|
int ret;
|
|
|
|
scoped_cond_guard(mutex_intr, return -EINTR, pf_migration_mutex(xe, vfid)) {
|
|
ret = pf_descriptor_init(xe, vfid);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = pf_trailer_init(xe, vfid);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pf_pending_init(xe, vfid);
|
|
}
|
|
|
|
return 0;
|
|
}
|