// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright ( c ) 2024 , NVIDIA CORPORATION & AFFILIATES . All rights reserved
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/vfio.h>
#include <linux/vfio_pci_core.h>
#include <linux/virtio_pci.h>
#include <linux/virtio_net.h>
#include <linux/virtio_pci_admin.h>
#include <linux/anon_inodes.h>
#include "common.h"
/* Device specification max parts size */
#define MAX_LOAD_SIZE (BIT_ULL(BITS_PER_TYPE \
(((struct virtio_admin_cmd_dev_parts_metadata_result *)0 )->parts_size.size)) - 1 )
/* Initial target buffer size */
#define VIRTIOVF_TARGET_INITIAL_BUF_SIZE SZ_1M
static int
virtiovf_read_device_context_chunk(struct virtiovf_migration_file *migf,
u32 ctx_size);
static struct page *
virtiovf_get_migration_page(struct virtiovf_data_buffer *buf,
unsigned long offset)
{
unsigned long cur_offset = 0 ;
struct scatterlist *sg;
unsigned int i;
/* All accesses are sequential */
if (offset < buf->last_offset || !buf->last_offset_sg) {
buf->last_offset = 0 ;
buf->last_offset_sg = buf->table.sgt.sgl;
buf->sg_last_entry = 0 ;
}
cur_offset = buf->last_offset;
for_each_sg(buf->last_offset_sg, sg,
buf->table.sgt.orig_nents - buf->sg_last_entry, i) {
if (offset < sg->length + cur_offset) {
buf->last_offset_sg = sg;
buf->sg_last_entry += i;
buf->last_offset = cur_offset;
return nth_page(sg_page(sg),
(offset - cur_offset) / PAGE_SIZE);
}
cur_offset += sg->length;
}
return NULL;
}
static int virtiovf_add_migration_pages(struct virtiovf_data_buffer *buf,
unsigned int npages)
{
unsigned int to_alloc = npages;
struct page **page_list;
unsigned long filled;
unsigned int to_fill;
int ret;
int i;
to_fill = min_t(unsigned int , npages, PAGE_SIZE / sizeof (*page_list));
page_list = kvcalloc(to_fill, sizeof (*page_list), GFP_KERNEL_ACCOUNT);
if (!page_list)
return -ENOMEM;
do {
filled = alloc_pages_bulk(GFP_KERNEL_ACCOUNT, to_fill,
page_list);
if (!filled) {
ret = -ENOMEM;
goto err;
}
to_alloc -= filled;
ret = sg_alloc_append_table_from_pages(&buf->table, page_list,
filled, 0 , filled << PAGE_SHIFT, UINT_MAX,
SG_MAX_SINGLE_ALLOC, GFP_KERNEL_ACCOUNT);
if (ret)
goto err_append;
buf->allocated_length += filled * PAGE_SIZE;
/* clean input for another bulk allocation */
memset(page_list, 0 , filled * sizeof (*page_list));
to_fill = min_t(unsigned int , to_alloc,
PAGE_SIZE / sizeof (*page_list));
} while (to_alloc > 0 );
kvfree(page_list);
return 0 ;
err_append:
for (i = filled - 1 ; i >= 0 ; i--)
__free_page(page_list[i]);
err:
kvfree(page_list);
return ret;
}
static void virtiovf_free_data_buffer(struct virtiovf_data_buffer *buf)
{
struct sg_page_iter sg_iter;
/* Undo alloc_pages_bulk() */
for_each_sgtable_page(&buf->table.sgt, &sg_iter, 0 )
__free_page(sg_page_iter_page(&sg_iter));
sg_free_append_table(&buf->table);
kfree(buf);
}
static struct virtiovf_data_buffer *
virtiovf_alloc_data_buffer(struct virtiovf_migration_file *migf, size_t length)
{
struct virtiovf_data_buffer *buf;
int ret;
buf = kzalloc(sizeof (*buf), GFP_KERNEL_ACCOUNT);
if (!buf)
return ERR_PTR(-ENOMEM);
ret = virtiovf_add_migration_pages(buf,
DIV_ROUND_UP_ULL(length, PAGE_SIZE));
if (ret)
goto end;
buf->migf = migf;
return buf;
end:
virtiovf_free_data_buffer(buf);
return ERR_PTR(ret);
}
static void virtiovf_put_data_buffer(struct virtiovf_data_buffer *buf)
{
spin_lock_irq(&buf->migf->list_lock);
list_add_tail(&buf->buf_elm, &buf->migf->avail_list);
spin_unlock_irq(&buf->migf->list_lock);
}
static int
virtiovf_pci_alloc_obj_id(struct virtiovf_pci_core_device *virtvdev, u8 type,
u32 *obj_id)
{
return virtio_pci_admin_obj_create(virtvdev->core_device.pdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS, type, obj_id);
}
static void
virtiovf_pci_free_obj_id(struct virtiovf_pci_core_device *virtvdev, u32 obj_id)
{
virtio_pci_admin_obj_destroy(virtvdev->core_device.pdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS, obj_id);
}
static struct virtiovf_data_buffer *
virtiovf_get_data_buffer(struct virtiovf_migration_file *migf, size_t length)
{
struct virtiovf_data_buffer *buf, *temp_buf;
struct list_head free_list;
INIT_LIST_HEAD(&free_list);
spin_lock_irq(&migf->list_lock);
list_for_each_entry_safe(buf, temp_buf, &migf->avail_list, buf_elm) {
list_del_init(&buf->buf_elm);
if (buf->allocated_length >= length) {
spin_unlock_irq(&migf->list_lock);
goto found;
}
/*
* Prevent holding redundant buffers . Put in a free
* list and call at the end not under the spin lock
* ( & migf - > list_lock ) to minimize its scope usage .
*/
list_add(&buf->buf_elm, &free_list);
}
spin_unlock_irq(&migf->list_lock);
buf = virtiovf_alloc_data_buffer(migf, length);
found:
while ((temp_buf = list_first_entry_or_null(&free_list,
struct virtiovf_data_buffer, buf_elm))) {
list_del(&temp_buf->buf_elm);
virtiovf_free_data_buffer(temp_buf);
}
return buf;
}
static void virtiovf_clean_migf_resources(struct virtiovf_migration_file *migf)
{
struct virtiovf_data_buffer *entry;
if (migf->buf) {
virtiovf_free_data_buffer(migf->buf);
migf->buf = NULL;
}
if (migf->buf_header) {
virtiovf_free_data_buffer(migf->buf_header);
migf->buf_header = NULL;
}
list_splice(&migf->avail_list, &migf->buf_list);
while ((entry = list_first_entry_or_null(&migf->buf_list,
struct virtiovf_data_buffer, buf_elm))) {
list_del(&entry->buf_elm);
virtiovf_free_data_buffer(entry);
}
if (migf->has_obj_id)
virtiovf_pci_free_obj_id(migf->virtvdev, migf->obj_id);
}
static void virtiovf_disable_fd(struct virtiovf_migration_file *migf)
{
mutex_lock(&migf->lock);
migf->state = VIRTIOVF_MIGF_STATE_ERROR;
migf->filp->f_pos = 0 ;
mutex_unlock(&migf->lock);
}
static void virtiovf_disable_fds(struct virtiovf_pci_core_device *virtvdev)
{
if (virtvdev->resuming_migf) {
virtiovf_disable_fd(virtvdev->resuming_migf);
virtiovf_clean_migf_resources(virtvdev->resuming_migf);
fput(virtvdev->resuming_migf->filp);
virtvdev->resuming_migf = NULL;
}
if (virtvdev->saving_migf) {
virtiovf_disable_fd(virtvdev->saving_migf);
virtiovf_clean_migf_resources(virtvdev->saving_migf);
fput(virtvdev->saving_migf->filp);
virtvdev->saving_migf = NULL;
}
}
/*
* This function is called in all state_mutex unlock cases to
* handle a ' deferred_reset ' if exists .
*/
static void virtiovf_state_mutex_unlock(struct virtiovf_pci_core_device *virtvdev)
{
again:
spin_lock(&virtvdev->reset_lock);
if (virtvdev->deferred_reset) {
virtvdev->deferred_reset = false ;
spin_unlock(&virtvdev->reset_lock);
virtvdev->mig_state = VFIO_DEVICE_STATE_RUNNING;
virtiovf_disable_fds(virtvdev);
goto again;
}
mutex_unlock(&virtvdev->state_mutex);
spin_unlock(&virtvdev->reset_lock);
}
void virtiovf_migration_reset_done(struct pci_dev *pdev)
{
struct virtiovf_pci_core_device *virtvdev = dev_get_drvdata(&pdev->dev);
if (!virtvdev->migrate_cap)
return ;
/*
* As the higher VFIO layers are holding locks across reset and using
* those same locks with the mm_lock we need to prevent ABBA deadlock
* with the state_mutex and mm_lock .
* In case the state_mutex was taken already we defer the cleanup work
* to the unlock flow of the other running context .
*/
spin_lock(&virtvdev->reset_lock);
virtvdev->deferred_reset = true ;
if (!mutex_trylock(&virtvdev->state_mutex)) {
spin_unlock(&virtvdev->reset_lock);
return ;
}
spin_unlock(&virtvdev->reset_lock);
virtiovf_state_mutex_unlock(virtvdev);
}
static int virtiovf_release_file(struct inode *inode, struct file *filp)
{
struct virtiovf_migration_file *migf = filp->private_data;
virtiovf_disable_fd(migf);
mutex_destroy(&migf->lock);
kfree(migf);
return 0 ;
}
static struct virtiovf_data_buffer *
virtiovf_get_data_buff_from_pos(struct virtiovf_migration_file *migf,
loff_t pos, bool *end_of_data)
{
struct virtiovf_data_buffer *buf;
bool found = false ;
*end_of_data = false ;
spin_lock_irq(&migf->list_lock);
if (list_empty(&migf->buf_list)) {
*end_of_data = true ;
goto end;
}
buf = list_first_entry(&migf->buf_list, struct virtiovf_data_buffer,
buf_elm);
if (pos >= buf->start_pos &&
pos < buf->start_pos + buf->length) {
found = true ;
goto end;
}
/*
* As we use a stream based FD we may expect having the data always
* on first chunk
*/
migf->state = VIRTIOVF_MIGF_STATE_ERROR;
end:
spin_unlock_irq(&migf->list_lock);
return found ? buf : NULL;
}
static ssize_t virtiovf_buf_read(struct virtiovf_data_buffer *vhca_buf,
char __user **buf, size_t *len, loff_t *pos)
{
unsigned long offset;
ssize_t done = 0 ;
size_t copy_len;
copy_len = min_t(size_t,
vhca_buf->start_pos + vhca_buf->length - *pos, *len);
while (copy_len) {
size_t page_offset;
struct page *page;
size_t page_len;
u8 *from_buff;
int ret;
offset = *pos - vhca_buf->start_pos;
page_offset = offset % PAGE_SIZE;
offset -= page_offset;
page = virtiovf_get_migration_page(vhca_buf, offset);
if (!page)
return -EINVAL;
page_len = min_t(size_t, copy_len, PAGE_SIZE - page_offset);
from_buff = kmap_local_page(page);
ret = copy_to_user(*buf, from_buff + page_offset, page_len);
kunmap_local(from_buff);
if (ret)
return -EFAULT;
*pos += page_len;
*len -= page_len;
*buf += page_len;
done += page_len;
copy_len -= page_len;
}
if (*pos >= vhca_buf->start_pos + vhca_buf->length) {
spin_lock_irq(&vhca_buf->migf->list_lock);
list_del_init(&vhca_buf->buf_elm);
list_add_tail(&vhca_buf->buf_elm, &vhca_buf->migf->avail_list);
spin_unlock_irq(&vhca_buf->migf->list_lock);
}
return done;
}
static ssize_t virtiovf_save_read(struct file *filp, char __user *buf, size_t len,
loff_t *pos)
{
struct virtiovf_migration_file *migf = filp->private_data;
struct virtiovf_data_buffer *vhca_buf;
bool first_loop_call = true ;
bool end_of_data;
ssize_t done = 0 ;
if (pos)
return -ESPIPE;
pos = &filp->f_pos;
mutex_lock(&migf->lock);
if (migf->state == VIRTIOVF_MIGF_STATE_ERROR) {
done = -ENODEV;
goto out_unlock;
}
while (len) {
ssize_t count;
vhca_buf = virtiovf_get_data_buff_from_pos(migf, *pos, &end_of_data);
if (first_loop_call) {
first_loop_call = false ;
/* Temporary end of file as part of PRE_COPY */
if (end_of_data && migf->state == VIRTIOVF_MIGF_STATE_PRECOPY) {
done = -ENOMSG;
goto out_unlock;
}
if (end_of_data && migf->state != VIRTIOVF_MIGF_STATE_COMPLETE) {
done = -EINVAL;
goto out_unlock;
}
}
if (end_of_data)
goto out_unlock;
if (!vhca_buf) {
done = -EINVAL;
goto out_unlock;
}
count = virtiovf_buf_read(vhca_buf, &buf, &len, pos);
if (count < 0 ) {
done = count;
goto out_unlock;
}
done += count;
}
out_unlock:
mutex_unlock(&migf->lock);
return done;
}
static long virtiovf_precopy_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct virtiovf_migration_file *migf = filp->private_data;
struct virtiovf_pci_core_device *virtvdev = migf->virtvdev;
struct vfio_precopy_info info = {};
loff_t *pos = &filp->f_pos;
bool end_of_data = false ;
unsigned long minsz;
u32 ctx_size = 0 ;
int ret;
if (cmd != VFIO_MIG_GET_PRECOPY_INFO)
return -ENOTTY;
minsz = offsetofend(struct vfio_precopy_info, dirty_bytes);
if (copy_from_user(&info, (void __user *)arg, minsz))
return -EFAULT;
if (info.argsz < minsz)
return -EINVAL;
mutex_lock(&virtvdev->state_mutex);
if (virtvdev->mig_state != VFIO_DEVICE_STATE_PRE_COPY &&
virtvdev->mig_state != VFIO_DEVICE_STATE_PRE_COPY_P2P) {
ret = -EINVAL;
goto err_state_unlock;
}
/*
* The virtio specification does not include a PRE_COPY concept .
* Since we can expect the data to remain the same for a certain period ,
* we use a rate limiter mechanism before making a call to the device .
*/
if (__ratelimit(&migf->pre_copy_rl_state)) {
ret = virtio_pci_admin_dev_parts_metadata_get(virtvdev->core_device.pdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS, migf->obj_id,
VIRTIO_ADMIN_CMD_DEV_PARTS_METADATA_TYPE_SIZE,
&ctx_size);
if (ret)
goto err_state_unlock;
}
mutex_lock(&migf->lock);
if (migf->state == VIRTIOVF_MIGF_STATE_ERROR) {
ret = -ENODEV;
goto err_migf_unlock;
}
if (migf->pre_copy_initial_bytes > *pos) {
info.initial_bytes = migf->pre_copy_initial_bytes - *pos;
} else {
info.dirty_bytes = migf->max_pos - *pos;
if (!info.dirty_bytes)
end_of_data = true ;
info.dirty_bytes += ctx_size;
}
if (!end_of_data || !ctx_size) {
mutex_unlock(&migf->lock);
goto done;
}
mutex_unlock(&migf->lock);
/*
* We finished transferring the current state and the device has a
* dirty state , read a new state .
*/
ret = virtiovf_read_device_context_chunk(migf, ctx_size);
if (ret)
/*
* The machine is running , and context size could be grow , so no reason to mark
* the device state as VIRTIOVF_MIGF_STATE_ERROR .
*/
goto err_state_unlock;
done:
virtiovf_state_mutex_unlock(virtvdev);
if (copy_to_user((void __user *)arg, &info, minsz))
return -EFAULT;
return 0 ;
err_migf_unlock:
mutex_unlock(&migf->lock);
err_state_unlock:
virtiovf_state_mutex_unlock(virtvdev);
return ret;
}
static const struct file_operations virtiovf_save_fops = {
.owner = THIS_MODULE,
.read = virtiovf_save_read,
.unlocked_ioctl = virtiovf_precopy_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.release = virtiovf_release_file,
};
static int
virtiovf_add_buf_header(struct virtiovf_data_buffer *header_buf,
u32 data_size)
{
struct virtiovf_migration_file *migf = header_buf->migf;
struct virtiovf_migration_header header = {};
struct page *page;
u8 *to_buff;
header.record_size = cpu_to_le64(data_size);
header.flags = cpu_to_le32(VIRTIOVF_MIGF_HEADER_FLAGS_TAG_MANDATORY);
header.tag = cpu_to_le32(VIRTIOVF_MIGF_HEADER_TAG_DEVICE_DATA);
page = virtiovf_get_migration_page(header_buf, 0 );
if (!page)
return -EINVAL;
to_buff = kmap_local_page(page);
memcpy(to_buff, &header, sizeof (header));
kunmap_local(to_buff);
header_buf->length = sizeof (header);
header_buf->start_pos = header_buf->migf->max_pos;
migf->max_pos += header_buf->length;
spin_lock_irq(&migf->list_lock);
list_add_tail(&header_buf->buf_elm, &migf->buf_list);
spin_unlock_irq(&migf->list_lock);
return 0 ;
}
static int
virtiovf_read_device_context_chunk(struct virtiovf_migration_file *migf,
u32 ctx_size)
{
struct virtiovf_data_buffer *header_buf;
struct virtiovf_data_buffer *buf;
bool unmark_end = false ;
struct scatterlist *sg;
unsigned int i;
u32 res_size;
int nent;
int ret;
buf = virtiovf_get_data_buffer(migf, ctx_size);
if (IS_ERR(buf))
return PTR_ERR(buf);
/* Find the total count of SG entries which satisfies the size */
nent = sg_nents_for_len(buf->table.sgt.sgl, ctx_size);
if (nent <= 0 ) {
ret = -EINVAL;
goto out;
}
/*
* Iterate to that SG entry and mark it as last ( if it ' s not already )
* to let underlay layers iterate only till that entry .
*/
for_each_sg(buf->table.sgt.sgl, sg, nent - 1 , i)
;
if (!sg_is_last(sg)) {
unmark_end = true ;
sg_mark_end(sg);
}
ret = virtio_pci_admin_dev_parts_get(migf->virtvdev->core_device.pdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS,
migf->obj_id,
VIRTIO_ADMIN_CMD_DEV_PARTS_GET_TYPE_ALL,
buf->table.sgt.sgl, &res_size);
/* Restore the original SG mark end */
if (unmark_end)
sg_unmark_end(sg);
if (ret)
goto out;
buf->length = res_size;
header_buf = virtiovf_get_data_buffer(migf,
sizeof (struct virtiovf_migration_header));
if (IS_ERR(header_buf)) {
ret = PTR_ERR(header_buf);
goto out;
}
ret = virtiovf_add_buf_header(header_buf, res_size);
if (ret)
goto out_header;
buf->start_pos = buf->migf->max_pos;
migf->max_pos += buf->length;
spin_lock(&migf->list_lock);
list_add_tail(&buf->buf_elm, &migf->buf_list);
spin_unlock_irq(&migf->list_lock);
return 0 ;
out_header:
virtiovf_put_data_buffer(header_buf);
out:
virtiovf_put_data_buffer(buf);
return ret;
}
static int
virtiovf_pci_save_device_final_data(struct virtiovf_pci_core_device *virtvdev)
{
struct virtiovf_migration_file *migf = virtvdev->saving_migf;
u32 ctx_size;
int ret;
if (migf->state == VIRTIOVF_MIGF_STATE_ERROR)
return -ENODEV;
ret = virtio_pci_admin_dev_parts_metadata_get(virtvdev->core_device.pdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS, migf->obj_id,
VIRTIO_ADMIN_CMD_DEV_PARTS_METADATA_TYPE_SIZE,
&ctx_size);
if (ret)
goto err;
if (!ctx_size) {
ret = -EINVAL;
goto err;
}
ret = virtiovf_read_device_context_chunk(migf, ctx_size);
if (ret)
goto err;
migf->state = VIRTIOVF_MIGF_STATE_COMPLETE;
return 0 ;
err:
migf->state = VIRTIOVF_MIGF_STATE_ERROR;
return ret;
}
static struct virtiovf_migration_file *
virtiovf_pci_save_device_data(struct virtiovf_pci_core_device *virtvdev,
bool pre_copy)
{
struct virtiovf_migration_file *migf;
u32 ctx_size;
u32 obj_id;
int ret;
migf = kzalloc(sizeof (*migf), GFP_KERNEL_ACCOUNT);
if (!migf)
return ERR_PTR(-ENOMEM);
migf->filp = anon_inode_getfile("virtiovf_mig" , &virtiovf_save_fops, migf,
O_RDONLY);
if (IS_ERR(migf->filp)) {
ret = PTR_ERR(migf->filp);
kfree(migf);
return ERR_PTR(ret);
}
stream_open(migf->filp->f_inode, migf->filp);
mutex_init(&migf->lock);
INIT_LIST_HEAD(&migf->buf_list);
INIT_LIST_HEAD(&migf->avail_list);
spin_lock_init(&migf->list_lock);
migf->virtvdev = virtvdev;
lockdep_assert_held(&virtvdev->state_mutex);
ret = virtiovf_pci_alloc_obj_id(virtvdev, VIRTIO_RESOURCE_OBJ_DEV_PARTS_TYPE_GET,
&obj_id);
if (ret)
goto out;
migf->obj_id = obj_id;
/* Mark as having a valid obj id which can be even 0 */
migf->has_obj_id = true ;
ret = virtio_pci_admin_dev_parts_metadata_get(virtvdev->core_device.pdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS, obj_id,
VIRTIO_ADMIN_CMD_DEV_PARTS_METADATA_TYPE_SIZE,
&ctx_size);
if (ret)
goto out_clean;
if (!ctx_size) {
ret = -EINVAL;
goto out_clean;
}
ret = virtiovf_read_device_context_chunk(migf, ctx_size);
if (ret)
goto out_clean;
if (pre_copy) {
migf->pre_copy_initial_bytes = migf->max_pos;
/* Arbitrarily set the pre-copy rate limit to 1-second intervals */
ratelimit_state_init(&migf->pre_copy_rl_state, 1 * HZ, 1 );
/* Prevent any rate messages upon its usage */
ratelimit_set_flags(&migf->pre_copy_rl_state,
RATELIMIT_MSG_ON_RELEASE);
migf->state = VIRTIOVF_MIGF_STATE_PRECOPY;
} else {
migf->state = VIRTIOVF_MIGF_STATE_COMPLETE;
}
return migf;
out_clean:
virtiovf_clean_migf_resources(migf);
out:
fput(migf->filp);
return ERR_PTR(ret);
}
/*
* Set the required object header at the beginning of the buffer .
* The actual device parts data will be written post of the header offset .
*/
static int virtiovf_set_obj_cmd_header(struct virtiovf_data_buffer *vhca_buf)
{
struct virtio_admin_cmd_resource_obj_cmd_hdr obj_hdr = {};
struct page *page;
u8 *to_buff;
obj_hdr.type = cpu_to_le16(VIRTIO_RESOURCE_OBJ_DEV_PARTS);
obj_hdr.id = cpu_to_le32(vhca_buf->migf->obj_id);
page = virtiovf_get_migration_page(vhca_buf, 0 );
if (!page)
return -EINVAL;
to_buff = kmap_local_page(page);
memcpy(to_buff, &obj_hdr, sizeof (obj_hdr));
kunmap_local(to_buff);
/* Mark the buffer as including the header object data */
vhca_buf->include_header_object = 1 ;
return 0 ;
}
static int
virtiovf_append_page_to_mig_buf(struct virtiovf_data_buffer *vhca_buf,
const char __user **buf, size_t *len,
loff_t *pos, ssize_t *done)
{
unsigned long offset;
size_t page_offset;
struct page *page;
size_t page_len;
u8 *to_buff;
int ret;
offset = *pos - vhca_buf->start_pos;
if (vhca_buf->include_header_object)
/* The buffer holds the object header, update the offset accordingly */
offset += sizeof (struct virtio_admin_cmd_resource_obj_cmd_hdr);
page_offset = offset % PAGE_SIZE;
page = virtiovf_get_migration_page(vhca_buf, offset - page_offset);
if (!page)
return -EINVAL;
page_len = min_t(size_t, *len, PAGE_SIZE - page_offset);
to_buff = kmap_local_page(page);
ret = copy_from_user(to_buff + page_offset, *buf, page_len);
kunmap_local(to_buff);
if (ret)
return -EFAULT;
*pos += page_len;
*done += page_len;
*buf += page_len;
*len -= page_len;
vhca_buf->length += page_len;
return 0 ;
}
static ssize_t
virtiovf_resume_read_chunk(struct virtiovf_migration_file *migf,
struct virtiovf_data_buffer *vhca_buf,
size_t chunk_size, const char __user **buf,
size_t *len, loff_t *pos, ssize_t *done,
bool *has_work)
{
size_t copy_len, to_copy;
int ret;
to_copy = min_t(size_t, *len, chunk_size - vhca_buf->length);
copy_len = to_copy;
while (to_copy) {
ret = virtiovf_append_page_to_mig_buf(vhca_buf, buf, &to_copy,
pos, done);
if (ret)
return ret;
}
*len -= copy_len;
if (vhca_buf->length == chunk_size) {
migf->load_state = VIRTIOVF_LOAD_STATE_LOAD_CHUNK;
migf->max_pos += chunk_size;
*has_work = true ;
}
return 0 ;
}
static int
virtiovf_resume_read_header_data(struct virtiovf_migration_file *migf,
struct virtiovf_data_buffer *vhca_buf,
const char __user **buf, size_t *len,
loff_t *pos, ssize_t *done)
{
size_t copy_len, to_copy;
size_t required_data;
int ret;
required_data = migf->record_size - vhca_buf->length;
to_copy = min_t(size_t, *len, required_data);
copy_len = to_copy;
while (to_copy) {
ret = virtiovf_append_page_to_mig_buf(vhca_buf, buf, &to_copy,
pos, done);
if (ret)
return ret;
}
*len -= copy_len;
if (vhca_buf->length == migf->record_size) {
switch (migf->record_tag) {
default :
/* Optional tag */
break ;
}
migf->load_state = VIRTIOVF_LOAD_STATE_READ_HEADER;
migf->max_pos += migf->record_size;
vhca_buf->length = 0 ;
}
return 0 ;
}
static int
virtiovf_resume_read_header(struct virtiovf_migration_file *migf,
struct virtiovf_data_buffer *vhca_buf,
const char __user **buf,
size_t *len, loff_t *pos,
ssize_t *done, bool *has_work)
{
struct page *page;
size_t copy_len;
u8 *to_buff;
int ret;
copy_len = min_t(size_t, *len,
sizeof (struct virtiovf_migration_header) - vhca_buf->length);
page = virtiovf_get_migration_page(vhca_buf, 0 );
if (!page)
return -EINVAL;
to_buff = kmap_local_page(page);
ret = copy_from_user(to_buff + vhca_buf->length, *buf, copy_len);
if (ret) {
ret = -EFAULT;
goto end;
}
*buf += copy_len;
*pos += copy_len;
*done += copy_len;
*len -= copy_len;
vhca_buf->length += copy_len;
if (vhca_buf->length == sizeof (struct virtiovf_migration_header)) {
u64 record_size;
u32 flags;
record_size = le64_to_cpup((__le64 *)to_buff);
if (record_size > MAX_LOAD_SIZE) {
ret = -ENOMEM;
goto end;
}
migf->record_size = record_size;
flags = le32_to_cpup((__le32 *)(to_buff +
offsetof(struct virtiovf_migration_header, flags)));
migf->record_tag = le32_to_cpup((__le32 *)(to_buff +
offsetof(struct virtiovf_migration_header, tag)));
switch (migf->record_tag) {
case VIRTIOVF_MIGF_HEADER_TAG_DEVICE_DATA:
migf->load_state = VIRTIOVF_LOAD_STATE_PREP_CHUNK;
break ;
default :
if (!(flags & VIRTIOVF_MIGF_HEADER_FLAGS_TAG_OPTIONAL)) {
ret = -EOPNOTSUPP;
goto end;
}
/* We may read and skip this optional record data */
migf->load_state = VIRTIOVF_LOAD_STATE_PREP_HEADER_DATA;
}
migf->max_pos += vhca_buf->length;
vhca_buf->length = 0 ;
*has_work = true ;
}
end:
kunmap_local(to_buff);
return ret;
}
static ssize_t virtiovf_resume_write(struct file *filp, const char __user *buf,
size_t len, loff_t *pos)
{
struct virtiovf_migration_file *migf = filp->private_data;
struct virtiovf_data_buffer *vhca_buf = migf->buf;
struct virtiovf_data_buffer *vhca_buf_header = migf->buf_header;
unsigned int orig_length;
bool has_work = false ;
ssize_t done = 0 ;
int ret = 0 ;
if (pos)
return -ESPIPE;
pos = &filp->f_pos;
if (*pos < vhca_buf->start_pos)
return -EINVAL;
mutex_lock(&migf->virtvdev->state_mutex);
mutex_lock(&migf->lock);
if (migf->state == VIRTIOVF_MIGF_STATE_ERROR) {
done = -ENODEV;
goto out_unlock;
}
while (len || has_work) {
has_work = false ;
switch (migf->load_state) {
case VIRTIOVF_LOAD_STATE_READ_HEADER:
ret = virtiovf_resume_read_header(migf, vhca_buf_header, &buf,
&len, pos, &done, &has_work);
if (ret)
goto out_unlock;
break ;
case VIRTIOVF_LOAD_STATE_PREP_HEADER_DATA:
if (vhca_buf_header->allocated_length < migf->record_size) {
virtiovf_free_data_buffer(vhca_buf_header);
migf->buf_header = virtiovf_alloc_data_buffer(migf,
migf->record_size);
if (IS_ERR(migf->buf_header)) {
ret = PTR_ERR(migf->buf_header);
migf->buf_header = NULL;
goto out_unlock;
}
vhca_buf_header = migf->buf_header;
}
vhca_buf_header->start_pos = migf->max_pos;
migf->load_state = VIRTIOVF_LOAD_STATE_READ_HEADER_DATA;
break ;
case VIRTIOVF_LOAD_STATE_READ_HEADER_DATA:
ret = virtiovf_resume_read_header_data(migf, vhca_buf_header,
&buf, &len, pos, &done);
if (ret)
goto out_unlock;
break ;
case VIRTIOVF_LOAD_STATE_PREP_CHUNK:
{
u32 cmd_size = migf->record_size +
sizeof (struct virtio_admin_cmd_resource_obj_cmd_hdr);
/*
* The DMA map / unmap is managed in virtio layer , we just need to extend
* the SG pages to hold the extra required chunk data .
*/
if (vhca_buf->allocated_length < cmd_size) {
ret = virtiovf_add_migration_pages(vhca_buf,
DIV_ROUND_UP_ULL(cmd_size - vhca_buf->allocated_length,
PAGE_SIZE));
if (ret)
goto out_unlock;
}
vhca_buf->start_pos = migf->max_pos;
migf->load_state = VIRTIOVF_LOAD_STATE_READ_CHUNK;
break ;
}
case VIRTIOVF_LOAD_STATE_READ_CHUNK:
ret = virtiovf_resume_read_chunk(migf, vhca_buf, migf->record_size,
&buf, &len, pos, &done, &has_work);
if (ret)
goto out_unlock;
break ;
case VIRTIOVF_LOAD_STATE_LOAD_CHUNK:
/* Mark the last SG entry and set its length */
sg_mark_end(vhca_buf->last_offset_sg);
orig_length = vhca_buf->last_offset_sg->length;
/* Length should include the resource object command header */
vhca_buf->last_offset_sg->length = vhca_buf->length +
sizeof (struct virtio_admin_cmd_resource_obj_cmd_hdr) -
vhca_buf->last_offset;
ret = virtio_pci_admin_dev_parts_set(migf->virtvdev->core_device.pdev,
vhca_buf->table.sgt.sgl);
/* Restore the original SG data */
vhca_buf->last_offset_sg->length = orig_length;
sg_unmark_end(vhca_buf->last_offset_sg);
if (ret)
goto out_unlock;
migf->load_state = VIRTIOVF_LOAD_STATE_READ_HEADER;
/* be ready for reading the next chunk */
vhca_buf->length = 0 ;
break ;
default :
break ;
}
}
out_unlock:
if (ret)
migf->state = VIRTIOVF_MIGF_STATE_ERROR;
mutex_unlock(&migf->lock);
virtiovf_state_mutex_unlock(migf->virtvdev);
return ret ? ret : done;
}
static const struct file_operations virtiovf_resume_fops = {
.owner = THIS_MODULE,
.write = virtiovf_resume_write,
.release = virtiovf_release_file,
};
static struct virtiovf_migration_file *
virtiovf_pci_resume_device_data(struct virtiovf_pci_core_device *virtvdev)
{
struct virtiovf_migration_file *migf;
struct virtiovf_data_buffer *buf;
u32 obj_id;
int ret;
migf = kzalloc(sizeof (*migf), GFP_KERNEL_ACCOUNT);
if (!migf)
return ERR_PTR(-ENOMEM);
migf->filp = anon_inode_getfile("virtiovf_mig" , &virtiovf_resume_fops, migf,
O_WRONLY);
if (IS_ERR(migf->filp)) {
ret = PTR_ERR(migf->filp);
kfree(migf);
return ERR_PTR(ret);
}
stream_open(migf->filp->f_inode, migf->filp);
mutex_init(&migf->lock);
INIT_LIST_HEAD(&migf->buf_list);
INIT_LIST_HEAD(&migf->avail_list);
spin_lock_init(&migf->list_lock);
buf = virtiovf_alloc_data_buffer(migf, VIRTIOVF_TARGET_INITIAL_BUF_SIZE);
if (IS_ERR(buf)) {
ret = PTR_ERR(buf);
goto out;
}
migf->buf = buf;
buf = virtiovf_alloc_data_buffer(migf,
sizeof (struct virtiovf_migration_header));
if (IS_ERR(buf)) {
ret = PTR_ERR(buf);
goto out_clean;
}
migf->buf_header = buf;
migf->load_state = VIRTIOVF_LOAD_STATE_READ_HEADER;
migf->virtvdev = virtvdev;
ret = virtiovf_pci_alloc_obj_id(virtvdev, VIRTIO_RESOURCE_OBJ_DEV_PARTS_TYPE_SET,
&obj_id);
if (ret)
goto out_clean;
migf->obj_id = obj_id;
/* Mark as having a valid obj id which can be even 0 */
migf->has_obj_id = true ;
ret = virtiovf_set_obj_cmd_header(migf->buf);
if (ret)
goto out_clean;
return migf;
out_clean:
virtiovf_clean_migf_resources(migf);
out:
fput(migf->filp);
return ERR_PTR(ret);
}
static struct file *
virtiovf_pci_step_device_state_locked(struct virtiovf_pci_core_device *virtvdev,
u32 new )
{
u32 cur = virtvdev->mig_state;
int ret;
if (cur == VFIO_DEVICE_STATE_RUNNING_P2P && new == VFIO_DEVICE_STATE_STOP) {
/* NOP */
return NULL;
}
if (cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_RUNNING_P2P) {
/* NOP */
return NULL;
}
if ((cur == VFIO_DEVICE_STATE_RUNNING && new == VFIO_DEVICE_STATE_RUNNING_P2P) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY && new == VFIO_DEVICE_STATE_PRE_COPY_P2P)) {
ret = virtio_pci_admin_mode_set(virtvdev->core_device.pdev,
BIT(VIRTIO_ADMIN_CMD_DEV_MODE_F_STOPPED));
if (ret)
return ERR_PTR(ret);
return NULL;
}
if ((cur == VFIO_DEVICE_STATE_RUNNING_P2P && new == VFIO_DEVICE_STATE_RUNNING) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY_P2P && new == VFIO_DEVICE_STATE_PRE_COPY)) {
ret = virtio_pci_admin_mode_set(virtvdev->core_device.pdev, 0 );
if (ret)
return ERR_PTR(ret);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_STOP_COPY) {
struct virtiovf_migration_file *migf;
migf = virtiovf_pci_save_device_data(virtvdev, false );
if (IS_ERR(migf))
return ERR_CAST(migf);
get_file(migf->filp);
virtvdev->saving_migf = migf;
return migf->filp;
}
if ((cur == VFIO_DEVICE_STATE_STOP_COPY && new == VFIO_DEVICE_STATE_STOP) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY && new == VFIO_DEVICE_STATE_RUNNING) ||
(cur == VFIO_DEVICE_STATE_PRE_COPY_P2P && new == VFIO_DEVICE_STATE_RUNNING_P2P)) {
virtiovf_disable_fds(virtvdev);
return NULL;
}
if (cur == VFIO_DEVICE_STATE_STOP && new == VFIO_DEVICE_STATE_RESUMING) {
struct virtiovf_migration_file *migf;
migf = virtiovf_pci_resume_device_data(virtvdev);
if (IS_ERR(migf))
return ERR_CAST(migf);
get_file(migf->filp);
virtvdev->resuming_migf = migf;
return migf->filp;
}
if (cur == VFIO_DEVICE_STATE_RESUMING && new == VFIO_DEVICE_STATE_STOP) {
virtiovf_disable_fds(virtvdev);
return NULL;
}
if ((cur == VFIO_DEVICE_STATE_RUNNING && new == VFIO_DEVICE_STATE_PRE_COPY) ||
(cur == VFIO_DEVICE_STATE_RUNNING_P2P &&
new == VFIO_DEVICE_STATE_PRE_COPY_P2P)) {
struct virtiovf_migration_file *migf;
migf = virtiovf_pci_save_device_data(virtvdev, true );
if (IS_ERR(migf))
return ERR_CAST(migf);
get_file(migf->filp);
virtvdev->saving_migf = migf;
return migf->filp;
}
if (cur == VFIO_DEVICE_STATE_PRE_COPY_P2P && new == VFIO_DEVICE_STATE_STOP_COPY) {
ret = virtiovf_pci_save_device_final_data(virtvdev);
return ret ? ERR_PTR(ret) : NULL;
}
/*
* vfio_mig_get_next_state ( ) does not use arcs other than the above
*/
WARN_ON(true );
return ERR_PTR(-EINVAL);
}
static struct file *
virtiovf_pci_set_device_state(struct vfio_device *vdev,
enum vfio_device_mig_state new_state)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
vdev, struct virtiovf_pci_core_device, core_device.vdev);
enum vfio_device_mig_state next_state;
struct file *res = NULL;
int ret;
mutex_lock(&virtvdev->state_mutex);
while (new_state != virtvdev->mig_state) {
ret = vfio_mig_get_next_state(vdev, virtvdev->mig_state,
new_state, &next_state);
if (ret) {
res = ERR_PTR(ret);
break ;
}
res = virtiovf_pci_step_device_state_locked(virtvdev, next_state);
if (IS_ERR(res))
break ;
virtvdev->mig_state = next_state;
if (WARN_ON(res && new_state != virtvdev->mig_state)) {
fput(res);
res = ERR_PTR(-EINVAL);
break ;
}
}
virtiovf_state_mutex_unlock(virtvdev);
return res;
}
static int virtiovf_pci_get_device_state(struct vfio_device *vdev,
enum vfio_device_mig_state *curr_state)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
vdev, struct virtiovf_pci_core_device, core_device.vdev);
mutex_lock(&virtvdev->state_mutex);
*curr_state = virtvdev->mig_state;
virtiovf_state_mutex_unlock(virtvdev);
return 0 ;
}
static int virtiovf_pci_get_data_size(struct vfio_device *vdev,
unsigned long *stop_copy_length)
{
struct virtiovf_pci_core_device *virtvdev = container_of(
vdev, struct virtiovf_pci_core_device, core_device.vdev);
bool obj_id_exists;
u32 res_size;
u32 obj_id;
int ret;
mutex_lock(&virtvdev->state_mutex);
obj_id_exists = virtvdev->saving_migf && virtvdev->saving_migf->has_obj_id;
if (!obj_id_exists) {
ret = virtiovf_pci_alloc_obj_id(virtvdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS_TYPE_GET,
&obj_id);
if (ret)
goto end;
} else {
obj_id = virtvdev->saving_migf->obj_id;
}
ret = virtio_pci_admin_dev_parts_metadata_get(virtvdev->core_device.pdev,
VIRTIO_RESOURCE_OBJ_DEV_PARTS, obj_id,
VIRTIO_ADMIN_CMD_DEV_PARTS_METADATA_TYPE_SIZE,
&res_size);
if (!ret)
*stop_copy_length = res_size;
/*
* We can ' t leave this obj_id alive if didn ' t exist before , otherwise , it might
* stay alive , even without an active migration flow ( e . g . migration was cancelled )
*/
if (!obj_id_exists)
virtiovf_pci_free_obj_id(virtvdev, obj_id);
end:
virtiovf_state_mutex_unlock(virtvdev);
return ret;
}
static const struct vfio_migration_ops virtvdev_pci_mig_ops = {
.migration_set_state = virtiovf_pci_set_device_state,
.migration_get_state = virtiovf_pci_get_device_state,
.migration_get_data_size = virtiovf_pci_get_data_size,
};
void virtiovf_set_migratable(struct virtiovf_pci_core_device *virtvdev)
{
virtvdev->migrate_cap = 1 ;
mutex_init(&virtvdev->state_mutex);
spin_lock_init(&virtvdev->reset_lock);
virtvdev->core_device.vdev.migration_flags =
VFIO_MIGRATION_STOP_COPY |
VFIO_MIGRATION_P2P |
VFIO_MIGRATION_PRE_COPY;
virtvdev->core_device.vdev.mig_ops = &virtvdev_pci_mig_ops;
}
void virtiovf_open_migration(struct virtiovf_pci_core_device *virtvdev)
{
if (!virtvdev->migrate_cap)
return ;
virtvdev->mig_state = VFIO_DEVICE_STATE_RUNNING;
}
void virtiovf_close_migration(struct virtiovf_pci_core_device *virtvdev)
{
if (!virtvdev->migrate_cap)
return ;
virtiovf_disable_fds(virtvdev);
}
Messung V0.5 in Prozent C=97 H=89 G=93
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-10-11)
¤
*© Formatika GbR, Deutschland