| /* |
| * Copyright 2022 Advanced Micro Devices, Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
| * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
| * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| * OTHER DEALINGS IN THE SOFTWARE. |
| * |
| */ |
| #include "amdgpu.h" |
| #include "amdgpu_xcp.h" |
| #include "amdgpu_drv.h" |
| |
| #include <drm/drm_drv.h> |
| #include "../amdxcp/amdgpu_xcp_drv.h" |
| |
| static int __amdgpu_xcp_run(struct amdgpu_xcp_mgr *xcp_mgr, |
| struct amdgpu_xcp_ip *xcp_ip, int xcp_state) |
| { |
| int (*run_func)(void *handle, uint32_t inst_mask); |
| int ret = 0; |
| |
| if (!xcp_ip || !xcp_ip->valid || !xcp_ip->ip_funcs) |
| return 0; |
| |
| run_func = NULL; |
| |
| switch (xcp_state) { |
| case AMDGPU_XCP_PREPARE_SUSPEND: |
| run_func = xcp_ip->ip_funcs->prepare_suspend; |
| break; |
| case AMDGPU_XCP_SUSPEND: |
| run_func = xcp_ip->ip_funcs->suspend; |
| break; |
| case AMDGPU_XCP_PREPARE_RESUME: |
| run_func = xcp_ip->ip_funcs->prepare_resume; |
| break; |
| case AMDGPU_XCP_RESUME: |
| run_func = xcp_ip->ip_funcs->resume; |
| break; |
| } |
| |
| if (run_func) |
| ret = run_func(xcp_mgr->adev, xcp_ip->inst_mask); |
| |
| return ret; |
| } |
| |
| static int amdgpu_xcp_run_transition(struct amdgpu_xcp_mgr *xcp_mgr, int xcp_id, |
| int state) |
| { |
| struct amdgpu_xcp_ip *xcp_ip; |
| struct amdgpu_xcp *xcp; |
| int i, ret; |
| |
| if (xcp_id >= MAX_XCP || !xcp_mgr->xcp[xcp_id].valid) |
| return -EINVAL; |
| |
| xcp = &xcp_mgr->xcp[xcp_id]; |
| for (i = 0; i < AMDGPU_XCP_MAX_BLOCKS; ++i) { |
| xcp_ip = &xcp->ip[i]; |
| ret = __amdgpu_xcp_run(xcp_mgr, xcp_ip, state); |
| if (ret) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| int amdgpu_xcp_prepare_suspend(struct amdgpu_xcp_mgr *xcp_mgr, int xcp_id) |
| { |
| return amdgpu_xcp_run_transition(xcp_mgr, xcp_id, |
| AMDGPU_XCP_PREPARE_SUSPEND); |
| } |
| |
| int amdgpu_xcp_suspend(struct amdgpu_xcp_mgr *xcp_mgr, int xcp_id) |
| { |
| return amdgpu_xcp_run_transition(xcp_mgr, xcp_id, AMDGPU_XCP_SUSPEND); |
| } |
| |
| int amdgpu_xcp_prepare_resume(struct amdgpu_xcp_mgr *xcp_mgr, int xcp_id) |
| { |
| return amdgpu_xcp_run_transition(xcp_mgr, xcp_id, |
| AMDGPU_XCP_PREPARE_RESUME); |
| } |
| |
| int amdgpu_xcp_resume(struct amdgpu_xcp_mgr *xcp_mgr, int xcp_id) |
| { |
| return amdgpu_xcp_run_transition(xcp_mgr, xcp_id, AMDGPU_XCP_RESUME); |
| } |
| |
| static void __amdgpu_xcp_add_block(struct amdgpu_xcp_mgr *xcp_mgr, int xcp_id, |
| struct amdgpu_xcp_ip *ip) |
| { |
| struct amdgpu_xcp *xcp; |
| |
| if (!ip) |
| return; |
| |
| xcp = &xcp_mgr->xcp[xcp_id]; |
| xcp->ip[ip->ip_id] = *ip; |
| xcp->ip[ip->ip_id].valid = true; |
| |
| xcp->valid = true; |
| } |
| |
| int amdgpu_xcp_init(struct amdgpu_xcp_mgr *xcp_mgr, int num_xcps, int mode) |
| { |
| struct amdgpu_device *adev = xcp_mgr->adev; |
| struct amdgpu_xcp_ip ip; |
| uint8_t mem_id; |
| int i, j, ret; |
| |
| if (!num_xcps || num_xcps > MAX_XCP) |
| return -EINVAL; |
| |
| xcp_mgr->mode = mode; |
| |
| for (i = 0; i < MAX_XCP; ++i) |
| xcp_mgr->xcp[i].valid = false; |
| |
| /* This is needed for figuring out memory id of xcp */ |
| xcp_mgr->num_xcp_per_mem_partition = num_xcps / xcp_mgr->adev->gmc.num_mem_partitions; |
| |
| for (i = 0; i < num_xcps; ++i) { |
| for (j = AMDGPU_XCP_GFXHUB; j < AMDGPU_XCP_MAX_BLOCKS; ++j) { |
| ret = xcp_mgr->funcs->get_ip_details(xcp_mgr, i, j, |
| &ip); |
| if (ret) |
| continue; |
| |
| __amdgpu_xcp_add_block(xcp_mgr, i, &ip); |
| } |
| |
| xcp_mgr->xcp[i].id = i; |
| |
| if (xcp_mgr->funcs->get_xcp_mem_id) { |
| ret = xcp_mgr->funcs->get_xcp_mem_id( |
| xcp_mgr, &xcp_mgr->xcp[i], &mem_id); |
| if (ret) |
| continue; |
| else |
| xcp_mgr->xcp[i].mem_id = mem_id; |
| } |
| } |
| |
| xcp_mgr->num_xcps = num_xcps; |
| amdgpu_xcp_update_partition_sched_list(adev); |
| |
| return 0; |
| } |
| |
| static int __amdgpu_xcp_switch_partition_mode(struct amdgpu_xcp_mgr *xcp_mgr, |
| int mode) |
| { |
| int ret, curr_mode, num_xcps = 0; |
| |
| if (!xcp_mgr->funcs || !xcp_mgr->funcs->switch_partition_mode) |
| return 0; |
| |
| mutex_lock(&xcp_mgr->xcp_lock); |
| |
| curr_mode = xcp_mgr->mode; |
| /* State set to transient mode */ |
| xcp_mgr->mode = AMDGPU_XCP_MODE_TRANS; |
| |
| ret = xcp_mgr->funcs->switch_partition_mode(xcp_mgr, mode, &num_xcps); |
| |
| if (ret) { |
| /* Failed, get whatever mode it's at now */ |
| if (xcp_mgr->funcs->query_partition_mode) |
| xcp_mgr->mode = amdgpu_xcp_query_partition_mode( |
| xcp_mgr, AMDGPU_XCP_FL_LOCKED); |
| else |
| xcp_mgr->mode = curr_mode; |
| |
| goto out; |
| } |
| |
| out: |
| mutex_unlock(&xcp_mgr->xcp_lock); |
| |
| return ret; |
| } |
| |
| int amdgpu_xcp_switch_partition_mode(struct amdgpu_xcp_mgr *xcp_mgr, int mode) |
| { |
| if (!xcp_mgr || mode == AMDGPU_XCP_MODE_NONE) |
| return -EINVAL; |
| |
| if (xcp_mgr->mode == mode) |
| return 0; |
| |
| return __amdgpu_xcp_switch_partition_mode(xcp_mgr, mode); |
| } |
| |
| int amdgpu_xcp_restore_partition_mode(struct amdgpu_xcp_mgr *xcp_mgr) |
| { |
| if (!xcp_mgr || xcp_mgr->mode == AMDGPU_XCP_MODE_NONE) |
| return 0; |
| |
| return __amdgpu_xcp_switch_partition_mode(xcp_mgr, xcp_mgr->mode); |
| } |
| |
| int amdgpu_xcp_query_partition_mode(struct amdgpu_xcp_mgr *xcp_mgr, u32 flags) |
| { |
| int mode; |
| |
| if (xcp_mgr->mode == AMDGPU_XCP_MODE_NONE) |
| return xcp_mgr->mode; |
| |
| if (!xcp_mgr->funcs || !xcp_mgr->funcs->query_partition_mode) |
| return xcp_mgr->mode; |
| |
| if (!(flags & AMDGPU_XCP_FL_LOCKED)) |
| mutex_lock(&xcp_mgr->xcp_lock); |
| mode = xcp_mgr->funcs->query_partition_mode(xcp_mgr); |
| if (xcp_mgr->mode != AMDGPU_XCP_MODE_TRANS && mode != xcp_mgr->mode) |
| dev_WARN( |
| xcp_mgr->adev->dev, |
| "Cached partition mode %d not matching with device mode %d", |
| xcp_mgr->mode, mode); |
| |
| if (!(flags & AMDGPU_XCP_FL_LOCKED)) |
| mutex_unlock(&xcp_mgr->xcp_lock); |
| |
| return mode; |
| } |
| |
| static int amdgpu_xcp_dev_alloc(struct amdgpu_device *adev) |
| { |
| struct drm_device *p_ddev; |
| struct drm_device *ddev; |
| int i, ret; |
| |
| ddev = adev_to_drm(adev); |
| |
| /* xcp #0 shares drm device setting with adev */ |
| adev->xcp_mgr->xcp->ddev = ddev; |
| |
| for (i = 1; i < MAX_XCP; i++) { |
| ret = amdgpu_xcp_drm_dev_alloc(&p_ddev); |
| if (ret == -ENOSPC) { |
| dev_warn(adev->dev, |
| "Skip xcp node #%d when out of drm node resource.", i); |
| return 0; |
| } else if (ret) { |
| return ret; |
| } |
| |
| /* Redirect all IOCTLs to the primary device */ |
| adev->xcp_mgr->xcp[i].rdev = p_ddev->render->dev; |
| adev->xcp_mgr->xcp[i].pdev = p_ddev->primary->dev; |
| adev->xcp_mgr->xcp[i].driver = (struct drm_driver *)p_ddev->driver; |
| adev->xcp_mgr->xcp[i].vma_offset_manager = p_ddev->vma_offset_manager; |
| p_ddev->render->dev = ddev; |
| p_ddev->primary->dev = ddev; |
| p_ddev->vma_offset_manager = ddev->vma_offset_manager; |
| p_ddev->driver = &amdgpu_partition_driver; |
| adev->xcp_mgr->xcp[i].ddev = p_ddev; |
| } |
| |
| return 0; |
| } |
| |
| int amdgpu_xcp_mgr_init(struct amdgpu_device *adev, int init_mode, |
| int init_num_xcps, |
| struct amdgpu_xcp_mgr_funcs *xcp_funcs) |
| { |
| struct amdgpu_xcp_mgr *xcp_mgr; |
| |
| if (!xcp_funcs || !xcp_funcs->switch_partition_mode || |
| !xcp_funcs->get_ip_details) |
| return -EINVAL; |
| |
| xcp_mgr = kzalloc(sizeof(*xcp_mgr), GFP_KERNEL); |
| |
| if (!xcp_mgr) |
| return -ENOMEM; |
| |
| xcp_mgr->adev = adev; |
| xcp_mgr->funcs = xcp_funcs; |
| xcp_mgr->mode = init_mode; |
| mutex_init(&xcp_mgr->xcp_lock); |
| |
| if (init_mode != AMDGPU_XCP_MODE_NONE) |
| amdgpu_xcp_init(xcp_mgr, init_num_xcps, init_mode); |
| |
| adev->xcp_mgr = xcp_mgr; |
| |
| return amdgpu_xcp_dev_alloc(adev); |
| } |
| |
| int amdgpu_xcp_get_partition(struct amdgpu_xcp_mgr *xcp_mgr, |
| enum AMDGPU_XCP_IP_BLOCK ip, int instance) |
| { |
| struct amdgpu_xcp *xcp; |
| int i, id_mask = 0; |
| |
| if (ip >= AMDGPU_XCP_MAX_BLOCKS) |
| return -EINVAL; |
| |
| for (i = 0; i < xcp_mgr->num_xcps; ++i) { |
| xcp = &xcp_mgr->xcp[i]; |
| if ((xcp->valid) && (xcp->ip[ip].valid) && |
| (xcp->ip[ip].inst_mask & BIT(instance))) |
| id_mask |= BIT(i); |
| } |
| |
| if (!id_mask) |
| id_mask = -ENXIO; |
| |
| return id_mask; |
| } |
| |
| int amdgpu_xcp_get_inst_details(struct amdgpu_xcp *xcp, |
| enum AMDGPU_XCP_IP_BLOCK ip, |
| uint32_t *inst_mask) |
| { |
| if (!xcp->valid || !inst_mask || !(xcp->ip[ip].valid)) |
| return -EINVAL; |
| |
| *inst_mask = xcp->ip[ip].inst_mask; |
| |
| return 0; |
| } |
| |
| int amdgpu_xcp_dev_register(struct amdgpu_device *adev, |
| const struct pci_device_id *ent) |
| { |
| int i, ret; |
| |
| if (!adev->xcp_mgr) |
| return 0; |
| |
| for (i = 1; i < MAX_XCP; i++) { |
| if (!adev->xcp_mgr->xcp[i].ddev) |
| break; |
| |
| ret = drm_dev_register(adev->xcp_mgr->xcp[i].ddev, ent->driver_data); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| void amdgpu_xcp_dev_unplug(struct amdgpu_device *adev) |
| { |
| struct drm_device *p_ddev; |
| int i; |
| |
| if (!adev->xcp_mgr) |
| return; |
| |
| for (i = 1; i < MAX_XCP; i++) { |
| if (!adev->xcp_mgr->xcp[i].ddev) |
| break; |
| |
| p_ddev = adev->xcp_mgr->xcp[i].ddev; |
| drm_dev_unplug(p_ddev); |
| p_ddev->render->dev = adev->xcp_mgr->xcp[i].rdev; |
| p_ddev->primary->dev = adev->xcp_mgr->xcp[i].pdev; |
| p_ddev->driver = adev->xcp_mgr->xcp[i].driver; |
| p_ddev->vma_offset_manager = adev->xcp_mgr->xcp[i].vma_offset_manager; |
| } |
| } |
| |
| int amdgpu_xcp_open_device(struct amdgpu_device *adev, |
| struct amdgpu_fpriv *fpriv, |
| struct drm_file *file_priv) |
| { |
| int i; |
| |
| if (!adev->xcp_mgr) |
| return 0; |
| |
| fpriv->xcp_id = AMDGPU_XCP_NO_PARTITION; |
| for (i = 0; i < MAX_XCP; ++i) { |
| if (!adev->xcp_mgr->xcp[i].ddev) |
| break; |
| |
| if (file_priv->minor == adev->xcp_mgr->xcp[i].ddev->render) { |
| if (adev->xcp_mgr->xcp[i].valid == FALSE) { |
| dev_err(adev->dev, "renderD%d partition %d not valid!", |
| file_priv->minor->index, i); |
| return -ENOENT; |
| } |
| dev_dbg(adev->dev, "renderD%d partition %d opened!", |
| file_priv->minor->index, i); |
| fpriv->xcp_id = i; |
| break; |
| } |
| } |
| |
| fpriv->vm.mem_id = fpriv->xcp_id == AMDGPU_XCP_NO_PARTITION ? -1 : |
| adev->xcp_mgr->xcp[fpriv->xcp_id].mem_id; |
| return 0; |
| } |
| |
| void amdgpu_xcp_release_sched(struct amdgpu_device *adev, |
| struct amdgpu_ctx_entity *entity) |
| { |
| struct drm_gpu_scheduler *sched; |
| struct amdgpu_ring *ring; |
| |
| if (!adev->xcp_mgr) |
| return; |
| |
| sched = entity->entity.rq->sched; |
| if (sched->ready) { |
| ring = to_amdgpu_ring(entity->entity.rq->sched); |
| atomic_dec(&adev->xcp_mgr->xcp[ring->xcp_id].ref_cnt); |
| } |
| } |
| |