| // SPDX-License-Identifier: GPL-2.0 OR MIT | |
| /* | |
| * Copyright 2026 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 <linux/xarray.h> | |
| #include <drm/drm_mm.h> | |
| #include <linux/random.h> | |
| #include "amdgpu.h" | |
| #include "amdgpu_sdma.h" | |
| #include "amdgpu_ualink.h" | |
| #include "amdgpu_xgmi.h" | |
| #include "amdgpu_dma_buf.h" | |
| #include "psp_gfx_if.h" | |
| #include <linux/math.h> | |
| #include <linux/sysfs.h> | |
| #include <linux/string.h> | |
| static void deactivate_accelerator(struct amdgpu_device *adev); | |
| static int amdgpu_ualink_remote_interrupt(struct amdgpu_device *adev, | |
| u32 remote_accel_id, u32 dw0, u32 dw1, | |
| u32 dw2, u32 dw3); | |
| static void amdgpu_ualink_flush_tlb(struct amdgpu_device *adev, | |
| u32 flush_type); | |
| static int amdgpu_ualink_reserve_npa_vm_and_bos(struct amdgpu_device *adev, | |
| struct amdgpu_bo *bos[], u32 n_bos, | |
| struct drm_exec *exec, | |
| bool interruptible); | |
| static void amdgpu_ualink_unreserve_npa_vm_and_bos(struct amdgpu_device *adev, | |
| struct drm_exec *exec); | |
| static void amdgpu_ualink_handle_connection_reset(struct amdgpu_device *adev, | |
| u32 remote_accel_id, u32 state, | |
| u32 generation_count); | |
| static void amdgpu_ualink_invalidate_import_mappings(struct amdgpu_bo *bo); | |
| static int amdgpu_ualink_remote_shootdown(struct amdgpu_device *adev, | |
| u32 remote_accel_id, u64 addr, | |
| u32 size_in_pages, u32 flush_type); | |
| static void __amdgpu_ualink_activate_vpod_locked(struct amdgpu_device *adev); | |
| static bool amdgpu_ualink_vpod_membership_changed(struct amdgpu_device *adev); | |
| static void amdgpu_ualink_reset_peer_rings(struct amdgpu_device *adev, | |
| u32 remote_accel_id); | |
| static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev, | |
| u32 remote_accel_id); | |
| #define STRIP_NPA(addr) \ | |
| (((u64)(addr) & ~AMDGPU_UALINK_NPA_ADDR_GPUID_MASK)) | |
| #define GENERATE_NPA(addr, remote_acc_id) \ | |
| ((u64)(((u64)(addr)) | \ | |
| ((u64)(remote_acc_id) << AMDGPU_UALINK_NPA_ADDR_GPUID_SHIFT))) | |
| static const struct drm_client_funcs ualink_client_funcs = { | |
| .unregister = drm_client_release, | |
| }; | |
| static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev) | |
| { | |
| int ret; | |
| ret = drm_client_init(&adev->ddev, &adev->ualink.client, "ualink", | |
| &ualink_client_funcs); | |
| if (ret) { | |
| dev_err(adev->dev, "Failed to init UALink DRM client: %d\n", | |
| ret); | |
| return ret; | |
| } | |
| drm_client_register(&adev->ualink.client); | |
| return 0; | |
| } | |
| static void amdgpu_ualink_object_fini(struct amdgpu_device *adev) | |
| { | |
| if (!adev->ualink.info) | |
| return; | |
| kobject_put(&adev->ualink.stations->kobj); | |
| kobject_put(&adev->ualink.config->kobj); | |
| kobject_put(&adev->ualink.setup->kobj); | |
| kobject_put(&adev->ualink.info->kobj); | |
| adev->ualink.stations = NULL; | |
| adev->ualink.config = NULL; | |
| adev->ualink.setup = NULL; | |
| adev->ualink.info = NULL; | |
| } | |
| static bool __check_ppod_info(struct amdgpu_device *adev, | |
| const struct amdgpu_ualink_info *info) | |
| { | |
| const struct amdgpu_ualink_ppod_info *ppod = &info->ppod; | |
| if (ppod->size <= 0 || ppod->size > AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_dbg(adev->dev, "pPod size %u out of range [1..%u]\n", | |
| ppod->size, AMDGPU_UALINK_ACCEL_MAX); | |
| return false; | |
| } | |
| if (ppod->accel_id >= ppod->size) { | |
| dev_dbg(adev->dev, | |
| "Accelerator ID %u greater or equal pPod size %u\n", | |
| ppod->accel_id, ppod->size); | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool __check_vpod_info(struct amdgpu_device *adev, | |
| const struct amdgpu_ualink_info *info) | |
| { | |
| const struct amdgpu_ualink_ppod_info *ppod = &info->ppod; | |
| const struct amdgpu_ualink_vpod_info *vpod = &info->vpod; | |
| unsigned int weight; | |
| if (vpod->size == 0 || vpod->size > ppod->size) { | |
| dev_warn(adev->dev, | |
| "UALINK: vPod size %u out of range [1..%u]\n", | |
| vpod->size, ppod->size); | |
| return false; | |
| } | |
| switch (vpod->addr_mode) { | |
| case AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS: | |
| break; | |
| case AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT: | |
| dev_warn(adev->dev, | |
| "UALINK: source-identification addr mode is not supported\n"); | |
| return false; | |
| default: | |
| dev_warn(adev->dev, "UALINK: invalid addr mode %u\n", | |
| vpod->addr_mode); | |
| return false; | |
| } | |
| weight = bitmap_weight(vpod->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX); | |
| if (weight != vpod->size) { | |
| dev_warn(adev->dev, | |
| "UALINK: vPod size doesn't match vpod_active_accels list: %u != %u\n", | |
| vpod->size, weight); | |
| return false; | |
| } | |
| if (!test_bit(ppod->accel_id, vpod->active_accel_bits)) { | |
| dev_warn(adev->dev, | |
| "UALINK: accelerator ID %u not listed in vpod_active_accels\n", | |
| ppod->accel_id); | |
| return false; | |
| } | |
| return true; | |
| } | |
| static void | |
| amdgpu_ualink_info_set_accel_state(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_info *info, | |
| enum psp_gfx_ual_config_state cfg_state) | |
| { | |
| enum amdgpu_ualink_accel_state cur; | |
| enum amdgpu_ualink_accel_state target; | |
| bool ppod_validated; | |
| bool vpod_validated; | |
| if (!info) | |
| return; | |
| cur = info->accel_state; | |
| ppod_validated = cur >= AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED && | |
| cur <= AMDGPU_UALINK_ACCEL_STATE_ACTIVE; | |
| vpod_validated = cur >= AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED && | |
| cur <= AMDGPU_UALINK_ACCEL_STATE_ACTIVE; | |
| switch (cfg_state) { | |
| case UAL_CFG_IDLE: | |
| return; | |
| case UAL_CFG_PPOD: | |
| target = AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED; | |
| break; | |
| case UAL_CFG_VPOD: | |
| case UAL_CFG_STATION: | |
| target = AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED; | |
| break; | |
| case UAL_CFG_COMPLETE: | |
| target = AMDGPU_UALINK_ACCEL_STATE_READY; | |
| break; | |
| default: | |
| dev_dbg(adev->dev, "invalid configuration state %u", cfg_state); | |
| return; | |
| } | |
| /* ppod stage: should be part of a ppod first */ | |
| if (!ppod_validated && !__check_ppod_info(adev, info)) { | |
| if (target == AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED) { | |
| info->accel_state = | |
| AMDGPU_UALINK_ACCEL_STATE_UNCONFIGURED; | |
| } else { | |
| info->accel_state = AMDGPU_UALINK_ACCEL_STATE_ERROR; | |
| dev_err(adev->dev, | |
| "ppod configuration is invalid, setting to error state"); | |
| } | |
| return; | |
| } | |
| if (target == AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED) { | |
| if (!ppod_validated) | |
| info->accel_state = | |
| AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED; | |
| return; | |
| } | |
| /* A vpod_id of 0 means the GPU is not part of any vPod */ | |
| if (info->vpod.id == AMDGPU_UALINK_VPOD_ID_INVALID) { | |
| info->accel_state = AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED; | |
| return; | |
| } | |
| /* vpod stage: required to reach vpod_configured or ready */ | |
| if (!vpod_validated && !__check_vpod_info(adev, info)) { | |
| info->accel_state = AMDGPU_UALINK_ACCEL_STATE_ERROR; | |
| dev_err(adev->dev, | |
| "vpod configuration is invalid, setting to error state"); | |
| return; | |
| } | |
| if (target == AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED) { | |
| if (!vpod_validated) | |
| info->accel_state = | |
| AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED; | |
| return; | |
| } | |
| /* complete stage: advance to ready unless already ready/active */ | |
| if (cur < AMDGPU_UALINK_ACCEL_STATE_READY || | |
| cur > AMDGPU_UALINK_ACCEL_STATE_ACTIVE) | |
| info->accel_state = AMDGPU_UALINK_ACCEL_STATE_READY; | |
| } | |
| static int amdgpu_ualink_update_vpod_config(struct amdgpu_device *adev) | |
| { | |
| /* TBD: Do updates/cleanup based on updated vpod configuration */ | |
| return 0; | |
| } | |
| static int amdgpu_ualink_update_accel_state( | |
| struct amdgpu_device *adev, enum amdgpu_ualink_accel_state prev_state, | |
| u32 prev_vpod_id, enum psp_gfx_ual_config_state cfg_state) | |
| { | |
| dev_info(adev->dev, | |
| "UALINK: update_accel_state: prev_state=%d prev_vpod_id=%u new_vpod_id=%u cfg_state=%d\n", | |
| prev_state, prev_vpod_id, adev->ualink.info->vpod.id, | |
| cfg_state); | |
| /* If the device is already active and its vpod_id is unchanged, the | |
| * update does not affect vpod membership. Skip the local vpod | |
| * integrity check and re-activation. | |
| */ | |
| if (prev_state == AMDGPU_UALINK_ACCEL_STATE_ACTIVE && | |
| adev->ualink.info->vpod.id == prev_vpod_id) { | |
| amdgpu_ualink_update_vpod_config(adev); | |
| /* Same vpod_id, but the member set may have changed (vPod grown | |
| * or shrunk while this GPU stayed ACTIVE). If so, run the local | |
| * vpod activation path: once every local peer has committed the | |
| * new config (integrity passes), it bounces the affected ACTIVE | |
| * peers to rebuild links/GART for the new member set. | |
| */ | |
| if (amdgpu_ualink_vpod_membership_changed(adev)) { | |
| dev_info(adev->dev, | |
| "UALINK: update_accel_state: ACTIVE vpod_id=%u membership changed, reconfiguring\n", | |
| adev->ualink.info->vpod.id); | |
| scoped_guard(mutex, &mgpu_info.mutex) | |
| __amdgpu_ualink_activate_vpod_locked(adev); | |
| } else { | |
| dev_info(adev->dev, | |
| "UALINK: update_accel_state: already ACTIVE, vpod_id/membership unchanged\n"); | |
| } | |
| return 0; | |
| } | |
| /* A new vpod_id of 0 means this GPU was removed from the vPod. */ | |
| if (adev->ualink.info->vpod.id == AMDGPU_UALINK_VPOD_ID_INVALID) { | |
| dev_info(adev->dev, | |
| "UALINK: update_accel_state: vpod_id=0, removing accelerator from vPod\n"); | |
| amdgpu_ualink_update_vpod_config(adev); | |
| scoped_guard(mutex, &mgpu_info.mutex) | |
| deactivate_accelerator(adev); | |
| return 0; | |
| } | |
| /* GPU joining a new vpod should be with invalid vpod id*/ | |
| scoped_guard(mutex, &mgpu_info.mutex) { | |
| if (prev_vpod_id == AMDGPU_UALINK_VPOD_ID_INVALID) { | |
| dev_info(adev->dev, | |
| "UALINK: update_accel_state: joining vpod_id=%u\n", | |
| adev->ualink.info->vpod.id); | |
| amdgpu_ualink_info_set_accel_state( | |
| adev, adev->ualink.info, cfg_state); | |
| __amdgpu_ualink_activate_vpod_locked(adev); | |
| } else { | |
| /* GPU should first get removal which will set invalid vpod_id | |
| * and then join a new vpod | |
| */ | |
| dev_err(adev->dev, | |
| "Invalid vpod transition from %u to %u\n", | |
| prev_vpod_id, adev->ualink.info->vpod.id); | |
| return -EINVAL; | |
| } | |
| } | |
| return 0; | |
| } | |
| int amdgpu_ualink_config_update_handler(struct amdgpu_device *adev) | |
| { | |
| enum amdgpu_ualink_accel_state prev_state; | |
| enum psp_gfx_ual_config_state cfg_state; | |
| u32 prev_vpod_id; | |
| int r, qerr; | |
| /* TBD: Stop ASP interrupts if driver faced an issue */ | |
| if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE) { | |
| u32 status; | |
| dev_dbg(adev->dev, | |
| "UALink not initialized, skipping config update\n"); | |
| status = !!(adev->ualink.mgr_state == AMDGPU_UALINK_INIT_ERROR); | |
| return psp_ual_send_completion( | |
| &adev->psp, adev->ualink.psp_if_ver, | |
| PSP_GFX_INT_CTXT_UAL_CMD_CFG_UPDATE_ID, status); | |
| } | |
| prev_state = adev->ualink.info->accel_state; | |
| prev_vpod_id = adev->ualink.info->vpod.id; | |
| qerr = psp_ual_query_info(&adev->psp, adev->ualink.psp_if_ver, | |
| adev->ualink.info, &cfg_state); | |
| /*TBD: find the right value of status to be sent to ASP*/ | |
| r = psp_ual_send_completion(&adev->psp, adev->ualink.psp_if_ver, | |
| PSP_GFX_INT_CTXT_UAL_CMD_CFG_UPDATE_ID, 0); | |
| if (r || qerr) | |
| goto err; | |
| r = amdgpu_ualink_update_accel_state(adev, prev_state, prev_vpod_id, | |
| cfg_state); | |
| if (r) | |
| goto err; | |
| return 0; | |
| err: | |
| scoped_guard(mutex, &mgpu_info.mutex) { | |
| deactivate_accelerator(adev); | |
| adev->ualink.info->accel_state = | |
| AMDGPU_UALINK_ACCEL_STATE_ERROR; | |
| } | |
| dev_err(adev->dev, | |
| "UALink config update failed, setting to error state"); | |
| return r; | |
| } | |
| int amdgpu_ualink_pause_handler(struct amdgpu_device *adev) | |
| { | |
| u32 status = 0; | |
| if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE) { | |
| dev_dbg(adev->dev, | |
| "UALink not initialized, skipping pause update\n"); | |
| status = !!(adev->ualink.mgr_state == AMDGPU_UALINK_INIT_ERROR); | |
| goto out; | |
| } | |
| dev_dbg(adev->dev, "UALink pause command is not handled\n"); | |
| out: | |
| return psp_ual_send_completion(&adev->psp, adev->ualink.psp_if_ver, | |
| PSP_GFX_INT_CTXT_UAL_CMD_PAUSE_ID, | |
| status); | |
| } | |
| int amdgpu_ualink_resume_handler(struct amdgpu_device *adev) | |
| { | |
| u32 status = 0; | |
| if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE) { | |
| dev_dbg(adev->dev, | |
| "UALink not initialized, skipping pause update\n"); | |
| status = !!(adev->ualink.mgr_state == AMDGPU_UALINK_INIT_ERROR); | |
| goto out; | |
| } | |
| dev_dbg(adev->dev, "UALink resume command is not handled\n"); | |
| out: | |
| return psp_ual_send_completion(&adev->psp, adev->ualink.psp_if_ver, | |
| PSP_GFX_INT_CTXT_UAL_CMD_RESUME_ID, | |
| status); | |
| } | |
| int ualink_ip_hw_init(struct amdgpu_ip_block *ip_block) | |
| { | |
| struct amdgpu_device *adev = ip_block->adev; | |
| int r; | |
| if (!adev->ualink.info) | |
| return 0; | |
| adev->ualink.info->accel_state = AMDGPU_UALINK_ACCEL_STATE_UNCONFIGURED; | |
| r = psp_ual_get_interface_version(&adev->psp, &adev->ualink.psp_if_ver); | |
| if (r) { | |
| adev->ualink.psp_if_ver = 0xffffffff; | |
| goto disable; | |
| } | |
| dev_info(adev->dev, "Found UALink interface version 0x%x\n", | |
| adev->ualink.psp_if_ver); | |
| adev->ualink.mgr_state = AMDGPU_UALINK_INIT_HW; | |
| return 0; | |
| disable: | |
| adev->ualink.mgr_state = AMDGPU_UALINK_INIT_ERROR; | |
| return 0; | |
| } | |
| int ualink_ip_hw_fini(struct amdgpu_ip_block *ip_block) | |
| { | |
| struct amdgpu_device *adev = ip_block->adev; | |
| if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE) | |
| return 0; | |
| scoped_guard(mutex, &mgpu_info.mutex) | |
| deactivate_accelerator(adev); | |
| return 0; | |
| } | |
| int ualink_ip_late_init(struct amdgpu_ip_block *ip_block) | |
| { | |
| struct amdgpu_device *adev = ip_block->adev; | |
| enum psp_gfx_ual_config_state cfg_state; | |
| int r; | |
| if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_HW) | |
| return 0; | |
| r = psp_ual_query_info(&adev->psp, adev->ualink.psp_if_ver, | |
| adev->ualink.info, &cfg_state); | |
| if (r) | |
| return r; | |
| scoped_guard(mutex, &mgpu_info.mutex) { | |
| amdgpu_ualink_info_set_accel_state(adev, adev->ualink.info, | |
| cfg_state); | |
| __amdgpu_ualink_activate_vpod_locked(adev); | |
| } | |
| r = amdgpu_ualink_drm_client_create(adev); | |
| if (r) { | |
| dev_err(adev->dev, "Failed to create UALink DRM client: %d\n", | |
| r); | |
| goto error; | |
| } | |
| /* Consider UALink initialized only at this stage */ | |
| adev->ualink.mgr_state = AMDGPU_UALINK_INIT_COMPLETE; | |
| return 0; | |
| error: | |
| adev->ualink.mgr_state = AMDGPU_UALINK_INIT_ERROR; | |
| return r; | |
| } | |
| /**************************************************************************** | |
| * UALink info and configuration in sysfs | |
| * | |
| * Using kobj_attribute and not device_attribute here because the UALink | |
| * attributes are part of their own nested kobjects and not the amdgpu | |
| * device itself or a subdevice. | |
| ****************************************************************************/ | |
| /* UALink configuration APIs are being deprecated before upstreaming and are | |
| * not enabled by default. They are replaced by a unified in-band/side-band | |
| * approach through the IFoE driver. Querying of the current configuration | |
| * will continue to be supported in sysfs. | |
| * | |
| * Uncomment the following line to enable the deprecated configuration APIs | |
| * for testing purposes only. The code to support these APIs will be removed | |
| * in a future patch. | |
| */ | |
| /* #define UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS */ | |
| #define UALINK_VALUE_SHOW(prefix, name, field, format) \ | |
| static ssize_t ualink_##prefix##_##name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| char *buf) \ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| \ | |
| return sysfs_emit(buf, format"\n", info->field); \ | |
| } | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| #define UALINK_VALUE_STORE(prefix, name, field, type, base) \ | |
| static ssize_t ualink_##prefix##_##name##_store(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| const char *buf, size_t count)\ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| int r; \ | |
| \ | |
| r = kstrto##type(buf, base, &info->field); \ | |
| if (r < 0) \ | |
| return r; \ | |
| return count; \ | |
| } | |
| #endif | |
| static ssize_t show_idbits(unsigned long *bits, unsigned int nbits, char *buf) | |
| { | |
| ssize_t len = 0; | |
| unsigned int id; | |
| for_each_set_bit(id, bits, nbits) | |
| len += sysfs_emit_at(buf, len, "%u ", id); | |
| if (len) | |
| len--; | |
| return len + sysfs_emit_at(buf, len, "\n"); | |
| } | |
| #define UALINK_IDBITS_SHOW(prefix, name, field) \ | |
| static ssize_t ualink_##prefix##_##name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| char *buf) \ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| \ | |
| return show_idbits(info->field, sizeof(info->field)*8, buf); \ | |
| } | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| static ssize_t store_idbits(unsigned long *bits, unsigned int nbits, | |
| const char *buf, size_t count) | |
| { | |
| char *dup_buf, *str, *tok; | |
| ssize_t r; | |
| u32 id; | |
| bitmap_zero(bits, nbits); | |
| if (!count) | |
| return 0; | |
| dup_buf = kstrndup(buf, count, GFP_KERNEL); | |
| if (unlikely(!dup_buf)) | |
| return -ENOMEM; | |
| str = dup_buf; | |
| do { | |
| str += strspn(str, " "); | |
| tok = strsep(&str, ", "); | |
| r = kstrtou32(tok, 10, &id); | |
| if (r < 0) | |
| goto err; | |
| if (id >= nbits) { | |
| r = -ERANGE; | |
| goto err; | |
| } | |
| set_bit(id, bits); | |
| } while (str); | |
| kfree(dup_buf); | |
| return count; | |
| err: | |
| kfree(dup_buf); | |
| return r; | |
| } | |
| #define UALINK_IDBITS_STORE(prefix, name, field, nbits) \ | |
| static ssize_t ualink_##prefix##_##name##_store(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| const char *buf, size_t count)\ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| DECLARE_BITMAP(bits, nbits); \ | |
| ssize_t r = store_idbits(bits, nbits, buf, count); \ | |
| \ | |
| if (r >= 0) \ | |
| bitmap_copy(info->field, bits, nbits); \ | |
| return r; \ | |
| } | |
| #endif | |
| static ssize_t show_idarray(const u32 *array, u32 size, char *buf) | |
| { | |
| ssize_t len = 0; | |
| unsigned int i; | |
| for (i = 0; i < size; i++) | |
| len += sysfs_emit_at(buf, len, "%u ", array[i]); | |
| if (len) | |
| len--; | |
| return len + sysfs_emit_at(buf, len, "\n"); | |
| } | |
| #define UALINK_IDARRAY_SHOW(prefix, name, field, size) \ | |
| static ssize_t ualink_##prefix##_##name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| char *buf) \ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| \ | |
| return show_idarray(info->field, info->size, buf); \ | |
| } | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| static ssize_t store_idarray(u32 *array, u32 *size, u32 max_id, u32 max_size, | |
| const char *buf, size_t count) | |
| { | |
| char *dup_buf, *str, *tok; | |
| unsigned int index = 0; | |
| ssize_t r; | |
| u32 id; | |
| *size = 0; | |
| if (!count) | |
| return 0; | |
| dup_buf = kstrndup(buf, count, GFP_KERNEL); | |
| if (unlikely(!dup_buf)) | |
| return -ENOMEM; | |
| str = dup_buf; | |
| do { | |
| str += strspn(str, " "); | |
| tok = strsep(&str, ", "); | |
| r = kstrtou32(tok, 10, &id); | |
| if (r < 0) | |
| goto err; | |
| if (index >= max_size) { | |
| r = -ENOSPC; | |
| goto err; | |
| } | |
| if (id >= max_id) { | |
| r = -ERANGE; | |
| goto err; | |
| } | |
| array[index++] = id; | |
| } while (str); | |
| kfree(dup_buf); | |
| *size = index; | |
| return count; | |
| err: | |
| kfree(dup_buf); | |
| return r; | |
| } | |
| #define UALINK_IDARRAY_STORE(prefix, name, field, size, max_id) \ | |
| static ssize_t ualink_##prefix##_##name##_store(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| const char *buf, size_t count)\ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| u32 new_array[ARRAY_SIZE(info->field)]; \ | |
| u32 new_size; \ | |
| ssize_t r = store_idarray(new_array, &new_size, max_id, \ | |
| ARRAY_SIZE(new_array), buf, count); \ | |
| \ | |
| if (r >= 0) { \ | |
| memcpy(info->field, new_array, sizeof(new_array)); \ | |
| info->size = new_size; \ | |
| } \ | |
| return r; \ | |
| } | |
| #endif | |
| #define UALINK_UUID_SHOW(prefix, name, field) \ | |
| static ssize_t ualink_##prefix##_##name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| char *buf) \ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| \ | |
| return sysfs_emit(buf, "%pU\n", &info->field); \ | |
| } | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| #define UALINK_UUID_STORE(prefix, name, field) \ | |
| static ssize_t ualink_##prefix##_##name##_store(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| const char *buf, size_t count)\ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| int r; \ | |
| \ | |
| r = uuid_parse(buf, &info->field); \ | |
| return r ? r : count; \ | |
| } | |
| #endif | |
| static ssize_t show_enum(u32 x, const char * const values[], unsigned int n, | |
| char *buf) | |
| { | |
| return sysfs_emit(buf, "%s\n", x < n && values[x] ? values[x] : "invalid"); | |
| } | |
| #define UALINK_ENUM_SHOW(prefix, name, field) \ | |
| static ssize_t ualink_##prefix##_##name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| char *buf) \ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| \ | |
| return show_enum(info->field, ualink_##name##_values, \ | |
| ARRAY_SIZE(ualink_##name##_values), buf); \ | |
| } | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| static ssize_t store_enum(u32 *x, const char * const values[], unsigned int n, | |
| const char *buf, size_t count) | |
| { | |
| unsigned int i; | |
| for (i = 0; i < n; i++) { | |
| if (values[i] && sysfs_streq(buf, values[i])) { | |
| *x = i; | |
| return count; | |
| } | |
| } | |
| return -EINVAL; | |
| } | |
| #define UALINK_ENUM_STORE(prefix, name, field) \ | |
| static ssize_t ualink_##prefix##_##name##_store(struct kobject *kobj, \ | |
| struct kobj_attribute *attr,\ | |
| const char *buf, size_t count)\ | |
| { \ | |
| struct amdgpu_ualink_##prefix *info = to_ualink_##prefix(kobj); \ | |
| \ | |
| return store_enum(&info->field, ualink_##name##_values, \ | |
| ARRAY_SIZE(ualink_##name##_values), \ | |
| buf, count); \ | |
| } | |
| #endif | |
| static const char * const ualink_link_type_values[] = { | |
| "UALoE", "UALink" | |
| }; | |
| static const char * const ualink_addr_mode_values[] = { | |
| "source-aliasing", "source-identification" | |
| }; | |
| static const char * const ualink_accel_state_values[] = { | |
| [AMDGPU_UALINK_ACCEL_STATE_UNCONFIGURED] = "unconfigured", | |
| [AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED] = "ppod_configured", | |
| [AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED] = "vpod_configured", | |
| [AMDGPU_UALINK_ACCEL_STATE_READY] = "ready", | |
| [AMDGPU_UALINK_ACCEL_STATE_ACTIVE] = "active", | |
| [AMDGPU_UALINK_ACCEL_STATE_ERROR] = "error", | |
| }; | |
| static_assert(ARRAY_SIZE(ualink_accel_state_values) == | |
| AMDGPU_UALINK_ACCEL_STATE_MAX); | |
| UALINK_ENUM_SHOW(info, link_type, link_type); | |
| UALINK_VALUE_SHOW(info, accel_id, ppod.accel_id, "%u"); | |
| UALINK_VALUE_SHOW(info, bandwidth, ppod.bandwidth, "%u"); | |
| UALINK_VALUE_SHOW(info, latency, ppod.latency, "%u"); | |
| UALINK_UUID_SHOW(info, ppod_id, ppod.id); | |
| UALINK_VALUE_SHOW(info, ppod_size, ppod.size, "%u"); | |
| UALINK_VALUE_SHOW(info, vpod_id, vpod.id, "%u"); | |
| UALINK_VALUE_SHOW(info, vpod_size, vpod.size, "%u"); | |
| UALINK_IDBITS_SHOW(info, vpod_active_accels, vpod.active_accel_bits); | |
| UALINK_ENUM_SHOW(info, addr_mode, vpod.addr_mode); | |
| UALINK_ENUM_SHOW(info, accel_state, accel_state); | |
| UALINK_IDARRAY_SHOW(info, local_accels, local_accels, n_local_accels); | |
| static struct amdgpu_device *ualink_xcp_kobj_to_adev(struct kobject *kobj) | |
| { | |
| struct amdgpu_xcp *xcp = container_of(kobj, struct amdgpu_xcp, | |
| ualink.kobj); | |
| return xcp->xcp_mgr->adev; | |
| } | |
| #define UALINK_XCP_INFO_SHOW(name) \ | |
| static ssize_t ualink_xcp_info_##name##_show(struct kobject *kobj, \ | |
| struct kobj_attribute *attr, char *buf) \ | |
| { \ | |
| struct amdgpu_device *adev = ualink_xcp_kobj_to_adev(kobj); \ | |
| \ | |
| return ualink_info_##name##_show(&adev->ualink.info->kobj, \ | |
| attr, buf); \ | |
| } | |
| UALINK_XCP_INFO_SHOW(link_type) | |
| UALINK_XCP_INFO_SHOW(accel_id) | |
| UALINK_XCP_INFO_SHOW(bandwidth) | |
| UALINK_XCP_INFO_SHOW(latency) | |
| UALINK_XCP_INFO_SHOW(ppod_id) | |
| UALINK_XCP_INFO_SHOW(ppod_size) | |
| UALINK_XCP_INFO_SHOW(vpod_id) | |
| UALINK_XCP_INFO_SHOW(vpod_size) | |
| UALINK_XCP_INFO_SHOW(vpod_active_accels) | |
| UALINK_XCP_INFO_SHOW(addr_mode) | |
| UALINK_XCP_INFO_SHOW(accel_state) | |
| UALINK_XCP_INFO_SHOW(local_accels) | |
| #define UALINK_INFO_ATTR(name) __ATTR(name, 0444, ualink_info_##name##_show, NULL) | |
| static struct kobj_attribute ualink_info_link_type = UALINK_INFO_ATTR(link_type); | |
| static struct kobj_attribute ualink_info_accel_id = UALINK_INFO_ATTR(accel_id); | |
| static struct kobj_attribute ualink_info_bandwidth = UALINK_INFO_ATTR(bandwidth); | |
| static struct kobj_attribute ualink_info_latency = UALINK_INFO_ATTR(latency); | |
| static struct kobj_attribute ualink_info_ppod_id = UALINK_INFO_ATTR(ppod_id); | |
| static struct kobj_attribute ualink_info_ppod_size = UALINK_INFO_ATTR(ppod_size); | |
| static struct kobj_attribute ualink_info_vpod_id = UALINK_INFO_ATTR(vpod_id); | |
| static struct kobj_attribute ualink_info_vpod_size = UALINK_INFO_ATTR(vpod_size); | |
| static struct kobj_attribute ualink_info_vpod_active_accels = UALINK_INFO_ATTR(vpod_active_accels); | |
| static struct kobj_attribute ualink_info_addr_mode = UALINK_INFO_ATTR(addr_mode); | |
| static struct kobj_attribute ualink_info_accel_state = UALINK_INFO_ATTR(accel_state); | |
| static struct kobj_attribute ualink_info_local_accels = UALINK_INFO_ATTR(local_accels); | |
| static const struct attribute *ualink_info_attrs[] = { | |
| &ualink_info_link_type.attr, | |
| &ualink_info_accel_id.attr, | |
| &ualink_info_bandwidth.attr, | |
| &ualink_info_latency.attr, | |
| &ualink_info_ppod_id.attr, | |
| &ualink_info_ppod_size.attr, | |
| &ualink_info_vpod_id.attr, | |
| &ualink_info_vpod_size.attr, | |
| &ualink_info_vpod_active_accels.attr, | |
| &ualink_info_addr_mode.attr, | |
| &ualink_info_accel_state.attr, | |
| &ualink_info_local_accels.attr, | |
| NULL | |
| }; | |
| #define UALINK_XCP_INFO_ATTR(name) \ | |
| __ATTR(name, 0444, ualink_xcp_info_##name##_show, NULL) | |
| static struct kobj_attribute ualink_xcp_info_link_type = UALINK_XCP_INFO_ATTR(link_type); | |
| static struct kobj_attribute ualink_xcp_info_accel_id = UALINK_XCP_INFO_ATTR(accel_id); | |
| static struct kobj_attribute ualink_xcp_info_bandwidth = UALINK_XCP_INFO_ATTR(bandwidth); | |
| static struct kobj_attribute ualink_xcp_info_latency = UALINK_XCP_INFO_ATTR(latency); | |
| static struct kobj_attribute ualink_xcp_info_ppod_id = UALINK_XCP_INFO_ATTR(ppod_id); | |
| static struct kobj_attribute ualink_xcp_info_ppod_size = UALINK_XCP_INFO_ATTR(ppod_size); | |
| static struct kobj_attribute ualink_xcp_info_vpod_id = UALINK_XCP_INFO_ATTR(vpod_id); | |
| static struct kobj_attribute ualink_xcp_info_vpod_size = UALINK_XCP_INFO_ATTR(vpod_size); | |
| static struct kobj_attribute ualink_xcp_info_vpod_active_accels = UALINK_XCP_INFO_ATTR(vpod_active_accels); | |
| static struct kobj_attribute ualink_xcp_info_addr_mode = UALINK_XCP_INFO_ATTR(addr_mode); | |
| static struct kobj_attribute ualink_xcp_info_accel_state = UALINK_XCP_INFO_ATTR(accel_state); | |
| static struct kobj_attribute ualink_xcp_info_local_accels = UALINK_XCP_INFO_ATTR(local_accels); | |
| static struct attribute *ualink_xcp_info_attrs[] = { | |
| &ualink_xcp_info_link_type.attr, | |
| &ualink_xcp_info_accel_id.attr, | |
| &ualink_xcp_info_bandwidth.attr, | |
| &ualink_xcp_info_latency.attr, | |
| &ualink_xcp_info_ppod_id.attr, | |
| &ualink_xcp_info_ppod_size.attr, | |
| &ualink_xcp_info_vpod_id.attr, | |
| &ualink_xcp_info_vpod_size.attr, | |
| &ualink_xcp_info_vpod_active_accels.attr, | |
| &ualink_xcp_info_addr_mode.attr, | |
| &ualink_xcp_info_accel_state.attr, | |
| &ualink_xcp_info_local_accels.attr, | |
| NULL | |
| }; | |
| static umode_t ualink_xcp_info_is_visible(struct kobject *kobj, | |
| struct attribute *attr, int n) | |
| { | |
| struct amdgpu_xcp *xcp = container_of(kobj, struct amdgpu_xcp, | |
| ualink.kobj); | |
| if (!xcp->valid) | |
| return 0; | |
| return attr->mode; | |
| } | |
| static const struct attribute_group ualink_xcp_info_group = { | |
| .attrs = ualink_xcp_info_attrs, | |
| .is_visible = ualink_xcp_info_is_visible, | |
| }; | |
| static const struct kobj_type ualink_xcp_info_ktype = { | |
| .sysfs_ops = &kobj_sysfs_ops | |
| }; | |
| static void ualink_info_release(struct kobject *kobj) | |
| { | |
| kfree(to_ualink_info(kobj)); | |
| } | |
| static const struct kobj_type ualink_info_ktype = { | |
| .release = ualink_info_release, | |
| .sysfs_ops = &kobj_sysfs_ops | |
| }; | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| UALINK_VALUE_SHOW(ppod_setup, accel_id, ppod.accel_id, "%u"); | |
| UALINK_VALUE_SHOW(ppod_setup, bandwidth, ppod.bandwidth, "%u"); | |
| UALINK_VALUE_SHOW(ppod_setup, latency, ppod.latency, "%u"); | |
| UALINK_UUID_SHOW(ppod_setup, ppod_id, ppod.id); | |
| UALINK_VALUE_SHOW(ppod_setup, ppod_size, ppod.size, "%u"); | |
| UALINK_IDARRAY_SHOW(ppod_setup, local_accels, local_accels, n_local_accels); | |
| UALINK_VALUE_STORE(ppod_setup, accel_id, ppod.accel_id, u32, 10); | |
| UALINK_VALUE_STORE(ppod_setup, bandwidth, ppod.bandwidth, u32, 10); | |
| UALINK_VALUE_STORE(ppod_setup, latency, ppod.latency, u32, 10); | |
| UALINK_UUID_STORE(ppod_setup, ppod_id, ppod.id); | |
| UALINK_VALUE_STORE(ppod_setup, ppod_size, ppod.size, u32, 10); | |
| UALINK_IDARRAY_STORE(ppod_setup, local_accels, local_accels, n_local_accels, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| static enum amdgpu_ualink_accel_state | |
| check_ppod_state(struct amdgpu_device *adev, | |
| const struct amdgpu_ualink_ppod_setup *setup) | |
| { | |
| if (setup->ppod.size <= 0 || setup->ppod.size > AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_dbg(adev->dev, "pPod size %u out of range [1..%u]\n", | |
| setup->ppod.size, AMDGPU_UALINK_ACCEL_MAX); | |
| return AMDGPU_UALINK_ACCEL_STATE_UNCONFIGURED; | |
| } | |
| if (setup->ppod.accel_id >= setup->ppod.size) { | |
| dev_dbg(adev->dev, "Accelerator ID %u greater or equal pPod size %u\n", | |
| setup->ppod.accel_id, setup->ppod.size); | |
| return AMDGPU_UALINK_ACCEL_STATE_UNCONFIGURED; | |
| } | |
| return AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED; | |
| } | |
| static ssize_t ualink_ppod_setup_commit_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct amdgpu_ualink_ppod_setup *setup = to_ualink_ppod_setup(kobj); | |
| struct amdgpu_ualink_info *info = to_ualink_info(kobj->parent); | |
| struct device *dev = kobj_to_dev(info->kobj.parent); | |
| struct drm_device *ddev = dev_get_drvdata(dev); | |
| struct amdgpu_device *adev = drm_to_adev(ddev); | |
| int r; | |
| if (!sysfs_streq(buf, "true")) | |
| return -EINVAL; | |
| r = psp_ual_set_ppod_config(&adev->psp, adev->ualink.psp_if_ver, | |
| setup); | |
| if (r) | |
| return r; | |
| r = psp_ual_query_info(&adev->psp, adev->ualink.psp_if_ver, info, NULL); | |
| if (r) | |
| return r; | |
| /* | |
| * Hold mgpu_info.mutex to serialize with activate_local_vpod() in | |
| * vpod_config_commit_store which also holds this lock. Without it, | |
| * deactivate_accelerator() can race with activate_accelerator(), | |
| * causing concurrent vm_fini / vm_init on the same NPA VM. | |
| */ | |
| mutex_lock(&mgpu_info.mutex); | |
| if (info->accel_state >= AMDGPU_UALINK_ACCEL_STATE_READY) | |
| deactivate_accelerator(adev); | |
| info->accel_state = check_ppod_state(adev, setup); | |
| /* PPOD is expected to be configured first */ | |
| info->vpod.id = AMDGPU_UALINK_VPOD_ID_INVALID; | |
| mutex_unlock(&mgpu_info.mutex); | |
| /* TODO: If accel_state was ACTIVE, reset all connections */ | |
| return count; | |
| } | |
| #define UALINK_PPOD_SETUP_ATTR(name) __ATTR(name, 0600, \ | |
| ualink_ppod_setup_##name##_show, \ | |
| ualink_ppod_setup_##name##_store) | |
| static struct kobj_attribute ualink_ppod_setup_accel_id = UALINK_PPOD_SETUP_ATTR(accel_id); | |
| static struct kobj_attribute ualink_ppod_setup_bandwidth = UALINK_PPOD_SETUP_ATTR(bandwidth); | |
| static struct kobj_attribute ualink_ppod_setup_latency = UALINK_PPOD_SETUP_ATTR(latency); | |
| static struct kobj_attribute ualink_ppod_setup_ppod_id = UALINK_PPOD_SETUP_ATTR(ppod_id); | |
| static struct kobj_attribute ualink_ppod_setup_ppod_size = UALINK_PPOD_SETUP_ATTR(ppod_size); | |
| static struct kobj_attribute ualink_ppod_setup_local_accels = UALINK_PPOD_SETUP_ATTR(local_accels); | |
| static struct kobj_attribute ualink_ppod_setup_commit = __ATTR(commit, 0200, NULL, | |
| ualink_ppod_setup_commit_store); | |
| static const struct attribute *ualink_ppod_setup_attrs[] = { | |
| &ualink_ppod_setup_accel_id.attr, | |
| &ualink_ppod_setup_bandwidth.attr, | |
| &ualink_ppod_setup_latency.attr, | |
| &ualink_ppod_setup_ppod_id.attr, | |
| &ualink_ppod_setup_ppod_size.attr, | |
| &ualink_ppod_setup_local_accels.attr, | |
| &ualink_ppod_setup_commit.attr, | |
| NULL | |
| }; | |
| #endif | |
| static void ualink_ppod_setup_release(struct kobject *kobj) | |
| { | |
| kfree(to_ualink_ppod_setup(kobj)); | |
| } | |
| static const struct kobj_type ualink_ppod_setup_ktype = { | |
| .release = ualink_ppod_setup_release, | |
| .sysfs_ops = &kobj_sysfs_ops | |
| }; | |
| static struct amdgpu_device *find_peer_adev(unsigned int accel_id) | |
| { | |
| unsigned int i; | |
| for (i = 0; i < mgpu_info.num_gpu; i++) { | |
| struct amdgpu_device *peer_adev = mgpu_info.gpu_ins[i].adev; | |
| if (peer_adev->ualink.info && | |
| peer_adev->ualink.info->ppod.accel_id == accel_id) | |
| return peer_adev; | |
| } | |
| return NULL; | |
| } | |
| static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev, | |
| u32 accel_id) | |
| { | |
| struct amdgpu_ualink_info *info = adev->ualink.info; | |
| unsigned int i; | |
| if (!info) | |
| return false; | |
| for (i = 0; i < info->n_local_accels; i++) { | |
| if (info->local_accels[i] == accel_id) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static void activate_accelerator(struct amdgpu_device *adev) | |
| { | |
| int r; | |
| if (adev->ualink.info->accel_state >= AMDGPU_UALINK_ACCEL_STATE_ACTIVE) | |
| return; | |
| dev_info(adev->dev, | |
| "UALINK: activating accelerator accel_id=%u (accel_state=%d)\n", | |
| adev->ualink.info->ppod.accel_id, | |
| adev->ualink.info->accel_state); | |
| /* Enable incoming NPA address translation with NPA VMID */ | |
| r = psp_ual_set_npa_config(&adev->psp, adev->ualink.psp_if_ver, | |
| adev->vm_manager.npa_vmid, true); | |
| if (r) { | |
| dev_err(adev->dev, "Failed to set NPA config\n"); | |
| return; | |
| } | |
| r = amdgpu_ualink_manager_start(adev); | |
| if (r) { | |
| dev_err(adev->dev, "Failed to start UALink manager\n"); | |
| return; | |
| } | |
| r = amdgpu_ualink_sw_init(adev); | |
| if (r) { | |
| dev_err(adev->dev, "Failed to init UALink sw r=%d\n", r); | |
| amdgpu_ualink_manager_stop(adev); | |
| return; | |
| } | |
| adev->ualink.info->accel_state = AMDGPU_UALINK_ACCEL_STATE_ACTIVE; | |
| dev_info(adev->dev, | |
| "UALINK: accelerator accel_id=%u is now ACTIVE\n", | |
| adev->ualink.info->ppod.accel_id); | |
| } | |
| static void deactivate_accelerator(struct amdgpu_device *adev) | |
| { | |
| if (adev->ualink.info->accel_state < AMDGPU_UALINK_ACCEL_STATE_ACTIVE) | |
| return; | |
| dev_info(adev->dev, | |
| "UALINK: deactivating accelerator accel_id=%u (accel_state=%d -> PPOD_CONFIGURED)\n", | |
| adev->ualink.info->ppod.accel_id, | |
| adev->ualink.info->accel_state); | |
| /* Disable incoming NPA address translation with NPA VMID */ | |
| psp_ual_set_npa_config(&adev->psp, adev->ualink.psp_if_ver, | |
| adev->vm_manager.npa_vmid, false); | |
| /* ignore return value */ | |
| adev->ualink.info->accel_state = | |
| AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED; | |
| amdgpu_ualink_sw_fini(adev); | |
| amdgpu_ualink_manager_stop(adev); | |
| } | |
| static void activate_local_vpod(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_info *info = adev->ualink.info; | |
| struct amdgpu_device *peer_adev; | |
| unsigned int accel_id; | |
| unsigned int i; | |
| for (i = 0; i < info->n_local_accels; i++) { | |
| accel_id = info->local_accels[i]; | |
| peer_adev = find_peer_adev(accel_id); | |
| if (WARN_ON(!peer_adev || !peer_adev->ualink.info)) | |
| /* info->local_accels is corrupted? */ | |
| continue; | |
| /* Bring the peer up to match the current vPod membership. | |
| * | |
| * A peer that is not yet ACTIVE (or whose ACTIVE membership | |
| * changed on a grow/shrink) has its remote metadata, links and | |
| * GART mappings built for a stale member set (or none at all). | |
| * Firmware only accepts a full metadata reload while halted, so | |
| * surgical per-peer deltas are not possible: fully bounce the | |
| * accelerator. deactivate_accelerator() is a no-op when the peer | |
| * is not ACTIVE, so this handles first-time bring-up too. | |
| * Unchanged ACTIVE peers are left untouched. | |
| */ | |
| if (amdgpu_ualink_vpod_membership_changed(peer_adev)) { | |
| dev_info(peer_adev->dev, | |
| "UALINK: (re)configuring vpod for accel_id=%u\n", | |
| peer_adev->ualink.info->ppod.accel_id); | |
| deactivate_accelerator(peer_adev); | |
| activate_accelerator(peer_adev); | |
| } | |
| } | |
| } | |
| static inline bool __is_vpod_peer(struct amdgpu_ualink_info *info, | |
| struct amdgpu_ualink_info *peer_info) | |
| { | |
| return peer_info->vpod.id == info->vpod.id && | |
| uuid_equal(&peer_info->ppod.id, &info->ppod.id); | |
| } | |
| static int __check_local_vpod_integrity(struct amdgpu_device *adev) | |
| { | |
| DECLARE_BITMAP(local_accel_ids, AMDGPU_UALINK_ACCEL_MAX); | |
| struct amdgpu_ualink_info *info = adev->ualink.info; | |
| u32 local_accels[AMDGPU_UALINK_LOCAL_ACCELS_MAX]; | |
| struct amdgpu_ualink_info *peer_info; | |
| struct amdgpu_device *peer_adev; | |
| unsigned int i, n_local_accels; | |
| unsigned int accel_id; | |
| /* Check that all local accelerators listed in vpod_active_accels have | |
| * matching pod IDs | |
| */ | |
| bitmap_zero(local_accel_ids, AMDGPU_UALINK_ACCEL_MAX); | |
| n_local_accels = 0; | |
| __set_bit(info->ppod.accel_id, local_accel_ids); | |
| local_accels[n_local_accels++] = info->ppod.accel_id; | |
| for (i = 0; i < mgpu_info.num_gpu; i++) { | |
| peer_adev = mgpu_info.gpu_ins[i].adev; | |
| if (peer_adev == adev || !peer_adev->ualink.info) | |
| continue; | |
| peer_info = peer_adev->ualink.info; | |
| /* peer device ppod not configured */ | |
| if (peer_info->accel_state < | |
| AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED) { | |
| dev_info(adev->dev, | |
| "UALINK: integrity EAGAIN: peer accel_id=%u not ppod-configured (accel_state=%d)\n", | |
| peer_info->ppod.accel_id, | |
| peer_info->accel_state); | |
| return -EAGAIN; | |
| } | |
| accel_id = peer_info->ppod.accel_id; | |
| if (!test_bit(accel_id, info->vpod.active_accel_bits)) | |
| continue; | |
| /* peer device vpod not configured */ | |
| if (peer_info->accel_state < | |
| AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED) { | |
| dev_info(adev->dev, | |
| "UALINK: integrity EAGAIN: vpod peer accel_id=%u not vpod-configured (accel_state=%d)\n", | |
| accel_id, peer_info->accel_state); | |
| return -EAGAIN; | |
| } | |
| if (!uuid_equal(&peer_info->ppod.id, &info->ppod.id)) { | |
| dev_warn(adev->dev, | |
| "UALINK: integrity fail: peer %u ppod_id doesn't match: %pU != %pU\n", | |
| peer_info->ppod.accel_id, &peer_info->ppod.id, | |
| &info->ppod.id); | |
| return -EINVAL; | |
| } | |
| if (peer_info->ppod.size != info->ppod.size) { | |
| dev_warn(adev->dev, | |
| "UALINK: integrity fail: peer %u ppod_size doesn't match: %u != %u\n", | |
| accel_id, peer_info->ppod.size, | |
| info->ppod.size); | |
| return -EINVAL; | |
| } | |
| if (peer_info->vpod.id != info->vpod.id) { | |
| dev_warn(adev->dev, | |
| "UALINK: integrity fail: peer %u vpod_id doesn't match: %u != %u\n", | |
| accel_id, peer_info->vpod.id, info->vpod.id); | |
| return -EINVAL; | |
| } | |
| if (peer_info->vpod.size != info->vpod.size) { | |
| dev_warn(adev->dev, | |
| "UALINK: integrity fail: peer %u vpod_size doesn't match: %u != %u\n", | |
| accel_id, peer_info->vpod.size, | |
| info->vpod.size); | |
| return -EINVAL; | |
| } | |
| if (peer_info->vpod.addr_mode != info->vpod.addr_mode) { | |
| dev_warn(adev->dev, | |
| "UALINK: integrity fail: peer %u addr_mode doesn't match: %u != %u\n", | |
| accel_id, peer_info->vpod.addr_mode, | |
| info->vpod.addr_mode); | |
| return -EINVAL; | |
| } | |
| if (!bitmap_equal(peer_info->vpod.active_accel_bits, | |
| info->vpod.active_accel_bits, | |
| AMDGPU_UALINK_ACCEL_MAX)) { | |
| dev_warn(adev->dev, | |
| "UALINK: integrity fail: peer %u vpod_active_accels don't match\n", | |
| accel_id); | |
| return -EINVAL; | |
| } | |
| if (__test_and_set_bit(accel_id, local_accel_ids)) { | |
| dev_warn(adev->dev, | |
| "UALINK: integrity fail: duplicate accel_id %u among local vpod peers\n", | |
| accel_id); | |
| return -EINVAL; | |
| } | |
| local_accels[n_local_accels++] = accel_id; | |
| } | |
| for (i = 0; i < mgpu_info.num_gpu; i++) { | |
| peer_adev = mgpu_info.gpu_ins[i].adev; | |
| peer_info = peer_adev->ualink.info; | |
| if (!peer_info) | |
| continue; | |
| if (peer_adev != adev && !__is_vpod_peer(info, peer_info)) | |
| continue; | |
| peer_info->n_local_accels = n_local_accels; | |
| memcpy(peer_info->local_accels, local_accels, | |
| sizeof(local_accels)); | |
| } | |
| return 0; | |
| } | |
| static void __amdgpu_ualink_activate_vpod_locked(struct amdgpu_device *adev) | |
| { | |
| int ret; | |
| if (adev->ualink.info->accel_state < | |
| AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED || | |
| adev->ualink.info->accel_state == | |
| AMDGPU_UALINK_ACCEL_STATE_ERROR) | |
| return; | |
| ret = __check_local_vpod_integrity(adev); | |
| if (ret && ret != -EAGAIN) { | |
| dev_err(adev->dev, | |
| "Local vpod integrity check failed: %d\n", ret); | |
| return; | |
| } | |
| if (ret == -EAGAIN) { | |
| dev_info(adev->dev, | |
| "UALINK: activate deferred, waiting for local vpod peers to reach VPOD_CONFIGURED\n"); | |
| return; | |
| } | |
| dev_info(adev->dev, | |
| "UALINK: integrity OK, applying local vpod (%u local accels)\n", | |
| adev->ualink.info->n_local_accels); | |
| activate_local_vpod(adev); | |
| } | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| UALINK_VALUE_SHOW(vpod_config, vpod_id, vpod.id, "%u"); | |
| UALINK_VALUE_SHOW(vpod_config, vpod_size, vpod.size, "%u"); | |
| UALINK_IDBITS_SHOW(vpod_config, vpod_active_accels, vpod.active_accel_bits); | |
| UALINK_ENUM_SHOW(vpod_config, addr_mode, vpod.addr_mode); | |
| UALINK_VALUE_STORE(vpod_config, vpod_id, vpod.id, u32, 10); | |
| UALINK_VALUE_STORE(vpod_config, vpod_size, vpod.size, u32, 10); | |
| UALINK_IDBITS_STORE(vpod_config, vpod_active_accels, vpod.active_accel_bits, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| UALINK_ENUM_STORE(vpod_config, addr_mode, vpod.addr_mode); | |
| static ssize_t ualink_vpod_config_commit_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct amdgpu_ualink_vpod_config *config = to_ualink_vpod_config(kobj); | |
| struct amdgpu_ualink_info *info = to_ualink_info(kobj->parent); | |
| struct device *dev = kobj_to_dev(info->kobj.parent); | |
| struct drm_device *ddev = dev_get_drvdata(dev); | |
| struct amdgpu_device *adev = drm_to_adev(ddev); | |
| enum amdgpu_ualink_accel_state prev_state; | |
| u32 prev_vpod_id; | |
| int r; | |
| if (!sysfs_streq(buf, "true")) | |
| return -EINVAL; | |
| dev_dbg(adev->dev, | |
| "UALINK: vpod-commit enter: accel_state=%d staged vpod_id=%u vpod_size=%u addr_mode=%u\n", | |
| info->accel_state, config->vpod.id, config->vpod.size, | |
| config->vpod.addr_mode); | |
| if (info->accel_state < AMDGPU_UALINK_ACCEL_STATE_PPOD_CONFIGURED) { | |
| dev_warn(adev->dev, | |
| "UALINK: vpod-commit rejected, ppod not configured (accel_state=%d)\n", | |
| info->accel_state); | |
| return -EINVAL; | |
| } | |
| prev_state = info->accel_state; | |
| prev_vpod_id = info->vpod.id; | |
| r = psp_ual_set_vpod_config(&adev->psp, adev->ualink.psp_if_ver, | |
| config); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "UALINK: vpod-commit psp_ual_set_vpod_config failed: %d\n", | |
| r); | |
| return r; | |
| } | |
| r = psp_ual_query_info(&adev->psp, adev->ualink.psp_if_ver, info, NULL); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "UALINK: vpod-commit psp_ual_query_info failed: %d\n", | |
| r); | |
| return r; | |
| } | |
| dev_dbg(adev->dev, | |
| "UALINK: vpod-commit fw read-back: vpod_id=%u vpod_size=%u accel_id=%u ppod.size=%u addr_mode=%u\n", | |
| info->vpod.id, info->vpod.size, info->ppod.accel_id, | |
| info->ppod.size, info->vpod.addr_mode); | |
| if (info->vpod.id != AMDGPU_UALINK_VPOD_ID_INVALID && | |
| !__check_vpod_info(adev, info)) { | |
| dev_err(adev->dev, | |
| "UALINK: vpod-commit __check_vpod_info() failed\n"); | |
| return -EINVAL; | |
| } | |
| /* The integrity check makes sure each new GPU is consistent with the | |
| * other GPUs already in the vPod. All known local GPUs can become | |
| * "ready" at the same time. | |
| * | |
| * Misconfiguration of one GPU does not reduce the state of other GPUs | |
| * already in the vPod. GPU is intentionally not put to ERROR state if | |
| * misconfiguration occurs. | |
| */ | |
| r = amdgpu_ualink_update_accel_state(adev, prev_state, prev_vpod_id, | |
| UAL_CFG_VPOD); | |
| if (r) | |
| return r; | |
| return count; | |
| } | |
| #define UALINK_VPOD_CONFIG_ATTR(name) __ATTR(name, 0600, \ | |
| ualink_vpod_config_##name##_show, \ | |
| ualink_vpod_config_##name##_store) | |
| static struct kobj_attribute ualink_vpod_config_vpod_id = UALINK_VPOD_CONFIG_ATTR(vpod_id); | |
| static struct kobj_attribute ualink_vpod_config_vpod_size = UALINK_VPOD_CONFIG_ATTR(vpod_size); | |
| static struct kobj_attribute ualink_vpod_config_vpod_active_accels = | |
| UALINK_VPOD_CONFIG_ATTR(vpod_active_accels); | |
| static struct kobj_attribute ualink_vpod_config_addr_mode = UALINK_VPOD_CONFIG_ATTR(addr_mode); | |
| static struct kobj_attribute ualink_vpod_config_commit = __ATTR(commit, 0200, NULL, | |
| ualink_vpod_config_commit_store); | |
| static const struct attribute *ualink_vpod_config_attrs[] = { | |
| &ualink_vpod_config_vpod_id.attr, | |
| &ualink_vpod_config_vpod_size.attr, | |
| &ualink_vpod_config_vpod_active_accels.attr, | |
| &ualink_vpod_config_addr_mode.attr, | |
| &ualink_vpod_config_commit.attr, | |
| NULL | |
| }; | |
| #endif | |
| static void ualink_vpod_config_release(struct kobject *kobj) | |
| { | |
| kfree(to_ualink_vpod_config(kobj)); | |
| } | |
| static const struct kobj_type ualink_vpod_config_ktype = { | |
| .release = ualink_vpod_config_release, | |
| .sysfs_ops = &kobj_sysfs_ops | |
| }; | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| UALINK_VALUE_SHOW(station_config, flags, flags, "0x%x"); | |
| UALINK_VALUE_STORE(station_config, flags, flags, u8, 16); | |
| static size_t lane_bitmap_to_string(char *str, | |
| const struct amdgpu_ualink_station_config *stations) | |
| { | |
| size_t i; | |
| for (i = 0; i < stations->n_stations; i++) | |
| str[i] = stations->lane_en_bitmap[i] + | |
| (stations->lane_en_bitmap[i] < 10 ? '0' : 'a' - 10); | |
| str[i] = '\0'; | |
| return i; | |
| } | |
| static ssize_t | |
| ualink_station_config_lane_en_bitmap_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, | |
| char *buf) | |
| { | |
| struct amdgpu_ualink_station_config *stations = to_ualink_station_config(kobj); | |
| char str[AMDGPU_UALINK_STATIONS_MAX + 1]; | |
| lane_bitmap_to_string(str, stations); | |
| return sysfs_emit(buf, "%s\n", str); | |
| } | |
| static ssize_t | |
| ualink_station_config_lane_en_bitmap_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct amdgpu_ualink_station_config *stations = to_ualink_station_config(kobj); | |
| u8 lane_en_bitmap[AMDGPU_UALINK_STATIONS_MAX]; | |
| u32 n_stations = 0; | |
| bool end = false; | |
| size_t i; | |
| for (i = 0; i < count; i++) { | |
| /* Accept any number of \n in the end */ | |
| if (buf[i] == '\n') { | |
| end = true; | |
| continue; | |
| } | |
| /* Don't accept any more data after \n */ | |
| if (end) | |
| return -EINVAL; | |
| /* Don't accept more data than the array size */ | |
| if (i >= sizeof(lane_en_bitmap)) | |
| return -ENOSPC; | |
| /* Accept hex digits */ | |
| if (buf[i] >= '0' && buf[i] <= '9') | |
| lane_en_bitmap[n_stations++] = buf[i] - '0'; | |
| else if (buf[i] >= 'a' && buf[i] <= 'f') | |
| lane_en_bitmap[n_stations++] = buf[i] + 10 - 'a'; | |
| else if (buf[i] >= 'A' && buf[i] <= 'F') | |
| lane_en_bitmap[n_stations++] = buf[i] + 10 - 'A'; | |
| else | |
| return -EINVAL; | |
| } | |
| memcpy(stations->lane_en_bitmap, lane_en_bitmap, sizeof(lane_en_bitmap)); | |
| stations->n_stations = n_stations; | |
| return i; | |
| } | |
| static ssize_t ualink_station_config_commit_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct amdgpu_ualink_station_config *stations = to_ualink_station_config(kobj); | |
| struct amdgpu_ualink_info *info = to_ualink_info(kobj->parent); | |
| struct device *dev = kobj_to_dev(info->kobj.parent); | |
| struct drm_device *ddev = dev_get_drvdata(dev); | |
| struct amdgpu_device *adev = drm_to_adev(ddev); | |
| int r; | |
| /* DF reconfiguration does not interact with accelerator state */ | |
| if (!sysfs_streq(buf, "true")) | |
| return -EINVAL; | |
| r = psp_ual_set_station_config(&adev->psp, adev->ualink.psp_if_ver, stations); | |
| if (r) | |
| return r; | |
| return count; | |
| } | |
| #define UALINK_STATION_CONFIG_ATTR(name) __ATTR(name, 0600, \ | |
| ualink_station_config_##name##_show, \ | |
| ualink_station_config_##name##_store) | |
| static struct kobj_attribute ualink_station_config_flags = UALINK_STATION_CONFIG_ATTR(flags); | |
| static struct kobj_attribute ualink_station_config_lane_en_bitmap = | |
| UALINK_STATION_CONFIG_ATTR(lane_en_bitmap); | |
| static struct kobj_attribute ualink_station_config_commit = | |
| __ATTR(commit, 0200, NULL, ualink_station_config_commit_store); | |
| static const struct attribute *ualink_station_config_attrs[] = { | |
| &ualink_station_config_flags.attr, | |
| &ualink_station_config_lane_en_bitmap.attr, | |
| &ualink_station_config_commit.attr, | |
| NULL | |
| }; | |
| #endif | |
| static void ualink_station_config_release(struct kobject *kobj) | |
| { | |
| kfree(to_ualink_station_config(kobj)); | |
| } | |
| static const struct kobj_type ualink_station_config_ktype = { | |
| .release = ualink_station_config_release, | |
| .sysfs_ops = &kobj_sysfs_ops | |
| }; | |
| int ualink_ip_sw_init(struct amdgpu_ip_block *ip_block) | |
| { | |
| struct amdgpu_ualink_station_config *stations; | |
| struct amdgpu_ualink_vpod_config *vpod_config; | |
| struct amdgpu_ualink_ppod_setup *ppod_setup; | |
| struct amdgpu_device *adev = ip_block->adev; | |
| struct amdgpu_ualink_info *info; | |
| info = kzalloc(sizeof(*info), GFP_KERNEL); | |
| ppod_setup = kzalloc(sizeof(*ppod_setup), GFP_KERNEL); | |
| vpod_config = kzalloc(sizeof(*vpod_config), GFP_KERNEL); | |
| stations = kzalloc(sizeof(*stations), GFP_KERNEL); | |
| if (!info || !ppod_setup || !vpod_config || !stations) { | |
| kfree(info); | |
| kfree(ppod_setup); | |
| kfree(vpod_config); | |
| kfree(stations); | |
| return -ENOMEM; | |
| } | |
| info->ppod.accel_id = 0xffffffff; | |
| info->ppod.bandwidth = 0xffffffff; | |
| info->ppod.latency = 0xffffffff; | |
| info->vpod.id = AMDGPU_UALINK_VPOD_ID_INVALID; | |
| info->vpod.addr_mode = AMDGPU_UALINK_ADDR_MODE_MAX; | |
| /* | |
| * Initialize the kobjects here so their lifetime is tied to the UALink | |
| * manager software state. amdgpu_ualink_object_fini() drops the final | |
| * reference via kobject_put(). | |
| */ | |
| kobject_init(&info->kobj, &ualink_info_ktype); | |
| kobject_init(&ppod_setup->kobj, &ualink_ppod_setup_ktype); | |
| kobject_init(&vpod_config->kobj, &ualink_vpod_config_ktype); | |
| kobject_init(&stations->kobj, &ualink_station_config_ktype); | |
| adev->ualink.info = info; | |
| adev->ualink.setup = ppod_setup; | |
| adev->ualink.config = vpod_config; | |
| adev->ualink.stations = stations; | |
| return 0; | |
| } | |
| int ualink_ip_sw_fini(struct amdgpu_ip_block *ip_block) | |
| { | |
| amdgpu_ualink_object_fini(ip_block->adev); | |
| return 0; | |
| } | |
| static int ualink_kobj_add(struct kobject *kobj, struct kobject *parent, | |
| const char *name, const struct attribute **attrs) | |
| { | |
| int r; | |
| r = kobject_add(kobj, parent, "%s", name); | |
| if (r) | |
| return r; | |
| r = sysfs_create_files(kobj, attrs); | |
| if (r) | |
| kobject_del(kobj); | |
| return r; | |
| } | |
| static void amdgpu_ualink_xcp_sysfs_init(struct amdgpu_device *adev); | |
| static void amdgpu_ualink_xcp_sysfs_fini(struct amdgpu_device *adev); | |
| int amdgpu_ualink_sysfs_init(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_info *info = adev->ualink.info; | |
| int r; | |
| if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE) | |
| return 0; | |
| /* ualink parent node */ | |
| r = ualink_kobj_add(&info->kobj, &adev->dev->kobj, "ualink", | |
| ualink_info_attrs); | |
| if (r) | |
| goto err; | |
| #ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS | |
| r = ualink_kobj_add(&adev->ualink.setup->kobj, &info->kobj, "setup", | |
| ualink_ppod_setup_attrs); | |
| if (r) | |
| goto err_info; | |
| r = ualink_kobj_add(&adev->ualink.config->kobj, &info->kobj, "config", | |
| ualink_vpod_config_attrs); | |
| if (r) | |
| goto err_setup; | |
| r = ualink_kobj_add(&adev->ualink.stations->kobj, &info->kobj, | |
| "stations", ualink_station_config_attrs); | |
| if (r) | |
| goto err_config; | |
| #else | |
| r = kobject_add(&adev->ualink.setup->kobj, &info->kobj, "%s", | |
| "setup"); | |
| if (r) | |
| goto err_info; | |
| r = kobject_add(&adev->ualink.config->kobj, &info->kobj, "%s", | |
| "config"); | |
| if (r) | |
| goto err_setup; | |
| r = kobject_add(&adev->ualink.stations->kobj, &info->kobj, "%s", | |
| "stations"); | |
| if (r) | |
| goto err_config; | |
| #endif | |
| amdgpu_ualink_xcp_sysfs_init(adev); | |
| adev->ualink.sysfs_init = true; | |
| return 0; | |
| err_config: | |
| kobject_del(&adev->ualink.config->kobj); | |
| err_setup: | |
| kobject_del(&adev->ualink.setup->kobj); | |
| err_info: | |
| kobject_del(&info->kobj); | |
| err: | |
| dev_warn(adev->dev, "Failed to create UALink sysfs: %d\n", r); | |
| return 0; | |
| } | |
| void amdgpu_ualink_sysfs_fini(struct amdgpu_device *adev) | |
| { | |
| if (!adev->ualink.sysfs_init) | |
| return; | |
| amdgpu_ualink_xcp_sysfs_fini(adev); | |
| kobject_del(&adev->ualink.stations->kobj); | |
| kobject_del(&adev->ualink.config->kobj); | |
| kobject_del(&adev->ualink.setup->kobj); | |
| kobject_del(&adev->ualink.info->kobj); | |
| adev->ualink.sysfs_init = false; | |
| } | |
| static int amdgpu_ualink_xcp_sysfs_add(struct amdgpu_xcp *xcp) | |
| { | |
| struct amdgpu_device *adev = xcp->xcp_mgr->adev; | |
| int r; | |
| r = kobject_init_and_add(&xcp->ualink.kobj, &ualink_xcp_info_ktype, | |
| &xcp->ddev->dev->kobj, "ualink"); | |
| if (r) | |
| goto err; | |
| r = sysfs_create_group(&xcp->ualink.kobj, &ualink_xcp_info_group); | |
| if (r) | |
| goto err; | |
| /* pin info so it outlives this node regardless of teardown order */ | |
| kobject_get(&adev->ualink.info->kobj); | |
| xcp->ualink.sysfs = true; | |
| return 0; | |
| err: | |
| kobject_put(&xcp->ualink.kobj); | |
| return r; | |
| } | |
| void amdgpu_ualink_xcp_sysfs_update(struct amdgpu_xcp *xcp) | |
| { | |
| if (!xcp->ualink.sysfs) | |
| return; | |
| sysfs_update_group(&xcp->ualink.kobj, &ualink_xcp_info_group); | |
| } | |
| static void amdgpu_ualink_xcp_sysfs_remove(struct amdgpu_xcp *xcp) | |
| { | |
| struct amdgpu_device *adev = xcp->xcp_mgr->adev; | |
| if (!xcp->ualink.sysfs) | |
| return; | |
| /* group is only populated for valid partitions */ | |
| if (xcp->valid) | |
| sysfs_remove_group(&xcp->ualink.kobj, &ualink_xcp_info_group); | |
| kobject_put(&adev->ualink.info->kobj); | |
| kobject_put(&xcp->ualink.kobj); | |
| xcp->ualink.sysfs = false; | |
| } | |
| static void amdgpu_ualink_xcp_sysfs_init(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_xcp *xcp; | |
| int i; | |
| if (!adev->xcp_mgr) | |
| return; | |
| for (i = 0; i < MAX_XCP; i++) { | |
| xcp = &adev->xcp_mgr->xcp[i]; | |
| if (!xcp->ddev || amdgpu_xcp_is_primary(xcp)) | |
| continue; | |
| amdgpu_ualink_xcp_sysfs_add(xcp); | |
| } | |
| } | |
| static void amdgpu_ualink_xcp_sysfs_fini(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_xcp *xcp; | |
| int i; | |
| if (!adev->xcp_mgr) | |
| return; | |
| for (i = 0; i < MAX_XCP; i++) { | |
| xcp = &adev->xcp_mgr->xcp[i]; | |
| amdgpu_ualink_xcp_sysfs_remove(xcp); | |
| } | |
| } | |
| static int amdgpu_ualink_npa_alloc_va(struct amdgpu_device *adev, | |
| struct drm_mm_node *mm_node, | |
| u64 va, u64 range_start, | |
| u64 range_end, int size) | |
| { | |
| struct amdgpu_ualink_npa_mm *npa_mm = &adev->ualink.npa_mm; | |
| u64 alignment = 0; | |
| int ret; | |
| mutex_lock(&npa_mm->mm_lock); | |
| if (va) { | |
| mm_node->start = va; | |
| mm_node->size = size; | |
| ret = drm_mm_reserve_node(&npa_mm->mm, mm_node); | |
| } else { | |
| if (!range_start && !range_end) { | |
| range_start = npa_mm->va_start; | |
| range_end = npa_mm->va_start + npa_mm->va_size; | |
| } | |
| /* If size is greater than 2MB (or 512 pages), | |
| * align it to 2MB (or 512 pages) granularity. | |
| */ | |
| if (size >= 0x200) | |
| alignment = 0x200; | |
| ret = drm_mm_insert_node_in_range(&npa_mm->mm, mm_node, size, | |
| alignment, 0, range_start, | |
| range_end, 0); | |
| } | |
| mutex_unlock(&npa_mm->mm_lock); | |
| if (ret) | |
| dev_err(adev->dev, "Failed to allocate NPA address\n"); | |
| return ret; | |
| } | |
| static void amdgpu_ualink_npa_free_va(struct amdgpu_device *adev, | |
| struct drm_mm_node *mm_node) | |
| { | |
| struct amdgpu_ualink_npa_mm *npa_mm = &adev->ualink.npa_mm; | |
| mutex_lock(&npa_mm->mm_lock); | |
| drm_mm_remove_node(mm_node); | |
| mutex_unlock(&npa_mm->mm_lock); | |
| } | |
| static void amdgpu_ualink_npa_mm_init(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_npa_mm *npa_mm = &adev->ualink.npa_mm; | |
| u32 addr_mode = adev->ualink.info->vpod.addr_mode; | |
| u64 va_size; | |
| mutex_init(&npa_mm->mm_lock); | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) { | |
| /* 51 bit address space in Source-Identification mode. | |
| * 39 bits in page granularity. | |
| */ | |
| npa_mm->va_start = 0; | |
| va_size = GENMASK_ULL(38, 0); | |
| } else { | |
| /* 41 bit address space in Source-Alias mode. | |
| * 29 bits in page granularity. | |
| */ | |
| /* First 8MB is reserved for Metadata NPAs */ | |
| npa_mm->va_start = 0x800; | |
| va_size = GENMASK_ULL(28, 0); | |
| } | |
| npa_mm->va_size = va_size - npa_mm->va_start; | |
| drm_mm_init(&npa_mm->mm, npa_mm->va_start, npa_mm->va_size); | |
| } | |
| static void amdgpu_ualink_npa_mm_fini(struct amdgpu_device *adev) | |
| { | |
| mutex_destroy(&adev->ualink.npa_mm.mm_lock); | |
| drm_mm_takedown(&adev->ualink.npa_mm.mm); | |
| } | |
| /* The caller of this function is expected to hold the XA lock when calling | |
| * this function. | |
| */ | |
| static void amdgpu_generate_ualink_handle(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_handle *handle) | |
| { | |
| bool unique = false; | |
| do { | |
| handle->handle_lo = get_random_u64(); | |
| /* Replace bottom 10 bits in handle_lo with accId */ | |
| handle->handle_lo &= ~AMDGPU_UALINK_HANDLE_ACCID_MASK; | |
| handle->handle_lo |= adev->ualink.info->ppod.accel_id; | |
| /* Don't generate/store a Handle with value 0. */ | |
| if (!handle->handle_lo) | |
| continue; | |
| /* Find if the handle already exists in the exporter xarray. | |
| * If it already exists, then regenerate the handle since we | |
| * want the handle to be unique. | |
| */ | |
| unique = !xa_load(&adev->ualink.exp_xa, handle->handle_lo); | |
| } while (!unique); | |
| handle->handle_hi = get_random_u64(); | |
| dev_dbg(adev->dev, "GENERATE-HANDLE: generated handle: %llx:%llx\n", | |
| handle->handle_hi, handle->handle_lo); | |
| } | |
| static void amdgpu_ualink_cleanup_exp_xa_node(struct kref *ref) | |
| { | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| struct amdgpu_device *adev; | |
| exp_xa_node = container_of(ref, struct amdgpu_ualink_exp_xa_node, | |
| refcount); | |
| adev = amdgpu_ttm_adev(exp_xa_node->bo->tbo.bdev); | |
| queue_work(adev->ualink.npa_wq, &exp_xa_node->cleanup_work); | |
| } | |
| static void amdgpu_ualink_cleanup_imp_xa_node(struct kref *ref) | |
| { | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node; | |
| struct amdgpu_device *adev; | |
| imp_xa_node = container_of(ref, struct amdgpu_ualink_imp_xa_node, | |
| refcount); | |
| adev = imp_xa_node->adev; | |
| dev_dbg(adev->dev, "IMP-CLEANUP: Remove entry from XA for handle:%llx:%llx\n", | |
| imp_xa_node->handle.handle_hi, imp_xa_node->handle.handle_lo); | |
| /* Remove node from the Xarray */ | |
| if (!xa_erase(&adev->ualink.imp_xa, imp_xa_node->handle.handle_lo)) | |
| dev_err(adev->dev, "IMP-CLEANUP: Failed to find entry in XA for handle:%llx:%llx\n", | |
| imp_xa_node->handle.handle_hi, imp_xa_node->handle.handle_lo); | |
| if (imp_xa_node->npa_addr && imp_xa_node->size && imp_xa_node->dmabuf) { | |
| dev_dbg(adev->dev, | |
| "IMP-CLEANUP: dmabuf free for NPA:%llx handle:%llx:%llx, fc:%lu\n", | |
| imp_xa_node->npa_addr, imp_xa_node->handle.handle_hi, | |
| imp_xa_node->handle.handle_lo, | |
| file_count(imp_xa_node->dmabuf->file)); | |
| dma_buf_put(imp_xa_node->dmabuf); | |
| drm_gem_handle_delete(adev->ualink.client.file, | |
| imp_xa_node->gem_handle); | |
| } | |
| dev_dbg(adev->dev, "IMP-CLEANUP: Freeing XA entry for handle:%llx:%llx\n", | |
| imp_xa_node->handle.handle_hi, imp_xa_node->handle.handle_lo); | |
| kfree(imp_xa_node); | |
| } | |
| static int amdgpu_ualink_exp_xa_entry_get(struct amdgpu_ualink_exp_xa_node *exp_xa_node) | |
| { | |
| return kref_get_unless_zero(&exp_xa_node->refcount); | |
| } | |
| static void amdgpu_ualink_exp_xa_entry_put(struct amdgpu_ualink_exp_xa_node *exp_xa_node) | |
| { | |
| kref_put(&exp_xa_node->refcount, amdgpu_ualink_cleanup_exp_xa_node); | |
| } | |
| static int amdgpu_ualink_imp_xa_entry_get(struct amdgpu_ualink_imp_xa_node *imp_xa_node) | |
| { | |
| return kref_get_unless_zero(&imp_xa_node->refcount); | |
| } | |
| static void amdgpu_ualink_imp_xa_entry_put(struct amdgpu_ualink_imp_xa_node *imp_xa_node) | |
| { | |
| kref_put(&imp_xa_node->refcount, amdgpu_ualink_cleanup_imp_xa_node); | |
| } | |
| static int amdgpu_ualink_send_npa_release_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle) | |
| { | |
| u32 dw0, dw1, dw2, dw3; | |
| dw0 = lower_32_bits(handle.handle_lo); | |
| dw0 &= ~AMDGPU_UALINK_MESSAGE_HEADER_MASK; | |
| dw0 |= AMDGPU_UALINK_NPA_RELEASE_MSG; | |
| dw1 = upper_32_bits(handle.handle_lo); | |
| dw2 = lower_32_bits(handle.handle_hi); | |
| dw3 = upper_32_bits(handle.handle_hi); | |
| dev_dbg(adev->dev, "SEND NPA-RELEASE: remote_acc_id %u handle %llx:%llx dw[0-3] 0x%x 0x%x 0x%x 0x%x\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo, dw0, dw1, dw2, dw3); | |
| return amdgpu_ualink_remote_interrupt(adev, remote_acc_id, dw0, dw1, | |
| dw2, dw3); | |
| } | |
| static int amdgpu_ualink_send_npa_revoke_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle) | |
| { | |
| u32 dw0, dw1, dw2, dw3; | |
| dw0 = lower_32_bits(handle.handle_lo); | |
| dw0 &= ~AMDGPU_UALINK_MESSAGE_HEADER_MASK; | |
| dw0 |= AMDGPU_UALINK_NPA_REVOKE_MSG; | |
| dw1 = upper_32_bits(handle.handle_lo); | |
| dw2 = lower_32_bits(handle.handle_hi); | |
| dw3 = upper_32_bits(handle.handle_hi); | |
| dev_dbg(adev->dev, "SEND NPA-REVOKE: remote_acc_id %u handle %llx:%llx dw[0-3] 0x%x 0x%x 0x%x 0x%x\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo, dw0, dw1, dw2, dw3); | |
| return amdgpu_ualink_remote_interrupt(adev, remote_acc_id, dw0, dw1, | |
| dw2, dw3); | |
| } | |
| static int amdgpu_ualink_send_npa_fail_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle, | |
| u32 fail_reason) | |
| { | |
| u32 dw0, dw1, dw2, dw3; | |
| dw0 = lower_32_bits(handle.handle_lo); | |
| dw0 &= ~AMDGPU_UALINK_MESSAGE_HEADER_MASK; | |
| dw0 |= AMDGPU_UALINK_NPA_FAIL_MSG; | |
| dw1 = upper_32_bits(handle.handle_lo); | |
| dw2 = fail_reason & 0xFF; | |
| dw3 = 0; | |
| dev_dbg(adev->dev, "SEND NPA-FAIL: remote_acc_id %u handle 0x%llx:%llx dw[0-3] 0x%x 0x%x 0x%x 0x%x\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo, dw0, dw1, dw2, dw3); | |
| return amdgpu_ualink_remote_interrupt(adev, remote_acc_id, dw0, dw1, | |
| dw2, dw3); | |
| } | |
| static int amdgpu_ualink_send_npa_rsp_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle, | |
| u32 npa_addr, u32 size) | |
| { | |
| u32 dw0, dw1, dw2, dw3; | |
| dw0 = lower_32_bits(handle.handle_lo); | |
| dw0 &= ~AMDGPU_UALINK_MESSAGE_HEADER_MASK; | |
| dw0 |= AMDGPU_UALINK_NPA_RSP_MSG; | |
| dw1 = upper_32_bits(handle.handle_lo); | |
| dw2 = size; | |
| dw3 = npa_addr; | |
| dev_dbg(adev->dev, "SEND NPA-RSP: remote_acc_id %u handle %llx:%llx dw[0-3] 0x%x 0x%x 0x%x 0x%x\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo, dw0, dw1, dw2, dw3); | |
| return amdgpu_ualink_remote_interrupt(adev, remote_acc_id, dw0, dw1, | |
| dw2, dw3); | |
| } | |
| static int amdgpu_ualink_send_npa_req_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle) | |
| { | |
| u32 dw0, dw1, dw2, dw3; | |
| dw0 = lower_32_bits(handle.handle_lo); | |
| dw0 &= ~AMDGPU_UALINK_MESSAGE_HEADER_MASK; | |
| dw0 |= AMDGPU_UALINK_NPA_REQ_MSG; | |
| dw1 = upper_32_bits(handle.handle_lo); | |
| dw2 = lower_32_bits(handle.handle_hi); | |
| dw3 = upper_32_bits(handle.handle_hi); | |
| dev_dbg(adev->dev, "SEND NPA-REQ: remote_acc_id %u handle 0x%llx:%llx dw[0-3] 0x%x 0x%x 0x%x 0x%x\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo, dw0, dw1, dw2, dw3); | |
| return amdgpu_ualink_remote_interrupt(adev, remote_acc_id, dw0, dw1, | |
| dw2, dw3); | |
| } | |
| static u64 amdgpu_ualink_get_export_pte_flags(struct amdgpu_device *adev, | |
| struct amdgpu_bo *bo, | |
| u64 mapping_flags) | |
| { | |
| u64 pte_flags = adev->gmc.init_pte_flags; | |
| pte_flags |= (AMDGPU_PTE_VALID | AMDGPU_PTE_READABLE | | |
| AMDGPU_PTE_WRITEABLE); | |
| mapping_flags |= AMDGPU_VM_MTYPE_DEFAULT; | |
| amdgpu_gmc_get_vm_pte(adev, &adev->ualink.npa_vm, bo, mapping_flags, | |
| &pte_flags); | |
| return pte_flags; | |
| } | |
| static int amdgpu_ualink_unmap_npa_addr(struct amdgpu_device *adev, | |
| struct amdgpu_bo *bo, | |
| u64 npa_addr, u64 size) | |
| { | |
| struct amdgpu_vm *vm = &adev->ualink.npa_vm; | |
| uint64_t pte_value = adev->gmc.noretry_flags; | |
| struct amdgpu_bo *bos[] = { bo }; | |
| struct dma_fence *fence = NULL; | |
| struct drm_exec exec; | |
| int r; | |
| r = amdgpu_ualink_reserve_npa_vm_and_bos(adev, bos, ARRAY_SIZE(bos), &exec, false); | |
| if (unlikely(r)) { | |
| dev_err(adev->dev, "Failed to reserve VM and BO in unmap_npa_addr\n"); | |
| return r; | |
| } | |
| r = amdgpu_vm_update_range(adev, vm, false, false, true, | |
| false, NULL, npa_addr, npa_addr + size - 1, | |
| pte_value, 0, 0, NULL, NULL, &vm->last_update); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "Failed to unmap NPA addr (%llx) from NPA VM\n", npa_addr); | |
| goto out; | |
| } | |
| r = amdgpu_vm_update_pdes(adev, vm, false); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "Failed %d to update page directories during unmapping NPA: 0x%llx\n", | |
| r, npa_addr); | |
| goto out; | |
| } | |
| fence = dma_fence_get(vm->last_update); | |
| if (fence) { | |
| r = dma_fence_wait(fence, false); | |
| dma_fence_put(fence); | |
| fence = NULL; | |
| if (r) { | |
| dev_dbg(adev->dev, | |
| "UNMAP-NPA: dma fence wait failed, error: %d\n", r); | |
| goto out; | |
| } | |
| } | |
| amdgpu_ualink_flush_tlb(adev, TLB_FLUSH_HEAVYWEIGHT); | |
| out: | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| return r; | |
| } | |
| static int amdgpu_ualink_map_npa_addr(struct amdgpu_device *adev, u64 npa_addr, | |
| u64 size, struct amdgpu_bo *bo, u64 offset, | |
| u64 pte_flags) | |
| { | |
| struct amdgpu_vm *vm = &adev->ualink.npa_vm; | |
| struct amdgpu_bo *bos[] = { bo }; | |
| struct dma_fence *fence = NULL; | |
| struct drm_exec exec; | |
| int r; | |
| r = amdgpu_ualink_reserve_npa_vm_and_bos(adev, bos, ARRAY_SIZE(bos), &exec, false); | |
| if (unlikely(r)) { | |
| dev_err(adev->dev, "Failed to reserve VM and BO in map_npa_addr\n"); | |
| return r; | |
| } | |
| r = amdgpu_vm_update_range(adev, vm, false, false, true, | |
| false, NULL, npa_addr, npa_addr + size - 1, | |
| pte_flags, offset, adev->vm_manager.vram_base_offset, | |
| bo->tbo.resource, NULL, &vm->last_update); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "Failed to map NPA addr (%llx) into NPA VM\n", npa_addr); | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| goto out; | |
| } | |
| r = amdgpu_vm_update_pdes(adev, vm, false); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "failed %d to update page directories for NPA: 0x%llx\n", | |
| r, npa_addr); | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| goto unmap_npa; | |
| } | |
| fence = dma_fence_get(vm->last_update); | |
| if (fence) { | |
| r = dma_fence_wait(fence, false); | |
| dma_fence_put(fence); | |
| fence = NULL; | |
| if (r) { | |
| pr_debug("failed %d to dma fence wait\n", r); | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| goto unmap_npa; | |
| } | |
| } | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| /* TLB flush may be needed after updated page directories */ | |
| amdgpu_ualink_flush_tlb(adev, TLB_FLUSH_HEAVYWEIGHT); | |
| return 0; | |
| unmap_npa: | |
| amdgpu_ualink_unmap_npa_addr(adev, bo, npa_addr, size); | |
| out: | |
| return r; | |
| } | |
| static int amdgpu_ualink_send_hello_ack_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id) | |
| { | |
| dev_dbg(adev->dev, "SEND HELLO-ACK: HELLO-ACK message to remote AccId:%u\n", | |
| remote_acc_id); | |
| return amdgpu_ualink_remote_interrupt(adev, remote_acc_id, | |
| AMDGPU_UALINK_HELLO_ACK_MSG, | |
| 0, 0, 0); | |
| } | |
| static int amdgpu_ualink_send_hello_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id) | |
| { | |
| u32 dw0; | |
| dw0 = AMDGPU_UALINK_HELLO_MSG; | |
| dw0 |= (remote_acc_id << AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT); | |
| dw0 |= (adev->ualink.info->ppod.accel_id << | |
| AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT); | |
| dev_dbg(adev->dev, "SEND HELLO: HELLO message to remote AccId:%u\n", | |
| remote_acc_id); | |
| return amdgpu_ualink_remote_interrupt(adev, remote_acc_id, dw0, 0, | |
| 0, 0); | |
| } | |
| static u32 amdgpu_ualink_check_conn_ready(struct amdgpu_device *adev, | |
| u32 remote_acc_id, u32 gen_count) | |
| { | |
| struct amdgpu_ualink_connection *conn_state; | |
| u32 current_gen_count = 0; | |
| conn_state = &adev->ualink.conn_state[remote_acc_id]; | |
| /* Check if the connection is established. */ | |
| mutex_lock(&conn_state->lock); | |
| if ((conn_state->state == AMDGPU_UALINK_CONN_ESTABLISHED) && | |
| (!gen_count || conn_state->generation_count == gen_count)) | |
| current_gen_count = conn_state->generation_count; | |
| mutex_unlock(&conn_state->lock); | |
| return current_gen_count; | |
| } | |
| static void amdgpu_ualink_process_hello_ack_msg(struct amdgpu_device *adev, | |
| u32 sender_acc_id) | |
| { | |
| struct amdgpu_ualink_connection *conn_state; | |
| if (sender_acc_id >= AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_err(adev->dev, | |
| "HELLO-ACK: sender AccId out of range:%u\n", | |
| sender_acc_id); | |
| return; | |
| } | |
| conn_state = &adev->ualink.conn_state[sender_acc_id]; | |
| /* If we are in IN_PROGRESS state, then transition the connection | |
| * state to established and signal that the HELLO ACK is received. | |
| * Otherwise, ignore the HELLO ACK. | |
| */ | |
| mutex_lock(&conn_state->lock); | |
| if (conn_state->state == AMDGPU_UALINK_CONN_IN_PROGRESS) { | |
| conn_state->state = AMDGPU_UALINK_CONN_ESTABLISHED; | |
| conn_state->generation_count++; | |
| complete(&conn_state->hello_done); | |
| } else { | |
| dev_dbg(adev->dev, | |
| "HELLO-ACK: already connected, ignoring from AccId:%u\n", | |
| sender_acc_id); | |
| } | |
| mutex_unlock(&conn_state->lock); | |
| } | |
| static void amdgpu_ualink_process_hello_msg(struct amdgpu_device *adev, | |
| u32 receiver_acc_id, | |
| u32 sender_acc_id, | |
| u32 src_acc_id) | |
| { | |
| struct amdgpu_ualink_connection *conn_state; | |
| u32 generation_count; | |
| int r; | |
| if (receiver_acc_id != adev->ualink.info->ppod.accel_id) { | |
| dev_err(adev->dev, | |
| "HELLO: receiver AccId mismatch got:%u self:%u\n", | |
| receiver_acc_id, adev->ualink.info->ppod.accel_id); | |
| return; | |
| } | |
| /* src_acc_id is the AccId received in IH cookie. Confirm it | |
| * matches with the sender AccId. | |
| */ | |
| if (sender_acc_id != src_acc_id) { | |
| dev_err(adev->dev, | |
| "HELLO: sender AccId mismatch sender:%u IH cookie:%u\n", | |
| sender_acc_id, src_acc_id); | |
| return; | |
| } | |
| if (sender_acc_id >= AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_err(adev->dev, | |
| "HELLO: sender AccId out of range:%u\n", | |
| sender_acc_id); | |
| return; | |
| } | |
| conn_state = &adev->ualink.conn_state[sender_acc_id]; | |
| /* Check if connection is already established. If yes, then receiving HELLO msg | |
| * triggers a reset handling scenario. | |
| * If the connection is not ready, and we receive a HELLO msg, then | |
| * transition the state to PENDING. | |
| * Otherwise, leave it IN_PROGRESS. | |
| */ | |
| mutex_lock(&conn_state->lock); | |
| if (conn_state->state != AMDGPU_UALINK_CONN_ESTABLISHED) { | |
| if (conn_state->state == AMDGPU_UALINK_CONN_NOT_READY) | |
| conn_state->state = AMDGPU_UALINK_CONN_PENDING; | |
| /* otherwise, leave it IN_PROGRESS to signal the completion below */ | |
| mutex_unlock(&conn_state->lock); | |
| } else { | |
| /* Leave state ESTABLISHED so handle_connection_reset() | |
| * performs the transition and imp/exp XA cleanup. | |
| */ | |
| generation_count = conn_state->generation_count; | |
| mutex_unlock(&conn_state->lock); | |
| amdgpu_ualink_handle_connection_reset(adev, sender_acc_id, | |
| AMDGPU_UALINK_CONN_PENDING, | |
| generation_count); | |
| } | |
| /* Peer may have rebooted. */ | |
| r = amdgpu_ualink_ring_resync_ptrs(adev, sender_acc_id); | |
| if (r < 0) { | |
| dev_err(adev->dev, | |
| "HELLO: resync sender AccId %u ptrs failed %d\n", | |
| sender_acc_id, r); | |
| goto out_set_state; | |
| } | |
| if (r) | |
| amdgpu_ualink_reset_peer_rings(adev, sender_acc_id); | |
| r = amdgpu_ualink_send_hello_ack_msg(adev, sender_acc_id); | |
| if (r) | |
| dev_err(adev->dev, "HELLO-ACK: send failed to remote AccId:%u\n", | |
| sender_acc_id); | |
| out_set_state: | |
| mutex_lock(&conn_state->lock); | |
| if (r) { | |
| conn_state->state = AMDGPU_UALINK_CONN_NOT_READY; | |
| } else { | |
| /* If we are in IN_PROGRESS state and we receivied the HELLO message, | |
| * upon receiving the HELLO message, transition the state to ESTABLISHED | |
| * and signal the completion. | |
| */ | |
| if (conn_state->state == AMDGPU_UALINK_CONN_IN_PROGRESS) | |
| complete(&conn_state->hello_done); | |
| conn_state->state = AMDGPU_UALINK_CONN_ESTABLISHED; | |
| conn_state->generation_count++; | |
| } | |
| mutex_unlock(&conn_state->lock); | |
| } | |
| /* True if nothing is exported to, or imported from, this remote accelerator. */ | |
| static bool amdgpu_ualink_peer_idle(struct amdgpu_device *adev, u32 remote_accel_id) | |
| { | |
| /* Unlocked: a racing import at worst costs one redundant ring resync. */ | |
| return list_empty(&adev->ualink.imp_handles_list[remote_accel_id]) && | |
| list_empty(&adev->ualink.exp_handles_list[remote_accel_id]); | |
| } | |
| static int amdgpu_ualink_setup_connection(struct amdgpu_device *adev, | |
| u32 remote_acc_id) | |
| { | |
| struct amdgpu_ualink_connection *conn_state; | |
| int r; | |
| conn_state = &adev->ualink.conn_state[remote_acc_id]; | |
| /* Connection state management goes through different states. | |
| * The states are: | |
| * - NOT_READY: The connection is not ready. | |
| * - IN_PROGRESS: GPU sent HELLO message and is waiting for the HELLO_ACK. | |
| * - PENDING: GPU received HELLO message and is in the process of sending | |
| * the HELLO_ACK. | |
| * - ESTABLISHED: The connection is established. | |
| */ | |
| /* Grab the lock and check if connection establishment was | |
| * already done by another thread. | |
| */ | |
| mutex_lock(&conn_state->lock); | |
| if (conn_state->state == AMDGPU_UALINK_CONN_IN_PROGRESS || | |
| conn_state->state == AMDGPU_UALINK_CONN_PENDING) { | |
| r = -EAGAIN; | |
| goto out; | |
| } | |
| if (conn_state->state == AMDGPU_UALINK_CONN_ESTABLISHED && | |
| !amdgpu_ualink_peer_idle(adev, remote_acc_id)) { | |
| r = 0; | |
| goto out; | |
| } | |
| /* Peer is idle or not connected, it may have rebooted. Skip the | |
| * handshake if the rings are still in sync, it would reset the remote. | |
| */ | |
| r = amdgpu_ualink_ring_resync_ptrs(adev, remote_acc_id); | |
| if (r < 0) | |
| goto out; | |
| if (!r && conn_state->state == AMDGPU_UALINK_CONN_ESTABLISHED) | |
| goto out; | |
| amdgpu_ualink_reset_peer_rings(adev, remote_acc_id); | |
| conn_state->state = AMDGPU_UALINK_CONN_IN_PROGRESS; | |
| mutex_unlock(&conn_state->lock); | |
| /* Send HELLO message */ | |
| r = amdgpu_ualink_send_hello_msg(adev, remote_acc_id); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "HELLO: Send failed to remote AccId:%u\n", | |
| remote_acc_id); | |
| goto reset_state; | |
| } | |
| /* Wait for the HELLO_ACK to come back */ | |
| /* complete(conn_state->hello_done) should be called from the IRQ | |
| * handler when the HELLO_ACK is received. | |
| */ | |
| r = wait_for_completion_interruptible_timeout(&conn_state->hello_done, | |
| msecs_to_jiffies(AMDGPU_UALINK_RESP_TIMEOUT)); | |
| if (r == -ERESTARTSYS) { | |
| dev_err_ratelimited(adev->dev, | |
| "HELLO-ACK: interrupted by signal\n"); | |
| goto reset_state; | |
| } else if (r == 0) { | |
| dev_warn(adev->dev, | |
| "HELLO-ACK: Timeout from remote AccId:%u\n", | |
| remote_acc_id); | |
| r = -ETIMEDOUT; | |
| goto reset_state; | |
| } | |
| return 0; | |
| reset_state: | |
| mutex_lock(&conn_state->lock); | |
| conn_state->state = AMDGPU_UALINK_CONN_NOT_READY; | |
| out: | |
| mutex_unlock(&conn_state->lock); | |
| return r; | |
| } | |
| /* Invalidate an importer node's mappings and drop its last ref (frees the | |
| * node, dma-buf and GEM handle). Caller must have unlinked it from the | |
| * per-remote list and must not hold the imp_xa lock. | |
| */ | |
| static void amdgpu_ualink_release_imp_xa_node(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node) | |
| { | |
| struct amdgpu_bo *bo; | |
| dev_dbg(adev->dev, | |
| "IMP-CLEANUP: handle:%llx:%llx npa:%llx size:%llx\n", | |
| imp_xa_node->handle.handle_hi, imp_xa_node->handle.handle_lo, | |
| imp_xa_node->npa_addr, imp_xa_node->size); | |
| if (imp_xa_node->dmabuf) { | |
| bo = gem_to_amdgpu_bo(imp_xa_node->dmabuf->priv); | |
| amdgpu_ualink_invalidate_import_mappings(bo); | |
| } | |
| amdgpu_ualink_imp_xa_entry_put(imp_xa_node); | |
| } | |
| static void amdgpu_ualink_cleanup_imp_xa_entries(struct amdgpu_device *adev, | |
| u32 remote_acc_id) | |
| { | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node; | |
| struct list_head *imp_handles_list; | |
| dev_dbg(adev->dev, | |
| "IMP-RESET: Cleaning up all XA entries for remote:%u\n", | |
| remote_acc_id); | |
| imp_handles_list = &adev->ualink.imp_handles_list[remote_acc_id]; | |
| xa_lock(&adev->ualink.imp_xa); | |
| while (!list_empty(imp_handles_list)) { | |
| imp_xa_node = list_first_entry(imp_handles_list, | |
| struct amdgpu_ualink_imp_xa_node, list); | |
| list_del_init(&imp_xa_node->list); | |
| WRITE_ONCE(imp_xa_node->node_state, AMDGPU_UALINK_NODE_TEARDOWN); | |
| xa_unlock(&adev->ualink.imp_xa); | |
| amdgpu_ualink_release_imp_xa_node(adev, imp_xa_node); | |
| xa_lock(&adev->ualink.imp_xa); | |
| } | |
| xa_unlock(&adev->ualink.imp_xa); | |
| } | |
| static void amdgpu_ualink_cleanup_exp_xa_entries(struct amdgpu_device *adev, | |
| u32 remote_acc_id) | |
| { | |
| struct amdgpu_ualink_importer_entry *importer_entry; | |
| u32 addr_mode = adev->ualink.info->vpod.addr_mode; | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| struct list_head *exp_handles_list; | |
| struct drm_mm_node *mm_node; | |
| u64 npa_addr, size; | |
| /* Get the list head for the list containing all the handles | |
| * exported to this remote GPU. | |
| */ | |
| exp_handles_list = &adev->ualink.exp_handles_list[remote_acc_id]; | |
| dev_dbg(adev->dev, | |
| "EXP-RESET: Cleaning up all XA entries for remote:%u\n", | |
| remote_acc_id); | |
| xa_lock(&adev->ualink.exp_xa); | |
| while (!list_empty(exp_handles_list)) { | |
| importer_entry = list_first_entry(exp_handles_list, | |
| struct amdgpu_ualink_importer_entry, list); | |
| list_del_init(&importer_entry->list); | |
| exp_xa_node = importer_entry->parent; | |
| if (!amdgpu_ualink_exp_xa_entry_get(exp_xa_node)) | |
| continue; | |
| xa_unlock(&adev->ualink.exp_xa); | |
| /* Clear the bit corresponding to this remote GPU in | |
| * the importer bitmap. | |
| */ | |
| if (!test_and_clear_bit(remote_acc_id, | |
| exp_xa_node->importers_bitmap)) { | |
| amdgpu_ualink_exp_xa_entry_put(exp_xa_node); | |
| xa_lock(&adev->ualink.exp_xa); | |
| continue; | |
| } | |
| mutex_lock(&exp_xa_node->node_lock); | |
| size = amdgpu_bo_ngpu_pages(exp_xa_node->bo); | |
| /* Unpin the BO */ | |
| if (likely(!amdgpu_bo_reserve(exp_xa_node->bo, true))) { | |
| amdgpu_bo_unpin(exp_xa_node->bo); | |
| amdgpu_bo_unreserve(exp_xa_node->bo); | |
| } else { | |
| dev_warn(adev->dev, | |
| "EXP-RESET: BO reserve to unpin failed handle:%llx:%llx\n", | |
| exp_xa_node->handle.handle_hi, exp_xa_node->handle.handle_lo); | |
| } | |
| dev_dbg(adev->dev, | |
| "EXP-RESET: handle:%llx:%llx pin_count:%d, importers:%d\n", | |
| exp_xa_node->handle.handle_hi, exp_xa_node->handle.handle_lo, | |
| exp_xa_node->bo->tbo.pin_count, | |
| bitmap_weight(exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)); | |
| WARN_ON(exp_xa_node->bo->tbo.pin_count < | |
| bitmap_weight(exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)); | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) { | |
| npa_addr = importer_entry->npa_addr; | |
| mm_node = importer_entry->mm_node; | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| dev_dbg(adev->dev, | |
| "EXP-RESET: Unmap NPA:%llx size: %llx remote:%u handle:%llx:%llx\n", | |
| npa_addr, size, remote_acc_id, | |
| exp_xa_node->handle.handle_hi, | |
| exp_xa_node->handle.handle_lo); | |
| amdgpu_ualink_unmap_npa_addr(adev, exp_xa_node->bo, | |
| npa_addr, size); | |
| amdgpu_ualink_npa_free_va(adev, mm_node); | |
| kfree(mm_node); | |
| /* Reset the NPA addr and mm_node */ | |
| mutex_lock(&exp_xa_node->node_lock); | |
| importer_entry->npa_addr = 0; | |
| importer_entry->mm_node = NULL; | |
| } else if (bitmap_empty(exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)) { | |
| /* In Source-Aliasing mode, if there are no importers | |
| * for this handle, then we can unmap and free the | |
| * NPA address. | |
| */ | |
| mm_node = exp_xa_node->importer_entries[0].mm_node; | |
| npa_addr = exp_xa_node->importer_entries[0].npa_addr; | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| dev_dbg(adev->dev, | |
| "EXP-RESET: Unmap NPA:%llx size: %llx handle:%llx:%llx\n", | |
| npa_addr, size, exp_xa_node->handle.handle_hi, | |
| exp_xa_node->handle.handle_lo); | |
| amdgpu_ualink_unmap_npa_addr(adev, exp_xa_node->bo, | |
| npa_addr, size); | |
| amdgpu_ualink_npa_free_va(adev, mm_node); | |
| kfree(mm_node); | |
| mutex_lock(&exp_xa_node->node_lock); | |
| exp_xa_node->importer_entries[0].npa_addr = 0; | |
| exp_xa_node->importer_entries[0].mm_node = NULL; | |
| } | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| xa_lock(&adev->ualink.exp_xa); | |
| amdgpu_ualink_exp_xa_entry_put(exp_xa_node); | |
| } | |
| xa_unlock(&adev->ualink.exp_xa); | |
| } | |
| static void amdgpu_ualink_handle_connection_reset(struct amdgpu_device *adev, | |
| u32 remote_acc_id, u32 state, | |
| u32 generation_count) | |
| { | |
| struct amdgpu_ualink_connection *conn_state; | |
| conn_state = &adev->ualink.conn_state[remote_acc_id]; | |
| mutex_lock(&conn_state->lock); | |
| if ((conn_state->state == AMDGPU_UALINK_CONN_ESTABLISHED) && | |
| (conn_state->generation_count == generation_count)) { | |
| conn_state->state = state; | |
| mutex_unlock(&conn_state->lock); | |
| amdgpu_ualink_cleanup_imp_xa_entries(adev, remote_acc_id); | |
| amdgpu_ualink_cleanup_exp_xa_entries(adev, remote_acc_id); | |
| } else { | |
| mutex_unlock(&conn_state->lock); | |
| } | |
| } | |
| /* Set PTE.X = 1 for all importer entries to retry RPCs. */ | |
| static void amdgpu_ualink_force_retry_rpcs(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node) | |
| { | |
| u32 addr_mode = adev->ualink.info->vpod.addr_mode; | |
| struct amdgpu_ualink_importer_entry *imp_entry; | |
| struct amdgpu_vm *vm = &adev->ualink.npa_vm; | |
| u64 pte_flags, npa_addr, size; | |
| struct dma_fence *fence = NULL; | |
| struct amdgpu_bo *bo; | |
| struct drm_exec exec; | |
| u32 remote_acc_id; | |
| int r; | |
| bo = exp_xa_node->bo; | |
| size = amdgpu_bo_ngpu_pages(bo); | |
| r = amdgpu_ualink_reserve_npa_vm_and_bos(adev, &bo, 1, &exec, false); | |
| if (unlikely(r)) { | |
| dev_err(adev->dev, "Failed to reserve VM and BO in force_retry_rpcs\n"); | |
| return; | |
| } | |
| for_each_set_bit(remote_acc_id, exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) { | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| imp_entry = &exp_xa_node->importer_entries[0]; | |
| else | |
| imp_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| npa_addr = imp_entry->npa_addr; | |
| /* If the connection state changed while we are freeing | |
| * the BO, then ignore this importer. We will unmap this | |
| * address eventually in amdgpu_ualink_unmap_all_npa_addr() | |
| * function. Also, we will clear the corresponding bit in | |
| * the importer_bitmap in the same function. | |
| */ | |
| if (!amdgpu_ualink_check_conn_ready(adev, remote_acc_id, | |
| imp_entry->generation_count)) | |
| continue; | |
| /* Set PTE.X = 1 */ | |
| pte_flags = amdgpu_ualink_get_export_pte_flags(adev, bo, | |
| AMDGPU_VM_PAGE_EXECUTABLE); | |
| dev_dbg(adev->dev, | |
| "RETRY-RPC: setting PTE.X=1 for NPA:%llx remote:%u pte:0x%llx\n", | |
| npa_addr, remote_acc_id, pte_flags); | |
| r = amdgpu_vm_update_range(adev, vm, false, false, true, | |
| false, NULL, npa_addr, npa_addr + size - 1, | |
| pte_flags, 0, adev->vm_manager.vram_base_offset, | |
| bo->tbo.resource, NULL, &vm->last_update); | |
| if (r) | |
| dev_warn(adev->dev, | |
| "RETRY-RPC: PTE.X update failed for NPA:%llx remote:%u, r: %d\n", | |
| npa_addr, remote_acc_id, r); | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| break; | |
| } | |
| r = amdgpu_vm_update_pdes(adev, vm, false); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "Failed %d to update page directories during force retry rpcs\n", | |
| r); | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| return; | |
| } | |
| fence = dma_fence_get(vm->last_update); | |
| if (fence) { | |
| r = dma_fence_wait(fence, false); | |
| dma_fence_put(fence); | |
| fence = NULL; | |
| if (r) | |
| dev_dbg(adev->dev, "RETRY-RPC: dma fence wait failed err:%d\n", r); | |
| } | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| amdgpu_ualink_flush_tlb(adev, TLB_FLUSH_HEAVYWEIGHT); | |
| } | |
| static void amdgpu_ualink_send_tlb_shootdown(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node) | |
| { | |
| u32 addr_mode = adev->ualink.info->vpod.addr_mode; | |
| u32 accel_id = adev->ualink.info->ppod.accel_id; | |
| struct amdgpu_ualink_importer_entry *imp_entry; | |
| u64 npa_addr, size; | |
| u32 remote_acc_id; | |
| int r; | |
| size = amdgpu_bo_ngpu_pages(exp_xa_node->bo); | |
| for_each_set_bit(remote_acc_id, exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) { | |
| /* | |
| * Replace the GPU-id bits with local accel_id | |
| */ | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| imp_entry = &exp_xa_node->importer_entries[0]; | |
| else | |
| imp_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| npa_addr = STRIP_NPA(imp_entry->npa_addr); | |
| npa_addr = GENERATE_NPA(npa_addr, accel_id); | |
| if (!amdgpu_ualink_check_conn_ready(adev, remote_acc_id, | |
| imp_entry->generation_count)) { | |
| clear_bit(remote_acc_id, | |
| exp_xa_node->importers_bitmap); | |
| continue; | |
| } | |
| dev_dbg(adev->dev, | |
| "EXP-CLEANUP: Sending TLB-shootdown to remote:%u\n", | |
| remote_acc_id); | |
| r = amdgpu_ualink_remote_shootdown(adev, remote_acc_id, | |
| npa_addr, size, | |
| AMDGPU_UALINK_HEAVYWEIGHT_TLB_SHOOTDOWN); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "EXP-CLEANUP: TLB shootdown send failed to remote:%u\n", | |
| remote_acc_id); | |
| /* Send failed: mark the connection NOT_READY so the | |
| * rest of this cleanup (and other cleanups) skip the | |
| * unreachable peer via amdgpu_ualink_check_conn_ready(). | |
| */ | |
| amdgpu_ualink_handle_connection_reset(adev, remote_acc_id, | |
| AMDGPU_UALINK_CONN_NOT_READY, | |
| imp_entry->generation_count); | |
| } | |
| } | |
| } | |
| /* Unmap all NPA addresses associated with a BO (UALink handle). This function is used | |
| * only in Source Identification mode. | |
| */ | |
| static void amdgpu_ualink_unmap_all_npa_addr(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node) | |
| { | |
| struct amdgpu_vm *vm = &adev->ualink.npa_vm; | |
| u32 addr_mode = adev->ualink.info->vpod.addr_mode; | |
| struct amdgpu_ualink_importer_entry *imp_entry; | |
| u64 pte_value = adev->gmc.noretry_flags; | |
| struct dma_fence *fence = NULL; | |
| struct drm_exec exec; | |
| u64 npa_addr, size; | |
| u32 remote_acc_id; | |
| int r; | |
| size = amdgpu_bo_ngpu_pages(exp_xa_node->bo); | |
| r = amdgpu_ualink_reserve_npa_vm_and_bos(adev, &exp_xa_node->bo, 1, | |
| &exec, false); | |
| if (unlikely(r)) { | |
| dev_err(adev->dev, "Failed to reserve VM and BO in unmap_all_npa_addr\n"); | |
| return; | |
| } | |
| for_each_set_bit(remote_acc_id, exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) { | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| imp_entry = &exp_xa_node->importer_entries[0]; | |
| else | |
| imp_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| npa_addr = imp_entry->npa_addr; | |
| if (!amdgpu_ualink_check_conn_ready(adev, remote_acc_id, | |
| imp_entry->generation_count)) { | |
| clear_bit(remote_acc_id, | |
| exp_xa_node->importers_bitmap); | |
| continue; | |
| } | |
| dev_dbg(adev->dev, | |
| "UNMAP-NPA: Unmapping NPA:%llx, handle:%llx:%llx remote:%u pte:0x%llx\n", | |
| npa_addr, exp_xa_node->handle.handle_hi, exp_xa_node->handle.handle_lo, | |
| remote_acc_id, pte_value); | |
| r = amdgpu_vm_update_range(adev, vm, false, | |
| false, true, false, NULL, npa_addr, | |
| npa_addr + size - 1, pte_value, 0, | |
| 0, NULL, NULL, &vm->last_update); | |
| if (r) | |
| dev_err(adev->dev, | |
| "UNMAP-NPA: Unmap failed NPA:%llx, handle:%llx:%llx remote:%u\n", | |
| npa_addr, exp_xa_node->handle.handle_hi, | |
| exp_xa_node->handle.handle_lo, remote_acc_id); | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| break; | |
| } | |
| r = amdgpu_vm_update_pdes(adev, vm, false); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "Failed %d to update page directories during all NPA addresses unmapping\n", | |
| r); | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| return; | |
| } | |
| fence = dma_fence_get(vm->last_update); | |
| if (fence) { | |
| r = dma_fence_wait(fence, false); | |
| dma_fence_put(fence); | |
| fence = NULL; | |
| if (r) | |
| dev_err(adev->dev, | |
| "UNMAP-NPA: dma fence wait failed\n"); | |
| } | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| amdgpu_ualink_flush_tlb(adev, TLB_FLUSH_HEAVYWEIGHT); | |
| } | |
| static void amdgpu_ualink_free_all_npa_va(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node, | |
| unsigned long *importers_bitmap) | |
| { | |
| u32 addr_mode = adev->ualink.info->vpod.addr_mode; | |
| struct drm_mm_node *mm_node; | |
| u32 remote_acc_id; | |
| for_each_set_bit(remote_acc_id, importers_bitmap, AMDGPU_UALINK_ACCEL_MAX) { | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| mm_node = exp_xa_node->importer_entries[0].mm_node; | |
| else | |
| mm_node = exp_xa_node->importer_entries[remote_acc_id].mm_node; | |
| if (!mm_node) { | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| break; | |
| continue; | |
| } | |
| dev_dbg(adev->dev, | |
| "FREE-NPA: freeing NPA address:%llx, handle:%llx:%llx remote:%u\n", | |
| mm_node->start, exp_xa_node->handle.handle_hi, | |
| exp_xa_node->handle.handle_lo, remote_acc_id); | |
| amdgpu_ualink_npa_free_va(adev, mm_node); | |
| kfree(mm_node); | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| break; | |
| } | |
| } | |
| static void amdgpu_ualink_exp_cleanup_worker(struct work_struct *work) | |
| { | |
| DECLARE_BITMAP(orig_importers_bitmap, AMDGPU_UALINK_ACCEL_MAX); | |
| struct amdgpu_ualink_importer_entry *imp_entry; | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| struct amdgpu_ualink_handle handle; | |
| struct amdgpu_device *adev; | |
| struct amdgpu_bo *bo; | |
| u32 remote_acc_id; | |
| int r; | |
| exp_xa_node = container_of(work, struct amdgpu_ualink_exp_xa_node, | |
| cleanup_work); | |
| bo = exp_xa_node->bo; | |
| adev = amdgpu_ttm_adev(bo->tbo.bdev); | |
| handle = exp_xa_node->handle; | |
| /* Revoking access to an exported memory follows the steps: | |
| * 1. Set PTE.X = 1 to retry for RPCs. | |
| * 2. Send Remote TLB Shootdowns to all importers. | |
| * 3. Unmap the NPA address from NPA VM. | |
| * 4. Drop the ref count for the BO. | |
| * 5. Send NPA_REVOKE to all importers. | |
| * 6. Wait for NPA_RELEASE from all importers. | |
| * 7. Free the NPA address once all responses are received. | |
| */ | |
| /* If there are no importers for this BO/handle */ | |
| if (bitmap_empty(exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)) { | |
| /* Release the dma_buf created at export time (used for the | |
| * local-import shortcut). The has-importers path below drops | |
| * it too; this branch must not skip it or the dma_buf and the | |
| * BO it pins are leaked. | |
| */ | |
| dma_buf_put(exp_xa_node->dmabuf); | |
| /* Drop the BO reference so it can be freed. */ | |
| amdgpu_bo_unref(&bo); | |
| exp_xa_node->bo = NULL; | |
| goto free_node; | |
| } | |
| dev_dbg(adev->dev, | |
| "EXP-CLEANUP: handle:%llx:%llx importers bitmap: %*pbl\n", | |
| handle.handle_hi, handle.handle_lo, | |
| AMDGPU_UALINK_ACCEL_MAX, exp_xa_node->importers_bitmap); | |
| bitmap_copy(orig_importers_bitmap, exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| /* Set PTE.X = 1 for NPA addresses from all importers*/ | |
| amdgpu_ualink_force_retry_rpcs(adev, exp_xa_node); | |
| /* Send TLB-shootdown to all importer GPUs */ | |
| amdgpu_ualink_send_tlb_shootdown(adev, exp_xa_node); | |
| /* Unmap all NPA addresses for this BO from NPA VM */ | |
| amdgpu_ualink_unmap_all_npa_addr(adev, exp_xa_node); | |
| dev_dbg(adev->dev, "EXP-CLEANUP: handle:%llx:%llx Unpin BO, pin_count:%u, importers:%u\n", | |
| handle.handle_hi, handle.handle_lo, bo->tbo.pin_count, | |
| bitmap_weight(orig_importers_bitmap, AMDGPU_UALINK_ACCEL_MAX)); | |
| WARN_ON(bo->tbo.pin_count < bitmap_weight(orig_importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)); | |
| /* Unpin the BO */ | |
| if (likely(!amdgpu_bo_reserve(bo, true))) { | |
| bo->ualink_handle_lo = 0ULL; | |
| for_each_set_bit(remote_acc_id, orig_importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) | |
| amdgpu_bo_unpin(bo); | |
| amdgpu_bo_unreserve(bo); | |
| } else { | |
| dev_warn(adev->dev, | |
| "EXP-CLEANUP: BO reserve to unpin failed for handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| } | |
| /* Free the DMABuf */ | |
| dma_buf_put(exp_xa_node->dmabuf); | |
| /* Drop the reference to the BO so it can be freed. */ | |
| amdgpu_bo_unref(&bo); | |
| exp_xa_node->bo = NULL; | |
| /* Seed npa_release_bitmap with all importers before any NPA-REVOKE, so | |
| * the release IRQ only sees it empty once every responder has replied. | |
| * Dead peers are dropped from it in the send loop below. | |
| */ | |
| bitmap_copy(exp_xa_node->npa_release_bitmap, | |
| exp_xa_node->importers_bitmap, AMDGPU_UALINK_ACCEL_MAX); | |
| /* Send NPA-REVOKE to all importers which have imported this memory. | |
| * On send failure clear the bit (no response will arrive) and mark the | |
| * connection NOT_READY so other cleanups skip this unreachable peer. | |
| */ | |
| for_each_set_bit(remote_acc_id, exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) { | |
| imp_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| /* Re-check the connection right before the send: concurrent | |
| * cleanup workers all seed the bitmap while the peer is still | |
| * ESTABLISHED, so once its egress dies and the first worker | |
| * marks it NOT_READY, this skips the rest instead of flooding | |
| * the log with failed LSDMA sends to the same dead peer. | |
| */ | |
| if (!amdgpu_ualink_check_conn_ready(adev, remote_acc_id, | |
| imp_entry->generation_count)) { | |
| clear_bit(remote_acc_id, | |
| exp_xa_node->npa_release_bitmap); | |
| continue; | |
| } | |
| dev_dbg(adev->dev, | |
| "EXP-CLEANUP: Sending NPA-REVOKE to remote:%u\n", | |
| remote_acc_id); | |
| r = amdgpu_ualink_send_npa_revoke_msg(adev, remote_acc_id, handle); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "EXP-CLEANUP: NPA-REVOKE send failed to remote:%u\n", | |
| remote_acc_id); | |
| clear_bit(remote_acc_id, exp_xa_node->npa_release_bitmap); | |
| amdgpu_ualink_handle_connection_reset(adev, remote_acc_id, | |
| AMDGPU_UALINK_CONN_NOT_READY, | |
| imp_entry->generation_count); | |
| } | |
| } | |
| /* Wait for the NPA_RELEASE to come back from all importers */ | |
| if (!bitmap_empty(exp_xa_node->npa_release_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)) { | |
| dev_dbg(adev->dev, | |
| "EXP-CLEANUP: handle:%llx:%llx NPA-RELEASE bitmap: %*pbl\n", | |
| handle.handle_hi, handle.handle_lo, | |
| AMDGPU_UALINK_ACCEL_MAX, exp_xa_node->npa_release_bitmap); | |
| /* Wait for the NPA_RELEASE to come back from all importers */ | |
| r = wait_for_completion_timeout(&exp_xa_node->npa_done, | |
| msecs_to_jiffies(AMDGPU_UALINK_RESP_TIMEOUT)); | |
| if (r == 0) | |
| dev_warn(adev->dev, | |
| "EXP-CLEANUP: NPA-RELEASE timeout for handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| } | |
| /* Free the NPA addresses given to all the importers */ | |
| amdgpu_ualink_free_all_npa_va(adev, exp_xa_node, | |
| orig_importers_bitmap); | |
| /* Warn about all importers that didn't respond back with | |
| * NPA-RELEASE message and trigger connection timeout handling. | |
| */ | |
| for_each_set_bit(remote_acc_id, exp_xa_node->npa_release_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_warn(adev->dev, | |
| "EXP-CLEANUP: handle:%llx:%llx NPA-RELEASE timeout from remote:%u\n", | |
| handle.handle_hi, handle.handle_lo, remote_acc_id); | |
| imp_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| amdgpu_ualink_handle_connection_reset(adev, remote_acc_id, | |
| AMDGPU_UALINK_CONN_NOT_READY, | |
| imp_entry->generation_count); | |
| } | |
| free_node: | |
| xa_erase(&adev->ualink.handle_invalid_xa, handle.handle_lo); | |
| mutex_destroy(&exp_xa_node->node_lock); | |
| kfree(exp_xa_node); | |
| } | |
| /* This function is a copy of amdgpu_dma_buf_move_notify() function. | |
| * amdgpu_dma_buf_move_notify is only called for import attachments. | |
| * But NPA DMABufs don't use attachments because they are imported | |
| * on the same device. So we need to invalidate the GPUVM mappings | |
| * manually. | |
| */ | |
| static void amdgpu_ualink_invalidate_import_mappings(struct amdgpu_bo *bo) | |
| { | |
| struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); | |
| struct ttm_operation_ctx ctx = { false, false }; | |
| struct ttm_placement placement = {}; | |
| struct amdgpu_vm_bo_base *bo_base; | |
| struct drm_exec exec; | |
| int r; | |
| /* | |
| * Lock the BO together with every client VM page directory it is | |
| * mapped into in a single drm_exec transaction. | |
| */ | |
| drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 0); | |
| drm_exec_until_all_locked(&exec) { | |
| r = drm_exec_lock_obj(&exec, &bo->tbo.base); | |
| drm_exec_retry_on_contention(&exec); | |
| if (unlikely(r)) | |
| goto fini; | |
| for (bo_base = bo->vm_bo; bo_base; bo_base = bo_base->next) { | |
| r = amdgpu_vm_lock_pd(bo_base->vm, &exec, 0); | |
| drm_exec_retry_on_contention(&exec); | |
| if (unlikely(r)) | |
| goto fini; | |
| } | |
| } | |
| amdgpu_vm_bo_move(bo, NULL, false); | |
| r = ttm_bo_validate(&bo->tbo, &placement, &ctx); | |
| if (r) { | |
| dev_err(adev->dev, "Failed to invalidate NPA DMA-buf import (%d)\n", | |
| r); | |
| goto fini; | |
| } | |
| for (bo_base = bo->vm_bo; bo_base; bo_base = bo_base->next) { | |
| struct amdgpu_vm *vm = bo_base->vm; | |
| /* | |
| * Fences for the two SDMA page table updates were already | |
| * reserved by amdgpu_vm_lock_pd() above (it reserves 2 + | |
| * num_fences on the same VM root PD dma_resv). | |
| */ | |
| r = amdgpu_vm_clear_freed(adev, vm, NULL); | |
| if (!r) | |
| r = amdgpu_vm_handle_moved(adev, vm, &exec.ticket); | |
| if (r && r != -EBUSY) | |
| dev_err(adev->dev, "Failed to invalidate VM page tables (%d))\n", | |
| r); | |
| } | |
| fini: | |
| drm_exec_fini(&exec); | |
| } | |
| static int amdgpu_ualink_map_npa_to_dmabuf(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node) | |
| { | |
| struct ttm_operation_ctx ctx = { false, false }; | |
| u32 initial_domain = AMDGPU_GEM_DOMAIN_NPA; | |
| struct drm_gem_object *gobj = NULL; | |
| u64 alloc_flags, npa_addr, size; | |
| struct dma_buf *dmabuf; | |
| struct amdgpu_bo *bo; | |
| u32 handle; | |
| int r; | |
| npa_addr = imp_xa_node->npa_addr; | |
| size = imp_xa_node->size; | |
| dev_dbg(adev->dev, "Create NPA BO addr 0x%llx size in pages 0x%llx\n", | |
| npa_addr, size); | |
| /* | |
| * Create the BO directly in the NPA domain. | |
| */ | |
| alloc_flags = AMDGPU_GEM_CREATE_NO_CPU_ACCESS; | |
| r = amdgpu_gem_object_create(adev, size * AMDGPU_GPU_PAGE_SIZE, 1, | |
| initial_domain, alloc_flags, | |
| ttm_bo_type_device, NULL, &gobj, 0); | |
| if (r) { | |
| dev_err(adev->dev, "Failed to create NPA BO. ret %d\n", r); | |
| return r; | |
| } | |
| bo = gem_to_amdgpu_bo(gobj); | |
| r = amdgpu_bo_reserve(bo, false); | |
| if (unlikely(r != 0)) { | |
| dev_err(adev->dev, "Failed to reserve NPA BO, r: %d\n", r); | |
| goto err_reserve_failed; | |
| } | |
| /* | |
| * Drop the arbitrarily-placed NPA node and re-create it at the exact | |
| * remote window, mirroring amdgpu_bo_create_kernel_at(). | |
| */ | |
| ttm_resource_free(&bo->tbo, &bo->tbo.resource); | |
| bo->placements[0].fpfn = npa_addr; | |
| bo->placements[0].lpfn = npa_addr + size; | |
| r = ttm_bo_mem_space(&bo->tbo, &bo->placement, &bo->tbo.resource, &ctx); | |
| amdgpu_bo_unreserve(bo); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "Failed to place NPA BO at 0x%llx, r: %d\n", npa_addr, r); | |
| goto err_validate_failed; | |
| } | |
| r = drm_gem_handle_create(adev->ualink.client.file, gobj, &handle); | |
| if (r) { | |
| dev_err(adev->dev, | |
| "Failed to get handle for NPA GEM object, r: %d\n", r); | |
| goto err_validate_failed; | |
| } | |
| drm_gem_object_put(gobj); | |
| dmabuf = drm_gem_prime_handle_to_dmabuf(&adev->ddev, adev->ualink.client.file, | |
| handle, DRM_CLOEXEC | DRM_RDWR); | |
| if (IS_ERR(dmabuf)) { | |
| r = PTR_ERR(dmabuf); | |
| dev_err(adev->dev, | |
| "Failed to generate DMABuf for NPA GEM object\n"); | |
| goto err_dmabuf_failed; | |
| } | |
| imp_xa_node->dmabuf = dmabuf; | |
| imp_xa_node->gem_handle = handle; | |
| return 0; | |
| err_dmabuf_failed: | |
| drm_gem_handle_delete(adev->ualink.client.file, handle); | |
| return r; | |
| err_validate_failed: | |
| err_reserve_failed: | |
| drm_gem_object_put(gobj); | |
| return r; | |
| } | |
| void amdgpu_ualink_revoke_exported_memory(struct amdgpu_bo *bo) | |
| { | |
| struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); | |
| struct amdgpu_ualink_importer_entry *imp_entry; | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| u32 remote_acc_id; | |
| int r; | |
| if (!bo->ualink_handle_lo) | |
| return; | |
| /* Remove the entry from the Xarray. */ | |
| xa_lock(&adev->ualink.exp_xa); | |
| exp_xa_node = __xa_erase(&adev->ualink.exp_xa, | |
| bo->ualink_handle_lo); | |
| if (!exp_xa_node) { | |
| xa_unlock(&adev->ualink.exp_xa); | |
| dev_warn(adev->dev, | |
| "Exp XA: handle_lo:%llx not found\n", | |
| bo->ualink_handle_lo); | |
| return; | |
| } | |
| for_each_set_bit(remote_acc_id, exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) { | |
| imp_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| list_del_init(&imp_entry->list); | |
| } | |
| xa_unlock(&adev->ualink.exp_xa); | |
| /* Add it to the Handle_Invalid xarray */ | |
| r = xa_err(xa_store(&adev->ualink.handle_invalid_xa, | |
| bo->ualink_handle_lo, | |
| exp_xa_node, GFP_KERNEL)); | |
| if (r) | |
| dev_err(adev->dev, | |
| "Handle_Invalid XA store failed handle:%llx:%llx error:%d\n", | |
| exp_xa_node->handle.handle_hi, | |
| exp_xa_node->handle.handle_lo, r); | |
| amdgpu_ualink_exp_xa_entry_put(exp_xa_node); | |
| } | |
| static void amdgpu_ualink_process_npa_release_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle) | |
| { | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| xa_lock(&adev->ualink.handle_invalid_xa); | |
| exp_xa_node = xa_load(&adev->ualink.handle_invalid_xa, handle.handle_lo); | |
| if (!exp_xa_node) { | |
| dev_warn(adev->dev, | |
| "NPA-RELEASE: Handle (%llx:%llx) not found\n", | |
| handle.handle_hi, handle.handle_lo); | |
| goto out; | |
| } | |
| /* Confirm that the complete handle matches. */ | |
| if (handle.handle_hi != exp_xa_node->handle.handle_hi) { | |
| dev_warn(adev->dev, | |
| "NPA-RELEASE: handle_hi mismatch exp:%llx got:%llx:%llx remote:%u\n", | |
| exp_xa_node->handle.handle_hi, | |
| handle.handle_hi, handle.handle_lo, remote_acc_id); | |
| goto out; | |
| } | |
| /* Test and clear the bit corresponding to the remote GPU id to signal | |
| * the arrival of NPA_RELEASE message from it. | |
| * If the corresponding bit wasn't set, then raise a warning and ignore | |
| * the NPA-Release message from the remote GPU. | |
| */ | |
| if (!test_and_clear_bit(remote_acc_id, exp_xa_node->npa_release_bitmap)) { | |
| dev_warn(adev->dev, | |
| "NPA-RELEASE: unexpected from remote:%u handle:%llx:%llx\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo); | |
| goto out; | |
| } | |
| /* Signal completion if NPA_Release received from all importers */ | |
| if (bitmap_empty(exp_xa_node->npa_release_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)) | |
| complete(&exp_xa_node->npa_done); | |
| out: | |
| xa_unlock(&adev->ualink.handle_invalid_xa); | |
| } | |
| static void amdgpu_ualink_process_npa_revoke_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle) | |
| { | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node; | |
| struct amdgpu_bo *bo; | |
| int r = 0; | |
| /* Remove the entry from the Xarray. */ | |
| xa_lock(&adev->ualink.imp_xa); | |
| imp_xa_node = xa_load(&adev->ualink.imp_xa, handle.handle_lo); | |
| if (!imp_xa_node) { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| dev_warn(adev->dev, | |
| "NPA-REVOKE: Handle (%llx:%llx) not found\n", | |
| handle.handle_hi, handle.handle_lo); | |
| return; | |
| } | |
| /* Confirm that the complete handle matches. */ | |
| if (handle.handle_hi != imp_xa_node->handle.handle_hi) { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| dev_warn(adev->dev, | |
| "NPA-REVOKE: handle_hi mismatch exp:%llx got:%llx:%llx remote:%u\n", | |
| imp_xa_node->handle.handle_hi, | |
| handle.handle_hi, handle.handle_lo, remote_acc_id); | |
| return; | |
| } | |
| switch (READ_ONCE(imp_xa_node->node_state)) { | |
| case AMDGPU_UALINK_NODE_READY: | |
| WRITE_ONCE(imp_xa_node->node_state, AMDGPU_UALINK_NODE_TEARDOWN); | |
| list_del_init(&imp_xa_node->list); | |
| xa_unlock(&adev->ualink.imp_xa); | |
| /* Invalidate the GPUVM mappings */ | |
| bo = gem_to_amdgpu_bo(imp_xa_node->dmabuf->priv); | |
| amdgpu_ualink_invalidate_import_mappings(bo); | |
| /* Drop the refcount for the node */ | |
| amdgpu_ualink_imp_xa_entry_put(imp_xa_node); | |
| break; | |
| case AMDGPU_UALINK_NODE_PENDING: | |
| /* The import is still building the dma-buf and nothing has | |
| * been handed to user-space yet. The importing thread sees | |
| * the teardown state and unwinds. | |
| */ | |
| WRITE_ONCE(imp_xa_node->node_state, AMDGPU_UALINK_NODE_TEARDOWN); | |
| xa_unlock(&adev->ualink.imp_xa); | |
| break; | |
| default: | |
| /* NPA-REVOKE always follows NPA-RSP, so a NOT_READY node means | |
| * a stale revoke, and a node in teardown is already being | |
| * released by whoever moved it there. | |
| */ | |
| xa_unlock(&adev->ualink.imp_xa); | |
| return; | |
| } | |
| r = amdgpu_ualink_send_npa_release_msg(adev, remote_acc_id, handle); | |
| if (r) | |
| dev_err(adev->dev, | |
| "NPA-Release send failed remote:%u handle:%llx:%llx error:%d\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo, r); | |
| } | |
| static void amdgpu_ualink_process_npa_fail_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, u64 partial_handle, | |
| u32 fail_reason) | |
| { | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node; | |
| int r = 0; | |
| if (!amdgpu_ualink_check_conn_ready(adev, remote_acc_id, 0)) { | |
| dev_warn(adev->dev, | |
| "NPA-FAIL: no connection with remote AccId:%u\n", | |
| remote_acc_id); | |
| goto conn_setup; | |
| } | |
| xa_lock(&adev->ualink.imp_xa); | |
| imp_xa_node = xa_load(&adev->ualink.imp_xa, partial_handle); | |
| if (!imp_xa_node) { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| dev_warn(adev->dev, | |
| "NPA-FAIL: imp XA handle not found:%llx\n", | |
| partial_handle); | |
| return; | |
| } | |
| imp_xa_node->fail_reason = fail_reason; | |
| /* Signal completion done to signal response received for NPA-REQ | |
| * message. | |
| * If the node is in NOT_READY state, then set the node state to | |
| * PENDING and signal the completion. If the node is not in NOT_READY | |
| * state, then it is an unsolicited NPA-FAIL message and we | |
| * log a debug message. | |
| */ | |
| if (READ_ONCE(imp_xa_node->node_state) == AMDGPU_UALINK_NODE_NOT_READY) { | |
| WRITE_ONCE(imp_xa_node->node_state, AMDGPU_UALINK_NODE_PENDING); | |
| complete(&imp_xa_node->npa_done); | |
| } else { | |
| dev_dbg(adev->dev, | |
| "NPA-FAIL: unsolicited for handle:%llx:%llx from AccId:%u\n", | |
| imp_xa_node->handle.handle_hi, imp_xa_node->handle.handle_lo, | |
| remote_acc_id); | |
| } | |
| xa_unlock(&adev->ualink.imp_xa); | |
| return; | |
| conn_setup: | |
| r = amdgpu_ualink_setup_connection(adev, remote_acc_id); | |
| if (r) | |
| dev_warn(adev->dev, | |
| "NPA-FAIL: connection setup failed with remote AccId:%u\n", | |
| remote_acc_id); | |
| } | |
| static void amdgpu_ualink_process_npa_rsp_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, u64 partial_handle, | |
| u64 npa_addr, u64 size) | |
| { | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node; | |
| int r = 0; | |
| /* Check if the connection is established. If it is not, then start | |
| * connection setup. | |
| */ | |
| if (!amdgpu_ualink_check_conn_ready(adev, remote_acc_id, 0)) { | |
| dev_warn(adev->dev, | |
| "NPA-RSP: no connection with remote AccId:%u\n", | |
| remote_acc_id); | |
| goto conn_setup; | |
| } | |
| xa_lock(&adev->ualink.imp_xa); | |
| imp_xa_node = xa_load(&adev->ualink.imp_xa, partial_handle); | |
| if (!imp_xa_node) { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| dev_warn(adev->dev, | |
| "NPA-RSP: imp XA handle not found:%llx\n", partial_handle); | |
| return; | |
| } | |
| /* NPA addr received in NPA-RSP is page aligned and without the remote | |
| * GPU-id in Bits 41-50. Assemble back the NPA address before storing | |
| * it. | |
| */ | |
| imp_xa_node->npa_addr = GENERATE_NPA(npa_addr, remote_acc_id); | |
| /* Size is in number of GPU pages granularity. */ | |
| imp_xa_node->size = size; | |
| /* Signal completion done to signal NPA_RSP received. | |
| * If the node is in NOT_READY state, then set the node state to | |
| * PENDING and signal the completion. If the node is not in NOT_READY | |
| * state, then it is an unsolicited NPA-RSP message and we | |
| * log a debug message. | |
| */ | |
| if (READ_ONCE(imp_xa_node->node_state) == AMDGPU_UALINK_NODE_NOT_READY) { | |
| WRITE_ONCE(imp_xa_node->node_state, AMDGPU_UALINK_NODE_PENDING); | |
| complete(&imp_xa_node->npa_done); | |
| } else { | |
| dev_dbg(adev->dev, | |
| "NPA-RSP: unsolicited for handle:%llx:%llx from AccId:%u\n", | |
| imp_xa_node->handle.handle_hi, imp_xa_node->handle.handle_lo, | |
| remote_acc_id); | |
| } | |
| xa_unlock(&adev->ualink.imp_xa); | |
| return; | |
| conn_setup: | |
| r = amdgpu_ualink_setup_connection(adev, remote_acc_id); | |
| if (r) | |
| dev_warn(adev->dev, | |
| "NPA-RSP: connection setup failed with remote AccId:%u\n", | |
| remote_acc_id); | |
| } | |
| static void amdgpu_ualink_process_npa_req_msg(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle handle) | |
| { | |
| struct amdgpu_ualink_importer_entry *importer_entry, *npa_addr_entry; | |
| u32 addr_mode = adev->ualink.info->vpod.addr_mode; | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| u64 range_start, range_end, pte_flags; | |
| struct drm_mm_node *mm_node = NULL; | |
| int r = 0, fail_reason = 0; | |
| bool send_npa_fail = true; | |
| u64 npa_addr = 0, size; | |
| struct amdgpu_bo *bo; | |
| u32 gen_count; | |
| /* Check if the connection is established. If it is not, then start | |
| * connection setup. | |
| */ | |
| gen_count = amdgpu_ualink_check_conn_ready(adev, remote_acc_id, 0); | |
| if (!gen_count) { | |
| dev_warn(adev->dev, | |
| "NPA-REQ: no connection with remote AccId:%u\n", | |
| remote_acc_id); | |
| goto conn_setup; | |
| } | |
| /* Check entry exists in Exporter XA. If yes, increase the refcount | |
| * for the node. | |
| */ | |
| xa_lock(&adev->ualink.exp_xa); | |
| exp_xa_node = xa_load(&adev->ualink.exp_xa, handle.handle_lo); | |
| if (!exp_xa_node || (handle.handle_hi != exp_xa_node->handle.handle_hi) || | |
| !amdgpu_ualink_exp_xa_entry_get(exp_xa_node)) { | |
| xa_unlock(&adev->ualink.exp_xa); | |
| dev_warn(adev->dev, | |
| "NPA-REQ: exp XA handle not found handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| fail_reason = AMDGPU_UALINK_NPA_FAIL_INVALID_HANDLE; | |
| goto handle_invalid_fail; | |
| } | |
| xa_unlock(&adev->ualink.exp_xa); | |
| bo = exp_xa_node->bo; | |
| size = amdgpu_bo_ngpu_pages(bo); | |
| /* Pin the BO */ | |
| r = amdgpu_bo_reserve(bo, true); | |
| if (unlikely(r)) { | |
| dev_warn(adev->dev, | |
| "NPA-REQ: BO reserve failed handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| fail_reason = AMDGPU_UALINK_NPA_FAIL_ERROR; | |
| goto bo_reserve_fail; | |
| } | |
| r = amdgpu_bo_pin(bo, AMDGPU_GEM_DOMAIN_VRAM); | |
| amdgpu_bo_unreserve(bo); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "NPA-REQ: BO pin failed handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| fail_reason = AMDGPU_UALINK_NPA_FAIL_ERROR; | |
| goto bo_pin_fail; | |
| } | |
| if (addr_mode == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) { | |
| mutex_lock(&exp_xa_node->node_lock); | |
| importer_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| npa_addr_entry = importer_entry; | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| /* Check if NPA address is already allocated for this importer. | |
| * If yes, then send the NPA-FAIL message back to the remote GPU. | |
| */ | |
| if (importer_entry->npa_addr) { | |
| fail_reason = AMDGPU_UALINK_NPA_FAIL_DUPLICATE; | |
| goto npa_duplicate_fail; | |
| } | |
| range_start = ((u64)remote_acc_id << AMDGPU_UALINK_NPA_ADDR_GPUID_SHIFT) | | |
| AMDGPU_UALINK_NPA_ADDR_RANGE_RESERVED; | |
| range_end = range_start | AMDGPU_UALINK_NPA_ADDR_RANGE_MASK; | |
| } else { | |
| /* We store NPA-address in importer_entries[0] in | |
| * Source-Aliasing mode. | |
| */ | |
| mutex_lock(&exp_xa_node->node_lock); | |
| npa_addr_entry = &exp_xa_node->importer_entries[0]; | |
| importer_entry = &exp_xa_node->importer_entries[remote_acc_id]; | |
| /* Check if NPA address is already allocated for this importer. | |
| * If yes, then set the corresponding bit in the importers_bitmap, | |
| * set the generation count and send the NPA-RSP back to the remote GPU. | |
| */ | |
| if (npa_addr_entry->npa_addr) { | |
| npa_addr = npa_addr_entry->npa_addr; | |
| set_bit(remote_acc_id, exp_xa_node->importers_bitmap); | |
| importer_entry->generation_count = gen_count; | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| dev_dbg(adev->dev, | |
| "NPA-REQ: NPA:%llx size:%llx handle:%llx:%llx\n", | |
| npa_addr, size, handle.handle_hi, handle.handle_lo); | |
| goto send_npa_rsp; | |
| } | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| range_start = 0; | |
| range_end = 0; | |
| } | |
| mm_node = kzalloc(sizeof(*mm_node), GFP_KERNEL); | |
| if (!mm_node) { | |
| dev_warn(adev->dev, | |
| "NPA-REQ: mm_node alloc failed handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| fail_reason = AMDGPU_UALINK_NPA_FAIL_NOSPACE; | |
| goto mem_alloc_fail; | |
| } | |
| /* Allocate NPA address */ | |
| r = amdgpu_ualink_npa_alloc_va(adev, mm_node, 0, range_start, | |
| range_end, size); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "NPA-REQ: NPA addr alloc failed handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| fail_reason = AMDGPU_UALINK_NPA_FAIL_NOSPACE; | |
| goto npa_alloc_fail; | |
| } | |
| npa_addr = mm_node->start; | |
| pte_flags = amdgpu_ualink_get_export_pte_flags(adev, bo, 0); | |
| dev_dbg(adev->dev, | |
| "NPA-REQ: Allocated NPA:%llx size:%llx PTE:%llx handle:%llx:%llx\n", | |
| npa_addr, size, pte_flags, handle.handle_hi, handle.handle_lo); | |
| /* Map the NPA address into NPA VM*/ | |
| r = amdgpu_ualink_map_npa_addr(adev, npa_addr, size, bo, 0, pte_flags); | |
| if (r) { | |
| fail_reason = AMDGPU_UALINK_NPA_FAIL_ERROR; | |
| dev_warn(adev->dev, | |
| "NPA-REQ: NPA addr (%llx) map failed handle:%llx:%llx\n", | |
| npa_addr, handle.handle_hi, handle.handle_lo); | |
| goto map_npa_fail; | |
| } | |
| dev_dbg(adev->dev, | |
| "NPA-REQ: Mapped NPA:%llx size:%llx pte:%llx handle:%llx:%llx\n", | |
| npa_addr, size, pte_flags, handle.handle_hi, handle.handle_lo); | |
| send_npa_rsp: | |
| /* Send NPA-RSP back to the remote GPU */ | |
| r = amdgpu_ualink_send_npa_rsp_msg(adev, remote_acc_id, handle, | |
| STRIP_NPA(npa_addr), size); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "NPA-REQ: send NPA-RSP failed remote:%u handle:%llx:%llx\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo); | |
| send_npa_fail = false; | |
| goto send_npa_rsp_fail; | |
| } | |
| dev_dbg(adev->dev, | |
| "NPA-REQ: Sent NPA-RSP with NPA:%llx size:%llx handle:%llx:%llx\n", | |
| npa_addr, size, handle.handle_hi, handle.handle_lo); | |
| /* If this is the first time we are setting the bit for this importer, | |
| * then store the NPA address, mm_node and generation count. | |
| */ | |
| mutex_lock(&exp_xa_node->node_lock); | |
| if (!test_and_set_bit(remote_acc_id, exp_xa_node->importers_bitmap)) { | |
| npa_addr_entry->npa_addr = npa_addr; | |
| npa_addr_entry->mm_node = mm_node; | |
| importer_entry->generation_count = gen_count; | |
| } | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| dev_dbg(adev->dev, | |
| "NPA-REQ: BO pin_count:%d, importers:%d, handle:%llx:%llx\n", | |
| bo->tbo.pin_count, bitmap_weight(exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX), handle.handle_hi, handle.handle_lo); | |
| WARN_ON(bo->tbo.pin_count < bitmap_weight(exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX)); | |
| /* Add this node to the exported handles list for the remote GPU, | |
| * but only if the node is still in exp_xa. If revoke already erased | |
| * it, skip the list_add to avoid a dangling list entry. The cleanup | |
| * worker is guaranteed to run after we drop our ref, so it will see | |
| * this importer in the bitmap and send NPA-REVOKE. | |
| */ | |
| xa_lock(&adev->ualink.exp_xa); | |
| if (xa_load(&adev->ualink.exp_xa, exp_xa_node->handle.handle_lo) == exp_xa_node) | |
| list_add(&importer_entry->list, &adev->ualink.exp_handles_list[remote_acc_id]); | |
| xa_unlock(&adev->ualink.exp_xa); | |
| amdgpu_ualink_exp_xa_entry_put(exp_xa_node); | |
| return; | |
| send_npa_rsp_fail: | |
| mutex_lock(&exp_xa_node->node_lock); | |
| clear_bit(remote_acc_id, exp_xa_node->importers_bitmap); | |
| mutex_unlock(&exp_xa_node->node_lock); | |
| if (mm_node) | |
| amdgpu_ualink_unmap_npa_addr(adev, bo, npa_addr, size); | |
| map_npa_fail: | |
| if (mm_node) | |
| amdgpu_ualink_npa_free_va(adev, mm_node); | |
| npa_alloc_fail: | |
| kfree(mm_node); | |
| mem_alloc_fail: | |
| npa_duplicate_fail: | |
| r = amdgpu_bo_reserve(bo, true); | |
| if (likely(!r)) { | |
| amdgpu_bo_unpin(bo); | |
| amdgpu_bo_unreserve(bo); | |
| } else { | |
| dev_warn(adev->dev, | |
| "NPA-REQ: BO reserve to unpin failed for handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| } | |
| bo_pin_fail: | |
| bo_reserve_fail: | |
| amdgpu_ualink_exp_xa_entry_put(exp_xa_node); | |
| handle_invalid_fail: | |
| if (send_npa_fail) { | |
| r = amdgpu_ualink_send_npa_fail_msg(adev, remote_acc_id, | |
| handle, fail_reason); | |
| if (r) | |
| dev_warn(adev->dev, | |
| "NPA-REQ: send NPA-FAIL failed remote:%u handle:%llx:%llx\n", | |
| remote_acc_id, handle.handle_hi, handle.handle_lo); | |
| } | |
| return; | |
| conn_setup: | |
| r = amdgpu_ualink_setup_connection(adev, remote_acc_id); | |
| if (r) | |
| dev_warn(adev->dev, | |
| "NPA-REQ: connection setup failed with remote AccId:%u\n", | |
| remote_acc_id); | |
| } | |
| static int amdgpu_ualink_translate_npa_fail_reason(struct amdgpu_device *adev, | |
| u32 fail_reason) | |
| { | |
| switch (fail_reason) { | |
| case AMDGPU_UALINK_NPA_FAIL_NOSPACE: | |
| return -ENOSPC; | |
| case AMDGPU_UALINK_NPA_FAIL_INVALID_HANDLE: | |
| case AMDGPU_UALINK_NPA_FAIL_DUPLICATE: | |
| case AMDGPU_UALINK_NPA_FAIL_ERROR: | |
| return -EINVAL; | |
| default: | |
| dev_err(adev->dev, | |
| "IMPORT: invalid NPA-FAIL reason:%u\n", | |
| fail_reason); | |
| return -EINVAL; | |
| } | |
| } | |
| static int amdgpu_ualink_do_import_handle(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node, | |
| u32 remote_acc_id) | |
| { | |
| struct amdgpu_ualink_handle handle = imp_xa_node->handle; | |
| u32 generation_count; | |
| int r; | |
| /* First check if the connection is setup with the | |
| * remote GPU. If yes, then initiate the NPA protocol to | |
| * get the NPA address. | |
| * If not, then initiate the HELLO protocol to first setup | |
| * the connection and once the connection is setup, then | |
| * initiate the NPA protocol. | |
| */ | |
| r = amdgpu_ualink_setup_connection(adev, remote_acc_id); | |
| if (r) { | |
| if (r != -EAGAIN) | |
| dev_warn(adev->dev, | |
| "IMPORT: connection setup failed with remote AccId:%u\n", | |
| remote_acc_id); | |
| return r; | |
| } | |
| /* Send NPA_REQ message */ | |
| r = amdgpu_ualink_send_npa_req_msg(adev, remote_acc_id, handle); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "IMPORT: NPA-REQ send failed to remote AccId:%u\n", | |
| remote_acc_id); | |
| goto reset_conn; | |
| } | |
| /* Wait for the NPA_RSP to come back */ | |
| r = wait_for_completion_interruptible_timeout(&imp_xa_node->npa_done, | |
| msecs_to_jiffies(AMDGPU_UALINK_RESP_TIMEOUT)); | |
| if (r == -ERESTARTSYS) { | |
| dev_err_ratelimited(adev->dev, | |
| "IMPORT: NPA-RSP wait interrupted by signal\n"); | |
| return r; | |
| } else if (r == 0) { | |
| dev_warn(adev->dev, | |
| "IMPORT: NPA-RSP timeout from remote AccId:%u\n", | |
| remote_acc_id); | |
| r = -ETIMEDOUT; | |
| goto reset_conn; | |
| } | |
| /* If the NPA addr/size isn't filled with valid values, then either | |
| * we got a NPA_FAIL or something bad happened. In either case, we | |
| * return the error back to user-space. | |
| */ | |
| if (imp_xa_node->fail_reason) { | |
| dev_warn(adev->dev, | |
| "IMPORT: NPA-REQ failed with fail_reason:%d handle:%llx:%llx\n", | |
| imp_xa_node->fail_reason, handle.handle_hi, handle.handle_lo); | |
| return amdgpu_ualink_translate_npa_fail_reason(adev, | |
| imp_xa_node->fail_reason); | |
| } | |
| if (!imp_xa_node->npa_addr || !imp_xa_node->size) { | |
| dev_warn(adev->dev, | |
| "IMPORT: invalid npa:%llx or size:%llx\n", | |
| imp_xa_node->npa_addr, imp_xa_node->size); | |
| imp_xa_node->npa_addr = 0; | |
| imp_xa_node->size = 0; | |
| return -EINVAL; | |
| } | |
| r = amdgpu_ualink_map_npa_to_dmabuf(adev, imp_xa_node); | |
| if (r) { | |
| dev_warn(adev->dev, | |
| "IMPORT: dmabuf creation failed npa:%llx size:%llx\n", | |
| imp_xa_node->npa_addr, imp_xa_node->size); | |
| imp_xa_node->npa_addr = 0; | |
| imp_xa_node->size = 0; | |
| return r; | |
| } | |
| /* Add this node to the imported handles list for the remote GPU, | |
| * unless the exporter revoked the handle while the import was in | |
| * flight. The dmabuf is released with the last node reference. | |
| */ | |
| xa_lock(&adev->ualink.imp_xa); | |
| if (READ_ONCE(imp_xa_node->node_state) == AMDGPU_UALINK_NODE_TEARDOWN) { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| dev_dbg(adev->dev, | |
| "IMPORT: handle:%llx:%llx revoked during import\n", | |
| handle.handle_hi, handle.handle_lo); | |
| return -EINVAL; | |
| } | |
| list_add(&imp_xa_node->list, &adev->ualink.imp_handles_list[remote_acc_id]); | |
| WRITE_ONCE(imp_xa_node->node_state, AMDGPU_UALINK_NODE_READY); | |
| xa_unlock(&adev->ualink.imp_xa); | |
| return 0; | |
| reset_conn: | |
| dev_dbg(adev->dev, | |
| "IMPORT: Resetting connection for remote:%u\n", remote_acc_id); | |
| generation_count = amdgpu_ualink_check_conn_ready(adev, remote_acc_id, 0); | |
| amdgpu_ualink_handle_connection_reset(adev, remote_acc_id, | |
| AMDGPU_UALINK_CONN_NOT_READY, | |
| generation_count); | |
| return r; | |
| } | |
| static int amdgpu_ualink_local_import(struct amdgpu_device *adev, | |
| u32 remote_acc_id, | |
| struct amdgpu_ualink_handle *handle, | |
| int *fd_out) | |
| { | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| struct amdgpu_device *peer_adev; | |
| int fd; | |
| mutex_lock(&mgpu_info.mutex); | |
| peer_adev = find_peer_adev(remote_acc_id); | |
| mutex_unlock(&mgpu_info.mutex); | |
| if (!peer_adev) { | |
| dev_err(adev->dev, | |
| "IMPORT LOCAL: peer adev not found for AccId:%u\n", | |
| remote_acc_id); | |
| return -ENODEV; | |
| } | |
| xa_lock(&peer_adev->ualink.exp_xa); | |
| exp_xa_node = xa_load(&peer_adev->ualink.exp_xa, handle->handle_lo); | |
| if (!exp_xa_node || | |
| exp_xa_node->handle.handle_hi != handle->handle_hi || | |
| !amdgpu_ualink_exp_xa_entry_get(exp_xa_node)) { | |
| xa_unlock(&peer_adev->ualink.exp_xa); | |
| dev_err(adev->dev, | |
| "IMPORT LOCAL: handle:%llx:%llx not found in peer exp_xa\n", | |
| handle->handle_hi, handle->handle_lo); | |
| return -EINVAL; | |
| } | |
| xa_unlock(&peer_adev->ualink.exp_xa); | |
| get_dma_buf(exp_xa_node->dmabuf); | |
| /* Get a new fd for the DMABuf */ | |
| fd = dma_buf_fd(exp_xa_node->dmabuf, O_CLOEXEC | O_RDWR); | |
| if (fd < 0) { | |
| dev_err(adev->dev, | |
| "IMPORT LOCAL: dma-buf fd failed handle:%llx:%llx\n", | |
| handle->handle_hi, handle->handle_lo); | |
| dma_buf_put(exp_xa_node->dmabuf); | |
| amdgpu_ualink_exp_xa_entry_put(exp_xa_node); | |
| return fd; | |
| } | |
| amdgpu_ualink_exp_xa_entry_put(exp_xa_node); | |
| *fd_out = fd; | |
| return 0; | |
| } | |
| int amdgpu_ualink_import_handle(struct drm_device *dev, | |
| const struct amdgpu_ualink_handle *ualink_handle, | |
| int *fd_out) | |
| { | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node; | |
| struct amdgpu_device *adev = drm_to_adev(dev); | |
| struct amdgpu_ualink_handle handle = *ualink_handle; | |
| u32 remote_acc_id, node_state; | |
| int r = 0, fd; | |
| remote_acc_id = (handle.handle_lo & | |
| AMDGPU_UALINK_HANDLE_ACCID_MASK); | |
| if (remote_acc_id >= AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_err(adev->dev, | |
| "IMPORT: invalid remote AccId:%u\n", remote_acc_id); | |
| return -EINVAL; | |
| } | |
| if (amdgpu_ualink_is_local_accel(adev, remote_acc_id)) | |
| return amdgpu_ualink_local_import(adev, remote_acc_id, | |
| &handle, fd_out); | |
| xa_lock(&adev->ualink.imp_xa); | |
| imp_xa_node = xa_load(&adev->ualink.imp_xa, handle.handle_lo); | |
| if (imp_xa_node) { | |
| /* If node state is Not_ready/Pending, then some other | |
| * thread is already trying the NPA protocol for the same | |
| * ualink handle. Back off and let the thread finish. | |
| * If the node state is Teardown, then it means this | |
| * node is about to be removed. So let user-space know | |
| * that this handle is invalid. | |
| */ | |
| node_state = READ_ONCE(imp_xa_node->node_state); | |
| if (node_state == AMDGPU_UALINK_NODE_NOT_READY || | |
| node_state == AMDGPU_UALINK_NODE_PENDING) { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| r = -EAGAIN; | |
| goto out; | |
| } else if (node_state == AMDGPU_UALINK_NODE_TEARDOWN) { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| r = -EINVAL; | |
| goto out; | |
| } | |
| /* Increase the refcount while we are processing the request */ | |
| r = amdgpu_ualink_imp_xa_entry_get(imp_xa_node) ? 0 : -EINVAL; | |
| xa_unlock(&adev->ualink.imp_xa); | |
| /* If the refcount has become 0 but the entry is not yet | |
| * removed from the Xarray, then return error to user-space. | |
| */ | |
| if (r) | |
| goto out; | |
| } else { | |
| xa_unlock(&adev->ualink.imp_xa); | |
| /* if the partial handle doesn't exist in the Importer xarray then | |
| * initiate the NPA protocol and generate the DMABuf corresponding | |
| * to the NPA address. | |
| * First store the entry in the Xarray. | |
| */ | |
| imp_xa_node = kzalloc(sizeof(*imp_xa_node), GFP_KERNEL); | |
| if (!imp_xa_node) { | |
| r = -ENOMEM; | |
| goto out; | |
| } | |
| imp_xa_node->adev = adev; | |
| imp_xa_node->node_state = AMDGPU_UALINK_NODE_NOT_READY; | |
| imp_xa_node->handle = handle; | |
| init_completion(&imp_xa_node->npa_done); | |
| INIT_LIST_HEAD(&imp_xa_node->list); | |
| kref_init(&imp_xa_node->refcount); | |
| /* Take an extra reference to store in the Xarray. The error | |
| * handling paths will drop both these references, while a | |
| * successful path will drop only one reference to the Xarray | |
| * entry. | |
| */ | |
| amdgpu_ualink_imp_xa_entry_get(imp_xa_node); | |
| /* Check if another thread created a node for the same handle while | |
| * we were trying to create and initialize the node. | |
| */ | |
| r = xa_insert(&adev->ualink.imp_xa, handle.handle_lo, | |
| imp_xa_node, GFP_KERNEL); | |
| if (r) { | |
| kfree(imp_xa_node); | |
| /* -EBUSY means another thread raced us and inserted a | |
| * node for the same handle. Ask user-space to retry. | |
| */ | |
| if (r == -EBUSY) | |
| r = -EAGAIN; | |
| else | |
| dev_err(adev->dev, | |
| "IMPORT: XA insert failed for handle:%llx:%llx err:%d\n", | |
| handle.handle_hi, handle.handle_lo, r); | |
| goto out; | |
| } | |
| r = amdgpu_ualink_do_import_handle(adev, imp_xa_node, remote_acc_id); | |
| /* If error is returned, then cleanup the xarray entry before returning | |
| * the error back to user-space | |
| */ | |
| if (r) { | |
| amdgpu_ualink_imp_xa_entry_put(imp_xa_node); | |
| if (r != -EAGAIN) | |
| dev_err(adev->dev, | |
| "IMPORT: XA import failed for handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| goto cleanup; | |
| } | |
| } | |
| /* dma_buf_fd consumes a reference and assigns it to the fd. | |
| * Therefore take an extra reference to be consumed. It will be | |
| * released when user mode closes the fd. | |
| */ | |
| get_dma_buf(imp_xa_node->dmabuf); | |
| fd = dma_buf_fd(imp_xa_node->dmabuf, O_CLOEXEC | O_RDWR); | |
| if (fd >= 0) { | |
| *fd_out = fd; | |
| } else { | |
| dma_buf_put(imp_xa_node->dmabuf); | |
| r = fd; | |
| dev_err(adev->dev, | |
| "IMPORT: dma-buf fd creation failed handle:%llx:%llx\n", | |
| handle.handle_hi, handle.handle_lo); | |
| } | |
| cleanup: | |
| amdgpu_ualink_imp_xa_entry_put(imp_xa_node); | |
| out: | |
| return r; | |
| } | |
| int amdgpu_ualink_export_handle(struct drm_device *dev, struct drm_file *filp, | |
| u32 gem_handle, | |
| struct amdgpu_ualink_handle *handle_out) | |
| { | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| struct amdgpu_ualink_handle handle; | |
| struct drm_gem_object *gobj; | |
| struct amdgpu_device *adev; | |
| struct amdgpu_bo *robj; | |
| int r = 0, i; | |
| gobj = drm_gem_object_lookup(filp, gem_handle); | |
| if (!gobj) | |
| return -ENOENT; | |
| robj = gem_to_amdgpu_bo(gobj); | |
| adev = amdgpu_ttm_adev(robj->tbo.bdev); | |
| if (!(robj->preferred_domains & AMDGPU_GEM_DOMAIN_VRAM)) { | |
| dev_err(adev->dev, "Only VRAM BOs can be exported\n"); | |
| r = -EOPNOTSUPP; | |
| goto out; | |
| } | |
| if (!robj->ualink_handle_lo) { | |
| /* If no ualink handle generated for BO, then generate one and | |
| * add it to the exporter Xarray. | |
| */ | |
| exp_xa_node = kzalloc(sizeof(*exp_xa_node), GFP_KERNEL); | |
| if (!exp_xa_node) { | |
| dev_err(adev->dev, "Failed to allocate exp_xa_node\n"); | |
| r = -ENOMEM; | |
| goto out; | |
| } | |
| amdgpu_bo_ref(robj); | |
| exp_xa_node->bo = robj; | |
| init_completion(&exp_xa_node->npa_done); | |
| bitmap_zero(exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| bitmap_zero(exp_xa_node->npa_release_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| kref_init(&exp_xa_node->refcount); | |
| mutex_init(&exp_xa_node->node_lock); | |
| INIT_WORK(&exp_xa_node->cleanup_work, | |
| amdgpu_ualink_exp_cleanup_worker); | |
| for (i = 0; i < AMDGPU_UALINK_ACCEL_MAX; i++) { | |
| INIT_LIST_HEAD(&exp_xa_node->importer_entries[i].list); | |
| exp_xa_node->importer_entries[i].parent = exp_xa_node; | |
| } | |
| /* DMABuf handle for local import of fabric handles */ | |
| exp_xa_node->dmabuf = drm_gem_prime_handle_to_dmabuf(&adev->ddev, filp, | |
| gem_handle, DRM_CLOEXEC | DRM_RDWR); | |
| if (IS_ERR(exp_xa_node->dmabuf)) { | |
| r = PTR_ERR(exp_xa_node->dmabuf); | |
| dev_err(adev->dev, "Failed to generate DMABuf for the BO\n"); | |
| goto err_unref_bo; | |
| } | |
| xa_lock(&adev->ualink.exp_xa); | |
| amdgpu_generate_ualink_handle(adev, &handle); | |
| exp_xa_node->handle = handle; | |
| r = __xa_insert(&adev->ualink.exp_xa, handle.handle_lo, | |
| exp_xa_node, GFP_KERNEL); | |
| xa_unlock(&adev->ualink.exp_xa); | |
| if (r) { | |
| dev_err(adev->dev, "Failed to insert exp_xa_node into XA: %d\n", r); | |
| goto err_put_dmabuf; | |
| } | |
| r = amdgpu_bo_reserve(robj, false); | |
| if (r) | |
| goto err_erase_xa; | |
| if (robj->ualink_handle_lo) { | |
| amdgpu_bo_unreserve(robj); | |
| r = -EAGAIN; | |
| goto err_erase_xa; | |
| } | |
| robj->ualink_handle_lo = handle.handle_lo; | |
| amdgpu_bo_unreserve(robj); | |
| /* Return the generated handle back to the caller */ | |
| *handle_out = handle; | |
| } else { | |
| handle_out->handle_lo = robj->ualink_handle_lo; | |
| /* Do a sanity check to ensure the handle exists in the XA */ | |
| xa_lock(&adev->ualink.exp_xa); | |
| exp_xa_node = xa_load(&adev->ualink.exp_xa, | |
| robj->ualink_handle_lo); | |
| xa_unlock(&adev->ualink.exp_xa); | |
| WARN(!exp_xa_node, "Exp XA: Handle_Lo: %llx not found", | |
| robj->ualink_handle_lo); | |
| if (exp_xa_node) | |
| handle_out->handle_hi = exp_xa_node->handle.handle_hi; | |
| } | |
| drm_gem_object_put(gobj); | |
| return 0; | |
| err_erase_xa: | |
| xa_erase(&adev->ualink.exp_xa, handle.handle_lo); | |
| err_put_dmabuf: | |
| dma_buf_put(exp_xa_node->dmabuf); | |
| err_unref_bo: | |
| amdgpu_bo_unref(&exp_xa_node->bo); | |
| kfree(exp_xa_node); | |
| out: | |
| drm_gem_object_put(gobj); | |
| return r; | |
| } | |
| int amdgpu_gem_ualink_handle_ioctl(struct drm_device *dev, void *data, | |
| struct drm_file *filp) | |
| { | |
| union drm_amdgpu_ualink_handle *args = data; | |
| struct amdgpu_device *adev = drm_to_adev(dev); | |
| struct amdgpu_ualink_handle handle = {}; | |
| u32 gem_handle; | |
| int r, fd = -1; | |
| /* The ioctl is registered for every amdgpu device, but the UALink | |
| * software state only exists on ASICs that instantiate the UALink IP | |
| * block. Bail out if it is absent, and short-circuit before the | |
| * accel_state dereference below. | |
| */ | |
| if (!adev->ualink.info || | |
| adev->ualink.info->accel_state != AMDGPU_UALINK_ACCEL_STATE_ACTIVE) { | |
| dev_dbg(adev->dev, | |
| "ualink device not available or not in active state\n"); | |
| return -EOPNOTSUPP; | |
| } | |
| /* The input and output members of the ioctl argument alias each other. | |
| * Latch every input field before invoking the handlers, and only write | |
| * the output fields afterwards. | |
| */ | |
| switch (args->in.op) { | |
| case DRM_AMDGPU_UALINK_HANDLE_OP_EXPORT: | |
| gem_handle = args->in.gem_handle; | |
| r = amdgpu_ualink_export_handle(dev, filp, gem_handle, &handle); | |
| if (!r) { | |
| args->out.export_ualink_handle[0] = handle.handle_lo; | |
| args->out.export_ualink_handle[1] = handle.handle_hi; | |
| } | |
| break; | |
| case DRM_AMDGPU_UALINK_HANDLE_OP_IMPORT: | |
| handle.handle_lo = args->in.import_ualink_handle[0]; | |
| handle.handle_hi = args->in.import_ualink_handle[1]; | |
| r = amdgpu_ualink_import_handle(dev, &handle, &fd); | |
| if (!r) | |
| args->out.import_dmabuf_handle = fd; | |
| break; | |
| default: | |
| r = -EINVAL; | |
| break; | |
| } | |
| return r; | |
| } | |
| int amdgpu_ualink_manager_start(struct amdgpu_device *adev) | |
| { | |
| int i, r; | |
| r = amdgpu_vm_init(adev, &adev->ualink.npa_vm, 0); | |
| if (r) | |
| goto out; | |
| /* For pinning page tables and using CPU for page table updates. */ | |
| r = amdgpu_vm_make_npa(adev, &adev->ualink.npa_vm); | |
| if (r) | |
| goto uninit_vm; | |
| adev->ualink.npa_wq = alloc_workqueue("NPA WQ", WQ_UNBOUND, 0); | |
| if (unlikely(!adev->ualink.npa_wq)) { | |
| dev_err(adev->dev, "Failed to allocate NPA WQ\n"); | |
| r = -ENOMEM; | |
| goto uninit_vm; | |
| } | |
| /* Map this VM to NPA VMID */ | |
| adev->mmhub.funcs->setup_vm_pt_regs(adev, adev->vm_manager.npa_vmid, | |
| amdgpu_gmc_pd_addr(adev->ualink.npa_vm.root.bo)); | |
| xa_init(&adev->ualink.exp_xa); | |
| xa_init(&adev->ualink.imp_xa); | |
| xa_init(&adev->ualink.handle_invalid_xa); | |
| for (i = 0; i < AMDGPU_UALINK_ACCEL_MAX; i++) { | |
| init_completion(&adev->ualink.conn_state[i].hello_done); | |
| mutex_init(&adev->ualink.conn_state[i].lock); | |
| adev->ualink.conn_state[i].state = AMDGPU_UALINK_CONN_NOT_READY; | |
| INIT_LIST_HEAD(&adev->ualink.exp_handles_list[i]); | |
| INIT_LIST_HEAD(&adev->ualink.imp_handles_list[i]); | |
| } | |
| amdgpu_ualink_npa_mm_init(adev); | |
| return 0; | |
| uninit_vm: | |
| amdgpu_vm_fini(adev, &adev->ualink.npa_vm); | |
| out: | |
| return r; | |
| } | |
| /* Free every importer entry left in imp_xa at manager stop. */ | |
| static void amdgpu_ualink_teardown_imp_xa_entries(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_imp_xa_node *imp_xa_node; | |
| unsigned long index; | |
| xa_for_each(&adev->ualink.imp_xa, index, imp_xa_node) { | |
| list_del_init(&imp_xa_node->list); | |
| WRITE_ONCE(imp_xa_node->node_state, AMDGPU_UALINK_NODE_TEARDOWN); | |
| amdgpu_ualink_release_imp_xa_node(adev, imp_xa_node); | |
| } | |
| } | |
| /* Free a single exporter node at manager stop. This is the message-free | |
| * portion of amdgpu_ualink_exp_cleanup_worker(). | |
| */ | |
| static void amdgpu_ualink_teardown_exp_xa_node(struct amdgpu_device *adev, | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node) | |
| { | |
| DECLARE_BITMAP(importers_bitmap, AMDGPU_UALINK_ACCEL_MAX); | |
| struct amdgpu_bo *bo = exp_xa_node->bo; | |
| u32 remote_acc_id; | |
| bitmap_copy(importers_bitmap, exp_xa_node->importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| dev_dbg(adev->dev, | |
| "EXP-STOP: handle:%llx:%llx importers bitmap: %*pbl\n", | |
| exp_xa_node->handle.handle_hi, exp_xa_node->handle.handle_lo, | |
| AMDGPU_UALINK_ACCEL_MAX, importers_bitmap); | |
| if (!bitmap_empty(importers_bitmap, AMDGPU_UALINK_ACCEL_MAX)) { | |
| amdgpu_ualink_unmap_all_npa_addr(adev, exp_xa_node); | |
| /* Unlink importers and unpin the BO once per importer. */ | |
| if (likely(!amdgpu_bo_reserve(bo, true))) { | |
| bo->ualink_handle_lo = 0ULL; | |
| for_each_set_bit(remote_acc_id, importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) { | |
| list_del_init(&exp_xa_node->importer_entries[remote_acc_id].list); | |
| amdgpu_bo_unpin(bo); | |
| } | |
| amdgpu_bo_unreserve(bo); | |
| } else { | |
| dev_warn(adev->dev, | |
| "EXP-STOP: BO reserve to unpin failed handle:%llx:%llx\n", | |
| exp_xa_node->handle.handle_hi, | |
| exp_xa_node->handle.handle_lo); | |
| for_each_set_bit(remote_acc_id, importers_bitmap, | |
| AMDGPU_UALINK_ACCEL_MAX) | |
| list_del_init(&exp_xa_node->importer_entries[remote_acc_id].list); | |
| } | |
| amdgpu_ualink_free_all_npa_va(adev, exp_xa_node, importers_bitmap); | |
| } | |
| /* Release the export dma-buf and drop the BO ref. */ | |
| dma_buf_put(exp_xa_node->dmabuf); | |
| amdgpu_bo_unref(&bo); | |
| exp_xa_node->bo = NULL; | |
| mutex_destroy(&exp_xa_node->node_lock); | |
| kfree(exp_xa_node); | |
| } | |
| /* Free every exporter entry left in exp_xa at manager stop. These were | |
| * exported but never revoked, so no cleanup worker was queued for them. | |
| */ | |
| static void amdgpu_ualink_teardown_exp_xa_entries(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_exp_xa_node *exp_xa_node; | |
| unsigned long index; | |
| xa_for_each(&adev->ualink.exp_xa, index, exp_xa_node) { | |
| xa_erase(&adev->ualink.exp_xa, index); | |
| amdgpu_ualink_teardown_exp_xa_node(adev, exp_xa_node); | |
| } | |
| } | |
| void amdgpu_ualink_manager_stop(struct amdgpu_device *adev) | |
| { | |
| int i; | |
| adev->mmhub.funcs->setup_vm_pt_regs(adev, adev->vm_manager.npa_vmid, 0); | |
| /* Mark connections down so the drained workers and the teardown below | |
| * take the message-free path in amdgpu_ualink_check_conn_ready(). | |
| */ | |
| for (i = 0; i < AMDGPU_UALINK_ACCEL_MAX; i++) { | |
| mutex_lock(&adev->ualink.conn_state[i].lock); | |
| adev->ualink.conn_state[i].state = AMDGPU_UALINK_CONN_NOT_READY; | |
| mutex_unlock(&adev->ualink.conn_state[i].lock); | |
| } | |
| /* Drain in-flight exporter cleanup work so revoked nodes in | |
| * handle_invalid_xa free themselves and empty that xarray. | |
| */ | |
| drain_workqueue(adev->ualink.npa_wq); | |
| /* Free live entries before the NPA allocator is torn down, so NPA | |
| * addresses can still be unmapped and freed. | |
| */ | |
| amdgpu_ualink_teardown_imp_xa_entries(adev); | |
| amdgpu_ualink_teardown_exp_xa_entries(adev); | |
| amdgpu_ualink_npa_mm_fini(adev); | |
| /* All three xarrays are empty by now. */ | |
| xa_destroy(&adev->ualink.exp_xa); | |
| xa_destroy(&adev->ualink.imp_xa); | |
| xa_destroy(&adev->ualink.handle_invalid_xa); | |
| for (i = 0; i < AMDGPU_UALINK_ACCEL_MAX; i++) | |
| mutex_destroy(&adev->ualink.conn_state[i].lock); | |
| destroy_workqueue(adev->ualink.npa_wq); | |
| amdgpu_vm_fini(adev, &adev->ualink.npa_vm); | |
| } | |
| /* | |
| * UALink remote interrupt and shootdown | |
| */ | |
| /* UALINK ring buffer size, same for both remote shootdown and interrupt ring */ | |
| #define AMDGPU_UALINK_RB_SIZE 4096 | |
| #define AMDGPU_UALINK_METADATA_HEADER 0x4E485446 | |
| /* 2MB NPA start address for 2MB page mapping */ | |
| #define AMDGPU_UALINK_SOURCE_ALIAS_NPA_OFFSET SZ_2M | |
| struct amdgpu_ualink_metadata { | |
| u32 header; | |
| /* | |
| * "ring entries" as unit. So the ring-size-in-bytes could be calculated | |
| * as entry-size * 2^RBsize. This would make the minimum size a single | |
| * entry and the maximum size 32786 entries. | |
| * | |
| * u32 rb_size:4; | |
| * u32 reserved0:4; | |
| * u32 vmid:4; | |
| * u32 reserved1:20; | |
| */ | |
| u32 rb_size; | |
| /* ring buffer base address for remote interrupt */ | |
| u64 ri_rb; | |
| /* tlb invalidate ring buffer base address for remote shootdown */ | |
| u64 tlb_inv_rb; | |
| /* remote interrupt Tail pointer, write pointer address */ | |
| u64 tailptr_ri; | |
| /* TLB invalidate ring buffer's Tail pointer, write pointer address */ | |
| u64 tailptr_tlb_inv; | |
| }; | |
| struct amdgpu_ualink_wb { | |
| /* last finished command seq number */ | |
| u32 data; | |
| /* command complete error code */ | |
| u32 status; | |
| /* ring rptr updated by FW */ | |
| u64 rptr; | |
| }; | |
| /* | |
| * For remote interrupt and shootdown | |
| */ | |
| struct amdgpu_ualink_ring { | |
| u32 rb_size; | |
| /* writeback data */ | |
| u32 seq; | |
| /* local copy */ | |
| u64 wptr, rptr; | |
| /* NPA gart mapping for SDMA */ | |
| u64 rb_npa_gart; | |
| u64 wptr_npa_gart; | |
| u64 doorbell_npa_gart; | |
| /* gart mapping node */ | |
| struct drm_mm_node mm_node_rb; | |
| struct drm_mm_node mm_node_wptr; | |
| struct drm_mm_node mm_node_doorbell; | |
| /* seq, complete status write back cpu address */ | |
| struct amdgpu_ualink_wb *wb_cpu; | |
| /* true if fw write back address updated successfully */ | |
| bool ready; | |
| }; | |
| struct amdgpu_ualink_peer { | |
| /* remote interrupt and shootdown uses same SDMA entity */ | |
| struct mutex lock; | |
| /* SDMA engine to send remote command via NPA */ | |
| struct drm_sched_entity entity; | |
| /* to select different DXS ports, cycles through different values */ | |
| u32 dxs_port; | |
| struct amdgpu_ualink_ring interrupt; | |
| struct amdgpu_ualink_ring shootdown; | |
| }; | |
| struct amdgpu_ualink_remote { | |
| /* ualink metadata passed to MPNHT FW */ | |
| struct amdgpu_bo *metadata_bo; | |
| u64 metadata_gpu_addr; | |
| void *metadata_cpu_addr; | |
| /* ualink ring, tlb ring buffer, wptr */ | |
| struct amdgpu_bo *ring_bo; | |
| u64 rb_gpu_addr; | |
| void *rb_cpu_addr; | |
| /* ualink rptr, wb data, statuss for address alias mode */ | |
| struct amdgpu_bo *rptr_bo; | |
| u64 rptr_gpu_addr; | |
| void *rptr_cpu_addr; | |
| u64 rptr_npa; | |
| /* address alias mode alloc npa address for shared wb */ | |
| struct drm_mm_node rptr_mm_node; | |
| /* | |
| * Owned snapshot of the vPod's active accelerator bitmap, taken at | |
| * sw_init time. Must NOT alias info->vpod.active_accel_bits, which | |
| * psp_ual_query_info() refreshes on every commit: aliasing it would let | |
| * teardown unmap a different set than setup mapped, leaking drm_mm nodes | |
| * in the shared GTT manager. | |
| */ | |
| DECLARE_BITMAP(active_accel_bits, AMDGPU_UALINK_ACCEL_MAX); | |
| u32 num_accel; | |
| /* remote GPUs ring buffer, read, write pointer local copy and gart mapping */ | |
| struct amdgpu_ualink_peer peer[AMDGPU_UALINK_ACCEL_MAX]; | |
| /* use lsdma write to NPA address for remote interrupt */ | |
| bool use_lsdma; | |
| }; | |
| static inline struct amdgpu_ualink_remote *to_remote(struct amdgpu_device *adev) | |
| { | |
| return adev->ualink.remote; | |
| } | |
| /* | |
| * Returns true if the vPod membership most recently reported by firmware | |
| * (info->vpod.active_accel_bits, read back via psp_ual_query_info() on the | |
| * committing GPU) differs from the owned snapshot captured when this | |
| * accelerator was last activated (remote->active_accel_bits). Used to | |
| * decide whether an accelerator must be (re)built to match the new member | |
| * set. A NULL remote means the accelerator has never been activated (no | |
| * snapshot yet), which also counts as "changed" so it gets brought up. | |
| */ | |
| static bool amdgpu_ualink_vpod_membership_changed(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| if (!remote) | |
| return true; | |
| return !bitmap_equal(remote->active_accel_bits, | |
| adev->ualink.info->vpod.active_accel_bits, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| } | |
| static inline u32 ualink_accel_id(struct amdgpu_device *adev) | |
| { | |
| return adev->ualink.info->ppod.accel_id; | |
| } | |
| static inline enum amdgpu_ualink_addr_mode ualink_addr_mode(struct amdgpu_device *adev) | |
| { | |
| return adev->ualink.info->vpod.addr_mode; | |
| } | |
| /* | |
| * address mode and NPA address for ring wptr, rptr, wb status, wb data | |
| * | |
| * source identification address mode | |
| * | |
| * we need to export different tail pointers for different remote GPU accel_id, | |
| * hence we need to allocate a whole page for each remote GPU separately. | |
| * | |
| * rptr, wb data, status share the whole page with wptr. | |
| * | |
| * 1 page reserved NPA address for 1 active remote GPU, max 255 pages | |
| * starting address: wptr NPA address + accel_id * 4K | |
| * offset: | |
| * remote interrupt ring wptr 0 | |
| * remote shootdown ring wptr 8 | |
| * remote interrupt wb status 16 | |
| * remote interrupt wb data 20 | |
| * remote interrupt ring rptr 24 | |
| * remote shootdown wb status 32 | |
| * remote shootdown wb data 36 | |
| * remote shootdown ring rptr 40 | |
| * | |
| * source aliasing address mode | |
| * | |
| * wptr for different remote GPU can share same reserved 1 page NPA address page. | |
| * | |
| * to save reserved NPA address space, rptr, wb data, status for different | |
| * remote GPU share same allocated 2 pages NPA address. | |
| * | |
| * 1 page reserved NPA address for wptr of all active remote GPUs | |
| * offset: | |
| * remote interrupt ring wptr accel_id * 16 | |
| * remote shootdown ring wptr accel_id * 16 + 8 | |
| * | |
| * 2 page allocated NPA address for rptr, writeback of all active remote GPUs | |
| * starting address: at accel_id * 32 for each remote GPU | |
| * offset: | |
| * remote interrupt wb status 0 | |
| * remote interrupt wb data 4 | |
| * remote interrupt ring rptr 8 | |
| * remote shootdown wb status 16 | |
| * remote shootdown wb data 20 | |
| * remote shootdown ring rptr 24 | |
| */ | |
| static inline u32 ualink_wptr_size(struct amdgpu_device *adev) | |
| { | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) | |
| return AMDGPU_GPU_PAGE_SIZE; | |
| else | |
| return 0; /* wptr uses the ring buffer for itself */ | |
| } | |
| static inline u32 ualink_wb_size(struct amdgpu_device *adev) | |
| { | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) | |
| return 0; /* share same page with wptr */ | |
| else | |
| return 2 * sizeof(struct amdgpu_ualink_wb); | |
| } | |
| static inline u32 ualink_wptr_offset(struct amdgpu_device *adev, u32 accel_id) | |
| { | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) | |
| return 0; /* one separate page per GPU */ | |
| else | |
| return accel_id * 2 * sizeof(u64); /* two pointers per GPU */ | |
| } | |
| static inline u32 ualink_tlb_wptr_offset(struct amdgpu_device *adev, u32 accel_id) | |
| { | |
| return ualink_wptr_offset(adev, accel_id) + sizeof(u64); | |
| } | |
| static inline u32 ualink_wb_offset(struct amdgpu_device *adev, u32 accel_id) | |
| { | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) | |
| return 2 * sizeof(u64); /* after two wptr pointers */ | |
| else | |
| return accel_id * ualink_wb_size(adev); | |
| } | |
| static inline u32 ualink_tlb_wb_offset(struct amdgpu_device *adev, u32 accel_id) | |
| { | |
| return ualink_wb_offset(adev, accel_id) + sizeof(struct amdgpu_ualink_wb); | |
| } | |
| static void amdgpu_ualink_flush_tlb(struct amdgpu_device *adev, u32 flush_type) | |
| { | |
| u32 bit; | |
| bit = AMDGPU_MMHUB0_START; | |
| for_each_set_bit_from(bit, adev->vmhubs_mask, AMDGPU_MAX_VMHUBS) | |
| amdgpu_gmc_flush_gpu_tlb(adev, adev->vm_manager.npa_vmid, | |
| bit, flush_type); | |
| } | |
| /** | |
| * amdgpu_ualink_npa_vm_map_range - Map a range in the NPA VM | |
| * @adev: amdgpu device pointer | |
| * @bo: buffer object backing the mapping | |
| * @pte_flags: page table entry flags | |
| * @offset: offset into the buffer object in bytes | |
| * @size_in_pages: size of the range to map in pages | |
| * @npa_in_pages: NPA target address in pages | |
| * | |
| * Maps a buffer object range into the NPA VM page table at the specified | |
| * NPA address. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_npa_vm_map_range(struct amdgpu_device *adev, struct amdgpu_bo *bo, | |
| u64 pte_flags, u64 offset, u64 size_in_pages, | |
| u64 npa_in_pages) | |
| { | |
| struct amdgpu_vm *npa_vm = &adev->ualink.npa_vm; | |
| int r; | |
| dev_dbg(adev->dev, "offset 0x%llx size 0x%llx flags 0x%llx npa 0x%llx\n", | |
| offset, size_in_pages << AMDGPU_GPU_PAGE_SHIFT, pte_flags, | |
| npa_in_pages << AMDGPU_GPU_PAGE_SHIFT); | |
| r = amdgpu_vm_update_range(adev, npa_vm, false, false, true, | |
| false, NULL, npa_in_pages, | |
| npa_in_pages + size_in_pages - 1, | |
| pte_flags, offset, adev->vm_manager.vram_base_offset, | |
| bo->tbo.resource, NULL, &npa_vm->last_update); | |
| if (r) | |
| dev_dbg(adev->dev, "failed %d to map npa 0x%llx to NPA VM\n", r, | |
| npa_in_pages << AMDGPU_GPU_PAGE_SHIFT); | |
| return r; | |
| } | |
| /** | |
| * amdgpu_npa_vm_unmap_range - Unmap a range from the NPA VM | |
| * @adev: amdgpu device pointer | |
| * @bo: buffer object backing the mapping | |
| * @pte_flags: page table entry flags | |
| * @offset: offset into the buffer object in bytes | |
| * @size_in_pages: size of the range to unmap in pages | |
| * @npa_in_pages: NPA target address in pages | |
| * | |
| * Removes a previously established mapping from the NPA VM page table. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_npa_vm_unmap_range(struct amdgpu_device *adev, struct amdgpu_bo *bo, | |
| u64 pte_flags, u64 offset, u64 size_in_pages, | |
| u64 npa_in_pages) | |
| { | |
| struct amdgpu_vm *npa_vm = &adev->ualink.npa_vm; | |
| int r; | |
| dev_dbg(adev->dev, "offset 0x%llx size 0x%llx flags 0x%llx npa 0x%llx\n", | |
| offset, size_in_pages << AMDGPU_GPU_PAGE_SHIFT, pte_flags, | |
| npa_in_pages << AMDGPU_GPU_PAGE_SHIFT); | |
| r = amdgpu_vm_update_range(adev, npa_vm, false, false, true, | |
| false, NULL, npa_in_pages, | |
| npa_in_pages + size_in_pages - 1, | |
| pte_flags, offset, 0, bo->tbo.resource, NULL, | |
| &npa_vm->last_update); | |
| if (r) | |
| dev_dbg(adev->dev, "failed %d to unmap npa 0x%llx from NPA VM\n", r, | |
| npa_in_pages << AMDGPU_GPU_PAGE_SHIFT); | |
| return r; | |
| } | |
| /* | |
| * Reserved NPA space for remote shootdown and interrupt ring buffer, | |
| * write pointers, read pointers and writeback buffers | |
| * | |
| * NPA ring type: 0 = shootdown, 1 = interrupt, 2 = tailptr (wptr) | |
| */ | |
| enum ring_buffer_type { | |
| RB_TYPE_TLB_INV = 0, | |
| RB_TYPE_REMOTE_INTERRUPT = 1, | |
| RB_TYPE_TAILPTR = 2 | |
| }; | |
| /** | |
| * amdgpu_ualink_npa_addr - Get reserved NPA address for ring buffer | |
| * @adev: amdgpu device pointer | |
| * @type: Ring buffer type (TLB_INV, REMOTE_INTERRUPT, or TAILPTR) | |
| * @src_accel_id: Source accelerator ID | |
| * @dst_accel_id: Destination accelerator ID | |
| * | |
| * Return: NPA address for the specified ring buffer type and GPUs | |
| */ | |
| static u64 amdgpu_ualink_npa_addr(struct amdgpu_device *adev, u32 type, | |
| u32 src_accel_id, u32 dst_accel_id) | |
| { | |
| u32 addr_mode = ualink_addr_mode(adev); | |
| u64 npa; | |
| WARN_ON_ONCE(src_accel_id >= AMDGPU_UALINK_ACCEL_MAX || | |
| dst_accel_id >= AMDGPU_UALINK_ACCEL_MAX || | |
| type > RB_TYPE_TAILPTR || | |
| (addr_mode != AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT && | |
| addr_mode != AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS)); | |
| switch (addr_mode) { | |
| case AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT: | |
| npa = (u64)src_accel_id << 41 | type << 12; | |
| break; | |
| case AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS: | |
| default: | |
| if (type == RB_TYPE_TAILPTR) | |
| npa = (u64)dst_accel_id << 13; | |
| else | |
| npa = (u64)src_accel_id << 13 | type << 12; | |
| /* | |
| * In order to avoid address conflicts between source-identification | |
| * and source-aliasing mode, add 2MB to the buffer addresses in | |
| * source-aliasing mode. This way in a misconfigured cluster, a GPU | |
| * configured in the wrong address mode will access unmapped NPA addresses | |
| * rather than the wrong buffer mappings. | |
| */ | |
| npa += AMDGPU_UALINK_SOURCE_ALIAS_NPA_OFFSET; | |
| break; | |
| } | |
| dev_dbg(adev->dev, "addr mode %d from accel %d to accel %d type %d NPA 0x%llx\n", | |
| addr_mode, src_accel_id, dst_accel_id, type, npa); | |
| return npa; | |
| } | |
| /** | |
| * amdgpu_ualink_gart_npa_addr - Get GART-mapped NPA address for ring buffer | |
| * @adev: amdgpu device pointer | |
| * @type: Ring buffer type (TLB_INV, REMOTE_INTERRUPT, or TAILPTR) | |
| * @src_accel_id: Source accelerator ID | |
| * @dst_accel_id: Destination accelerator ID | |
| * | |
| * Computes the NPA address suitable for GART mapping by adding dst_accid_id to | |
| * NPA address to access remote by SDMA. | |
| * clears the source GPU ID bits from the NPA address which is set for | |
| * address-identification mode. | |
| * | |
| * Return: NPA address adjusted for GART mapping | |
| */ | |
| #define AMDGPU_UALINK_GART_NPA_ADDR_GPUID_SHIFT 41 | |
| #define AMDGPU_UALINK_GART_NPA_ADDR_GPUID_MASK GENMASK_ULL(50, 41) | |
| static u64 amdgpu_ualink_gart_npa_addr(struct amdgpu_device *adev, u32 type, | |
| u32 src_accel_id, u32 dst_accel_id) | |
| { | |
| u64 npa; | |
| npa = amdgpu_ualink_npa_addr(adev, type, src_accel_id, dst_accel_id); | |
| /* wiping out source accelerator id */ | |
| npa &= ~AMDGPU_UALINK_GART_NPA_ADDR_GPUID_MASK; | |
| return npa | ((u64)dst_accel_id << AMDGPU_UALINK_GART_NPA_ADDR_GPUID_SHIFT); | |
| } | |
| /** | |
| * amdgpu_ualink_reserve_npa_vm_and_bos - Reserve the NPA VM page directory and BOs | |
| * @adev: amdgpu device pointer | |
| * @bos: array of buffer objects to reserve | |
| * @n_bos: number of entries in @bos | |
| * @exec: drm_exec context to initialize and use for locking | |
| * @interruptible: true to use the interruptible dma_resv_lock | |
| * | |
| * Initializes @exec and uses it to lock all BOs in @bos together with the | |
| * NPA VM page directory, retrying on contention. On failure, @exec is | |
| * finalized and the error code is returned. | |
| * | |
| * Returns: 0 on success, negative error code on failure. | |
| */ | |
| static int amdgpu_ualink_reserve_npa_vm_and_bos(struct amdgpu_device *adev, | |
| struct amdgpu_bo *bos[], u32 n_bos, | |
| struct drm_exec *exec, | |
| bool interruptible) | |
| { | |
| u32 flags = DRM_EXEC_IGNORE_DUPLICATES; | |
| int i, r = 0; | |
| if (interruptible) | |
| flags |= DRM_EXEC_INTERRUPTIBLE_WAIT; | |
| dev_dbg(adev->dev, "reserve NPA vm and %d bos\n", n_bos); | |
| drm_exec_init(exec, flags, 0); | |
| drm_exec_until_all_locked(exec) { | |
| for (i = 0; i < n_bos; i++) { | |
| r = drm_exec_lock_obj(exec, &bos[i]->tbo.base); | |
| drm_exec_retry_on_contention(exec); | |
| if (unlikely(r)) | |
| goto out; | |
| } | |
| r = amdgpu_vm_lock_pd(&adev->ualink.npa_vm, exec, 0); | |
| drm_exec_retry_on_contention(exec); | |
| if (unlikely(r)) | |
| goto out; | |
| } | |
| out: | |
| if (r) | |
| drm_exec_fini(exec); | |
| return r; | |
| } | |
| /** | |
| * amdgpu_ualink_unreserve_npa_vm_and_bos - Release the NPA VM page directory and BOs | |
| * @adev: amdgpu device pointer | |
| * @exec: drm_exec context previously initialized by | |
| * amdgpu_ualink_reserve_npa_vm_and_bos() | |
| * | |
| * Finalizes @exec, releasing all locks on the NPA VM page directory and | |
| * the associated BOs acquired during reservation. | |
| */ | |
| static void amdgpu_ualink_unreserve_npa_vm_and_bos(struct amdgpu_device *adev, | |
| struct drm_exec *exec) | |
| { | |
| dev_dbg(adev->dev, "unreserve NPA vm and bos\n"); | |
| drm_exec_fini(exec); | |
| } | |
| /** | |
| * amdgpu_ualink_metadata_npa_unmapping - Tear down NPA address mappings | |
| * @adev: amdgpu device pointer | |
| * | |
| * Unmaps all NPA address mappings from the NPA VM for ring buffers, | |
| * write pointers, and read pointers. Waits for outstanding DMA fences | |
| * and flushes the TLB. | |
| */ | |
| static void amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| u64 timeout = msecs_to_jiffies(2000); | |
| u32 dst_accel_id = ualink_accel_id(adev); | |
| struct amdgpu_bo *bos[2]; | |
| u32 n_bos; | |
| struct dma_fence *fence; | |
| struct drm_exec exec; | |
| u32 rptr_size, rptr_size_in_pages; | |
| u32 rb_size, rb_size_in_pages; | |
| u64 pte_flags = adev->gmc.noretry_flags; | |
| u64 npa; | |
| int r; | |
| if (!remote->ring_bo) | |
| return; | |
| rb_size = AMDGPU_UALINK_RB_SIZE; | |
| rb_size_in_pages = rb_size >> AMDGPU_GPU_PAGE_SHIFT; | |
| bos[0] = remote->ring_bo; | |
| n_bos = 1; | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) { | |
| bos[1] = remote->rptr_bo; | |
| n_bos = 2; | |
| } | |
| r = amdgpu_ualink_reserve_npa_vm_and_bos(adev, bos, n_bos, &exec, false); | |
| if (unlikely(r)) | |
| return; | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) { | |
| /* rptr npa mapping, up to allocated 2 pages npa address */ | |
| rptr_size = ualink_wb_size(adev); | |
| rptr_size = AMDGPU_GPU_PAGE_ALIGN(rptr_size * remote->num_accel); | |
| rptr_size_in_pages = rptr_size >> AMDGPU_GPU_PAGE_SHIFT; | |
| npa = remote->rptr_npa; | |
| amdgpu_ualink_npa_vm_unmap_range(adev, remote->rptr_bo, | |
| pte_flags, 0, rptr_size_in_pages, | |
| npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| amdgpu_ualink_npa_free_va(adev, &remote->rptr_mm_node); | |
| /* | |
| * unmap wptr, remote interrupt, shootdown ring. | |
| * wptr page is part of the single 2MB mapping for remote GPUs | |
| * interrupt and shootdown ring buffer | |
| */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TLB_INV, | |
| 0, dst_accel_id); | |
| amdgpu_ualink_npa_vm_unmap_range(adev, remote->ring_bo, pte_flags, | |
| 0, | |
| 2 * rb_size_in_pages * AMDGPU_UALINK_ACCEL_MAX, | |
| npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| } else { | |
| u32 wptr_offset = 2 * rb_size * remote->num_accel; | |
| u32 idx = 0; | |
| u32 accel_id; | |
| /* source identification mode */ | |
| for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) { | |
| if (accel_id == dst_accel_id) | |
| continue; | |
| /* remote interrupt ring npa mapping */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_REMOTE_INTERRUPT, | |
| accel_id, dst_accel_id); | |
| amdgpu_ualink_npa_vm_unmap_range(adev, remote->ring_bo, | |
| pte_flags, idx * 2 * rb_size, | |
| rb_size_in_pages, | |
| npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| /* remote shootdown ring npa mapping */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TLB_INV, accel_id, | |
| dst_accel_id); | |
| amdgpu_ualink_npa_vm_unmap_range(adev, remote->ring_bo, | |
| pte_flags, (idx * 2 + 1) * rb_size, | |
| rb_size_in_pages, | |
| npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| /* wptr, rptr npa mapping */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TAILPTR, accel_id, | |
| dst_accel_id); | |
| amdgpu_ualink_npa_vm_unmap_range(adev, remote->ring_bo, | |
| pte_flags, wptr_offset + idx * PAGE_SIZE, | |
| 1, npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| idx++; | |
| } | |
| } | |
| r = amdgpu_vm_update_pdes(adev, &adev->ualink.npa_vm, false); | |
| if (r) { | |
| dev_dbg(adev->dev, "failed %d to update directories\n", r); | |
| goto out_unreserve; | |
| } | |
| fence = dma_fence_get(adev->ualink.npa_vm.last_update); | |
| if (fence) { | |
| r = dma_fence_wait_timeout(fence, true, timeout); | |
| dma_fence_put(fence); | |
| if (r <= 0) | |
| dev_dbg(adev->dev, "failed %d to dma fence wait\n", r); | |
| } | |
| amdgpu_ualink_flush_tlb(adev, TLB_FLUSH_HEAVYWEIGHT); | |
| out_unreserve: | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| } | |
| /** | |
| * amdgpu_ualink_metadata_npa_mapping - Setup NPA address mapping in VM | |
| * @adev: amdgpu device pointer | |
| * | |
| * Maps NPA addresses to GPA for metadata ring buffers, write pointers, | |
| * on NPA VMID 15. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_metadata_npa_mapping(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| u32 dst_accel_id = ualink_accel_id(adev); | |
| u64 timeout = msecs_to_jiffies(2000); | |
| struct amdgpu_bo *bos[2]; | |
| u32 n_bos; | |
| struct dma_fence *fence; | |
| struct drm_exec exec; | |
| u32 rb_size, rb_size_in_pages; | |
| u64 npa, npa_in_pages, pte_flags; | |
| int r; | |
| rb_size = AMDGPU_UALINK_RB_SIZE; | |
| rb_size_in_pages = rb_size >> AMDGPU_GPU_PAGE_SHIFT; | |
| bos[0] = remote->ring_bo; | |
| n_bos = 1; | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) { | |
| bos[1] = remote->rptr_bo; | |
| n_bos = 2; | |
| } | |
| r = amdgpu_ualink_reserve_npa_vm_and_bos(adev, bos, n_bos, &exec, false); | |
| if (unlikely(r)) | |
| return r; | |
| pte_flags = amdgpu_ttm_tt_pte_flags(adev, remote->ring_bo->tbo.ttm, | |
| remote->ring_bo->tbo.resource); | |
| dev_dbg(adev->dev, "init pte_flags 0x%llx\n", pte_flags); | |
| amdgpu_gmc_get_vm_pte(adev, &adev->ualink.npa_vm, remote->ring_bo, | |
| AMDGPU_VM_MTYPE_DEFAULT, &pte_flags); | |
| dev_dbg(adev->dev, "after get coherent pte_flags 0x%llx\n", pte_flags); | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) { | |
| u32 rptr_size, rptr_size_in_pages; | |
| /* rptr npa mapping, up to allocated 2 pages npa address */ | |
| rptr_size = ualink_wb_size(adev); | |
| rptr_size = AMDGPU_GPU_PAGE_ALIGN(rptr_size * remote->num_accel); | |
| rptr_size_in_pages = rptr_size >> AMDGPU_GPU_PAGE_SHIFT; | |
| r = amdgpu_ualink_npa_alloc_va(adev, &remote->rptr_mm_node, | |
| 0, 0, 0, | |
| rptr_size_in_pages); | |
| if (r) | |
| goto out; | |
| npa_in_pages = remote->rptr_mm_node.start; | |
| dev_dbg(adev->dev, "source aliasing rptr alloc 0x%llx and map to npa vm\n", | |
| npa_in_pages << AMDGPU_GPU_PAGE_SHIFT); | |
| r = amdgpu_ualink_npa_vm_map_range(adev, remote->rptr_bo, pte_flags, 0, | |
| rptr_size_in_pages, | |
| npa_in_pages); | |
| if (r) | |
| goto error_npa_mapping; | |
| remote->rptr_npa = npa_in_pages << AMDGPU_GPU_PAGE_SHIFT; | |
| /* wptr, remote interrupt, shootdown ring, single big 2MB mapping */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TLB_INV, | |
| 0, dst_accel_id); | |
| r = amdgpu_ualink_npa_vm_map_range(adev, remote->ring_bo, pte_flags, | |
| 0, | |
| 2 * rb_size_in_pages * AMDGPU_UALINK_ACCEL_MAX, | |
| npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| if (r) | |
| goto error_npa_mapping; | |
| } else { | |
| u32 wptr_offset = 2 * rb_size * remote->num_accel; | |
| u32 accel_id, idx = 0; | |
| /* Source identification mode */ | |
| for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) { | |
| if (accel_id == dst_accel_id) | |
| continue; | |
| /* remote interrupt ring npa mapping */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_REMOTE_INTERRUPT, | |
| accel_id, dst_accel_id); | |
| r = amdgpu_ualink_npa_vm_map_range(adev, remote->ring_bo, pte_flags, | |
| idx * 2 * rb_size, | |
| rb_size_in_pages, | |
| npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| if (r) | |
| goto error_npa_mapping; | |
| /* remote shootdown ring npa mapping */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TLB_INV, accel_id, | |
| dst_accel_id); | |
| r = amdgpu_ualink_npa_vm_map_range(adev, remote->ring_bo, pte_flags, | |
| (idx * 2 + 1) * rb_size, | |
| rb_size_in_pages, | |
| npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| if (r) | |
| goto error_npa_mapping; | |
| /* wptr, rptr npa mapping */ | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TAILPTR, accel_id, | |
| dst_accel_id); | |
| r = amdgpu_ualink_npa_vm_map_range(adev, remote->ring_bo, | |
| pte_flags, | |
| wptr_offset + idx * PAGE_SIZE, | |
| 1, npa >> AMDGPU_GPU_PAGE_SHIFT); | |
| if (r) | |
| goto error_npa_mapping; | |
| idx++; | |
| } | |
| } | |
| r = amdgpu_vm_update_pdes(adev, &adev->ualink.npa_vm, false); | |
| if (r) { | |
| dev_dbg(adev->dev, "failed %d to update directories\n", r); | |
| goto error_npa_mapping; | |
| } | |
| /* TODO: only wait the last fence, then flush TLB */ | |
| fence = dma_fence_get(adev->ualink.npa_vm.last_update); | |
| if (fence) { | |
| r = dma_fence_wait_timeout(fence, true, timeout); | |
| dma_fence_put(fence); | |
| if (r <= 0) | |
| dev_dbg(adev->dev, "failed %d to dma fence wait\n", r); | |
| } | |
| amdgpu_ualink_flush_tlb(adev, TLB_FLUSH_HEAVYWEIGHT); | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| return 0; | |
| error_npa_mapping: | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| amdgpu_ualink_npa_free_va(adev, &remote->rptr_mm_node); | |
| if (r) | |
| dev_dbg(adev->dev, "failed %d to map NPA vm\n", r); | |
| out: | |
| amdgpu_ualink_unreserve_npa_vm_and_bos(adev, &exec); | |
| return r; | |
| } | |
| /** | |
| * amdgpu_ualink_sdma_entities_init - Initialize SDMA job scheduler entities | |
| * @adev: amdgpu device pointer | |
| * | |
| * Sets up SDMA job scheduler entities for remote interrupt and shootdown. | |
| * Each entity uses one SDMA scheduler/ring. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_sdma_entities_init(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct drm_gpu_scheduler *sched; | |
| u32 accel_id; | |
| int i = 0, r; | |
| dev_dbg(adev->dev, "enter\n"); | |
| for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) { | |
| sched = &adev->sdma.instance[accel_id % adev->sdma.num_instances].ring.sched; | |
| r = drm_sched_entity_init(&remote->peer[accel_id].entity, DRM_SCHED_PRIORITY_HIGH, | |
| &sched, 1, NULL); | |
| if (r) | |
| goto out_free; | |
| i++; | |
| } | |
| remote->use_lsdma = true; | |
| dev_dbg(adev->dev, "exit\n"); | |
| return 0; | |
| out_free: | |
| for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) { | |
| if (i--) | |
| drm_sched_entity_destroy(&remote->peer[accel_id].entity); | |
| else | |
| break; | |
| } | |
| return r; | |
| } | |
| static void amdgpu_ualink_sdma_entities_fini(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| u32 accel_id; | |
| dev_dbg(adev->dev, "enter\n"); | |
| for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) | |
| drm_sched_entity_destroy(&remote->peer[accel_id].entity); | |
| dev_dbg(adev->dev, "exit\n"); | |
| } | |
| /** | |
| * amdgpu_ualink_gart_map - Allocate GART entry and map NPA address | |
| * @adev: amdgpu device pointer | |
| * @npages: Number of pages to map | |
| * @npa: NPA address to map | |
| * @mm_node: DRM memory manager node for GART allocation | |
| * @pte_flags: the GART mapping flags | |
| * | |
| * Allocates GART entries and sets up NPA address mapping without TTM BO. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_gart_map(struct amdgpu_device *adev, u64 npages, | |
| u64 npa, struct drm_mm_node *mm_node, | |
| u64 pte_flags) | |
| { | |
| struct ttm_resource_manager *man = | |
| ttm_manager_type(&adev->mman.bdev, TTM_PL_TT); | |
| struct amdgpu_gtt_mgr *mgr = | |
| container_of(man, struct amdgpu_gtt_mgr, manager); | |
| dma_addr_t *dma_addr; | |
| int i, r; | |
| dev_dbg(adev->dev, "npa 0x%llx npages 0x%llx\n", npa, npages); | |
| if (npages > 1) { | |
| dma_addr = kmalloc_array(npages, sizeof(*dma_addr), GFP_KERNEL); | |
| if (!dma_addr) | |
| return -ENOMEM; | |
| for (i = 0; i < npages; i++) | |
| dma_addr[i] = npa + i * AMDGPU_GPU_PAGE_SIZE; | |
| } else { | |
| dma_addr = (dma_addr_t *)&npa; | |
| } | |
| r = amdgpu_gtt_mgr_alloc_entries(mgr, mm_node, npages, DRM_MM_INSERT_BEST); | |
| if (r) | |
| goto out; | |
| amdgpu_gart_bind(adev, mm_node->start << AMDGPU_GPU_PAGE_SHIFT, npages, dma_addr, | |
| pte_flags); | |
| out: | |
| dev_dbg(adev->dev, "npa 0x%llx mapped to 0x%llx npages 0x%llx r=%d\n", | |
| npa, mm_node->start << AMDGPU_GPU_PAGE_SHIFT, npages, r); | |
| if (npages > 1) | |
| kfree(dma_addr); | |
| return r; | |
| } | |
| /** | |
| * amdgpu_ualink_gart_unmap - Unmap and free GART entry | |
| * @adev: amdgpu device pointer | |
| * @npages: Number of pages to unmap | |
| * @mm_node: DRM memory manager node for GART allocation | |
| * | |
| * Unbinds GART mapping and frees allocated entries. | |
| */ | |
| static void amdgpu_ualink_gart_unmap(struct amdgpu_device *adev, u64 npages, | |
| struct drm_mm_node *mm_node) | |
| { | |
| struct ttm_resource_manager *man = ttm_manager_type(&adev->mman.bdev, TTM_PL_TT); | |
| struct amdgpu_gtt_mgr *mgr = | |
| container_of(man, struct amdgpu_gtt_mgr, manager); | |
| dev_dbg(adev->dev, "0x%llx npages 0x%llx\n", mm_node->start << AMDGPU_GPU_PAGE_SHIFT, | |
| npages); | |
| if (!drm_mm_node_allocated(mm_node)) | |
| return; | |
| amdgpu_gart_unbind(adev, mm_node->start << AMDGPU_GPU_PAGE_SHIFT, npages); | |
| amdgpu_gtt_mgr_free_entries(mgr, mm_node); | |
| } | |
| /** | |
| * amdgpu_ualink_metadata_fini - Clean up ualink metadata structures | |
| * @adev: amdgpu device pointer | |
| * | |
| * Unmaps NPA addresses and frees all allocated buffers for metadata, | |
| * ring buffers, and pointers. | |
| */ | |
| static void amdgpu_ualink_metadata_fini(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| dev_dbg(adev->dev, "accel_id %u\n", ualink_accel_id(adev)); | |
| amdgpu_bo_free_kernel(&remote->rptr_bo, &remote->rptr_gpu_addr, | |
| &remote->rptr_cpu_addr); | |
| amdgpu_bo_free_kernel(&remote->ring_bo, &remote->rb_gpu_addr, | |
| &remote->rb_cpu_addr); | |
| amdgpu_bo_free_kernel(&remote->metadata_bo, &remote->metadata_gpu_addr, | |
| &remote->metadata_cpu_addr); | |
| } | |
| /** | |
| * amdgpu_ualink_metadata_init - Initialize ualink metadata structures | |
| * @adev: amdgpu device pointer | |
| * | |
| * Allocates and initializes metadata structures, ring buffers, and write/read | |
| * pointers for multi-GPU ualink. Communicates with firmware to load metadata. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct amdgpu_ualink_metadata *metadata; | |
| u32 size, rb_size, wptr_size, rptr_size, metadata_size; | |
| u64 rb_gpu_addr, wptr_gpu_addr; | |
| u32 status, accel_id; | |
| int r; | |
| bitmap_copy(remote->active_accel_bits, | |
| adev->ualink.info->vpod.active_accel_bits, | |
| AMDGPU_UALINK_ACCEL_MAX); | |
| dev_dbg(adev->dev, "%d active accelerators config in vpod\n", | |
| bitmap_weight(remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX)); | |
| /* | |
| * allocate metadata entries and ring buffer for all remote GPUs, | |
| * to get 2MB page ring buffer NPA mapping for remote access. | |
| */ | |
| remote->num_accel = AMDGPU_UALINK_ACCEL_MAX; | |
| status = adev->ualink.msg_ctl->check_status(adev); | |
| if (status != AMDGPU_NHT_FW_ST_PREINIT && | |
| status != AMDGPU_NHT_FW_ST_HALT) { | |
| dev_dbg(adev->dev, "fw status 0x%x not preinit or halt\n", status); | |
| return -ENODEV; | |
| } | |
| dev_dbg(adev->dev, "accel_id %u addr_mode %d fw status 0x%x\n", | |
| ualink_accel_id(adev), ualink_addr_mode(adev), status); | |
| /* Alloc metadata structure for all GPUs */ | |
| metadata_size = sizeof(struct amdgpu_ualink_metadata) * AMDGPU_UALINK_ACCEL_MAX; | |
| metadata_size = AMDGPU_GPU_PAGE_ALIGN(metadata_size); | |
| dev_dbg(adev->dev, "metadata size 0x%x\n", metadata_size); | |
| /* pinned system memory */ | |
| r = amdgpu_bo_create_kernel(adev, metadata_size, PAGE_SIZE, | |
| AMDGPU_GEM_DOMAIN_GTT, | |
| &remote->metadata_bo, &remote->metadata_gpu_addr, | |
| &remote->metadata_cpu_addr); | |
| if (r) | |
| goto out; | |
| memset(remote->metadata_cpu_addr, 0, metadata_size); | |
| metadata = remote->metadata_cpu_addr; | |
| dev_dbg(adev->dev, "metadata gpu address 0x%llx\n", remote->metadata_gpu_addr); | |
| /* Allocate ring buffers, wptr for remote interrupt and shootdown */ | |
| rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE); | |
| wptr_size = ualink_wptr_size(adev); | |
| size = (rb_size + wptr_size) * remote->num_accel; | |
| size = AMDGPU_GPU_PAGE_ALIGN(size); | |
| dev_dbg(adev->dev, "rb_size 0x%x wptr_size 0x%x total alloc size 0x%x\n", | |
| rb_size, wptr_size, size); | |
| /* pinned VRAM */ | |
| r = amdgpu_bo_create_kernel(adev, size, PAGE_SIZE, | |
| AMDGPU_GEM_DOMAIN_VRAM, | |
| &remote->ring_bo, | |
| &remote->rb_gpu_addr, | |
| &remote->rb_cpu_addr); | |
| if (r) | |
| goto out; | |
| memset(remote->rb_cpu_addr, 0, size); | |
| dev_dbg(adev->dev, "rb gpu addr 0x%llx cpu addr 0x%p vram_start 0x%llx vram_base 0x%llx\n", | |
| remote->rb_gpu_addr, remote->rb_cpu_addr, adev->gmc.vram_start, | |
| adev->vm_manager.vram_base_offset); | |
| /* | |
| * address aliasing mode, shared wptr pagee left is not enough for wb data, | |
| * wb status and rptr, alloc another BO, and then alloc npa address and map | |
| * to npa vm, for remote to access. | |
| * | |
| * No gart mapping required for wb data, status and rptr because this is | |
| * updated by firmware. | |
| */ | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) { | |
| rptr_size = AMDGPU_GPU_PAGE_ALIGN(ualink_wb_size(adev) * remote->num_accel); | |
| dev_dbg(adev->dev, "source aliasing mode rptr_size 0x%x\n", rptr_size); | |
| /* pinned VRAM */ | |
| r = amdgpu_bo_create_kernel(adev, rptr_size, PAGE_SIZE, | |
| AMDGPU_GEM_DOMAIN_VRAM, | |
| &remote->rptr_bo, | |
| &remote->rptr_gpu_addr, | |
| &remote->rptr_cpu_addr); | |
| if (r) | |
| goto out; | |
| memset(remote->rptr_cpu_addr, 0, rptr_size); | |
| dev_dbg(adev->dev, "source aliasing rptr gpu addr 0x%llx cpu addr 0x%p\n", | |
| remote->rptr_gpu_addr, remote->rptr_cpu_addr); | |
| } | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS) | |
| wptr_gpu_addr = remote->rb_gpu_addr + rb_size * ualink_accel_id(adev); | |
| for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_dbg(adev->dev, "init for accel_id %u\n", accel_id); | |
| if (accel_id == ualink_accel_id(adev)) | |
| continue; | |
| rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id; | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) { | |
| wptr_gpu_addr = remote->rb_gpu_addr + rb_size * remote->num_accel; | |
| wptr_gpu_addr += wptr_size * accel_id; | |
| } | |
| metadata[accel_id].header = AMDGPU_UALINK_METADATA_HEADER; | |
| metadata[accel_id].rb_size = fls(AMDGPU_UALINK_RB_SIZE / 64) - 1; | |
| /* | |
| * remote shootdown type is 0, remote command type is 1 | |
| * with 2MB ring buffer mapping, remote shootdown ring NPA is before interrupt | |
| */ | |
| metadata[accel_id].ri_rb = rb_gpu_addr + AMDGPU_UALINK_RB_SIZE; | |
| metadata[accel_id].tlb_inv_rb = rb_gpu_addr; | |
| metadata[accel_id].tailptr_ri = wptr_gpu_addr + | |
| ualink_wptr_offset(adev, accel_id); | |
| metadata[accel_id].tailptr_tlb_inv = wptr_gpu_addr + | |
| ualink_tlb_wptr_offset(adev, accel_id); | |
| dev_dbg(adev->dev, "init from accel_id %u to accel_id %u, rb_size 0x%x\n", | |
| accel_id, ualink_accel_id(adev), metadata[accel_id].rb_size); | |
| dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n", | |
| metadata[accel_id].ri_rb, metadata[accel_id].tlb_inv_rb); | |
| dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n", | |
| metadata[accel_id].tailptr_ri, metadata[accel_id].tailptr_tlb_inv); | |
| } | |
| r = adev->ualink.msg_ctl->send_metadata(adev, | |
| remote->metadata_gpu_addr, | |
| AMDGPU_UALINK_ACCEL_MAX << 8); | |
| out: | |
| if (r) | |
| amdgpu_ualink_metadata_fini(adev); | |
| dev_dbg(adev->dev, "ret 0x%x\n", r); | |
| return r; | |
| } | |
| /* | |
| * nHT Firmware Error code | |
| * | |
| * Success 0x0 | |
| * FIFO overflow 0x1 | |
| * Invalid RB command 0x2 | |
| * Timeout 0x3 | |
| * Invalid metadata entry 0x4 | |
| * RB overflow 0x5 | |
| * Invalid wptr address 0x6 | |
| * Interrupt cookie send failure 0x7 | |
| * Invalid RB address 0x8 | |
| * Invalid writeback address 0x9 | |
| */ | |
| static void amdgpu_ualink_remote_error(struct amdgpu_device *adev, u32 status) | |
| { | |
| const char *fw_err_code_msg[] = { | |
| "Unknown FW error status", | |
| "FIFO overflow", /* error code 1 */ | |
| "Invalid RB command", | |
| "Timeout", | |
| "Invalid metadata entry", | |
| "RB overflow", | |
| "Invalid wptr address", | |
| "Interrupt cookie send failure", | |
| "Invalid RB address", | |
| "Invalid writeback address" /* error code 9 */ | |
| }; | |
| if (status >= ARRAY_SIZE(fw_err_code_msg)) | |
| status = 0; | |
| dev_err(adev->dev, "remote error %s\n", fw_err_code_msg[status]); | |
| return; | |
| } | |
| /** | |
| * amdgpu_ualink_remote_wait_timeout - Wait for remote operation completion | |
| * @remote_accel_id: remote accelator id to wait for reply | |
| * @adev: amdgpu device pointer | |
| * @wb_cpu: CPU virtual address of writeback buffer | |
| * @seq: Sequence number to wait for | |
| * | |
| * Polls the writeback buffer waiting for the sequence number to reach or | |
| * exceed the expected value. The writeback buffer contains status and data | |
| * fields updated by firmware to indicate completion and error conditions. | |
| * | |
| * Return: 0 on success | |
| * -ETIME on timeout | |
| * -ECOMM on firmware return error status | |
| */ | |
| static long amdgpu_ualink_remote_wait_timeout(struct amdgpu_device *adev, | |
| u32 remote_accel_id, | |
| struct amdgpu_ualink_wb *wb_cpu, | |
| u32 seq) | |
| { | |
| /* 2 seconds timeout, long enough for FW to reply */ | |
| ktime_t timeout = ktime_add_us(ktime_get(), 2 * USEC_PER_SEC); | |
| u32 status, data; | |
| u64 rptr; | |
| while (true) { | |
| data = READ_ONCE(wb_cpu->data); | |
| if (data >= seq) { | |
| status = READ_ONCE(wb_cpu->status); | |
| rptr = READ_ONCE(wb_cpu->rptr); | |
| break; | |
| } | |
| if (ktime_after(ktime_get(), timeout)) { | |
| dev_dbg(adev->dev, "remote %d wait timeout\n", remote_accel_id); | |
| return -ETIME; | |
| } | |
| usleep_range(10, 50); | |
| } | |
| if (status) { | |
| dev_dbg(adev->dev, "remote %u status 0x%x wb data 0x%x seq 0x%x rptr 0x%llx\n", | |
| remote_accel_id, status, data, seq, rptr); | |
| amdgpu_ualink_remote_error(adev, status); | |
| return -ECOMM; | |
| } | |
| dev_dbg(adev->dev, "remote %u succeed seq 0x%x wb data 0x%x rptr 0x%llx\n", | |
| remote_accel_id, seq, data, rptr); | |
| return 0; | |
| } | |
| #define AMDGPU_UALINK_REMOTE_OP_TLB_INV 0x1 | |
| #define AMDGPU_UALINK_REMOTE_OP_INT 0x2 | |
| #define AMDGPU_UALINK_REMOTE_OP_UPDATE_WB 0x3 | |
| static void amdgpu_ualink_emit_shootdown(u32 **cpu_addr_p, u32 wb_data, | |
| u32 dw0, u32 dw1, u32 dw2, u32 dw3) | |
| { | |
| u64 addr = (((u64)dw1 << 32) | (u64)dw2) << AMDGPU_GPU_PAGE_SHIFT; | |
| u32 *cpu_addr = *cpu_addr_p; | |
| u32 flush_type = dw0; | |
| u32 size_in_pages = dw3; | |
| u32 cmd; | |
| cmd = AMDGPU_UALINK_REMOTE_OP_TLB_INV; | |
| cmd |= 1 << 28; /* headptr update */ | |
| cmd |= 1 << 29; /* wb enable */ | |
| *cpu_addr++ = cmd; | |
| *cpu_addr++ = wb_data; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; /* metadata */ | |
| *cpu_addr++ = 0; /* metadata */ | |
| pr_debug("NPA addr 0x%llx npages 0x%x\n", addr, size_in_pages); | |
| /* Always shootdown everything with full address size s-field coding */ | |
| addr = GENMASK_ULL(51, 11); | |
| *cpu_addr++ = lower_32_bits(addr) | flush_type; | |
| *cpu_addr++ = upper_32_bits(addr); | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr_p = cpu_addr; | |
| } | |
| static void amdgpu_ualink_emit_interrupt(u32 **cpu_addr_p, u32 wb_data, | |
| u32 dw0, u32 dw1, u32 dw2, u32 dw3) | |
| { | |
| u32 *cpu_addr = *cpu_addr_p; | |
| u32 cmd; | |
| cmd = AMDGPU_UALINK_REMOTE_OP_INT; | |
| cmd |= 1 << 28; /* headptr_update */ | |
| cmd |= 1 << 29; /* wb enable */ | |
| *cpu_addr++ = cmd; | |
| *cpu_addr++ = wb_data; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; /* metadata */ | |
| *cpu_addr++ = 0; /* metadata */ | |
| /* updated by f/w, overwrite PASID with GPU ID */ | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| /* IH ContextID 4 dwords */ | |
| *cpu_addr++ = dw0; | |
| *cpu_addr++ = dw1; | |
| *cpu_addr++ = dw2; | |
| *cpu_addr++ = dw3; | |
| *cpu_addr_p = cpu_addr; | |
| } | |
| static void amdgpu_ualink_emit_update_wb_addr(u32 **cpu_addr_p, u32 wb_data, | |
| u32 dw0, u32 dw1, u32 dw2, u32 dw3) | |
| { | |
| u32 *cpu_addr = *cpu_addr_p; | |
| u32 cmd; | |
| cmd = AMDGPU_UALINK_REMOTE_OP_UPDATE_WB; | |
| cmd |= 1 << 28; /* headptr update */ | |
| cmd |= 1 << 29; /* wb enable */ | |
| *cpu_addr++ = cmd; | |
| *cpu_addr++ = wb_data; | |
| *cpu_addr++ = dw1 & 0xFFFFFFFC; /* lower32 wb_npa */ | |
| *cpu_addr++ = dw0 & 0xFFFFF; /* upper32 wb_npa */ | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; /* metadata */ | |
| *cpu_addr++ = 0; /* metadata */ | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr++ = 0; | |
| *cpu_addr_p = cpu_addr; | |
| } | |
| /* | |
| * Function proto to generate packet using above different emit_ functions | |
| */ | |
| typedef void (*ualink_emit_packet)(u32 **cpu_addr_p, u32 wb, u32 dw0, | |
| u32 dw1, u32 dw2, u32 dw3); | |
| /* | |
| * Refresh rptr from the writeback and check the ring has room for one more | |
| * command. The caller must hold the peer lock. | |
| * | |
| * Return 0 on success or a negative error code. | |
| */ | |
| static int amdgpu_ualink_ring_check_space(struct amdgpu_device *adev, | |
| u32 remote_accel_id, | |
| struct amdgpu_ualink_ring *ring, | |
| struct amdgpu_ualink_wb *wb_cpu) | |
| { | |
| ring->rptr = READ_ONCE(wb_cpu->rptr); | |
| if (WARN_ON_ONCE(ring->rptr > ring->wptr)) { | |
| dev_err(adev->dev, "accel_id %u ring overflow wptr 0x%llx rptr 0x%llx\n", | |
| remote_accel_id, ring->wptr, ring->rptr); | |
| return -EFAULT; | |
| } | |
| if ((ring->wptr + 1 - ring->rptr) >= ring->rb_size) { | |
| dev_err(adev->dev, "accel_id %u command ring full wptr 0x%llx rptr 0x%llx\n", | |
| remote_accel_id, ring->wptr, ring->rptr); | |
| return -ENOSPC; | |
| } | |
| return 0; | |
| } | |
| static int amdgpu_ualink_send_command(struct amdgpu_device *adev, | |
| u32 remote_accel_id, | |
| struct amdgpu_ualink_ring *ring, | |
| struct amdgpu_ualink_wb *wb_cpu, | |
| ualink_emit_packet emit_func, | |
| u32 dw0, u32 dw1, u32 dw2, u32 dw3) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct amdgpu_ualink_peer *peer; | |
| struct amdgpu_ring *sdma_ring; | |
| struct dma_fence *fence; | |
| struct amdgpu_job *job; | |
| struct amdgpu_ib *ib; | |
| u32 seq, ndw, ndw_copy_cmd, rem; | |
| u64 doorbell_npa_gart; | |
| u32 *src_cpu; | |
| u64 src; | |
| int r; | |
| /* Refuse sends unless active */ | |
| if (adev->ualink.info->accel_state != AMDGPU_UALINK_ACCEL_STATE_ACTIVE) | |
| return -ESHUTDOWN; | |
| peer = &remote->peer[remote_accel_id]; | |
| r = amdgpu_ualink_ring_check_space(adev, remote_accel_id, ring, wb_cpu); | |
| if (r) | |
| goto out; | |
| /* | |
| * 3 sdma copy commands: write data to ring buffer, update wptr, ring doorbell | |
| * | |
| * insert 2 NOP commands to break SDMA back-to-back copy command overlap, to | |
| * ensure the ordering of 3 sdma copy commands execution. | |
| */ | |
| /* buffer after sdma commands, starting at 8 dwords boundary */ | |
| ndw_copy_cmd = ALIGN(3 * adev->mman.buffer_funcs->copy_num_dw + 2, 8); | |
| /* remote command 16 dwords, wptr 2 dwords, doorbell 1 dword */ | |
| ndw = ndw_copy_cmd + 16 + 2 + 1; | |
| r = amdgpu_job_alloc_with_ib(adev, &peer->entity, AMDGPU_FENCE_OWNER_VM, | |
| ndw * 4, AMDGPU_IB_POOL_IMMEDIATE, | |
| AMDGPU_KERNEL_JOB_ID_TTM_COPY_BUFFER, &job); | |
| if (r) | |
| goto out; | |
| ib = &job->ibs[0]; | |
| src = ib->gpu_addr + ndw_copy_cmd * 4; | |
| src_cpu = ib->ptr + ndw_copy_cmd; | |
| seq = ++ring->seq; | |
| div_u64_rem(ring->wptr, ring->rb_size, &rem); | |
| dev_dbg(adev->dev, "src 0x%llx to npa gart rb 0x%llx wptr 0x%llx doorbell 0x%llx seq 0x%x\n", | |
| src, ring->rb_npa_gart + rem * 64, | |
| ring->wptr_npa_gart, ring->doorbell_npa_gart, seq); | |
| dev_dbg(adev->dev, "ring wptr 0x%llx rptr 0x%llx\n", ring->wptr, ring->rptr); | |
| /* remote command packet */ | |
| emit_func(&src_cpu, seq, dw0, dw1, dw2, dw3); | |
| /* wptr value */ | |
| *src_cpu++ = lower_32_bits(ring->wptr + 1); | |
| *src_cpu++ = upper_32_bits(ring->wptr + 1); | |
| /* doorbell value */ | |
| *src_cpu++ = lower_32_bits(ring->wptr + 1); | |
| /* | |
| * Add an offset to the doorbell address that cycles through different | |
| * values of bit 11:8 to use different DXS ports. | |
| */ | |
| peer->dxs_port = (peer->dxs_port + 1) & 0xF; | |
| doorbell_npa_gart = ring->doorbell_npa_gart | (peer->dxs_port << 8); | |
| dev_dbg(adev->dev, "dxs_port 0x%x, doorbell_npa_gart 0x%llx -> 0x%llx\n", | |
| peer->dxs_port, ring->doorbell_npa_gart, doorbell_npa_gart); | |
| dev_dbg(adev->dev, "use %s\n", remote->use_lsdma ? "lsdma" : "sdma"); | |
| if (remote->use_lsdma) { | |
| div_u64_rem(ring->wptr, ring->rb_size, &rem); | |
| r = amdgpu_lsdma_copy_mem(adev, src, | |
| ring->rb_npa_gart + rem * 64, | |
| 64); | |
| if (!r) | |
| r = amdgpu_lsdma_copy_mem(adev, src + 64, | |
| ring->wptr_npa_gart, 8); | |
| if (!r) | |
| r = amdgpu_lsdma_copy_mem(adev, src + 72, | |
| doorbell_npa_gart, 4); | |
| amdgpu_job_free(job); | |
| /* Copy failed: command never reached the remote, so no | |
| * writeback will arrive. Return now instead of polling the | |
| * completion for the full remote timeout. | |
| */ | |
| if (r) { | |
| dev_dbg(adev->dev, | |
| "remote %u lsdma copy failed (r %d), skip completion wait\n", | |
| remote_accel_id, r); | |
| return r; | |
| } | |
| goto out_wait_complete; | |
| } | |
| sdma_ring = &adev->sdma.instance[0].ring; | |
| /* | |
| * SDMA commands copy from ib to NPA, insert NOPs to ensure SDMA copy commands | |
| * execution doesn't overlap, doorbell update is after data and wptr update | |
| * finished. | |
| */ | |
| div_u64_rem(ring->wptr, ring->rb_size, &rem); | |
| amdgpu_emit_copy_buffer(adev, ib, src, | |
| ring->rb_npa_gart + rem * 64, | |
| 64, 0); | |
| ib->ptr[ib->length_dw++] = sdma_ring->funcs->nop; | |
| amdgpu_emit_copy_buffer(adev, ib, src + 64, ring->wptr_npa_gart, 8, 0); | |
| ib->ptr[ib->length_dw++] = sdma_ring->funcs->nop; | |
| amdgpu_emit_copy_buffer(adev, ib, src + 72, doorbell_npa_gart, 4, 0); | |
| amdgpu_ring_pad_ib(sdma_ring, ib); | |
| WARN_ON(ib->length_dw > ndw_copy_cmd); | |
| fence = amdgpu_job_submit(job); | |
| r = dma_fence_wait_timeout(fence, false, AMDGPU_FENCE_JIFFIES_TIMEOUT); | |
| dma_fence_put(fence); | |
| if (r <= 0) { | |
| dev_dbg(adev->dev, "remote %u sdma fence wait return r %d\n", | |
| remote_accel_id, r); | |
| if (r == 0) | |
| r = -ETIME; | |
| goto out; | |
| } | |
| out_wait_complete: | |
| /* | |
| * poll remote completion writeback data | |
| */ | |
| r = amdgpu_ualink_remote_wait_timeout(adev, remote_accel_id, wb_cpu, seq); | |
| /* | |
| * Advance wptr unless the remote timed out, even if the FW returned an | |
| * error status. On timeout the caller resets the connection, which | |
| * resyncs wptr from the remote in amdgpu_ualink_setup_connection. | |
| */ | |
| if (r != -ETIME) | |
| ring->wptr++; | |
| out: | |
| dev_dbg(adev->dev, "ret r = %d\n", r); | |
| return r; | |
| } | |
| static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev, | |
| u32 remote_accel_id, | |
| struct amdgpu_ualink_wb **wb_cpu, | |
| u64 *wb_npa, u32 type) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| u32 accel_id = ualink_accel_id(adev); | |
| u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE); | |
| uintptr_t wb; | |
| u64 npa; | |
| u32 offset = 0; | |
| if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) { | |
| wb = (uintptr_t)remote->rb_cpu_addr + rb_size * remote->num_accel; | |
| npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TAILPTR, | |
| remote_accel_id, accel_id); | |
| } else { | |
| wb = (uintptr_t)remote->rptr_cpu_addr; | |
| npa = remote->rptr_npa; | |
| npa |= (u64)accel_id << AMDGPU_UALINK_GART_NPA_ADDR_GPUID_SHIFT; | |
| } | |
| if (type == RB_TYPE_TLB_INV) | |
| offset = ualink_tlb_wb_offset(adev, remote_accel_id); | |
| else if (type == RB_TYPE_REMOTE_INTERRUPT) | |
| offset = ualink_wb_offset(adev, remote_accel_id); | |
| *wb_cpu = (struct amdgpu_ualink_wb *)(wb + offset); | |
| if (wb_npa) | |
| *wb_npa = npa + offset; | |
| dev_dbg(adev->dev, "source %d remote %d wb npa 0x%llx\n", accel_id, | |
| remote_accel_id, npa + offset); | |
| } | |
| struct amdgpu_ualink_wptr_fence_cb { | |
| struct dma_fence_cb base; | |
| u64 *wptr_cpu; | |
| u64 result; | |
| }; | |
| static void amdgpu_ualink_read_wptr_fence_cb(struct dma_fence *fence, | |
| struct dma_fence_cb *cb) | |
| { | |
| struct amdgpu_ualink_wptr_fence_cb *wptr_cb; | |
| wptr_cb = container_of(cb, typeof(*wptr_cb), base); | |
| wptr_cb->result = *wptr_cb->wptr_cpu; | |
| } | |
| static int amdgpu_ualink_read_remote_wptr(struct amdgpu_device *adev, | |
| u32 remote_accel_id, | |
| struct amdgpu_ualink_ring *ring, | |
| u64 *wptr) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct amdgpu_ualink_peer *peer = &remote->peer[remote_accel_id]; | |
| struct amdgpu_ualink_wptr_fence_cb wptr_cb = { }; | |
| struct amdgpu_ring *sdma_ring; | |
| struct dma_fence *fence; | |
| struct amdgpu_job *job; | |
| struct amdgpu_ib *ib; | |
| u32 ndw, ndw_copy_cmd; | |
| u64 wptr_gpu, *wptr_cpu; | |
| int r; | |
| *wptr = 0; | |
| /* Refuse remote access unless active */ | |
| if (adev->ualink.info->accel_state != AMDGPU_UALINK_ACCEL_STATE_ACTIVE) | |
| return -ESHUTDOWN; | |
| /* 1 sdma copy command to read wptr */ | |
| ndw_copy_cmd = ALIGN(adev->mman.buffer_funcs->copy_num_dw, 8); | |
| /* wptr 2 dwords */ | |
| ndw = ndw_copy_cmd + 2; | |
| r = amdgpu_job_alloc_with_ib(adev, &peer->entity, AMDGPU_FENCE_OWNER_VM, | |
| ndw * 4, AMDGPU_IB_POOL_IMMEDIATE, | |
| AMDGPU_KERNEL_JOB_ID_TTM_COPY_BUFFER, &job); | |
| if (r) | |
| return r; | |
| ib = &job->ibs[0]; | |
| wptr_gpu = ib->gpu_addr + ndw_copy_cmd * 4; | |
| wptr_cpu = (u64 *)(ib->ptr + ndw_copy_cmd); | |
| dev_dbg(adev->dev, "read wptr from remote %u npa gart wptr 0x%llx use %s\n", | |
| remote_accel_id, ring->wptr_npa_gart, | |
| remote->use_lsdma ? "lsdma" : "sdma"); | |
| if (remote->use_lsdma) { | |
| r = amdgpu_lsdma_copy_mem(adev, ring->wptr_npa_gart, wptr_gpu, 8); | |
| if (!r) | |
| *wptr = *wptr_cpu; | |
| amdgpu_job_free(job); | |
| goto out; | |
| } | |
| wptr_cb.wptr_cpu = wptr_cpu; | |
| amdgpu_emit_copy_buffer(adev, ib, ring->wptr_npa_gart, wptr_gpu, 8, 0); | |
| sdma_ring = &adev->sdma.instance[0].ring; | |
| amdgpu_ring_pad_ib(sdma_ring, ib); | |
| WARN_ON(ib->length_dw > ndw_copy_cmd); | |
| fence = amdgpu_job_submit(job); | |
| r = dma_fence_add_callback(fence, &wptr_cb.base, | |
| amdgpu_ualink_read_wptr_fence_cb); | |
| if (r == -ENOENT) { | |
| /* Fence already signaled; the callback won't run, do it inline. */ | |
| amdgpu_ualink_read_wptr_fence_cb(fence, &wptr_cb.base); | |
| r = 0; | |
| goto out_put_fence; | |
| } else if (r) { | |
| /* -EINVAL: NULL fence or func, not expected here. */ | |
| goto out_put_fence; | |
| } | |
| r = dma_fence_wait_timeout(fence, false, AMDGPU_FENCE_JIFFIES_TIMEOUT); | |
| if (r > 0) { | |
| r = 0; /* read wptr successfully */ | |
| goto out_put_fence; | |
| } | |
| dev_dbg(adev->dev, "remote %u sdma fence wait return r %d\n", | |
| remote_accel_id, r); | |
| if (r == 0) | |
| r = -ETIME; | |
| /* | |
| * Timed out: the callback is still armed and wptr_cb is on our stack. | |
| * Cancel it so it can't fire after we return and write into the freed | |
| * stack frame. | |
| */ | |
| dma_fence_remove_callback(fence, &wptr_cb.base); | |
| out_put_fence: | |
| if (!r) | |
| *wptr = wptr_cb.result; | |
| dma_fence_put(fence); | |
| out: | |
| dev_dbg(adev->dev, "remote %u wptr 0x%llx return %d\n", | |
| remote_accel_id, *wptr, r); | |
| return r; | |
| } | |
| /** | |
| * amdgpu_ualink_ring_resync_ptrs - sync local ring pointers from remote | |
| * @adev: amdgpu device pointer | |
| * @remote_accel_id: remote accelerator ID | |
| * | |
| * After a reboot on either side the two ends disagree on wptr. Read the remote | |
| * wptr of both peer rings and adopt it as the local wptr and rptr. | |
| * | |
| * Return 0 if both rings already match, 1 if they were resynced, or a negative | |
| * error code if a remote read failed, leaving the rings untouched. | |
| */ | |
| static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev, | |
| u32 remote_accel_id) | |
| { | |
| struct amdgpu_ualink_peer *peer = &to_remote(adev)->peer[remote_accel_id]; | |
| struct amdgpu_ualink_wb *int_wb, *tlb_wb; | |
| u64 int_wptr, tlb_wptr; | |
| int r; | |
| /* ring->wptr and ring->rptr are owned by the send path. */ | |
| guard(mutex)(&peer->lock); | |
| /* Read both before updating either, so a failure changes nothing. */ | |
| r = amdgpu_ualink_read_remote_wptr(adev, remote_accel_id, | |
| &peer->interrupt, &int_wptr); | |
| if (!r) | |
| r = amdgpu_ualink_read_remote_wptr(adev, remote_accel_id, | |
| &peer->shootdown, &tlb_wptr); | |
| if (r) { | |
| dev_dbg(adev->dev, "accel_id %u read remote %u wptr failed %d\n", | |
| ualink_accel_id(adev), remote_accel_id, r); | |
| return r; | |
| } | |
| if (peer->interrupt.wptr == int_wptr && peer->shootdown.wptr == tlb_wptr) { | |
| dev_dbg(adev->dev, "accel_id %u wptr 0x%llx 0x%llx is same as remote %u\n", | |
| ualink_accel_id(adev), int_wptr, tlb_wptr, remote_accel_id); | |
| return 0; | |
| } | |
| dev_dbg(adev->dev, | |
| "accel_id %u wptr 0x%llx 0x%llx sync to remote %u wptr 0x%llx 0x%llx\n", | |
| ualink_accel_id(adev), peer->interrupt.wptr, peer->shootdown.wptr, | |
| remote_accel_id, int_wptr, tlb_wptr); | |
| amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL, | |
| RB_TYPE_REMOTE_INTERRUPT); | |
| amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &tlb_wb, NULL, | |
| RB_TYPE_TLB_INV); | |
| peer->interrupt.wptr = int_wptr; | |
| peer->interrupt.rptr = int_wptr; | |
| peer->shootdown.wptr = tlb_wptr; | |
| peer->shootdown.rptr = tlb_wptr; | |
| /* Drop the stale writeback rptr, it disagrees with the resynced wptr. */ | |
| WRITE_ONCE(int_wb->rptr, int_wptr); | |
| WRITE_ONCE(tlb_wb->rptr, tlb_wptr); | |
| return 1; | |
| } | |
| static void amdgpu_ualink_reset_peer_rings(struct amdgpu_device *adev, | |
| u32 remote_accel_id) | |
| { | |
| struct amdgpu_ualink_peer *peer = &to_remote(adev)->peer[remote_accel_id]; | |
| dev_dbg(adev->dev, "remote accel_id %u\n", remote_accel_id); | |
| guard(mutex)(&peer->lock); | |
| peer->interrupt.ready = false; | |
| peer->shootdown.ready = false; | |
| } | |
| static int amdgpu_ualink_update_wb_address(struct amdgpu_device *adev, | |
| u32 remote_accel_id, u32 ring_type) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct amdgpu_ualink_peer *peer; | |
| struct amdgpu_ualink_ring *ring; | |
| struct amdgpu_ualink_wb *wb_cpu; | |
| u64 wb_npa; | |
| int r; | |
| peer = &remote->peer[remote_accel_id]; | |
| if (ring_type == RB_TYPE_REMOTE_INTERRUPT) | |
| ring = &peer->interrupt; | |
| else if (ring_type == RB_TYPE_TLB_INV) | |
| ring = &peer->shootdown; | |
| else | |
| return -EINVAL; | |
| amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &wb_cpu, &wb_npa, | |
| ring_type); | |
| r = amdgpu_ualink_send_command(adev, remote_accel_id, ring, wb_cpu, | |
| amdgpu_ualink_emit_update_wb_addr, | |
| upper_32_bits(wb_npa), | |
| lower_32_bits(wb_npa), | |
| 0, 0); | |
| if (!r) { | |
| ring->wb_cpu = wb_cpu; | |
| ring->ready = true; | |
| } | |
| return r; | |
| } | |
| /** | |
| * amdgpu_ualink_remote_shootdown - Send a remote TLB invalidation request | |
| * @adev: amdgpu device pointer | |
| * @remote_accel_id: accelerator ID of the remote GPU to shootdown | |
| * @addr: page-aligned address to invalidate | |
| * @size_in_pages: number of pages to invalidate, 0 to invalidate entire TLB | |
| * @flush_type: type of TLB flush to perform | |
| * | |
| * Sends a remote TLB shootdown command to the specified peer GPU via the | |
| * UALink ring buffer. The address and size are translated using the S-field | |
| * encoding before being written to the ring. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_remote_shootdown(struct amdgpu_device *adev, | |
| u32 remote_accel_id, u64 addr, | |
| u32 size_in_pages, u32 flush_type) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct amdgpu_ualink_peer *peer; | |
| struct amdgpu_ualink_ring *ring; | |
| int r; | |
| dev_dbg(adev->dev, "send remote shootdown to remote_accel_id %u\n", | |
| remote_accel_id); | |
| peer = &remote->peer[remote_accel_id]; | |
| ring = &peer->shootdown; | |
| guard(mutex)(&peer->lock); | |
| if (!ring->ready) { | |
| dev_dbg(adev->dev, "accel_id %u ring not ready\n", remote_accel_id); | |
| r = amdgpu_ualink_update_wb_address(adev, remote_accel_id, | |
| RB_TYPE_TLB_INV); | |
| if (r) | |
| return r; | |
| } | |
| return amdgpu_ualink_send_command(adev, remote_accel_id, ring, | |
| ring->wb_cpu, | |
| amdgpu_ualink_emit_shootdown, | |
| flush_type, upper_32_bits(addr), | |
| lower_32_bits(addr), size_in_pages); | |
| } | |
| /** | |
| * amdgpu_ualink_remote_interrupt - Send a remote interrupt to a peer GPU | |
| * @adev: amdgpu device pointer | |
| * @remote_accel_id: accelerator ID of the remote GPU to interrupt | |
| * @dw0: IH context ID dword 0 | |
| * @dw1: IH context ID dword 1 | |
| * @dw2: IH context ID dword 2 | |
| * @dw3: IH context ID dword 3 | |
| * | |
| * Sends a remote interrupt command to the specified peer GPU via the | |
| * UALink ring buffer. The four context ID dwords are delivered to the | |
| * remote GPU's interrupt handler. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_remote_interrupt(struct amdgpu_device *adev, | |
| u32 remote_accel_id, u32 dw0, u32 dw1, | |
| u32 dw2, u32 dw3) | |
| { | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct amdgpu_ualink_peer *peer; | |
| struct amdgpu_ualink_ring *ring; | |
| int r; | |
| peer = &remote->peer[remote_accel_id]; | |
| ring = &peer->interrupt; | |
| guard(mutex)(&peer->lock); | |
| if (!ring->ready) { | |
| dev_dbg(adev->dev, "accel_id %u ring not ready\n", remote_accel_id); | |
| r = amdgpu_ualink_update_wb_address(adev, remote_accel_id, | |
| RB_TYPE_REMOTE_INTERRUPT); | |
| if (r) | |
| return r; | |
| } | |
| return amdgpu_ualink_send_command(adev, remote_accel_id, ring, | |
| ring->wb_cpu, | |
| amdgpu_ualink_emit_interrupt, | |
| dw0, dw1, dw2, dw3); | |
| } | |
| /** | |
| * amdgpu_ualink_peer_remote_init - Initialize remote peer GPU connections | |
| * @adev: amdgpu device pointer | |
| * | |
| * Sets up ring buffers, GART mappings, and control structures for | |
| * communicating with remote GPUs in the fabric. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| static int amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev) | |
| { | |
| u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT; | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| struct amdgpu_bo *bo = remote->ring_bo; | |
| struct amdgpu_ualink_peer *peer; | |
| struct amdgpu_ualink_ring *interrupt; | |
| struct amdgpu_ualink_ring *shootdown; | |
| u32 src_accel_id = ualink_accel_id(adev); | |
| u32 dst_accel_id; | |
| u64 npa, flags; | |
| int r = 0; | |
| /* NPA mapping PTE flags VSCT = 0011 */ | |
| flags = amdgpu_ttm_tt_pte_flags(adev, bo->tbo.ttm, bo->tbo.resource); | |
| flags |= AMDGPU_PTE_SNOOPED | AMDGPU_PTE_PRT_GFX12 | AMDGPU_PTE_BUS_ATOMICS; | |
| flags &= ~AMDGPU_PTE_VALID; | |
| flags &= ~AMDGPU_PTE_SYSTEM; | |
| /* PTE.X=0 turn off RPC checks for RBs, wptr and doorbell NPA address */ | |
| flags &= ~AMDGPU_PTE_EXECUTABLE; | |
| dev_dbg(adev->dev, "src_accel_id %u gart mapping flags 0x%llx\n", | |
| src_accel_id, flags); | |
| for_each_set_bit(dst_accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) { | |
| if (dst_accel_id == src_accel_id) | |
| continue; | |
| dev_dbg(adev->dev, "peer_remote to dst_accel_id %u\n", dst_accel_id); | |
| peer = &remote->peer[dst_accel_id]; | |
| mutex_init(&peer->lock); | |
| interrupt = &peer->interrupt; | |
| shootdown = &peer->shootdown; | |
| /* ring buffer npa gart mapping */ | |
| npa = amdgpu_ualink_gart_npa_addr(adev, RB_TYPE_REMOTE_INTERRUPT, | |
| src_accel_id, dst_accel_id); | |
| r = amdgpu_ualink_gart_map(adev, rb_pages, npa, &interrupt->mm_node_rb, flags); | |
| if (r) | |
| break; | |
| interrupt->rb_npa_gart = adev->gmc.gart_start + | |
| (interrupt->mm_node_rb.start << AMDGPU_GPU_PAGE_SHIFT); | |
| dev_dbg(adev->dev, "rb npa 0x%llx mapped on gart 0x%llx\n", | |
| npa, interrupt->rb_npa_gart); | |
| /* tlb invalidate ring buffer npa gart mapping */ | |
| npa = amdgpu_ualink_gart_npa_addr(adev, RB_TYPE_TLB_INV, src_accel_id, | |
| dst_accel_id); | |
| r = amdgpu_ualink_gart_map(adev, rb_pages, npa, &shootdown->mm_node_rb, flags); | |
| if (r) | |
| break; | |
| shootdown->rb_npa_gart = adev->gmc.gart_start + | |
| (shootdown->mm_node_rb.start << AMDGPU_GPU_PAGE_SHIFT); | |
| dev_dbg(adev->dev, "tlb rb npa 0x%llx mapped on gart 0x%llx\n", | |
| npa, shootdown->rb_npa_gart); | |
| /* wptr npa gart mapping */ | |
| npa = amdgpu_ualink_gart_npa_addr(adev, RB_TYPE_TAILPTR, src_accel_id, | |
| dst_accel_id); | |
| r = amdgpu_ualink_gart_map(adev, 1, npa, &interrupt->mm_node_wptr, flags); | |
| if (r) | |
| break; | |
| interrupt->wptr_npa_gart = adev->gmc.gart_start + | |
| (interrupt->mm_node_wptr.start << AMDGPU_GPU_PAGE_SHIFT) | |
| + ualink_wptr_offset(adev, src_accel_id); | |
| shootdown->wptr_npa_gart = adev->gmc.gart_start + | |
| (interrupt->mm_node_wptr.start << AMDGPU_GPU_PAGE_SHIFT) | |
| + ualink_tlb_wptr_offset(adev, src_accel_id); | |
| dev_dbg(adev->dev, "rb wptr npa 0x%llx on gart 0x%llx, tlb wptr on gart 0x%llx\n", | |
| npa, interrupt->wptr_npa_gart, shootdown->wptr_npa_gart); | |
| /* | |
| * doorbell npa gart mapping, doorbell NPA address coding | |
| * | |
| * ReqAddr[51:40] = 0xFFF | |
| * ReqAddr[39:30] = GPU accel_id | |
| * ReqAddr[29:12] = 18'h00000 | |
| * ReqAddr[11:8] = Random value | |
| * ReqAddr[7:6] = PMI Index | |
| * 0 - PMI0 Remote Interrupt | |
| * 1 - PMI1 Remote TLB Shootdown | |
| * ReqAddr[5:0] = 6'h0 | |
| */ | |
| npa = 0xFFFULL << 40 | (u64)dst_accel_id << 30; | |
| r = amdgpu_ualink_gart_map(adev, 1, npa, &interrupt->mm_node_doorbell, flags); | |
| if (r) | |
| break; | |
| interrupt->doorbell_npa_gart = adev->gmc.gart_start + | |
| (interrupt->mm_node_doorbell.start << AMDGPU_GPU_PAGE_SHIFT); | |
| dev_dbg(adev->dev, "rb doorbell npa 0x%llx mapped on gart 0x%llx\n", | |
| npa, interrupt->doorbell_npa_gart); | |
| shootdown->doorbell_npa_gart = adev->gmc.gart_start+ | |
| (interrupt->mm_node_doorbell.start << AMDGPU_GPU_PAGE_SHIFT) + (1 << 6); | |
| dev_dbg(adev->dev, "tlb doorbell npa 0x%llx mapped on gart 0x%llx\n", | |
| npa, shootdown->doorbell_npa_gart); | |
| interrupt->rb_size = AMDGPU_UALINK_RB_SIZE / 64; | |
| interrupt->wptr = 0; | |
| interrupt->rptr = 0; | |
| interrupt->seq = 0; | |
| shootdown->rb_size = AMDGPU_UALINK_RB_SIZE / 64; | |
| shootdown->wptr = 0; | |
| shootdown->rptr = 0; | |
| shootdown->seq = 0; | |
| } | |
| dev_dbg(adev->dev, "peer remote init done r=%d\n", r); | |
| return r; | |
| } | |
| /** | |
| * amdgpu_ualink_peer_remote_fini - Clean up remote peer GPU connections | |
| * @adev: amdgpu device pointer | |
| * | |
| * Unmaps GART entries and frees resources for remote GPU communication. | |
| */ | |
| static void amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev) | |
| { | |
| u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT; | |
| struct amdgpu_ualink_remote *remote = to_remote(adev); | |
| u32 src_accel_id = ualink_accel_id(adev); | |
| u32 dst_accel_id; | |
| struct amdgpu_ualink_peer *peer; | |
| struct amdgpu_ualink_ring *ring; | |
| dev_dbg(adev->dev, "src_accel_id %u\n", src_accel_id); | |
| for_each_set_bit(dst_accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) { | |
| dev_dbg(adev->dev, "dst_accel_id %u\n", dst_accel_id); | |
| if (dst_accel_id == src_accel_id) | |
| continue; | |
| peer = &remote->peer[dst_accel_id]; | |
| mutex_destroy(&peer->lock); | |
| ring = &peer->interrupt; | |
| amdgpu_ualink_gart_unmap(adev, rb_pages, &ring->mm_node_rb); | |
| amdgpu_ualink_gart_unmap(adev, 1, &ring->mm_node_wptr); | |
| amdgpu_ualink_gart_unmap(adev, 1, &ring->mm_node_doorbell); | |
| ring = &peer->shootdown; | |
| amdgpu_ualink_gart_unmap(adev, rb_pages, &ring->mm_node_rb); | |
| } | |
| } | |
| /** | |
| * amdgpu_ualink_sw_init - Initialize UALink software resources | |
| * @adev: amdgpu device pointer | |
| * | |
| * Performs full UALink software initialization including metadata allocation, | |
| * NPA address mapping, SDMA scheduler entity setup, and remote peer GPU | |
| * connection initialization. On failure, all previously initialized resources | |
| * are cleaned up. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| int amdgpu_ualink_sw_init(struct amdgpu_device *adev) | |
| { | |
| struct amdgpu_ualink_remote *remote; | |
| int r; | |
| remote = kzalloc(sizeof(*remote), GFP_KERNEL); | |
| if (!remote) | |
| return -ENOMEM; | |
| adev->ualink.remote = remote; | |
| r = amdgpu_ualink_metadata_init(adev); | |
| if (r) | |
| goto out; | |
| r = amdgpu_ualink_metadata_npa_mapping(adev); | |
| if (r) | |
| goto out_metadata_fini; | |
| r = amdgpu_ualink_sdma_entities_init(adev); | |
| if (r) | |
| goto out_npa_unmap; | |
| r = amdgpu_ualink_peer_remote_init(adev); | |
| if (r) | |
| goto out_sdma_entities_fini; | |
| dev_dbg(adev->dev, "ualink sw init succeed\n"); | |
| return 0; | |
| out_sdma_entities_fini: | |
| amdgpu_ualink_sdma_entities_fini(adev); | |
| out_npa_unmap: | |
| amdgpu_ualink_metadata_npa_unmapping(adev); | |
| out_metadata_fini: | |
| amdgpu_ualink_metadata_fini(adev); | |
| out: | |
| kfree(adev->ualink.remote); | |
| adev->ualink.remote = NULL; | |
| dev_dbg(adev->dev, "ualink sw init failed %d\n", r); | |
| return r; | |
| } | |
| /** | |
| * amdgpu_ualink_sw_fini - Tear down UALink software resources | |
| * @adev: amdgpu device pointer | |
| * | |
| * Cleans up all UALink software resources in reverse order of initialization: | |
| * remote peer connections, SDMA entities, NPA mappings, and metadata. | |
| */ | |
| void amdgpu_ualink_sw_fini(struct amdgpu_device *adev) | |
| { | |
| dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n", ualink_accel_id(adev), | |
| ualink_addr_mode(adev)); | |
| adev->ualink.msg_ctl->send_halt(adev); | |
| amdgpu_ualink_peer_remote_fini(adev); | |
| amdgpu_ualink_sdma_entities_fini(adev); | |
| amdgpu_ualink_metadata_npa_unmapping(adev); | |
| amdgpu_ualink_metadata_fini(adev); | |
| kfree(adev->ualink.remote); | |
| adev->ualink.remote = NULL; | |
| } | |
| int ualink_send_hello(struct amdgpu_device *adev, u32 remote_accel_id) | |
| { | |
| return amdgpu_ualink_send_hello_msg(adev, remote_accel_id); | |
| } | |
| /* | |
| * The low bits of handle_lo carry the message-header field on the wire. | |
| * Strip those bits and splice in @acc_id to recover the | |
| * fully-qualified handle_lo value. | |
| */ | |
| static inline u64 amdgpu_ualink_reassemble_handle(u64 handle_lo, u32 acc_id) | |
| { | |
| return (handle_lo & ~AMDGPU_UALINK_MESSAGE_HEADER_MASK) | | |
| (acc_id & AMDGPU_UALINK_HANDLE_ACCID_MASK); | |
| } | |
| static int amdgpu_ualink_process_irq(struct amdgpu_device *adev, | |
| struct amdgpu_irq_src *source, | |
| struct amdgpu_iv_entry *entry) | |
| { | |
| u32 sender_acc_id, receiver_acc_id, msg_type, src_acc_id; | |
| enum amdgpu_ualink_accel_state accel_state; | |
| struct amdgpu_ualink_handle handle; | |
| u32 size, npa_addr, fail_reason; | |
| u32 dw0, dw1, dw2, dw3; | |
| u32 local_acc_id; | |
| int handled = 1; | |
| if (unlikely(adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE)) { | |
| dev_dbg(adev->dev, | |
| "UALink manager not initialized, dropping irq\n"); | |
| return handled; | |
| } | |
| /* Only handle interrupts while ACTIVE. Otherwise (e.g. during teardown) | |
| * consume stale ring entries but drop them, so they don't touch state | |
| * being freed. | |
| */ | |
| accel_state = adev->ualink.info->accel_state; | |
| if (accel_state != AMDGPU_UALINK_ACCEL_STATE_ACTIVE) { | |
| dev_dbg(adev->dev, | |
| "Dropping stale UALink interrupt, accel_state %d\n", | |
| accel_state); | |
| return handled; | |
| } | |
| dev_dbg(adev->dev, "%s client_id 0x%x src_id 0x%x ih\n", | |
| entry->ih == &adev->irq.ih ? "ring" : "ualink soft ring", | |
| entry->client_id, entry->src_id); | |
| /* ContextID 4 dwords */ | |
| src_acc_id = entry->pasid; | |
| dw0 = entry->src_data[0]; | |
| dw1 = entry->src_data[1]; | |
| dw2 = entry->src_data[2]; | |
| dw3 = entry->src_data[3]; | |
| /* Copy IH entry into ualink soft ring. */ | |
| if (entry->ih == &adev->irq.ih) { | |
| dev_dbg(adev->dev, "src accel_id %u context id 0x%x 0x%x 0x%x 0x%x\n", | |
| src_acc_id, dw0, dw1, dw2, dw3); | |
| dev_dbg(adev->dev, "delegate to ualink irq soft ring\n"); | |
| amdgpu_irq_ualink_delegate(adev, entry, 8); | |
| return handled; | |
| } | |
| msg_type = dw0 & AMDGPU_UALINK_MESSAGE_HEADER_MASK; | |
| local_acc_id = adev->ualink.info->ppod.accel_id; | |
| dev_dbg(adev->dev, "Got MSG: remote acc_id %u msg_type %u\n", | |
| src_acc_id, msg_type); | |
| /* Debug hook: if the bit corresponding to this msg_type is set in | |
| * drop_msg_bitmap, drop this single packet and clear the bit so that | |
| * any subsequent incoming messages are processed normally. Used to | |
| * exercise the connection reset/recovery paths via debugfs. | |
| */ | |
| if (msg_type < BITS_PER_LONG && | |
| test_and_clear_bit(msg_type, &adev->ualink.drop_msg_bitmap)) { | |
| dev_warn(adev->dev, | |
| "DROP MSG (debugfs): src acc_id %u msg_type %u dw[0-3] 0x%x 0x%x 0x%x 0x%x\n", | |
| src_acc_id, msg_type, dw0, dw1, dw2, dw3); | |
| return handled; | |
| } | |
| switch (msg_type) { | |
| case AMDGPU_UALINK_HELLO_MSG: | |
| receiver_acc_id = (dw0 >> AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT) & | |
| AMDGPU_UALINK_HELLO_MSG_ACCID_MASK; | |
| sender_acc_id = (dw0 >> AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT) & | |
| AMDGPU_UALINK_HELLO_MSG_ACCID_MASK; | |
| dev_dbg(adev->dev, | |
| "Got HELLO MSG: src acc_id %u receiver_acc_id %u sender_acc_id %u\n", | |
| src_acc_id, receiver_acc_id, sender_acc_id); | |
| amdgpu_ualink_process_hello_msg(adev, receiver_acc_id, sender_acc_id, | |
| src_acc_id); | |
| break; | |
| case AMDGPU_UALINK_HELLO_ACK_MSG: | |
| dev_dbg(adev->dev, | |
| "Got HELLO-ACK MSG: remote acc_id %u\n", src_acc_id); | |
| amdgpu_ualink_process_hello_ack_msg(adev, src_acc_id); | |
| break; | |
| case AMDGPU_UALINK_NPA_REQ_MSG: | |
| memcpy(&handle, entry->src_data, sizeof(struct amdgpu_ualink_handle)); | |
| handle.handle_lo = amdgpu_ualink_reassemble_handle(handle.handle_lo, | |
| local_acc_id); | |
| dev_dbg(adev->dev, | |
| "Got NPA-REQ MSG: remote acc_id %u handle %llx:%llx\n", | |
| src_acc_id, handle.handle_hi, handle.handle_lo); | |
| amdgpu_ualink_process_npa_req_msg(adev, src_acc_id, handle); | |
| break; | |
| case AMDGPU_UALINK_NPA_RSP_MSG: | |
| memcpy(&handle.handle_lo, entry->src_data, sizeof(handle.handle_lo)); | |
| handle.handle_lo = amdgpu_ualink_reassemble_handle(handle.handle_lo, | |
| src_acc_id); | |
| size = entry->src_data[2]; | |
| npa_addr = entry->src_data[3]; | |
| dev_dbg(adev->dev, | |
| "Got NPA-RSP MSG: remote acc_id %u handle_lo %llx size 0x%x npa_addr 0x%x\n", | |
| src_acc_id, handle.handle_lo, size, npa_addr); | |
| amdgpu_ualink_process_npa_rsp_msg(adev, src_acc_id, | |
| handle.handle_lo, | |
| npa_addr, size); | |
| break; | |
| case AMDGPU_UALINK_NPA_FAIL_MSG: | |
| memcpy(&handle.handle_lo, entry->src_data, sizeof(handle.handle_lo)); | |
| handle.handle_lo = amdgpu_ualink_reassemble_handle(handle.handle_lo, | |
| src_acc_id); | |
| fail_reason = entry->src_data[2] & | |
| AMDGPU_UALINK_NPA_FAIL_MSG_FAIL_REASON_MASK; | |
| dev_dbg(adev->dev, | |
| "Got NPA-FAIL MSG: remote acc_id %u handle_lo %llx fail_reason %u\n", | |
| src_acc_id, handle.handle_lo, fail_reason); | |
| amdgpu_ualink_process_npa_fail_msg(adev, src_acc_id, | |
| handle.handle_lo, | |
| fail_reason); | |
| break; | |
| case AMDGPU_UALINK_NPA_REVOKE_MSG: | |
| memcpy(&handle, entry->src_data, sizeof(struct amdgpu_ualink_handle)); | |
| handle.handle_lo = amdgpu_ualink_reassemble_handle(handle.handle_lo, | |
| src_acc_id); | |
| dev_dbg(adev->dev, | |
| "Got NPA-REVOKE MSG: remote acc_id %u handle %llx:%llx\n", | |
| src_acc_id, handle.handle_hi, handle.handle_lo); | |
| amdgpu_ualink_process_npa_revoke_msg(adev, src_acc_id, handle); | |
| break; | |
| case AMDGPU_UALINK_NPA_RELEASE_MSG: | |
| memcpy(&handle, entry->src_data, sizeof(struct amdgpu_ualink_handle)); | |
| handle.handle_lo = amdgpu_ualink_reassemble_handle(handle.handle_lo, | |
| local_acc_id); | |
| dev_dbg(adev->dev, | |
| "Got NPA-RELEASE MSG: remote acc_id %u handle %llx:%llx\n", | |
| src_acc_id, handle.handle_hi, handle.handle_lo); | |
| amdgpu_ualink_process_npa_release_msg(adev, src_acc_id, handle); | |
| break; | |
| default: | |
| dev_err(adev->dev, "Unknown message type (%u)\n", msg_type); | |
| break; | |
| } | |
| return handled; | |
| } | |
| static int amdgpu_ualink_set_irq_state(struct amdgpu_device *adev, | |
| struct amdgpu_irq_src *source, | |
| u32 type, | |
| enum amdgpu_interrupt_state state) | |
| { | |
| /* | |
| * Don't set register to enable/disable nHT controller interrupt. | |
| * | |
| * F/W running on MP2, which can always send cookie to IH block to | |
| * interrupt driver. | |
| */ | |
| dev_dbg(adev->dev, "ualink interrupt %s\n", | |
| state == AMDGPU_IRQ_STATE_ENABLE ? "enable" : "disable"); | |
| return 0; | |
| } | |
| static const struct amdgpu_irq_src_funcs ualink_irq_funcs = { | |
| .set = amdgpu_ualink_set_irq_state, | |
| .process = amdgpu_ualink_process_irq, | |
| }; | |
| /** | |
| * amdgpu_ualink_init_interrupt - register the UALink IRQ source | |
| * @adev: amdgpu device pointer | |
| * @client_id: IH client ID for the interrupt source | |
| * @src_id: source ID for the interrupt source | |
| * | |
| * Registers the UALink interrupt source with the IH subsystem. | |
| * | |
| * Return: 0 on success, negative error code on failure | |
| */ | |
| int amdgpu_ualink_init_interrupt(struct amdgpu_device *adev, | |
| unsigned int client_id, unsigned int src_id) | |
| { | |
| int r; | |
| dev_dbg(adev->dev, "init ualink irq client_id 0x%x src_id 0x%x\n", | |
| client_id, src_id); | |
| adev->ualink.irq.num_types = 1; | |
| adev->ualink.irq.funcs = &ualink_irq_funcs; | |
| r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->ualink.irq); | |
| return r; | |
| } | |