[PATCH v3 05/16] drm/amdkfd: CRIU Implement KFD restore ioctl
David Yat Sin
david.yatsin at amd.com
Wed Sep 29 12:00:27 UTC 2021
From: Rajneesh Bhardwaj <rajneesh.bhardwaj at amd.com>
This implements the KFD CRIU Restore ioctl that lays the basic
foundation for the CRIU restore operation. It provides support to
create the buffer objects corresponding to Non-Paged system memory
mapped for GPU and/or CPU access and lays basic foundation for the
userptrs buffer objects which will be added in a separate patch.
This ioctl creates various types of buffer objects such as VRAM,
MMIO, Doorbell, GTT based on the date sent from the userspace plugin.
The data mostly contains the previously checkpointed KFD images from
some KFD processs.
While restoring a criu process, attach old IDR values to newly
created BOs. This also adds the minimal gpu mapping support for a single
gpu checkpoint restore use case.
Signed-off-by: David Yat Sin <david.yatsin at amd.com>
Signed-off-by: Rajneesh Bhardwaj <rajneesh.bhardwaj at amd.com>
---
drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 297 ++++++++++++++++++++++-
1 file changed, 296 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c
index cc3d8fd1d26f..e5a6a98eae45 100644
--- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c
@@ -2053,10 +2053,305 @@ static int kfd_ioctl_criu_dumper(struct file *filep,
return ret;
}
+static int criu_restore_process(struct kfd_process *p, struct kfd_ioctl_criu_restorer_args *args)
+{
+ int ret = 0;
+ uint8_t *objects;
+ struct kfd_criu_process_bucket *process_bucket;
+ struct kfd_criu_process_priv_data *process_priv;
+
+ if (args->num_objects != 1) {
+ pr_err("Only 1 process supported\n");
+ return -EINVAL;
+ }
+
+ if (args->objects_size != sizeof(*process_bucket) + sizeof(*process_priv)) {
+ pr_err("Invalid objects size for process\n");
+ return -EINVAL;
+ }
+
+ objects = kmalloc(args->objects_size, GFP_KERNEL);
+ if (!objects)
+ return -ENOMEM;
+
+ ret = copy_from_user(objects, (void __user *)args->objects, args->objects_size);
+ if (ret) {
+ pr_err("Failed to copy process information from user\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ process_bucket = (struct kfd_criu_process_bucket *)objects;
+ /* Private data starts after process bucket */
+ process_priv = (struct kfd_criu_process_priv_data *)
+ (objects + sizeof(*process_bucket) + process_bucket->priv_data_offset);
+
+ if (process_priv->version != KFD_CRIU_PRIV_VERSION) {
+ pr_err("Invalid CRIU API version (checkpointed:%d current:%d)\n",
+ process_priv->version, KFD_CRIU_PRIV_VERSION);
+ return -EINVAL;
+ }
+
+exit:
+ kfree(objects);
+ return ret;
+}
+
+static int criu_restore_bos(struct kfd_process *p, struct kfd_ioctl_criu_restorer_args *args)
+{
+ struct kfd_criu_bo_bucket *bo_buckets;
+ uint8_t *objects, *private_data;
+ bool flush_tlbs = false;
+ int ret = 0, i, j = 0;
+
+ if (args->objects_size != args->num_objects *
+ (sizeof(*bo_buckets) + sizeof(struct kfd_criu_bo_priv_data))) {
+ pr_err("Invalid objects size for BOs\n");
+ return -EINVAL;
+ }
+
+ objects = kmalloc(args->objects_size, GFP_KERNEL);
+ if (!objects)
+ return -ENOMEM;
+
+ ret = copy_from_user(objects, (void __user *)args->objects, args->objects_size);
+ if (ret) {
+ pr_err("Failed to copy BOs information from user\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ bo_buckets = (struct kfd_criu_bo_bucket *) objects;
+ /* Private data for first BO starts after all bo_buckets */
+ private_data = (void *)(bo_buckets + args->num_objects);
+
+ /* Create and map new BOs */
+ for (i = 0; i < args->num_objects; i++) {
+ struct kfd_criu_bo_bucket *bo_bucket;
+ struct kfd_criu_bo_priv_data *bo_priv;
+ struct kfd_dev *dev;
+ struct kfd_process_device *pdd;
+ void *mem;
+ u64 offset;
+ int idr_handle;
+
+ bo_bucket = &bo_buckets[i];
+ bo_priv = (struct kfd_criu_bo_priv_data *)
+ (private_data + bo_bucket->priv_data_offset);
+
+ dev = kfd_device_by_id(bo_bucket->gpu_id);
+ if (!dev) {
+ ret = -EINVAL;
+ pr_err("Failed to get pdd\n");
+ goto exit;
+ }
+ pdd = kfd_get_process_device_data(dev, p);
+ if (!pdd) {
+ ret = -EINVAL;
+ pr_err("Failed to get pdd\n");
+ goto exit;
+ }
+
+ pr_debug("kfd restore ioctl - bo_bucket[%d]:\n", i);
+ pr_debug("size = 0x%llx, bo_addr = 0x%llx bo_offset = 0x%llx\n"
+ "gpu_id = 0x%x alloc_flags = 0x%x\n"
+ "idr_handle = 0x%x\n",
+ bo_bucket->size,
+ bo_bucket->addr,
+ bo_bucket->offset,
+ bo_bucket->gpu_id,
+ bo_bucket->alloc_flags,
+ bo_priv->idr_handle);
+
+ if (bo_bucket->alloc_flags & KFD_IOC_ALLOC_MEM_FLAGS_DOORBELL) {
+ pr_debug("restore ioctl: KFD_IOC_ALLOC_MEM_FLAGS_DOORBELL\n");
+ if (bo_bucket->size != kfd_doorbell_process_slice(dev)) {
+ ret = -EINVAL;
+ goto exit;
+ }
+ offset = kfd_get_process_doorbells(pdd);
+ } else if (bo_bucket->alloc_flags & KFD_IOC_ALLOC_MEM_FLAGS_MMIO_REMAP) {
+ /* MMIO BOs need remapped bus address */
+ pr_debug("restore ioctl :KFD_IOC_ALLOC_MEM_FLAGS_MMIO_REMAP\n");
+ if (bo_bucket->size != PAGE_SIZE) {
+ pr_err("Invalid page size\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ offset = amdgpu_amdkfd_get_mmio_remap_phys_addr(dev->kgd);
+ if (!offset) {
+ pr_err("amdgpu_amdkfd_get_mmio_remap_phys_addr failed\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ } else if (bo_bucket->alloc_flags & KFD_IOC_ALLOC_MEM_FLAGS_USERPTR) {
+ offset = bo_priv->user_addr;
+ }
+
+ /* Create the BO */
+ ret = amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu(dev->kgd,
+ bo_bucket->addr,
+ bo_bucket->size,
+ pdd->drm_priv,
+ (struct kgd_mem **) &mem,
+ &offset,
+ bo_bucket->alloc_flags);
+ if (ret) {
+ pr_err("Could not create the BO\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ pr_debug("New BO created: size = 0x%llx, bo_addr = 0x%llx bo_offset = 0x%llx\n",
+ bo_bucket->size, bo_bucket->addr, offset);
+
+ /* Restore previuos IDR handle */
+ pr_debug("Restoring old IDR handle for the BO");
+ idr_handle = idr_alloc(&pdd->alloc_idr, mem,
+ bo_priv->idr_handle,
+ bo_priv->idr_handle + 1, GFP_KERNEL);
+ if (idr_handle < 0) {
+ pr_err("Could not allocate idr\n");
+ amdgpu_amdkfd_gpuvm_free_memory_of_gpu(dev->kgd,
+ (struct kgd_mem *)mem,
+ pdd->drm_priv, NULL);
+
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ if (bo_bucket->alloc_flags & KFD_IOC_ALLOC_MEM_FLAGS_DOORBELL)
+ bo_bucket->restored_offset = KFD_MMAP_TYPE_DOORBELL |
+ KFD_MMAP_GPU_ID(pdd->dev->id);
+ if (bo_bucket->alloc_flags & KFD_IOC_ALLOC_MEM_FLAGS_MMIO_REMAP) {
+ bo_bucket->restored_offset = KFD_MMAP_TYPE_MMIO |
+ KFD_MMAP_GPU_ID(pdd->dev->id);
+ } else if (bo_bucket->alloc_flags & KFD_IOC_ALLOC_MEM_FLAGS_GTT) {
+ bo_bucket->restored_offset = offset;
+ pr_debug("updating offset for GTT\n");
+ } else if (bo_bucket->alloc_flags & KFD_IOC_ALLOC_MEM_FLAGS_VRAM) {
+ bo_bucket->restored_offset = offset;
+ /* Update the VRAM usage count */
+ WRITE_ONCE(pdd->vram_usage, pdd->vram_usage + bo_bucket->size);
+ pr_debug("updating offset for VRAM\n");
+ }
+
+ /* now map these BOs to GPU/s */
+ for (j = 0; j < p->n_pdds; j++) {
+ struct kfd_process_device *pdd = p->pdds[j];
+ struct kfd_dev *peer;
+ struct kfd_process_device *peer_pdd;
+ bool table_freed = false;
+
+ peer = kfd_device_by_id(pdd->dev->id);
+
+ pr_debug("Inside mapping loop with desired gpu_id = 0x%x\n",
+ pdd->dev->id);
+ if (!peer) {
+ pr_debug("Getting device by id failed for 0x%x\n",
+ pdd->dev->id);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ peer_pdd = kfd_bind_process_to_device(peer, p);
+ if (IS_ERR(peer_pdd)) {
+ ret = PTR_ERR(peer_pdd);
+ goto exit;
+ }
+ pr_debug("map mem in restore ioctl -> 0x%llx\n",
+ ((struct kgd_mem *)mem)->va);
+ ret = amdgpu_amdkfd_gpuvm_map_memory_to_gpu(peer->kgd,
+ (struct kgd_mem *)mem, peer_pdd->drm_priv, &table_freed);
+ if (ret) {
+ pr_err("Failed to map to gpu %d/%d\n",
+ j, p->n_pdds);
+ goto exit;
+ }
+ if (table_freed)
+ flush_tlbs = true;
+ }
+
+ ret = amdgpu_amdkfd_gpuvm_sync_memory(dev->kgd,
+ (struct kgd_mem *) mem, true);
+ if (ret) {
+ pr_debug("Sync memory failed, wait interrupted by user signal\n");
+ goto exit;
+ }
+
+ pr_debug("map memory was successful for the BO\n");
+ } /* done */
+
+ if (flush_tlbs) {
+ /* Flush TLBs after waiting for the page table updates to complete */
+ for (j = 0; j < p->n_pdds; j++) {
+ struct kfd_dev *peer;
+ struct kfd_process_device *pdd = p->pdds[j];
+ struct kfd_process_device *peer_pdd;
+
+ peer = kfd_device_by_id(pdd->dev->id);
+ if (WARN_ON_ONCE(!peer))
+ continue;
+ peer_pdd = kfd_get_process_device_data(peer, p);
+ if (WARN_ON_ONCE(!peer_pdd))
+ continue;
+ kfd_flush_tlb(peer_pdd, TLB_FLUSH_LEGACY);
+ }
+ }
+
+ /* Copy only the buckets back so user can read bo_buckets[N].restored_offset */
+ ret = copy_to_user((void __user *)args->objects,
+ bo_buckets,
+ (args->num_objects * sizeof(*bo_buckets)));
+ if (ret)
+ ret = -EFAULT;
+
+exit:
+ kvfree(objects);
+ return ret;
+}
+
static int kfd_ioctl_criu_restorer(struct file *filep,
struct kfd_process *p, void *data)
{
- return 0;
+ struct kfd_ioctl_criu_restorer_args *args = data;
+ int ret;
+
+ if (!args->objects || !args->objects_size)
+ return -EINVAL;
+
+ if (args->objects_index_start != 0) {
+ /* Partial to be implemented later */
+ pr_err("Partial dumps not supported\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&p->mutex);
+
+ switch (args->type) {
+ case KFD_CRIU_OBJECT_TYPE_PROCESS:
+ ret = criu_restore_process(p, args);
+ break;
+ case KFD_CRIU_OBJECT_TYPE_BO:
+ ret = criu_restore_bos(p, args);
+ break;
+ case KFD_CRIU_OBJECT_TYPE_QUEUE:
+ case KFD_CRIU_OBJECT_TYPE_EVENT:
+ case KFD_CRIU_OBJECT_TYPE_DEVICE:
+ case KFD_CRIU_OBJECT_TYPE_SVM_RANGE:
+ default:
+ pr_err("Unsupported object type:%d\n", args->type);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+exit:
+ mutex_unlock(&p->mutex);
+ if (ret)
+ pr_err("Failed to restore CRIU type:%d ret:%d\n", args->type, ret);
+ else
+ pr_debug("CRIU restore type:%d ret:%d\n", args->type, ret);
+
+ return ret;
}
static int kfd_ioctl_criu_pause(struct file *filep, struct kfd_process *p, void *data)
--
2.17.1
More information about the amd-gfx
mailing list