From: Rajneesh Bhardwaj <rajneesh.bhardwaj@xxxxxxx> 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@xxxxxxx> Signed-off-by: Rajneesh Bhardwaj <rajneesh.bhardwaj@xxxxxxx> (cherry picked from commit 47bb685701c336d1fde7e91be93d9cabe89a4c1b) (cherry picked from commit b71ba8158a7ddf9e4fd8d872be4e40ddd9a29b4f) --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 290 ++++++++++++++++++++++- 1 file changed, 288 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 40efc80f3604..59b5c4131f48 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -2016,12 +2016,298 @@ 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) +{ + uint8_t *objects, *private_data; + struct kfd_criu_bo_bucket *bo_buckets; + 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; + + 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); + if (ret) { + pr_err("Failed to map to gpu %d/%d\n", + j, p->n_pdds); + goto exit; + } + } + + 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 */ + + /* 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); + } + /* 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) { - pr_debug("Inside %s\n", __func__); + struct kfd_ioctl_criu_restorer_args *args = data; + int ret; - return 0; + 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