Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Yishai Hadas 5927 99.97% 3 75.00%
Luiz Fernando N. Capitulino 2 0.03% 1 25.00%
Total 5929 4


// 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);
}