Commit f232836a authored by Brett Creeley's avatar Brett Creeley Committed by Alex Williamson

vfio/pds: Add support for dirty page tracking

In order to support dirty page tracking, the driver has to implement
the VFIO subsystem's vfio_log_ops. This includes log_start, log_stop,
and log_read_and_clear.

All of the tracker resources are allocated and dirty tracking on the
device is started during log_start. The resources are cleaned up and
dirty tracking on the device is stopped during log_stop. The dirty
pages are determined and reported during log_read_and_clear.

In order to support these callbacks admin queue commands are used.
All of the adminq queue command structures and implementations
are included as part of this patch.

PDS_LM_CMD_DIRTY_STATUS is added to query the current status of
dirty tracking on the device. This includes if it's enabled (i.e.
number of regions being tracked from the device's perspective) and
the maximum number of regions supported from the device's perspective.

PDS_LM_CMD_DIRTY_ENABLE is added to enable dirty tracking on the
specified number of regions and their iova ranges.

PDS_LM_CMD_DIRTY_DISABLE is added to disable dirty tracking for all
regions on the device.

PDS_LM_CMD_READ_SEQ and PDS_LM_CMD_DIRTY_WRITE_ACK are added to
support reading and acknowledging the currently dirtied pages.
Signed-off-by: default avatarBrett Creeley <brett.creeley@amd.com>
Signed-off-by: default avatarShannon Nelson <shannon.nelson@amd.com>
Reviewed-by: default avatarSimon Horman <horms@kernel.org>
Reviewed-by: default avatarJason Gunthorpe <jgg@nvidia.com>
Reviewed-by: default avatarKevin Tian <kevin.tian@intel.com>
Reviewed-by: default avatarShameer Kolothum <shameerali.kolothum.thodi@huawei.com>
Link: https://lore.kernel.org/r/20230807205755.29579-7-brett.creeley@amd.comSigned-off-by: default avatarAlex Williamson <alex.williamson@redhat.com>
parent bb500dbe
......@@ -5,6 +5,7 @@ obj-$(CONFIG_PDS_VFIO_PCI) += pds-vfio-pci.o
pds-vfio-pci-y := \
cmds.o \
dirty.o \
lm.o \
pci_drv.o \
vfio_dev.o
......@@ -382,3 +382,128 @@ void pds_vfio_send_host_vf_lm_status_cmd(struct pds_vfio_pci_device *pds_vfio,
dev_warn(dev, "failed to send host VF migration status: %pe\n",
ERR_PTR(err));
}
int pds_vfio_dirty_status_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 *max_regions, u8 *num_regions)
{
union pds_core_adminq_cmd cmd = {
.lm_dirty_status = {
.opcode = PDS_LM_CMD_DIRTY_STATUS,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
dev_dbg(dev, "vf%u: Dirty status\n", pds_vfio->vf_id);
cmd.lm_dirty_status.regions_dma = cpu_to_le64(regions_dma);
cmd.lm_dirty_status.max_regions = *max_regions;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err) {
dev_err(dev, "failed to get dirty status: %pe\n", ERR_PTR(err));
return err;
}
/* only support seq_ack approach for now */
if (!(le32_to_cpu(comp.lm_dirty_status.bmp_type_mask) &
BIT(PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK))) {
dev_err(dev, "Dirty bitmap tracking SEQ_ACK not supported\n");
return -EOPNOTSUPP;
}
*num_regions = comp.lm_dirty_status.num_regions;
*max_regions = comp.lm_dirty_status.max_regions;
dev_dbg(dev,
"Page Tracking Status command successful, max_regions: %d, num_regions: %d, bmp_type: %s\n",
*max_regions, *num_regions, "PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK");
return 0;
}
int pds_vfio_dirty_enable_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 num_regions)
{
union pds_core_adminq_cmd cmd = {
.lm_dirty_enable = {
.opcode = PDS_LM_CMD_DIRTY_ENABLE,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.regions_dma = cpu_to_le64(regions_dma),
.bmp_type = PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK,
.num_regions = num_regions,
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err) {
dev_err(dev, "failed dirty tracking enable: %pe\n",
ERR_PTR(err));
return err;
}
return 0;
}
int pds_vfio_dirty_disable_cmd(struct pds_vfio_pci_device *pds_vfio)
{
union pds_core_adminq_cmd cmd = {
.lm_dirty_disable = {
.opcode = PDS_LM_CMD_DIRTY_DISABLE,
.vf_id = cpu_to_le16(pds_vfio->vf_id),
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err || comp.lm_dirty_status.num_regions != 0) {
/* in case num_regions is still non-zero after disable */
err = err ? err : -EIO;
dev_err(dev,
"failed dirty tracking disable: %pe, num_regions %d\n",
ERR_PTR(err), comp.lm_dirty_status.num_regions);
return err;
}
return 0;
}
int pds_vfio_dirty_seq_ack_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 sgl_dma, u16 num_sge, u32 offset,
u32 total_len, bool read_seq)
{
const char *cmd_type_str = read_seq ? "read_seq" : "write_ack";
union pds_core_adminq_cmd cmd = {
.lm_dirty_seq_ack = {
.vf_id = cpu_to_le16(pds_vfio->vf_id),
.len_bytes = cpu_to_le32(total_len),
.off_bytes = cpu_to_le32(offset),
.sgl_addr = cpu_to_le64(sgl_dma),
.num_sge = cpu_to_le16(num_sge),
},
};
struct device *dev = pds_vfio_to_dev(pds_vfio);
union pds_core_adminq_comp comp = {};
int err;
if (read_seq)
cmd.lm_dirty_seq_ack.opcode = PDS_LM_CMD_DIRTY_READ_SEQ;
else
cmd.lm_dirty_seq_ack.opcode = PDS_LM_CMD_DIRTY_WRITE_ACK;
err = pds_vfio_client_adminq_cmd(pds_vfio, &cmd, &comp, false);
if (err) {
dev_err(dev, "failed cmd Page Tracking %s: %pe\n", cmd_type_str,
ERR_PTR(err));
return err;
}
return 0;
}
......@@ -13,4 +13,13 @@ int pds_vfio_get_lm_state_cmd(struct pds_vfio_pci_device *pds_vfio);
int pds_vfio_set_lm_state_cmd(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_send_host_vf_lm_status_cmd(struct pds_vfio_pci_device *pds_vfio,
enum pds_lm_host_vf_status vf_status);
int pds_vfio_dirty_status_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 *max_regions,
u8 *num_regions);
int pds_vfio_dirty_enable_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 regions_dma, u8 num_regions);
int pds_vfio_dirty_disable_cmd(struct pds_vfio_pci_device *pds_vfio);
int pds_vfio_dirty_seq_ack_cmd(struct pds_vfio_pci_device *pds_vfio,
u64 sgl_dma, u16 num_sge, u32 offset,
u32 total_len, bool read_seq);
#endif /* _CMDS_H_ */
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2023 Advanced Micro Devices, Inc. */
#ifndef _DIRTY_H_
#define _DIRTY_H_
struct pds_vfio_bmp_info {
unsigned long *bmp;
u32 bmp_bytes;
struct pds_lm_sg_elem *sgl;
dma_addr_t sgl_addr;
u16 num_sge;
};
struct pds_vfio_dirty {
struct pds_vfio_bmp_info host_seq;
struct pds_vfio_bmp_info host_ack;
u64 region_size;
u64 region_start;
u64 region_page_size;
bool is_enabled;
};
struct pds_vfio_pci_device;
bool pds_vfio_dirty_is_enabled(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_dirty_set_enabled(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_dirty_set_disabled(struct pds_vfio_pci_device *pds_vfio);
void pds_vfio_dirty_disable(struct pds_vfio_pci_device *pds_vfio,
bool send_cmd);
int pds_vfio_dma_logging_report(struct vfio_device *vdev, unsigned long iova,
unsigned long length,
struct iova_bitmap *dirty);
int pds_vfio_dma_logging_start(struct vfio_device *vdev,
struct rb_root_cached *ranges, u32 nnodes,
u64 *page_size);
int pds_vfio_dma_logging_stop(struct vfio_device *vdev);
#endif /* _DIRTY_H_ */
......@@ -371,7 +371,7 @@ pds_vfio_step_device_state_locked(struct pds_vfio_pci_device *pds_vfio,
if (cur == VFIO_DEVICE_STATE_STOP_COPY && next == VFIO_DEVICE_STATE_STOP) {
pds_vfio_put_save_file(pds_vfio);
pds_vfio_send_host_vf_lm_status_cmd(pds_vfio, PDS_LM_STA_NONE);
pds_vfio_dirty_disable(pds_vfio, true);
return NULL;
}
......
......@@ -5,6 +5,7 @@
#include <linux/vfio_pci_core.h>
#include "lm.h"
#include "dirty.h"
#include "vfio_dev.h"
struct pci_dev *pds_vfio_to_pci_dev(struct pds_vfio_pci_device *pds_vfio)
......@@ -25,7 +26,7 @@ struct pds_vfio_pci_device *pds_vfio_pci_drvdata(struct pci_dev *pdev)
vfio_coredev);
}
static void pds_vfio_state_mutex_unlock(struct pds_vfio_pci_device *pds_vfio)
void pds_vfio_state_mutex_unlock(struct pds_vfio_pci_device *pds_vfio)
{
again:
spin_lock(&pds_vfio->reset_lock);
......@@ -35,6 +36,7 @@ static void pds_vfio_state_mutex_unlock(struct pds_vfio_pci_device *pds_vfio)
pds_vfio->state = VFIO_DEVICE_STATE_RUNNING;
pds_vfio_put_restore_file(pds_vfio);
pds_vfio_put_save_file(pds_vfio);
pds_vfio_dirty_disable(pds_vfio, false);
}
spin_unlock(&pds_vfio->reset_lock);
goto again;
......@@ -117,6 +119,12 @@ static const struct vfio_migration_ops pds_vfio_lm_ops = {
.migration_get_data_size = pds_vfio_get_device_state_size
};
static const struct vfio_log_ops pds_vfio_log_ops = {
.log_start = pds_vfio_dma_logging_start,
.log_stop = pds_vfio_dma_logging_stop,
.log_read_and_clear = pds_vfio_dma_logging_report,
};
static int pds_vfio_init_device(struct vfio_device *vdev)
{
struct pds_vfio_pci_device *pds_vfio =
......@@ -137,6 +145,7 @@ static int pds_vfio_init_device(struct vfio_device *vdev)
vdev->migration_flags = VFIO_MIGRATION_STOP_COPY | VFIO_MIGRATION_P2P;
vdev->mig_ops = &pds_vfio_lm_ops;
vdev->log_ops = &pds_vfio_log_ops;
pci_id = PCI_DEVID(pdev->bus->number, pdev->devfn);
dev_dbg(&pdev->dev,
......@@ -175,6 +184,7 @@ static void pds_vfio_close_device(struct vfio_device *vdev)
mutex_lock(&pds_vfio->state_mutex);
pds_vfio_put_restore_file(pds_vfio);
pds_vfio_put_save_file(pds_vfio);
pds_vfio_dirty_disable(pds_vfio, true);
mutex_unlock(&pds_vfio->state_mutex);
mutex_destroy(&pds_vfio->state_mutex);
vfio_pci_core_close_device(vdev);
......
......@@ -7,6 +7,7 @@
#include <linux/pci.h>
#include <linux/vfio_pci_core.h>
#include "dirty.h"
#include "lm.h"
struct pds_vfio_pci_device {
......@@ -14,6 +15,7 @@ struct pds_vfio_pci_device {
struct pds_vfio_lm_file *save_file;
struct pds_vfio_lm_file *restore_file;
struct pds_vfio_dirty dirty;
struct mutex state_mutex; /* protect migration state */
enum vfio_device_mig_state state;
spinlock_t reset_lock; /* protect reset_done flow */
......@@ -23,6 +25,8 @@ struct pds_vfio_pci_device {
u16 client_id;
};
void pds_vfio_state_mutex_unlock(struct pds_vfio_pci_device *pds_vfio);
const struct vfio_device_ops *pds_vfio_ops_info(void);
struct pds_vfio_pci_device *pds_vfio_pci_drvdata(struct pci_dev *pdev);
void pds_vfio_reset(struct pds_vfio_pci_device *pds_vfio);
......
......@@ -835,6 +835,13 @@ enum pds_lm_cmd_opcode {
PDS_LM_CMD_RESUME = 20,
PDS_LM_CMD_SAVE = 21,
PDS_LM_CMD_RESTORE = 22,
/* Dirty page tracking commands */
PDS_LM_CMD_DIRTY_STATUS = 32,
PDS_LM_CMD_DIRTY_ENABLE = 33,
PDS_LM_CMD_DIRTY_DISABLE = 34,
PDS_LM_CMD_DIRTY_READ_SEQ = 35,
PDS_LM_CMD_DIRTY_WRITE_ACK = 36,
};
/**
......@@ -992,6 +999,172 @@ enum pds_lm_host_vf_status {
PDS_LM_STA_MAX,
};
/**
* struct pds_lm_dirty_region_info - Memory region info for STATUS and ENABLE
* @dma_base: Base address of the DMA-contiguous memory region
* @page_count: Number of pages in the memory region
* @page_size_log2: Log2 page size in the memory region
* @rsvd: Word boundary padding
*/
struct pds_lm_dirty_region_info {
__le64 dma_base;
__le32 page_count;
u8 page_size_log2;
u8 rsvd[3];
};
/**
* struct pds_lm_dirty_status_cmd - DIRTY_STATUS command
* @opcode: Opcode PDS_LM_CMD_DIRTY_STATUS
* @rsvd: Word boundary padding
* @vf_id: VF id
* @max_regions: Capacity of the region info buffer
* @rsvd2: Word boundary padding
* @regions_dma: DMA address of the region info buffer
*
* The minimum of max_regions (from the command) and num_regions (from the
* completion) of struct pds_lm_dirty_region_info will be written to
* regions_dma.
*
* The max_regions may be zero, in which case regions_dma is ignored. In that
* case, the completion will only report the maximum number of regions
* supported by the device, and the number of regions currently enabled.
*/
struct pds_lm_dirty_status_cmd {
u8 opcode;
u8 rsvd;
__le16 vf_id;
u8 max_regions;
u8 rsvd2[3];
__le64 regions_dma;
} __packed;
/**
* enum pds_lm_dirty_bmp_type - Type of dirty page bitmap
* @PDS_LM_DIRTY_BMP_TYPE_NONE: No bitmap / disabled
* @PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK: Seq/Ack bitmap representation
*/
enum pds_lm_dirty_bmp_type {
PDS_LM_DIRTY_BMP_TYPE_NONE = 0,
PDS_LM_DIRTY_BMP_TYPE_SEQ_ACK = 1,
};
/**
* struct pds_lm_dirty_status_comp - STATUS command completion
* @status: Status of the command (enum pds_core_status_code)
* @rsvd: Word boundary padding
* @comp_index: Index in the desc ring for which this is the completion
* @max_regions: Maximum number of regions supported by the device
* @num_regions: Number of regions currently enabled
* @bmp_type: Type of dirty bitmap representation
* @rsvd2: Word boundary padding
* @bmp_type_mask: Mask of supported bitmap types, bit index per type
* @rsvd3: Word boundary padding
* @color: Color bit
*
* This completion descriptor is used for STATUS, ENABLE, and DISABLE.
*/
struct pds_lm_dirty_status_comp {
u8 status;
u8 rsvd;
__le16 comp_index;
u8 max_regions;
u8 num_regions;
u8 bmp_type;
u8 rsvd2;
__le32 bmp_type_mask;
u8 rsvd3[3];
u8 color;
};
/**
* struct pds_lm_dirty_enable_cmd - DIRTY_ENABLE command
* @opcode: Opcode PDS_LM_CMD_DIRTY_ENABLE
* @rsvd: Word boundary padding
* @vf_id: VF id
* @bmp_type: Type of dirty bitmap representation
* @num_regions: Number of entries in the region info buffer
* @rsvd2: Word boundary padding
* @regions_dma: DMA address of the region info buffer
*
* The num_regions must be nonzero, and less than or equal to the maximum
* number of regions supported by the device.
*
* The memory regions should not overlap.
*
* The information should be initialized by the driver. The device may modify
* the information on successful completion, such as by size-aligning the
* number of pages in a region.
*
* The modified number of pages will be greater than or equal to the page count
* given in the enable command, and at least as coarsly aligned as the given
* value. For example, the count might be aligned to a multiple of 64, but
* if the value is already a multiple of 128 or higher, it will not change.
* If the driver requires its own minimum alignment of the number of pages, the
* driver should account for that already in the region info of this command.
*
* This command uses struct pds_lm_dirty_status_comp for its completion.
*/
struct pds_lm_dirty_enable_cmd {
u8 opcode;
u8 rsvd;
__le16 vf_id;
u8 bmp_type;
u8 num_regions;
u8 rsvd2[2];
__le64 regions_dma;
} __packed;
/**
* struct pds_lm_dirty_disable_cmd - DIRTY_DISABLE command
* @opcode: Opcode PDS_LM_CMD_DIRTY_DISABLE
* @rsvd: Word boundary padding
* @vf_id: VF id
*
* Dirty page tracking will be disabled. This may be called in any state, as
* long as dirty page tracking is supported by the device, to ensure that dirty
* page tracking is disabled.
*
* This command uses struct pds_lm_dirty_status_comp for its completion. On
* success, num_regions will be zero.
*/
struct pds_lm_dirty_disable_cmd {
u8 opcode;
u8 rsvd;
__le16 vf_id;
};
/**
* struct pds_lm_dirty_seq_ack_cmd - DIRTY_READ_SEQ or _WRITE_ACK command
* @opcode: Opcode PDS_LM_CMD_DIRTY_[READ_SEQ|WRITE_ACK]
* @rsvd: Word boundary padding
* @vf_id: VF id
* @off_bytes: Byte offset in the bitmap
* @len_bytes: Number of bytes to transfer
* @num_sge: Number of DMA scatter gather elements
* @rsvd2: Word boundary padding
* @sgl_addr: DMA address of scatter gather list
*
* Read bytes from the SEQ bitmap, or write bytes into the ACK bitmap.
*
* This command treats the entire bitmap as a byte buffer. It does not
* distinguish between guest memory regions. The driver should refer to the
* number of pages in each region, according to PDS_LM_CMD_DIRTY_STATUS, to
* determine the region boundaries in the bitmap. Each region will be
* represented by exactly the number of bits as the page count for that region,
* immediately following the last bit of the previous region.
*/
struct pds_lm_dirty_seq_ack_cmd {
u8 opcode;
u8 rsvd;
__le16 vf_id;
__le32 off_bytes;
__le32 len_bytes;
__le16 num_sge;
u8 rsvd2[2];
__le64 sgl_addr;
} __packed;
/**
* struct pds_lm_host_vf_status_cmd - HOST_VF_STATUS command
* @opcode: Opcode PDS_LM_CMD_HOST_VF_STATUS
......@@ -1039,6 +1212,10 @@ union pds_core_adminq_cmd {
struct pds_lm_save_cmd lm_save;
struct pds_lm_restore_cmd lm_restore;
struct pds_lm_host_vf_status_cmd lm_host_vf_status;
struct pds_lm_dirty_status_cmd lm_dirty_status;
struct pds_lm_dirty_enable_cmd lm_dirty_enable;
struct pds_lm_dirty_disable_cmd lm_dirty_disable;
struct pds_lm_dirty_seq_ack_cmd lm_dirty_seq_ack;
};
union pds_core_adminq_comp {
......@@ -1065,6 +1242,7 @@ union pds_core_adminq_comp {
struct pds_vdpa_vq_reset_comp vdpa_vq_reset;
struct pds_lm_state_size_comp lm_state_size;
struct pds_lm_dirty_status_comp lm_dirty_status;
};
#ifndef __CHECKER__
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment