// SPDX-License-Identifier: GPL-2.0 OR MIT /* * Copyright 2014-2022 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE.
*/
/* * Creating the sysfs folders
*/
dev->kobj_node = kfd_alloc_struct(dev->kobj_node); if (!dev->kobj_node) return -ENOMEM;
ret = kobject_init_and_add(dev->kobj_node, &node_type,
sys_props.kobj_nodes, "%d", id); if (ret < 0) {
kobject_put(dev->kobj_node); return ret;
}
dev->kobj_mem = kobject_create_and_add("mem_banks", dev->kobj_node); if (!dev->kobj_mem) return -ENOMEM;
dev->kobj_cache = kobject_create_and_add("caches", dev->kobj_node); if (!dev->kobj_cache) return -ENOMEM;
dev->kobj_iolink = kobject_create_and_add("io_links", dev->kobj_node); if (!dev->kobj_iolink) return -ENOMEM;
dev->kobj_p2plink = kobject_create_and_add("p2p_links", dev->kobj_node); if (!dev->kobj_p2plink) return -ENOMEM;
dev->kobj_perf = kobject_create_and_add("perf", dev->kobj_node); if (!dev->kobj_perf) return -ENOMEM;
/* * Creating sysfs files for node properties
*/
dev->attr_gpuid.name = "gpu_id";
dev->attr_gpuid.mode = KFD_SYSFS_FILE_MODE;
sysfs_attr_init(&dev->attr_gpuid);
dev->attr_name.name = "name";
dev->attr_name.mode = KFD_SYSFS_FILE_MODE;
sysfs_attr_init(&dev->attr_name);
dev->attr_props.name = "properties";
dev->attr_props.mode = KFD_SYSFS_FILE_MODE;
sysfs_attr_init(&dev->attr_props);
ret = sysfs_create_file(dev->kobj_node, &dev->attr_gpuid); if (ret < 0) return ret;
ret = sysfs_create_file(dev->kobj_node, &dev->attr_name); if (ret < 0) return ret;
ret = sysfs_create_file(dev->kobj_node, &dev->attr_props); if (ret < 0) return ret;
i = 0;
list_for_each_entry(mem, &dev->mem_props, list) {
mem->kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); if (!mem->kobj) return -ENOMEM;
ret = kobject_init_and_add(mem->kobj, &mem_type,
dev->kobj_mem, "%d", i); if (ret < 0) {
kobject_put(mem->kobj); return ret;
}
mem->attr.name = "properties";
mem->attr.mode = KFD_SYSFS_FILE_MODE;
sysfs_attr_init(&mem->attr);
ret = sysfs_create_file(mem->kobj, &mem->attr); if (ret < 0) return ret;
i++;
}
i = 0;
list_for_each_entry(cache, &dev->cache_props, list) {
cache->kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); if (!cache->kobj) return -ENOMEM;
ret = kobject_init_and_add(cache->kobj, &cache_type,
dev->kobj_cache, "%d", i); if (ret < 0) {
kobject_put(cache->kobj); return ret;
}
cache->attr.name = "properties";
cache->attr.mode = KFD_SYSFS_FILE_MODE;
sysfs_attr_init(&cache->attr);
ret = sysfs_create_file(cache->kobj, &cache->attr); if (ret < 0) return ret;
i++;
}
i = 0;
list_for_each_entry(iolink, &dev->io_link_props, list) {
iolink->kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); if (!iolink->kobj) return -ENOMEM;
ret = kobject_init_and_add(iolink->kobj, &iolink_type,
dev->kobj_iolink, "%d", i); if (ret < 0) {
kobject_put(iolink->kobj); return ret;
}
iolink->attr.name = "properties";
iolink->attr.mode = KFD_SYSFS_FILE_MODE;
sysfs_attr_init(&iolink->attr);
ret = sysfs_create_file(iolink->kobj, &iolink->attr); if (ret < 0) return ret;
i++;
}
i = 0;
list_for_each_entry(p2plink, &dev->p2p_link_props, list) {
p2plink->kobj = kzalloc(sizeof(struct kobject), GFP_KERNEL); if (!p2plink->kobj) return -ENOMEM;
ret = kobject_init_and_add(p2plink->kobj, &iolink_type,
dev->kobj_p2plink, "%d", i); if (ret < 0) {
kobject_put(p2plink->kobj); return ret;
}
p2plink->attr.name = "properties";
p2plink->attr.mode = KFD_SYSFS_FILE_MODE;
sysfs_attr_init(&p2plink->attr);
ret = sysfs_create_file(p2plink->kobj, &p2plink->attr); if (ret < 0) return ret;
i++;
}
/* All hardware blocks have the same number of attributes. */
num_attrs = ARRAY_SIZE(perf_attr_iommu);
list_for_each_entry(perf, &dev->perf_props, list) {
perf->attr_group = kzalloc(sizeof(struct kfd_perf_attr)
* num_attrs + sizeof(struct attribute_group),
GFP_KERNEL); if (!perf->attr_group) return -ENOMEM;
attrs = (struct attribute **)(perf->attr_group + 1); if (!strcmp(perf->block_name, "iommu")) { /* Information of IOMMU's num_counters and counter_ids is shown * under /sys/bus/event_source/devices/amd_iommu. We don't * duplicate here.
*/
perf_attr_iommu[0].data = perf->max_concurrent; for (i = 0; i < num_attrs; i++)
attrs[i] = &perf_attr_iommu[i].attr.attr;
}
perf->attr_group->name = perf->block_name;
perf->attr_group->attrs = attrs;
ret = sysfs_create_group(dev->kobj_perf, perf->attr_group); if (ret < 0) return ret;
}
return 0;
}
/* Called with write topology lock acquired */ staticint kfd_build_sysfs_node_tree(void)
{ struct kfd_topology_device *dev; int ret;
uint32_t i = 0;
list_for_each_entry(dev, &topology_device_list, list) {
ret = kfd_build_sysfs_node_entry(dev, i); if (ret < 0) return ret;
i++;
}
return 0;
}
/* Called with write topology lock acquired */ staticvoid kfd_remove_sysfs_node_tree(void)
{ struct kfd_topology_device *dev;
dev = list_last_entry(&topology_device_list, struct kfd_topology_device, list); if (dev) { if (dev->node_props.cpu_cores_count &&
dev->node_props.simd_count) {
pr_info("Topology: Add APU node [0x%0x:0x%0x]\n",
dev->node_props.device_id,
dev->node_props.vendor_id);
} elseif (dev->node_props.cpu_cores_count)
pr_info("Topology: Add CPU node\n"); elseif (dev->node_props.simd_count)
pr_info("Topology: Add dGPU node [0x%0x:0x%0x]\n",
dev->node_props.device_id,
dev->node_props.vendor_id);
}
up_read(&topology_lock);
}
/* Helper function for intializing platform_xx members of * kfd_system_properties. Uses OEM info from the last CPU/APU node.
*/ staticvoid kfd_update_system_properties(void)
{ struct kfd_topology_device *dev;
/* kfd_add_non_crat_information - Add information that is not currently * defined in CRAT but is necessary for KFD topology * @dev - topology device to which addition info is added
*/ staticvoid kfd_add_non_crat_information(struct kfd_topology_device *kdev)
{ /* Check if CPU only node. */ if (!kdev->gpu) { /* Add system memory information */
dmi_walk(find_system_memory, kdev);
} /* TODO: For GPU node, rearrange code from kfd_topology_add_device */
}
int kfd_topology_init(void)
{ void *crat_image = NULL;
size_t image_size = 0; int ret; struct list_head temp_topology_device_list; int cpu_only_node = 0; struct kfd_topology_device *kdev; int proximity_domain;
/* topology_device_list - Master list of all topology devices * temp_topology_device_list - temporary list created while parsing CRAT * or VCRAT. Once parsing is complete the contents of list is moved to * topology_device_list
*/
/* Initialize the head for the both the lists */
INIT_LIST_HEAD(&topology_device_list);
INIT_LIST_HEAD(&temp_topology_device_list);
init_rwsem(&topology_lock);
memset(&sys_props, 0, sizeof(sys_props));
/* Proximity domains in ACPI CRAT tables start counting at * 0. The same should be true for virtual CRAT tables created * at this stage. GPUs added later in kfd_topology_add_device * use a counter.
*/
proximity_domain = 0;
ret = kfd_create_crat_image_virtual(&crat_image, &image_size,
COMPUTE_UNIT_CPU, NULL,
proximity_domain);
cpu_only_node = 1; if (ret) {
pr_err("Error creating VCRAT table for CPU\n"); return ret;
}
ret = kfd_parse_crat_table(crat_image,
&temp_topology_device_list,
proximity_domain); if (ret) {
pr_err("Error parsing VCRAT table for CPU\n"); goto err;
}
down_write(&topology_lock);
kfd_topology_update_device_list(&temp_topology_device_list,
&topology_device_list);
topology_crat_proximity_domain = sys_props.num_devices-1;
ret = kfd_topology_update_sysfs();
up_write(&topology_lock);
if (!ret) {
sys_props.generation_count++;
kfd_update_system_properties();
kfd_debug_print_topology();
} else
pr_err("Failed to update topology in sysfs ret=%d\n", ret);
/* For nodes with GPU, this information gets added * when GPU is detected (kfd_topology_add_device).
*/ if (cpu_only_node) { /* Add additional information to CPU only node created above */
down_write(&topology_lock);
kdev = list_first_entry(&topology_device_list, struct kfd_topology_device, list);
up_write(&topology_lock);
kfd_add_non_crat_information(kdev);
}
/* There is a very small possibility when generating a * 16 (KFD_GPU_ID_HASH_WIDTH) bit value from 8 word buffer * that the value could be 0 or non-unique. So, check if * it is unique and non-zero. If not unique increment till * unique one is found. In case of overflow, restart from 1
*/
down_read(&topology_lock); do {
is_unique = true; if (!gpu_id)
gpu_id = 1;
list_for_each_entry(dev, &topology_device_list, list) { if (dev->gpu && dev->gpu_id == gpu_id) {
is_unique = false; break;
}
} if (unlikely(!is_unique))
gpu_id = (gpu_id + 1) &
((1 << KFD_GPU_ID_HASH_WIDTH) - 1);
} while (!is_unique);
up_read(&topology_lock);
return gpu_id;
} /* kfd_assign_gpu - Attach @gpu to the correct kfd topology device. If * the GPU device is not already present in the topology device * list then return NULL. This means a new topology device has to * be created for this GPU.
*/ staticstruct kfd_topology_device *kfd_assign_gpu(struct kfd_node *gpu)
{ struct kfd_topology_device *dev; struct kfd_topology_device *out_dev = NULL; struct kfd_mem_properties *mem; struct kfd_cache_properties *cache; struct kfd_iolink_properties *iolink; struct kfd_iolink_properties *p2plink;
list_for_each_entry(dev, &topology_device_list, list) { /* Discrete GPUs need their own topology device list * entries. Don't assign them to CPU/APU nodes.
*/ if (dev->node_props.cpu_cores_count) continue;
staticvoid kfd_notify_gpu_change(uint32_t gpu_id, int arrival)
{ /* * TODO: Generate an event for thunk about the arrival/removal * of the GPU
*/
}
/* kfd_fill_mem_clk_max_info - Since CRAT doesn't have memory clock info, * patch this after CRAT parsing.
*/ staticvoid kfd_fill_mem_clk_max_info(struct kfd_topology_device *dev)
{ struct kfd_mem_properties *mem; struct kfd_local_mem_info local_mem_info;
if (!dev) return;
/* Currently, amdgpu driver (amdgpu_mc) deals only with GPUs with * single bank of VRAM local memory. * for dGPUs - VCRAT reports only one bank of Local Memory * for APUs - If CRAT from ACPI reports more than one bank, then * all the banks will report the same mem_clk_max information
*/
amdgpu_amdkfd_get_local_mem_info(dev->gpu->adev, &local_mem_info,
dev->gpu->xcp);
/* If recommended engine is out of range, need to reset the mask */ if (outbound_link->rec_sdma_eng_id_mask & sdma_eng_id_mask)
outbound_link->rec_sdma_eng_id_mask = xgmi_sdma_eng_id_mask; if (inbound_link->rec_sdma_eng_id_mask & sdma_eng_id_mask)
inbound_link->rec_sdma_eng_id_mask = xgmi_sdma_eng_id_mask;
/* GPU only creates direct links so apply flags setting to all */
list_for_each_entry(link, &dev->io_link_props, list) {
link->flags = CRAT_IOLINK_FLAGS_ENABLED;
kfd_set_iolink_no_atomics(dev, NULL, link);
peer_dev = kfd_topology_device_by_proximity_domain(
link->node_to);
if (!peer_dev) continue;
/* Include the CPU peer in GPU hive if connected over xGMI. */ if (!peer_dev->gpu &&
link->iolink_type == CRAT_IOLINK_TYPE_XGMI) { /* * If the GPU is not part of a GPU hive, use its pci * device location as the hive ID to bind with the CPU.
*/ if (!dev->node_props.hive_id)
dev->node_props.hive_id = pci_dev_id(dev->gpu->adev->pdev);
peer_dev->node_props.hive_id = dev->node_props.hive_id;
}
list_for_each_entry(inbound_link, &peer_dev->io_link_props,
list) { if (inbound_link->node_to != link->node_from) continue;
props->node_from = gpu_node;
props->node_to = i;
kdev->node_props.p2p_links_count++;
list_add_tail(&props->list, &kdev->p2p_link_props);
ret = kfd_build_p2p_node_entry(kdev, props); if (ret < 0) return ret;
/* for small Bar, no CPU --> GPU in-direct links */ if (kfd_dev_is_large_bar(kdev->gpu)) { /* CPU <--> CPU <--> GPU, CPU node*/
props2 = kfd_alloc_struct(props2); if (!props2) return -ENOMEM;
/* CU could be inactive. In case of shared cache find the first active * CU. and incase of non-shared cache check if the CU is inactive. If * inactive active skip it
*/ if (first_active_cu) {
pcache = kfd_alloc_struct(pcache); if (!pcache) return -ENOMEM;
/* Helper function. See kfd_fill_gpu_cache_info for parameter description */ staticint fill_in_l2_l3_pcache(struct kfd_cache_properties **props_ext, struct kfd_gpu_cache_info *pcache_info, struct amdgpu_cu_info *cu_info, struct amdgpu_gfx_config *gfx_info, int cache_type, unsignedint cu_processor_id, struct kfd_node *knode)
{ unsignedint cu_sibling_map_mask = 0; int first_active_cu; int i, j, k, xcc, start, end; int num_xcc = NUM_XCC(knode->xcc_mask); struct kfd_cache_properties *pcache = NULL; enum amdgpu_memory_partition mode; struct amdgpu_device *adev = knode->adev; bool found = false;
start = ffs(knode->xcc_mask) - 1;
end = start + num_xcc;
/* To find the bitmap in the first active cu in the first * xcc, it is based on the assumption that evrey xcc must * have at least one active cu.
*/ for (i = 0; i < gfx_info->max_shader_engines && !found; i++) { for (j = 0; j < gfx_info->max_sh_per_se && !found; j++) { if (cu_info->bitmap[start][i % 4][j % 4]) {
cu_sibling_map_mask =
cu_info->bitmap[start][i % 4][j % 4];
found = true;
}
}
}
/* CU could be inactive. In case of shared cache find the first active * CU. and incase of non-shared cache check if the CU is inactive. If * inactive active skip it
*/ if (first_active_cu) {
pcache = kfd_alloc_struct(pcache); if (!pcache) return -ENOMEM;
/* kfd_fill_cache_non_crat_info - Fill GPU cache info using kfd_gpu_cache_info * tables
*/ staticvoid kfd_fill_cache_non_crat_info(struct kfd_topology_device *dev, struct kfd_node *kdev)
{ struct kfd_gpu_cache_info *pcache_info = NULL; int i, j, k, xcc, start, end; int ct = 0; unsignedint cu_processor_id; int ret; unsignedint num_cu_shared; struct amdgpu_cu_info *cu_info = &kdev->adev->gfx.cu_info; struct amdgpu_gfx_config *gfx_info = &kdev->adev->gfx.config; int gpu_processor_id; struct kfd_cache_properties *props_ext = NULL; int num_of_entries = 0; int num_of_cache_types = 0; struct kfd_gpu_cache_info cache_info[KFD_MAX_CACHE_TYPES];
gpu_processor_id = dev->node_props.simd_id_base;
memset(cache_info, 0, sizeof(cache_info));
pcache_info = cache_info;
num_of_cache_types = kfd_get_gpu_cache_info(kdev, &pcache_info); if (!num_of_cache_types) {
pr_warn("no cache info found\n"); return;
}
/* For each type of cache listed in the kfd_gpu_cache_info table, * go through all available Compute Units. * The [i,j,k] loop will * if kfd_gpu_cache_info.num_cu_shared = 1 * will parse through all available CU * If (kfd_gpu_cache_info.num_cu_shared != 1) * then it will consider only one CU from * the shared unit
*/
start = ffs(kdev->xcc_mask) - 1;
end = start + NUM_XCC(kdev->xcc_mask);
for (ct = 0; ct < num_of_cache_types; ct++) {
cu_processor_id = gpu_processor_id; if (pcache_info[ct].cache_level == 1) { for (xcc = start; xcc < end; xcc++) { for (i = 0; i < gfx_info->max_shader_engines; i++) { for (j = 0; j < gfx_info->max_sh_per_se; j++) { for (k = 0; k < gfx_info->max_cu_per_sh; k += pcache_info[ct].num_cu_shared) {
ret = fill_in_l1_pcache(&props_ext, pcache_info,
cu_info->bitmap[xcc][i % 4][j + i / 4], ct,
cu_processor_id, k);
if (ret < 0) break;
if (!ret) {
num_of_entries++;
list_add_tail(&props_ext->list, &dev->cache_props);
}
/* Move to next CU block */
num_cu_shared = ((k + pcache_info[ct].num_cu_shared) <=
gfx_info->max_cu_per_sh) ?
pcache_info[ct].num_cu_shared :
(gfx_info->max_cu_per_sh - k);
cu_processor_id += num_cu_shared;
}
}
}
}
} else {
ret = fill_in_l2_l3_pcache(&props_ext, pcache_info,
cu_info, gfx_info, ct, cu_processor_id, kdev);
*dev = kfd_assign_gpu(gpu); if (WARN_ON(!*dev)) {
res = -ENODEV; goto err;
}
/* Fill the cache affinity information here for the GPUs * using VCRAT
*/
kfd_fill_cache_non_crat_info(*dev, gpu);
/* Update the SYSFS tree, since we added another topology * device
*/
res = kfd_topology_update_sysfs(); if (!res)
sys_props.generation_count++; else
dev_err(gpu->adev->dev, "Failed to update GPU to sysfs topology. res=%d\n",
res);
if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0))
dev->node_props.capability |=
HSA_CAP_TRAP_DEBUG_PRECISE_ALU_OPERATIONS_SUPPORTED;
}
kfd_topology_set_dbg_firmware_support(dev);
}
int kfd_topology_add_device(struct kfd_node *gpu)
{
uint32_t gpu_id; struct kfd_topology_device *dev; int res = 0; int i; constchar *asic_name = amdgpu_asic_name[gpu->adev->asic_type]; struct amdgpu_gfx_config *gfx_info = &gpu->adev->gfx.config; struct amdgpu_cu_info *cu_info = &gpu->adev->gfx.cu_info;
if (gpu->xcp && !gpu->xcp->ddev) {
dev_warn(gpu->adev->dev, "Won't add GPU to topology since it has no drm node assigned."); return 0;
} else {
dev_dbg(gpu->adev->dev, "Adding new GPU to topology\n");
}
/* Check to see if this gpu device exists in the topology_device_list. * If so, assign the gpu to that device, * else create a Virtual CRAT for this gpu device and then parse that * CRAT to create a new topology device. Once created assign the gpu to * that topology device
*/
down_write(&topology_lock);
dev = kfd_assign_gpu(gpu); if (!dev)
res = kfd_topology_add_device_locked(gpu, &dev);
up_write(&topology_lock); if (res) return res;
/* TODO: Move the following lines to function * kfd_add_non_crat_information
*/
/* Fill-in additional information that is not available in CRAT but * needed for the topology
*/ for (i = 0; i < KFD_TOPOLOGY_PUBLIC_NAME_SIZE-1; i++) {
dev->node_props.name[i] = __tolower(asic_name[i]); if (asic_name[i] == '\0') break;
}
dev->node_props.name[i] = '\0';
switch (dev->gpu->adev->asic_type) { case CHIP_KAVERI: case CHIP_HAWAII: case CHIP_TONGA:
dev->node_props.capability |= ((HSA_CAP_DOORBELL_TYPE_PRE_1_0 <<
HSA_CAP_DOORBELL_TYPE_TOTALBITS_SHIFT) &
HSA_CAP_DOORBELL_TYPE_TOTALBITS_MASK); break; case CHIP_CARRIZO: case CHIP_FIJI: case CHIP_POLARIS10: case CHIP_POLARIS11: case CHIP_POLARIS12: case CHIP_VEGAM:
pr_debug("Adding doorbell packet type capability\n");
dev->node_props.capability |= ((HSA_CAP_DOORBELL_TYPE_1_0 <<
HSA_CAP_DOORBELL_TYPE_TOTALBITS_SHIFT) &
HSA_CAP_DOORBELL_TYPE_TOTALBITS_MASK); break; default: if (KFD_GC_VERSION(dev->gpu) < IP_VERSION(9, 0, 1))
WARN(1, "Unexpected ASIC family %u",
dev->gpu->adev->asic_type); else
kfd_topology_set_capabilities(dev);
}
/* * Overwrite ATS capability according to needs_iommu_device to fix * potential missing corresponding bit in CRAT of BIOS.
*/
dev->node_props.capability &= ~HSA_CAP_ATS_PRESENT;
/* Fix errors in CZ CRAT. * simd_count: Carrizo CRAT reports wrong simd_count, probably * because it doesn't consider masked out CUs * max_waves_per_simd: Carrizo reports wrong max_waves_per_simd
*/ if (dev->gpu->adev->asic_type == CHIP_CARRIZO) {
dev->node_props.simd_count =
cu_info->simd_per_cu * cu_info->number;
dev->node_props.max_waves_per_simd = 10;
}
/* kfd only concerns sram ecc on GFX and HBM ecc on UMC */
dev->node_props.capability |=
((dev->gpu->adev->ras_enabled & BIT(AMDGPU_RAS_BLOCK__GFX)) != 0) ?
HSA_CAP_SRAM_EDCSUPPORTED : 0;
dev->node_props.capability |=
((dev->gpu->adev->ras_enabled & BIT(AMDGPU_RAS_BLOCK__UMC)) != 0) ?
HSA_CAP_MEM_EDCSUPPORTED : 0;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.