open-gpu-kernel-modules/kernel-open/nvidia-uvm/uvm_va_space.c
Bernhard Stoeckner a8e01be6b2
535.104.05
2023-08-22 15:09:37 +02:00

2324 lines
87 KiB
C

/*******************************************************************************
Copyright (c) 2015-2023 NVIDIA Corporation
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 AUTHORS OR COPYRIGHT HOLDERS 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 "uvm_api.h"
#include "uvm_va_space.h"
#include "uvm_va_range.h"
#include "uvm_lock.h"
#include "uvm_global.h"
#include "uvm_kvmalloc.h"
#include "uvm_perf_heuristics.h"
#include "uvm_user_channel.h"
#include "uvm_tools.h"
#include "uvm_thread_context.h"
#include "uvm_hal.h"
#include "uvm_map_external.h"
#include "uvm_ats.h"
#include "uvm_gpu_access_counters.h"
#include "uvm_hmm.h"
#include "uvm_va_space_mm.h"
#include "uvm_test.h"
#include "uvm_common.h"
#include "nv_uvm_interface.h"
#include "nv-kthread-q.h"
static bool processor_mask_array_test(const uvm_processor_mask_t *mask,
uvm_processor_id_t mask_id,
uvm_processor_id_t id)
{
return uvm_processor_mask_test(&mask[uvm_id_value(mask_id)], id);
}
static void processor_mask_array_clear(uvm_processor_mask_t *mask,
uvm_processor_id_t mask_id,
uvm_processor_id_t id)
{
uvm_processor_mask_clear(&mask[uvm_id_value(mask_id)], id);
}
static void processor_mask_array_set(uvm_processor_mask_t *mask,
uvm_processor_id_t mask_id,
uvm_processor_id_t id)
{
uvm_processor_mask_set(&mask[uvm_id_value(mask_id)], id);
}
static bool processor_mask_array_empty(const uvm_processor_mask_t *mask, uvm_processor_id_t mask_id)
{
return uvm_processor_mask_empty(&mask[uvm_id_value(mask_id)]);
}
static NV_STATUS enable_peers(uvm_va_space_t *va_space, uvm_gpu_t *gpu0, uvm_gpu_t *gpu1);
static void disable_peers(uvm_va_space_t *va_space,
uvm_gpu_t *gpu0,
uvm_gpu_t *gpu1,
struct list_head *deferred_free_list);
static void remove_gpu_va_space(uvm_gpu_va_space_t *gpu_va_space,
struct mm_struct *mm,
struct list_head *deferred_free_list);
static void va_space_remove_dummy_thread_contexts(uvm_va_space_t *va_space);
static void init_tools_data(uvm_va_space_t *va_space)
{
int i;
uvm_init_rwsem(&va_space->tools.lock, UVM_LOCK_ORDER_VA_SPACE_TOOLS);
for (i = 0; i < ARRAY_SIZE(va_space->tools.counters); i++)
INIT_LIST_HEAD(va_space->tools.counters + i);
for (i = 0; i < ARRAY_SIZE(va_space->tools.queues); i++)
INIT_LIST_HEAD(va_space->tools.queues + i);
}
static NV_STATUS register_gpu_nvlink_peers(uvm_va_space_t *va_space, uvm_gpu_t *gpu)
{
uvm_gpu_t *other_gpu;
uvm_assert_rwsem_locked(&va_space->lock);
for_each_va_space_gpu(other_gpu, va_space) {
uvm_gpu_peer_t *peer_caps;
if (uvm_id_equal(other_gpu->id, gpu->id))
continue;
peer_caps = uvm_gpu_peer_caps(gpu, other_gpu);
if (peer_caps->link_type >= UVM_GPU_LINK_NVLINK_1) {
NV_STATUS status = enable_peers(va_space, gpu, other_gpu);
if (status != NV_OK)
return status;
}
}
return NV_OK;
}
static bool va_space_check_processors_masks(uvm_va_space_t *va_space)
{
uvm_processor_id_t processor;
uvm_processor_mask_t processors;
uvm_assert_rwsem_locked_write(&va_space->lock);
uvm_processor_mask_copy(&processors, &va_space->registered_gpus);
uvm_processor_mask_set(&processors, UVM_ID_CPU);
for_each_id_in_mask(processor, &processors) {
uvm_processor_id_t other_processor;
UVM_ASSERT(processor_mask_array_test(va_space->can_access, processor, processor));
UVM_ASSERT(processor_mask_array_test(va_space->accessible_from, processor, processor));
UVM_ASSERT(processor_mask_array_test(va_space->can_copy_from, processor, processor));
UVM_ASSERT(processor_mask_array_test(va_space->can_copy_from, processor, UVM_ID_CPU));
UVM_ASSERT(processor_mask_array_test(va_space->can_copy_from, UVM_ID_CPU, processor));
// NVLINK
UVM_ASSERT(!processor_mask_array_test(va_space->has_nvlink, processor, processor));
UVM_ASSERT(uvm_processor_mask_subset(&va_space->has_nvlink[uvm_id_value(processor)],
&va_space->can_copy_from[uvm_id_value(processor)]));
// Peers
UVM_ASSERT(!processor_mask_array_test(va_space->indirect_peers, processor, processor));
UVM_ASSERT(uvm_processor_mask_subset(&va_space->indirect_peers[uvm_id_value(processor)],
&va_space->has_native_atomics[uvm_id_value(processor)]));
// Atomics
UVM_ASSERT(processor_mask_array_test(va_space->has_native_atomics, processor, processor));
UVM_ASSERT(uvm_processor_mask_subset(&va_space->has_native_atomics[uvm_id_value(processor)],
&va_space->can_copy_from[uvm_id_value(processor)]));
UVM_ASSERT(uvm_processor_mask_subset(&va_space->has_native_atomics[uvm_id_value(processor)],
&va_space->can_access[uvm_id_value(processor)]));
for_each_id_in_mask(other_processor, &va_space->can_access[uvm_id_value(processor)])
UVM_ASSERT(processor_mask_array_test(va_space->accessible_from, other_processor, processor));
for_each_id_in_mask(other_processor, &va_space->accessible_from[uvm_id_value(processor)])
UVM_ASSERT(processor_mask_array_test(va_space->can_access, other_processor, processor));
}
return true;
}
NV_STATUS uvm_va_space_create(struct address_space *mapping, uvm_va_space_t **va_space_ptr, NvU64 flags)
{
NV_STATUS status;
uvm_va_space_t *va_space = uvm_kvmalloc_zero(sizeof(*va_space));
uvm_gpu_id_t gpu_id;
*va_space_ptr = NULL;
if (!va_space)
return NV_ERR_NO_MEMORY;
if (flags & ~UVM_INIT_FLAGS_MASK) {
uvm_kvfree(va_space);
return NV_ERR_INVALID_ARGUMENT;
}
uvm_init_rwsem(&va_space->lock, UVM_LOCK_ORDER_VA_SPACE);
uvm_mutex_init(&va_space->serialize_writers_lock, UVM_LOCK_ORDER_VA_SPACE_SERIALIZE_WRITERS);
uvm_mutex_init(&va_space->read_acquire_write_release_lock,
UVM_LOCK_ORDER_VA_SPACE_READ_ACQUIRE_WRITE_RELEASE_LOCK);
uvm_spin_lock_init(&va_space->va_space_mm.lock, UVM_LOCK_ORDER_LEAF);
uvm_range_tree_init(&va_space->va_range_tree);
uvm_ats_init_va_space(va_space);
// Init to 0 since we rely on atomic_inc_return behavior to return 1 as the first ID
atomic64_set(&va_space->range_group_id_counter, 0);
INIT_RADIX_TREE(&va_space->range_groups, NV_UVM_GFP_FLAGS);
uvm_range_tree_init(&va_space->range_group_ranges);
bitmap_zero(va_space->enabled_peers, UVM_MAX_UNIQUE_GPU_PAIRS);
// CPU is not explicitly registered in the va space
processor_mask_array_set(va_space->can_access, UVM_ID_CPU, UVM_ID_CPU);
processor_mask_array_set(va_space->accessible_from, UVM_ID_CPU, UVM_ID_CPU);
processor_mask_array_set(va_space->can_copy_from, UVM_ID_CPU, UVM_ID_CPU);
processor_mask_array_set(va_space->has_native_atomics, UVM_ID_CPU, UVM_ID_CPU);
// CPU always participates in system-wide atomics
uvm_processor_mask_set(&va_space->system_wide_atomics_enabled_processors, UVM_ID_CPU);
uvm_processor_mask_set(&va_space->faultable_processors, UVM_ID_CPU);
// Initialize the CPU/GPU affinity array. New CPU NUMA nodes are added at
// GPU registration time, but they are never freed on unregister_gpu
// (although the GPU is removed from the corresponding mask).
for_each_gpu_id(gpu_id) {
uvm_cpu_gpu_affinity_t *affinity = &va_space->gpu_cpu_numa_affinity[uvm_id_gpu_index(gpu_id)];
affinity->numa_node = -1;
uvm_processor_mask_zero(&affinity->gpus);
}
init_waitqueue_head(&va_space->va_space_mm.last_retainer_wait_queue);
init_waitqueue_head(&va_space->gpu_va_space_deferred_free.wait_queue);
va_space->mapping = mapping;
va_space->test.page_prefetch_enabled = true;
init_tools_data(va_space);
uvm_down_write_mmap_lock(current->mm);
uvm_va_space_down_write(va_space);
status = uvm_perf_init_va_space_events(va_space, &va_space->perf_events);
if (status != NV_OK)
goto fail;
status = uvm_perf_heuristics_load(va_space);
if (status != NV_OK)
goto fail;
status = uvm_gpu_init_va_space(va_space);
if (status != NV_OK)
goto fail;
UVM_ASSERT(va_space_check_processors_masks(va_space));
va_space->initialization_flags = flags;
status = uvm_va_space_mm_register(va_space);
if (status != NV_OK)
goto fail;
uvm_hmm_va_space_initialize(va_space);
uvm_va_space_up_write(va_space);
uvm_up_write_mmap_lock(current->mm);
uvm_mutex_lock(&g_uvm_global.va_spaces.lock);
list_add_tail(&va_space->list_node, &g_uvm_global.va_spaces.list);
uvm_mutex_unlock(&g_uvm_global.va_spaces.lock);
*va_space_ptr = va_space;
return NV_OK;
fail:
uvm_perf_heuristics_unload(va_space);
uvm_perf_destroy_va_space_events(&va_space->perf_events);
uvm_va_space_up_write(va_space);
uvm_up_write_mmap_lock(current->mm);
// See the comment in uvm_va_space_mm_unregister() for why this has to be
// called after releasing the locks.
uvm_va_space_mm_unregister(va_space);
uvm_kvfree(va_space);
return status;
}
// This function does *not* release the GPU, nor the GPU's PCIE peer pairings.
// Those are returned so the caller can do it after dropping the VA space lock.
static void unregister_gpu(uvm_va_space_t *va_space,
uvm_gpu_t *gpu,
struct mm_struct *mm,
struct list_head *deferred_free_list,
uvm_global_processor_mask_t *peers_to_release)
{
uvm_gpu_t *peer_gpu;
uvm_va_range_t *va_range;
NvU32 peer_table_index;
uvm_assert_rwsem_locked_write(&va_space->lock);
if (peers_to_release)
uvm_global_processor_mask_zero(peers_to_release);
// If a GPU VA Space was explicitly registered, but not explicitly
// unregistered, unregister it and add all of its objects to the free list.
remove_gpu_va_space(uvm_gpu_va_space_get(va_space, gpu), mm, deferred_free_list);
uvm_for_each_va_range(va_range, va_space)
uvm_va_range_unregister_gpu(va_range, gpu, mm, deferred_free_list);
uvm_hmm_unregister_gpu(va_space, gpu, mm);
// If this GPU has any peer-to-peer pair that was explicitly enabled, but
// not explicitly disabled, disable it.
// Notably do this only after unregistering the GPU from VA ranges to make
// sure there is no pending work using the peer mappings within the VA
// blocks (in particular migrations using the peer identity mappings).
for_each_va_space_gpu(peer_gpu, va_space) {
if (gpu == peer_gpu)
continue;
peer_table_index = uvm_gpu_peer_table_index(gpu->id, peer_gpu->id);
if (test_bit(peer_table_index, va_space->enabled_peers)) {
disable_peers(va_space, gpu, peer_gpu, deferred_free_list);
// Only PCIE peers need to be globally released. NVLINK peers are
// brought up and torn down automatically within add_gpu and
// remove_gpu.
if (peers_to_release && g_uvm_global.peers[peer_table_index].link_type == UVM_GPU_LINK_PCIE)
uvm_global_processor_mask_set(peers_to_release, peer_gpu->global_id);
}
}
if (gpu->parent->isr.replayable_faults.handling)
uvm_processor_mask_clear(&va_space->faultable_processors, gpu->id);
uvm_processor_mask_clear(&va_space->system_wide_atomics_enabled_processors, gpu->id);
processor_mask_array_clear(va_space->can_access, gpu->id, gpu->id);
processor_mask_array_clear(va_space->can_access, gpu->id, UVM_ID_CPU);
processor_mask_array_clear(va_space->can_access, UVM_ID_CPU, gpu->id);
UVM_ASSERT(processor_mask_array_empty(va_space->can_access, gpu->id));
processor_mask_array_clear(va_space->accessible_from, gpu->id, gpu->id);
processor_mask_array_clear(va_space->accessible_from, gpu->id, UVM_ID_CPU);
processor_mask_array_clear(va_space->accessible_from, UVM_ID_CPU, gpu->id);
UVM_ASSERT(processor_mask_array_empty(va_space->accessible_from, gpu->id));
processor_mask_array_clear(va_space->can_copy_from, gpu->id, gpu->id);
processor_mask_array_clear(va_space->can_copy_from, gpu->id, UVM_ID_CPU);
processor_mask_array_clear(va_space->can_copy_from, UVM_ID_CPU, gpu->id);
UVM_ASSERT(processor_mask_array_empty(va_space->can_copy_from, gpu->id));
processor_mask_array_clear(va_space->has_nvlink, gpu->id, UVM_ID_CPU);
processor_mask_array_clear(va_space->has_nvlink, UVM_ID_CPU, gpu->id);
UVM_ASSERT(processor_mask_array_empty(va_space->has_nvlink, gpu->id));
UVM_ASSERT(processor_mask_array_empty(va_space->indirect_peers, gpu->id));
processor_mask_array_clear(va_space->has_native_atomics, gpu->id, gpu->id);
processor_mask_array_clear(va_space->has_native_atomics, gpu->id, UVM_ID_CPU);
processor_mask_array_clear(va_space->has_native_atomics, UVM_ID_CPU, gpu->id);
UVM_ASSERT(processor_mask_array_empty(va_space->has_native_atomics, gpu->id));
uvm_processor_mask_clear(&va_space->registered_gpus, gpu->id);
va_space->registered_gpus_table[uvm_id_gpu_index(gpu->id)] = NULL;
// Remove the GPU from the CPU/GPU affinity masks
if (gpu->parent->closest_cpu_numa_node != -1) {
uvm_gpu_id_t gpu_id;
for_each_gpu_id(gpu_id) {
uvm_cpu_gpu_affinity_t *affinity = &va_space->gpu_cpu_numa_affinity[uvm_id_gpu_index(gpu_id)];
if (affinity->numa_node == gpu->parent->closest_cpu_numa_node) {
uvm_processor_mask_clear(&affinity->gpus, gpu->id);
break;
}
}
}
if (va_space->gpu_unregister_dma_buffer[uvm_id_gpu_index(gpu->id)]) {
uvm_conf_computing_dma_buffer_free(&gpu->conf_computing.dma_buffer_pool,
va_space->gpu_unregister_dma_buffer[uvm_id_gpu_index(gpu->id)],
&va_space->gpu_unregister_dma_buffer[uvm_id_gpu_index(gpu->id)]->tracker);
}
va_space_check_processors_masks(va_space);
}
static void gpu_va_space_stop_all_channels(uvm_gpu_va_space_t *gpu_va_space)
{
uvm_user_channel_t *user_channel;
list_for_each_entry(user_channel, &gpu_va_space->registered_channels, list_node)
uvm_user_channel_stop(user_channel);
// Prevent new channels from being registered since we'll be dropping the
// VA space lock shortly with the expectation that no more channels will
// arrive.
atomic_set(&gpu_va_space->disallow_new_channels, 1);
}
// Detaches (unregisters) all user channels in a GPU VA space. The channels must
// have previously been stopped.
//
// The detached channels are added to the input list. The caller is expected to
// drop the VA space lock and call uvm_deferred_free_object_list to complete the
// destroy operation.
static void uvm_gpu_va_space_detach_all_user_channels(uvm_gpu_va_space_t *gpu_va_space,
struct list_head *deferred_free_list)
{
uvm_user_channel_t *user_channel, *next_channel;
list_for_each_entry_safe(user_channel, next_channel, &gpu_va_space->registered_channels, list_node)
uvm_user_channel_detach(user_channel, deferred_free_list);
}
void uvm_va_space_detach_all_user_channels(uvm_va_space_t *va_space, struct list_head *deferred_free_list)
{
uvm_gpu_va_space_t *gpu_va_space;
for_each_gpu_va_space(gpu_va_space, va_space)
uvm_gpu_va_space_detach_all_user_channels(gpu_va_space, deferred_free_list);
}
void uvm_va_space_destroy(uvm_va_space_t *va_space)
{
uvm_va_range_t *va_range, *va_range_next;
uvm_gpu_t *gpu;
uvm_gpu_id_t gpu_id;
uvm_global_gpu_id_t global_gpu_id;
uvm_global_processor_mask_t retained_gpus;
LIST_HEAD(deferred_free_list);
// Remove the VA space from the global list before we start tearing things
// down so other threads can't see the VA space in a partially-valid state.
uvm_mutex_lock(&g_uvm_global.va_spaces.lock);
list_del(&va_space->list_node);
uvm_mutex_unlock(&g_uvm_global.va_spaces.lock);
uvm_perf_heuristics_stop(va_space);
// Stop all channels before unmapping anything. This kills the channels and
// prevents spurious MMU faults from being generated (bug 1722021), but
// doesn't prevent the bottom half from servicing old faults for those
// channels.
//
// This involves making RM calls, so we have to do that with the VA space
// lock in read mode.
uvm_va_space_down_read_rm(va_space);
uvm_va_space_stop_all_user_channels(va_space);
uvm_va_space_up_read_rm(va_space);
// The bottom half GPU page fault handler(s) could still look up and use
// this va_space via the GPU's instance_ptr_table. Lock them out while we
// tear down. Once we're done, the bottom half will fail to find any
// registered GPUs in the VA space, so those faults will be canceled.
uvm_va_space_down_write(va_space);
uvm_va_space_global_gpus(va_space, &retained_gpus);
bitmap_copy(va_space->enabled_peers_teardown, va_space->enabled_peers, UVM_MAX_UNIQUE_GPU_PAIRS);
uvm_va_space_detach_all_user_channels(va_space, &deferred_free_list);
// Destroy all VA ranges. We do this before unregistering the GPUs for
// performance, since GPU unregister will walk all VA ranges in the VA space
// multiple times.
uvm_for_each_va_range_safe(va_range, va_range_next, va_space) {
// All channel ranges should've been destroyed by the channel unregister
// above
UVM_ASSERT(va_range->type != UVM_VA_RANGE_TYPE_CHANNEL);
uvm_va_range_destroy(va_range, &deferred_free_list);
}
uvm_hmm_va_space_destroy(va_space);
uvm_range_group_radix_tree_destroy(va_space);
// Unregister all GPUs in the VA space. Note that this does not release the
// GPUs nor peers. We do that below.
for_each_va_space_gpu(gpu, va_space)
unregister_gpu(va_space, gpu, NULL, &deferred_free_list, NULL);
uvm_perf_heuristics_unload(va_space);
uvm_perf_destroy_va_space_events(&va_space->perf_events);
va_space_remove_dummy_thread_contexts(va_space);
uvm_va_space_up_write(va_space);
UVM_ASSERT(uvm_processor_mask_empty(&va_space->registered_gpus));
UVM_ASSERT(uvm_processor_mask_empty(&va_space->registered_gpu_va_spaces));
for_each_gpu_id(gpu_id)
UVM_ASSERT(va_space->registered_gpus_table[uvm_id_gpu_index(gpu_id)] == NULL);
// The instance pointer mappings for this VA space have been removed so no
// new bottom halves can get to this VA space, but there could still be
// bottom halves running from before we removed the mapping. Rather than
// ref-count the VA space, just wait for them to finish.
//
// This is also required to synchronize any pending
// block_deferred_accessed_by() work items.
nv_kthread_q_flush(&g_uvm_global.global_q);
for_each_global_gpu_in_mask(gpu, &retained_gpus) {
if (!gpu->parent->isr.replayable_faults.handling) {
UVM_ASSERT(!gpu->parent->isr.non_replayable_faults.handling);
continue;
}
nv_kthread_q_flush(&gpu->parent->isr.bottom_half_q);
// The same applies to the kill channel kthreads. However, they need to
// be flushed after their bottom-half counterparts since the latter may
// schedule a channel kill.
if (gpu->parent->isr.non_replayable_faults.handling)
nv_kthread_q_flush(&gpu->parent->isr.kill_channel_q);
if (gpu->parent->access_counters_supported)
uvm_gpu_access_counters_disable(gpu, va_space);
}
// Check that all CPU/GPU affinity masks are empty
for_each_gpu_id(gpu_id) {
const uvm_cpu_gpu_affinity_t *affinity = &va_space->gpu_cpu_numa_affinity[uvm_id_gpu_index(gpu_id)];
UVM_ASSERT(uvm_processor_mask_empty(&affinity->gpus));
}
// ensure that there are no pending events that refer to this va_space
uvm_tools_flush_events();
// Perform cleanup we can't do while holding the VA space lock
uvm_deferred_free_object_list(&deferred_free_list);
// Normally we'd expect this to happen as part of uvm_mm_release()
// but if userspace never initialized uvm_mm_fd that won't happen.
// We don't have to take the va_space_mm spinlock and update state
// here because we know no other thread can be in or subsequently
// call uvm_api_mm_initialize successfully because the UVM
// file-descriptor has been released.
if (va_space->va_space_mm.state == UVM_VA_SPACE_MM_STATE_UNINITIALIZED)
uvm_va_space_mm_unregister(va_space);
UVM_ASSERT(!uvm_va_space_mm_alive(&va_space->va_space_mm));
uvm_mutex_lock(&g_uvm_global.global_lock);
// Release the GPUs and their peer counts. Do not use
// for_each_global_gpu_in_mask for the outer loop as it reads the GPU
// state, which might get destroyed.
for_each_global_gpu_id_in_mask(global_gpu_id, &retained_gpus) {
uvm_gpu_t *peer_gpu;
gpu = uvm_gpu_get(global_gpu_id);
uvm_global_processor_mask_clear(&retained_gpus, global_gpu_id);
for_each_global_gpu_in_mask(peer_gpu, &retained_gpus) {
NvU32 peer_table_index = uvm_gpu_peer_table_index(gpu->id, peer_gpu->id);
if (test_bit(peer_table_index, va_space->enabled_peers_teardown)) {
uvm_gpu_peer_t *peer_caps = &g_uvm_global.peers[peer_table_index];
if (peer_caps->link_type == UVM_GPU_LINK_PCIE)
uvm_gpu_release_pcie_peer_access(gpu, peer_gpu);
__clear_bit(peer_table_index, va_space->enabled_peers_teardown);
}
}
uvm_gpu_release_locked(gpu);
}
UVM_ASSERT(bitmap_empty(va_space->enabled_peers, UVM_MAX_UNIQUE_GPU_PAIRS));
UVM_ASSERT(bitmap_empty(va_space->enabled_peers_teardown, UVM_MAX_UNIQUE_GPU_PAIRS));
uvm_mutex_unlock(&g_uvm_global.global_lock);
uvm_kvfree(va_space->mapping);
uvm_kvfree(va_space);
}
void uvm_va_space_stop_all_user_channels(uvm_va_space_t *va_space)
{
uvm_gpu_va_space_t *gpu_va_space;
uvm_user_channel_t *user_channel;
// Skip if all channels have been already stopped.
if (atomic_read(&va_space->user_channels_stopped))
return;
uvm_assert_rwsem_locked_read(&va_space->lock);
for_each_gpu_va_space(gpu_va_space, va_space) {
list_for_each_entry(user_channel, &gpu_va_space->registered_channels, list_node)
uvm_user_channel_stop(user_channel);
}
// Since we're holding the VA space lock in read mode, multiple threads
// could set this concurrently. user_channels_stopped never transitions back
// to 0 after being set to 1 so that's not a problem.
atomic_set(&va_space->user_channels_stopped, 1);
}
uvm_gpu_t *uvm_va_space_get_gpu_by_uuid(uvm_va_space_t *va_space, const NvProcessorUuid *gpu_uuid)
{
uvm_gpu_t *gpu;
for_each_va_space_gpu(gpu, va_space) {
if (uvm_processor_uuid_eq(uvm_gpu_uuid(gpu), gpu_uuid))
return gpu;
}
return NULL;
}
uvm_gpu_t *uvm_va_space_get_gpu_by_uuid_with_gpu_va_space(uvm_va_space_t *va_space,
const NvProcessorUuid *gpu_uuid)
{
uvm_gpu_t *gpu;
gpu = uvm_va_space_get_gpu_by_uuid(va_space, gpu_uuid);
if (!gpu || !uvm_processor_mask_test(&va_space->registered_gpu_va_spaces, gpu->id))
return NULL;
return gpu;
}
uvm_gpu_t *uvm_va_space_retain_gpu_by_uuid(uvm_va_space_t *va_space, const NvProcessorUuid *gpu_uuid)
{
uvm_gpu_t *gpu;
uvm_va_space_down_read(va_space);
gpu = uvm_va_space_get_gpu_by_uuid(va_space, gpu_uuid);
if (gpu)
uvm_gpu_retain(gpu);
uvm_va_space_up_read(va_space);
return gpu;
}
bool uvm_va_space_can_read_duplicate(uvm_va_space_t *va_space, uvm_gpu_t *changing_gpu)
{
uvm_processor_mask_t changing_gpu_mask;
uvm_processor_mask_t non_faultable_gpus;
uvm_processor_mask_t registered_gpu_va_spaces;
uvm_processor_mask_zero(&changing_gpu_mask);
if (changing_gpu)
uvm_processor_mask_set(&changing_gpu_mask, changing_gpu->id);
// flip the bit of the changing GPU to represent the state change in progress
uvm_processor_mask_xor(&registered_gpu_va_spaces, &changing_gpu_mask, &va_space->registered_gpu_va_spaces);
// Can't enable read-duplication if any non-fault-capable GPUs have GPU VA spaces registered
return !uvm_processor_mask_andnot(&non_faultable_gpus, &registered_gpu_va_spaces, &va_space->faultable_processors);
}
// Note that the "VA space" in the function name refers to a UVM per-process VA space.
// (This is different from a per-GPU VA space.)
NV_STATUS uvm_va_space_register_gpu(uvm_va_space_t *va_space,
const NvProcessorUuid *gpu_uuid,
const uvm_rm_user_object_t *user_rm_device,
NvBool *numa_enabled,
NvS32 *numa_node_id)
{
NV_STATUS status;
uvm_va_range_t *va_range;
uvm_gpu_t *gpu;
uvm_gpu_t *other_gpu;
bool gpu_can_access_sysmem = true;
status = uvm_gpu_retain_by_uuid(gpu_uuid, user_rm_device, &gpu);
if (status != NV_OK)
return status;
// Enabling access counters requires taking the ISR lock, so it is done
// without holding the (deeper order) VA space lock. Enabling the counters
// after dropping the VA space lock would create a window of time in which
// another thread could see the GPU as registered, but access counters would
// be disabled. Therefore, the counters are enabled before taking the VA
// space lock.
if (uvm_gpu_access_counters_required(gpu->parent)) {
status = uvm_gpu_access_counters_enable(gpu, va_space);
if (status != NV_OK) {
uvm_gpu_release(gpu);
return status;
}
}
uvm_va_space_down_write(va_space);
// Make sure the gpu hasn't been already registered in this va space
if (uvm_processor_mask_test(&va_space->registered_gpus, gpu->id)) {
status = NV_ERR_INVALID_DEVICE;
goto done;
}
// Mixing coherent and non-coherent GPUs is not supported
for_each_va_space_gpu(other_gpu, va_space) {
if (uvm_gpu_is_coherent(gpu->parent) != uvm_gpu_is_coherent(other_gpu->parent)) {
status = NV_ERR_INVALID_DEVICE;
goto done;
}
}
// The VA space's mm is being torn down, so don't allow more work
if (va_space->disallow_new_registers) {
status = NV_ERR_PAGE_TABLE_NOT_AVAIL;
goto done;
}
if (uvm_conf_computing_mode_enabled(gpu)) {
NvU32 gpu_index = uvm_id_gpu_index(gpu->id);
status = uvm_conf_computing_dma_buffer_alloc(&gpu->conf_computing.dma_buffer_pool,
&va_space->gpu_unregister_dma_buffer[gpu_index],
NULL);
if (status != NV_OK)
goto done;
gpu_can_access_sysmem = false;
}
uvm_processor_mask_set(&va_space->registered_gpus, gpu->id);
va_space->registered_gpus_table[uvm_id_gpu_index(gpu->id)] = gpu;
if (gpu->parent->isr.replayable_faults.handling) {
uvm_processor_mask_set(&va_space->faultable_processors, gpu->id);
// System-wide atomics are enabled by default
uvm_processor_mask_set(&va_space->system_wide_atomics_enabled_processors, gpu->id);
}
// All GPUs have native atomics on their own memory
processor_mask_array_set(va_space->has_native_atomics, gpu->id, gpu->id);
// TODO: Bug 3252572: Support the new link type UVM_GPU_LINK_C2C
if (gpu->parent->system_bus.link >= UVM_GPU_LINK_NVLINK_1) {
processor_mask_array_set(va_space->has_nvlink, gpu->id, UVM_ID_CPU);
processor_mask_array_set(va_space->has_nvlink, UVM_ID_CPU, gpu->id);
}
if (uvm_gpu_is_coherent(gpu->parent)) {
processor_mask_array_set(va_space->has_native_atomics, gpu->id, UVM_ID_CPU);
if (gpu->mem_info.numa.enabled) {
processor_mask_array_set(va_space->can_access, UVM_ID_CPU, gpu->id);
processor_mask_array_set(va_space->accessible_from, gpu->id, UVM_ID_CPU);
processor_mask_array_set(va_space->has_native_atomics, UVM_ID_CPU, gpu->id);
}
}
// All processors have direct access to their own memory
processor_mask_array_set(va_space->can_access, gpu->id, gpu->id);
processor_mask_array_set(va_space->accessible_from, gpu->id, gpu->id);
if (gpu_can_access_sysmem) {
processor_mask_array_set(va_space->can_access, gpu->id, UVM_ID_CPU);
processor_mask_array_set(va_space->accessible_from, UVM_ID_CPU, gpu->id);
}
processor_mask_array_set(va_space->can_copy_from, gpu->id, gpu->id);
processor_mask_array_set(va_space->can_copy_from, gpu->id, UVM_ID_CPU);
processor_mask_array_set(va_space->can_copy_from, UVM_ID_CPU, gpu->id);
// Update the CPU/GPU affinity masks
if (gpu->parent->closest_cpu_numa_node != -1) {
uvm_gpu_id_t gpu_id;
for_each_gpu_id(gpu_id) {
uvm_cpu_gpu_affinity_t *affinity = &va_space->gpu_cpu_numa_affinity[uvm_id_gpu_index(gpu_id)];
// If this is the first time this node is seen, take a new entry of
// the array. Entries are never released in order to avoid having
// to deal with holes.
if (affinity->numa_node == -1) {
UVM_ASSERT(uvm_processor_mask_empty(&affinity->gpus));
affinity->numa_node = gpu->parent->closest_cpu_numa_node;
}
if (affinity->numa_node == gpu->parent->closest_cpu_numa_node) {
uvm_processor_mask_set(&affinity->gpus, gpu->id);
break;
}
}
}
status = register_gpu_nvlink_peers(va_space, gpu);
if (status != NV_OK)
goto cleanup;
status = uvm_perf_heuristics_register_gpu(va_space, gpu);
if (status != NV_OK)
goto cleanup;
uvm_for_each_va_range(va_range, va_space) {
status = uvm_va_range_register_gpu(va_range, gpu);
if (status != NV_OK)
goto cleanup;
}
if (gpu->mem_info.numa.enabled) {
*numa_enabled = NV_TRUE;
*numa_node_id = (NvS32)uvm_gpu_numa_node(gpu);
}
else {
*numa_enabled = NV_FALSE;
*numa_node_id = -1;
}
goto done;
cleanup:
// Clear out all of the processor mask bits. No VA ranges have mapped or
// allocated anything on this GPU yet if we fail here, so we don't need
// a deferred_free_list, mm, etc.
unregister_gpu(va_space, gpu, NULL, NULL, NULL);
done:
UVM_ASSERT(va_space_check_processors_masks(va_space));
uvm_va_space_up_write(va_space);
if (status != NV_OK) {
// There is no risk of disabling access counters on a previously
// registered GPU: the enablement step would have failed before even
// discovering that the GPU is already registed.
if (uvm_gpu_access_counters_required(gpu->parent))
uvm_gpu_access_counters_disable(gpu, va_space);
uvm_gpu_release(gpu);
}
return status;
}
NV_STATUS uvm_va_space_unregister_gpu(uvm_va_space_t *va_space, const NvProcessorUuid *gpu_uuid)
{
uvm_gpu_t *gpu;
uvm_gpu_va_space_t *gpu_va_space;
struct mm_struct *mm;
uvm_global_gpu_id_t peer_gpu_id;
uvm_global_processor_mask_t peers_to_release;
LIST_HEAD(deferred_free_list);
// Stopping channels requires holding the VA space lock in read mode, so do
// it first. We start in write mode then drop to read in order to flush out
// other threads which are in the read-mode portion of any of the register
// or unregister operations.
uvm_va_space_down_write(va_space);
gpu = uvm_va_space_get_gpu_by_uuid(va_space, gpu_uuid);
if (!gpu) {
uvm_va_space_up_write(va_space);
return NV_ERR_INVALID_DEVICE;
}
// We have to drop the VA space lock below mid-unregister. We have to
// prevent any other threads from coming in during that window and allowing
// new channels to enter the GPU. That means we must disallow:
// - GPU VA space register
// - GPU unregister (which would allow new GPU registers)
if (uvm_processor_mask_test(&va_space->gpu_unregister_in_progress, gpu->id)) {
uvm_va_space_up_write(va_space);
return NV_ERR_INVALID_DEVICE;
}
uvm_processor_mask_set(&va_space->gpu_unregister_in_progress, gpu->id);
uvm_va_space_downgrade_write_rm(va_space);
gpu_va_space = uvm_gpu_va_space_get(va_space, gpu);
if (gpu_va_space)
gpu_va_space_stop_all_channels(gpu_va_space);
// We need to drop the lock to re-take it in write mode. We don't have to
// retain the GPU because we've prevented other threads from unregistering
// it from the VA space until we're done.
uvm_va_space_up_read_rm(va_space);
// If uvm_gpu_access_counters_required(gpu->parent) is true, a concurrent
// registration could enable access counters after they are disabled here.
// The concurrent registration will fail later on if it acquires the VA
// space lock before the unregistration does (because the GPU is still
// registered) and undo the access counters enablement, or succeed if it
// acquires the VA space lock after the unregistration does. Both outcomes
// result on valid states.
if (gpu->parent->access_counters_supported)
uvm_gpu_access_counters_disable(gpu, va_space);
// mmap_lock is needed to establish CPU mappings to any pages evicted from
// the GPU if accessed by CPU is set for them.
mm = uvm_va_space_mm_or_current_retain_lock(va_space);
uvm_va_space_down_write(va_space);
// We blocked out other GPU unregisters, so this GPU must still be
// registered. However, the GPU VA space might have been unregistered on us.
UVM_ASSERT(uvm_processor_mask_test(&va_space->registered_gpus, gpu->id));
if (uvm_processor_mask_test(&va_space->registered_gpu_va_spaces, gpu->id))
UVM_ASSERT(uvm_gpu_va_space_get(va_space, gpu) == gpu_va_space);
// This will call disable_peers for all GPU's peers, including NVLink
unregister_gpu(va_space, gpu, mm, &deferred_free_list, &peers_to_release);
UVM_ASSERT(uvm_processor_mask_test(&va_space->gpu_unregister_in_progress, gpu->id));
uvm_processor_mask_clear(&va_space->gpu_unregister_in_progress, gpu->id);
uvm_va_space_up_write(va_space);
// Unlock the mm since the call to uvm_deferred_free_object_list() requires
// that we don't hold any locks. We don't release the mm yet because that
// could call uvm_va_space_mm_shutdown() which waits for the deferred free
// list to be empty which would cause a deadlock.
if (mm)
uvm_up_read_mmap_lock(mm);
uvm_deferred_free_object_list(&deferred_free_list);
// Release the VA space's GPU and peer counts
uvm_mutex_lock(&g_uvm_global.global_lock);
// Do not use for_each_global_gpu_in_mask as it reads the peer GPU state,
// which might get destroyed when we release the peer entry.
for_each_global_gpu_id_in_mask(peer_gpu_id, &peers_to_release) {
uvm_gpu_t *peer_gpu = uvm_gpu_get(peer_gpu_id);
UVM_ASSERT(uvm_gpu_peer_caps(gpu, peer_gpu)->link_type == UVM_GPU_LINK_PCIE);
uvm_gpu_release_pcie_peer_access(gpu, peer_gpu);
}
uvm_gpu_release_locked(gpu);
uvm_mutex_unlock(&g_uvm_global.global_lock);
uvm_va_space_mm_or_current_release(va_space, mm);
return NV_OK;
}
// This does *not* release the global GPU peer entry
static void disable_peers(uvm_va_space_t *va_space,
uvm_gpu_t *gpu0,
uvm_gpu_t *gpu1,
struct list_head *deferred_free_list)
{
NvU32 table_index;
uvm_va_range_t *va_range;
uvm_assert_rwsem_locked_write(&va_space->lock);
table_index = uvm_gpu_peer_table_index(gpu0->id, gpu1->id);
if (!test_bit(table_index, va_space->enabled_peers))
return;
// Unmap all page tables in this VA space which have peer mappings between
// these two GPUs.
uvm_for_each_va_range(va_range, va_space)
uvm_va_range_disable_peer(va_range, gpu0, gpu1, deferred_free_list);
processor_mask_array_clear(va_space->can_access, gpu0->id, gpu1->id);
processor_mask_array_clear(va_space->can_access, gpu1->id, gpu0->id);
processor_mask_array_clear(va_space->accessible_from, gpu0->id, gpu1->id);
processor_mask_array_clear(va_space->accessible_from, gpu1->id, gpu0->id);
processor_mask_array_clear(va_space->can_copy_from, gpu0->id, gpu1->id);
processor_mask_array_clear(va_space->can_copy_from, gpu1->id, gpu0->id);
processor_mask_array_clear(va_space->has_nvlink, gpu0->id, gpu1->id);
processor_mask_array_clear(va_space->has_nvlink, gpu1->id, gpu0->id);
processor_mask_array_clear(va_space->indirect_peers, gpu0->id, gpu1->id);
processor_mask_array_clear(va_space->indirect_peers, gpu1->id, gpu0->id);
processor_mask_array_clear(va_space->has_native_atomics, gpu0->id, gpu1->id);
processor_mask_array_clear(va_space->has_native_atomics, gpu1->id, gpu0->id);
__clear_bit(table_index, va_space->enabled_peers);
va_space_check_processors_masks(va_space);
}
static NV_STATUS enable_peers(uvm_va_space_t *va_space, uvm_gpu_t *gpu0, uvm_gpu_t *gpu1)
{
NV_STATUS status = NV_OK;
uvm_gpu_va_space_t *gpu_va_space0, *gpu_va_space1;
NvU32 table_index = 0;
uvm_gpu_peer_t *peer_caps;
uvm_va_range_t *va_range;
LIST_HEAD(deferred_free_list);
uvm_assert_rwsem_locked_write(&va_space->lock);
// We know the GPUs were retained already, so now verify that they've been
// registered by this specific VA space.
if (!uvm_processor_mask_test(&va_space->registered_gpus, gpu0->id) ||
!uvm_processor_mask_test(&va_space->registered_gpus, gpu1->id)) {
return NV_ERR_INVALID_DEVICE;
}
table_index = uvm_gpu_peer_table_index(gpu0->id, gpu1->id);
peer_caps = &g_uvm_global.peers[table_index];
UVM_ASSERT(!test_bit(table_index, va_space->enabled_peers));
// If both GPUs have registered GPU VA spaces already, their big page sizes
// must match.
gpu_va_space0 = uvm_gpu_va_space_get(va_space, gpu0);
gpu_va_space1 = uvm_gpu_va_space_get(va_space, gpu1);
if (gpu_va_space0 &&
gpu_va_space1 &&
gpu_va_space0->page_tables.big_page_size != gpu_va_space1->page_tables.big_page_size) {
return NV_ERR_NOT_COMPATIBLE;
}
// TODO: Bug 3848497: Disable GPU Peer Mapping when HCC is enabled
processor_mask_array_set(va_space->can_access, gpu0->id, gpu1->id);
processor_mask_array_set(va_space->can_access, gpu1->id, gpu0->id);
processor_mask_array_set(va_space->accessible_from, gpu0->id, gpu1->id);
processor_mask_array_set(va_space->accessible_from, gpu1->id, gpu0->id);
if (gpu0->parent->peer_copy_mode != UVM_GPU_PEER_COPY_MODE_UNSUPPORTED) {
UVM_ASSERT_MSG(gpu1->parent->peer_copy_mode == gpu0->parent->peer_copy_mode,
"GPU %s GPU %s\n",
uvm_gpu_name(gpu0),
uvm_gpu_name(gpu1));
processor_mask_array_set(va_space->can_copy_from, gpu1->id, gpu0->id);
processor_mask_array_set(va_space->can_copy_from, gpu0->id, gpu1->id);
}
// Pre-compute nvlink and native atomic masks for the new peers
if (peer_caps->link_type >= UVM_GPU_LINK_NVLINK_1) {
processor_mask_array_set(va_space->has_nvlink, gpu0->id, gpu1->id);
processor_mask_array_set(va_space->has_nvlink, gpu1->id, gpu0->id);
processor_mask_array_set(va_space->has_native_atomics, gpu0->id, gpu1->id);
processor_mask_array_set(va_space->has_native_atomics, gpu1->id, gpu0->id);
if (peer_caps->is_indirect_peer) {
UVM_ASSERT(peer_caps->link_type >= UVM_GPU_LINK_NVLINK_2);
UVM_ASSERT(gpu0->mem_info.numa.enabled);
UVM_ASSERT(gpu1->mem_info.numa.enabled);
processor_mask_array_set(va_space->indirect_peers, gpu0->id, gpu1->id);
processor_mask_array_set(va_space->indirect_peers, gpu1->id, gpu0->id);
}
}
UVM_ASSERT(va_space_check_processors_masks(va_space));
__set_bit(table_index, va_space->enabled_peers);
uvm_for_each_va_range(va_range, va_space) {
status = uvm_va_range_enable_peer(va_range, gpu0, gpu1);
if (status != NV_OK)
break;
}
if (status != NV_OK) {
disable_peers(va_space, gpu0, gpu1, &deferred_free_list);
// uvm_va_range_disable_peer adds only external allocations to the list,
// but uvm_va_range_enable_peer doesn't do anything for them.
UVM_ASSERT(list_empty(&deferred_free_list));
}
return status;
}
// On success the GPUs and the P2P access have been retained, but the caller
// must not assume that the GPUs are still registered in the VA space after the
// call since the VA space lock is dropped.
static NV_STATUS retain_pcie_peers_from_uuids(uvm_va_space_t *va_space,
const NvProcessorUuid *gpu_uuid_1,
const NvProcessorUuid *gpu_uuid_2,
uvm_gpu_t **gpu0,
uvm_gpu_t **gpu1)
{
NV_STATUS status = NV_OK;
uvm_va_space_down_read_rm(va_space);
// The UUIDs should have already been registered
*gpu0 = uvm_va_space_get_gpu_by_uuid(va_space, gpu_uuid_1);
*gpu1 = uvm_va_space_get_gpu_by_uuid(va_space, gpu_uuid_2);
if (*gpu0 && *gpu1 && !uvm_id_equal((*gpu0)->id, (*gpu1)->id))
status = uvm_gpu_retain_pcie_peer_access(*gpu0, *gpu1);
else
status = NV_ERR_INVALID_DEVICE;
uvm_va_space_up_read_rm(va_space);
return status;
}
static bool uvm_va_space_pcie_peer_enabled(uvm_va_space_t *va_space, uvm_gpu_t *gpu0, uvm_gpu_t *gpu1)
{
return !processor_mask_array_test(va_space->has_nvlink, gpu0->id, gpu1->id) &&
uvm_va_space_peer_enabled(va_space, gpu0, gpu1);
}
static bool uvm_va_space_nvlink_peer_enabled(uvm_va_space_t *va_space, uvm_gpu_t *gpu0, uvm_gpu_t *gpu1)
{
return processor_mask_array_test(va_space->has_nvlink, gpu0->id, gpu1->id);
}
static void free_gpu_va_space(nv_kref_t *nv_kref)
{
uvm_gpu_va_space_t *gpu_va_space = container_of(nv_kref, uvm_gpu_va_space_t, kref);
uvm_gpu_va_space_state_t state = uvm_gpu_va_space_state(gpu_va_space);
UVM_ASSERT(state == UVM_GPU_VA_SPACE_STATE_INIT || state == UVM_GPU_VA_SPACE_STATE_DEAD);
uvm_kvfree(gpu_va_space);
}
void uvm_gpu_va_space_release(uvm_gpu_va_space_t *gpu_va_space)
{
if (gpu_va_space)
nv_kref_put(&gpu_va_space->kref, free_gpu_va_space);
}
static void uvm_gpu_va_space_acquire_mmap_lock(struct mm_struct *mm)
{
if (mm) {
// uvm_ats_register_gpu_va_space() requires mmap_lock to be held in
// write mode if IBM ATS support is provided through the kernel.
// mmap_lock is optional if IBM ATS support is provided through the
// driver. In all cases, We need mmap_lock at least in read mode to
// handle potential CPU mapping changes in
// uvm_va_range_add_gpu_va_space().
if (UVM_ATS_IBM_SUPPORTED_IN_KERNEL())
uvm_down_write_mmap_lock(mm);
else
uvm_down_read_mmap_lock(mm);
}
}
static void uvm_gpu_va_space_release_mmap_lock(struct mm_struct *mm)
{
if (mm) {
if (UVM_ATS_IBM_SUPPORTED_IN_KERNEL())
uvm_up_write_mmap_lock(mm);
else
uvm_up_read_mmap_lock(mm);
}
}
static NV_STATUS uvm_gpu_va_space_set_page_dir(uvm_gpu_va_space_t *gpu_va_space)
{
NV_STATUS status;
uvm_gpu_phys_address_t pdb_phys;
NvU64 num_pdes;
NvU32 pasid = -1U;
if (gpu_va_space->ats.enabled) {
pasid = gpu_va_space->ats.pasid;
UVM_ASSERT(pasid != -1U);
}
// Replace the existing PDB, if present, with the new one allocated by UVM.
// This will fail if nvUvmInterfaceSetPageDirectory has already been called
// on the RM VA space object, which prevents the user from registering twice
// and corrupting our state.
//
// TODO: Bug 1733664: RM needs to preempt and disable channels during this
// operation.
pdb_phys = uvm_page_tree_pdb(&gpu_va_space->page_tables)->addr;
num_pdes = uvm_mmu_page_tree_entries(&gpu_va_space->page_tables, 0, UVM_PAGE_SIZE_AGNOSTIC);
status = uvm_rm_locked_call(nvUvmInterfaceSetPageDirectory(gpu_va_space->duped_gpu_va_space,
pdb_phys.address,
num_pdes,
pdb_phys.aperture == UVM_APERTURE_VID,
pasid));
if (status != NV_OK) {
if (status == NV_ERR_NOT_SUPPORTED) {
// Convert to the return code specified by uvm.h for
// already-registered PDBs.
status = NV_ERR_INVALID_DEVICE;
}
else {
UVM_DBG_PRINT("nvUvmInterfaceSetPageDirectory() failed: %s, GPU %s\n",
nvstatusToString(status),
uvm_gpu_name(gpu_va_space->gpu));
}
return status;
}
gpu_va_space->did_set_page_directory = true;
return status;
}
void uvm_gpu_va_space_unset_page_dir(uvm_gpu_va_space_t *gpu_va_space)
{
if (uvm_gpu_va_space_state(gpu_va_space) != UVM_GPU_VA_SPACE_STATE_INIT)
uvm_assert_rwsem_locked_read(&gpu_va_space->va_space->lock);
if (gpu_va_space->did_set_page_directory) {
NV_STATUS status = uvm_rm_locked_call(nvUvmInterfaceUnsetPageDirectory(gpu_va_space->duped_gpu_va_space));
UVM_ASSERT_MSG(status == NV_OK,
"nvUvmInterfaceUnsetPageDirectory() failed: %s, GPU %s\n",
nvstatusToString(status),
uvm_gpu_name(gpu_va_space->gpu));
gpu_va_space->did_set_page_directory = false;
}
}
static void destroy_gpu_va_space(uvm_gpu_va_space_t *gpu_va_space)
{
NvU64 delay_us = 0;
uvm_va_space_t *va_space;
uvm_gpu_va_space_state_t state;
if (!gpu_va_space)
return;
state = uvm_gpu_va_space_state(gpu_va_space);
UVM_ASSERT(state == UVM_GPU_VA_SPACE_STATE_INIT || state == UVM_GPU_VA_SPACE_STATE_DEAD);
va_space = gpu_va_space->va_space;
UVM_ASSERT(va_space);
delay_us = atomic64_read(&va_space->test.destroy_gpu_va_space_delay_us);
if (delay_us)
udelay(delay_us);
// Serialize this uvm_gpu_va_space_unset_page_dir call with the one in
// uvm_va_space_mm_shutdown, which also starts with the VA space lock in
// write mode. RM will serialize the calls internally, so we lock here only
// to avoid getting benign errors from nvUvmInterfaceUnsetPageDirectory.
//
// If we never got to add_gpu_va_space, then gpu_va_space was never
// registered within the va_space, so uvm_va_space_mm_shutdown couldn't see
// it and we don't have to take the lock. state is guaranteed to be
// UVM_GPU_VA_SPACE_STATE_INIT if add_gpu_va_space wasn't reached.
if (state != UVM_GPU_VA_SPACE_STATE_INIT) {
uvm_va_space_down_write(va_space);
uvm_va_space_downgrade_write_rm(va_space);
}
uvm_gpu_va_space_unset_page_dir(gpu_va_space);
if (state != UVM_GPU_VA_SPACE_STATE_INIT)
uvm_va_space_up_read_rm(va_space);
if (gpu_va_space->page_tables.root)
uvm_page_tree_deinit(&gpu_va_space->page_tables);
if (gpu_va_space->duped_gpu_va_space)
uvm_rm_locked_call_void(nvUvmInterfaceAddressSpaceDestroy(gpu_va_space->duped_gpu_va_space));
// If the state is DEAD, then this GPU VA space is tracked in
// va_space->gpu_va_space_deferred_free. uvm_ats_unregister_gpu_va_space may
// wait for this count to go to 0 via uvm_va_space_mm_shutdown, so we must
// decrement it before calling that function.
if (gpu_va_space->state == UVM_GPU_VA_SPACE_STATE_DEAD) {
int num_pending = atomic_dec_return(&va_space->gpu_va_space_deferred_free.num_pending);
if (num_pending == 0)
wake_up_all(&va_space->gpu_va_space_deferred_free.wait_queue);
else
UVM_ASSERT(num_pending > 0);
}
// Note that this call may wait for faults to finish being serviced, which
// means it may depend on the VA space lock and mmap_lock.
uvm_ats_unregister_gpu_va_space(gpu_va_space);
uvm_ats_unbind_gpu(gpu_va_space);
uvm_gpu_va_space_release(gpu_va_space);
}
static NV_STATUS create_gpu_va_space(uvm_gpu_t *gpu,
uvm_va_space_t *va_space,
uvm_rm_user_object_t *user_rm_va_space,
uvm_gpu_va_space_t **out_gpu_va_space)
{
NV_STATUS status;
uvm_gpu_va_space_t *gpu_va_space;
UvmGpuAddressSpaceInfo gpu_address_space_info;
*out_gpu_va_space = NULL;
gpu_va_space = uvm_kvmalloc_zero(sizeof(*gpu_va_space));
if (!gpu_va_space)
return NV_ERR_NO_MEMORY;
gpu_va_space->gpu = gpu;
gpu_va_space->va_space = va_space;
INIT_LIST_HEAD(&gpu_va_space->registered_channels);
INIT_LIST_HEAD(&gpu_va_space->channel_va_ranges);
nv_kref_init(&gpu_va_space->kref);
// TODO: Bug 1624521: This interface needs to use rm_control_fd to do
// validation.
(void)user_rm_va_space->rm_control_fd;
status = uvm_rm_locked_call(nvUvmInterfaceDupAddressSpace(uvm_gpu_device_handle(gpu),
user_rm_va_space->user_client,
user_rm_va_space->user_object,
&gpu_va_space->duped_gpu_va_space,
&gpu_address_space_info));
if (status != NV_OK) {
UVM_DBG_PRINT("failed to dup address space with error: %s, for GPU:%s \n",
nvstatusToString(status), uvm_gpu_name(gpu));
goto error;
}
gpu_va_space->ats.enabled = gpu_address_space_info.atsEnabled;
// If ATS support in the UVM driver isn't enabled, fail registration of GPU
// VA spaces which have ATS enabled.
if (!g_uvm_global.ats.enabled && gpu_va_space->ats.enabled) {
UVM_INFO_PRINT("GPU VA space requires ATS, but ATS is not supported or enabled\n");
status = NV_ERR_INVALID_FLAGS;
goto error;
}
// If this GPU VA space uses ATS then pageable memory access must not have
// been disabled in the VA space.
if (gpu_va_space->ats.enabled && !uvm_va_space_pageable_mem_access_supported(va_space)) {
UVM_INFO_PRINT("GPU VA space requires ATS, but pageable memory access is not supported\n");
status = NV_ERR_INVALID_FLAGS;
goto error;
}
// RM allows the creation of VA spaces on Pascal with 128k big pages. We
// don't support that, so just fail those attempts.
//
// TODO: Bug 1789555: Remove this check once RM disallows this case.
if (!gpu->parent->arch_hal->mmu_mode_hal(gpu_address_space_info.bigPageSize)) {
status = NV_ERR_INVALID_FLAGS;
goto error;
}
// Set up this GPU's page tables
UVM_ASSERT(gpu_va_space->page_tables.root == NULL);
status = uvm_page_tree_init(gpu,
gpu_va_space,
UVM_PAGE_TREE_TYPE_USER,
gpu_address_space_info.bigPageSize,
uvm_gpu_page_tree_init_location(gpu),
&gpu_va_space->page_tables);
if (status != NV_OK) {
UVM_ERR_PRINT("Initializing the page tree failed: %s, GPU %s\n", nvstatusToString(status), uvm_gpu_name(gpu));
goto error;
}
status = uvm_ats_bind_gpu(gpu_va_space);
if (status != NV_OK)
goto error;
*out_gpu_va_space = gpu_va_space;
return NV_OK;
error:
destroy_gpu_va_space(gpu_va_space);
return status;
}
static void add_gpu_va_space(uvm_gpu_va_space_t *gpu_va_space)
{
uvm_va_space_t *va_space = gpu_va_space->va_space;
uvm_gpu_t *gpu = gpu_va_space->gpu;
UVM_ASSERT(va_space);
uvm_assert_rwsem_locked_write(&va_space->lock);
uvm_processor_mask_set(&va_space->registered_gpu_va_spaces, gpu->id);
va_space->gpu_va_spaces[uvm_id_gpu_index(gpu->id)] = gpu_va_space;
gpu_va_space->state = UVM_GPU_VA_SPACE_STATE_ACTIVE;
}
static NV_STATUS check_gpu_va_space(uvm_gpu_va_space_t *gpu_va_space)
{
uvm_va_space_t *va_space = gpu_va_space->va_space;
uvm_gpu_t *gpu = gpu_va_space->gpu;
uvm_gpu_t *other_gpu;
uvm_gpu_va_space_t *other_gpu_va_space;
UVM_ASSERT(va_space);
uvm_assert_rwsem_locked_write(&va_space->lock);
UVM_ASSERT(uvm_gpu_va_space_state(gpu_va_space) == UVM_GPU_VA_SPACE_STATE_INIT);
if (!uvm_processor_mask_test(&va_space->registered_gpus, gpu->id))
return NV_ERR_INVALID_DEVICE;
// RM will return an error from create_gpu_va_space if the given RM VA space
// object has already been registered by any VA space. Now we just need to
// check if a different VA space has already been registered.
if (uvm_processor_mask_test(&va_space->registered_gpu_va_spaces, gpu->id))
return NV_ERR_INVALID_DEVICE;
// If a GPU unregister is in progress but temporarily dropped the VA space
// lock, we can't register new GPU VA spaces.
if (uvm_processor_mask_test(&va_space->gpu_unregister_in_progress, gpu->id))
return NV_ERR_INVALID_DEVICE;
// The VA space's mm is being torn down, so don't allow more work
if (va_space->disallow_new_registers)
return NV_ERR_PAGE_TABLE_NOT_AVAIL;
// This GPU VA space must match its big page size with all enabled peers.
// Also, the new GPU VA space must have the same ATS setting as previously-
// registered GPU VA spaces
for_each_va_space_gpu_in_mask(other_gpu, va_space, &va_space->registered_gpu_va_spaces) {
UVM_ASSERT(other_gpu != gpu);
other_gpu_va_space = uvm_gpu_va_space_get(va_space, other_gpu);
if (other_gpu_va_space->ats.enabled != gpu_va_space->ats.enabled)
return NV_ERR_INVALID_FLAGS;
if (!test_bit(uvm_gpu_peer_table_index(gpu->id, other_gpu->id), va_space->enabled_peers))
continue;
if (gpu_va_space->page_tables.big_page_size != other_gpu_va_space->page_tables.big_page_size)
return NV_ERR_NOT_COMPATIBLE;
}
return NV_OK;
}
NV_STATUS uvm_va_space_register_gpu_va_space(uvm_va_space_t *va_space,
uvm_rm_user_object_t *user_rm_va_space,
const NvProcessorUuid *gpu_uuid)
{
NV_STATUS status;
uvm_gpu_t *gpu;
uvm_gpu_va_space_t *gpu_va_space;
uvm_va_range_t *va_range;
struct mm_struct *mm;
LIST_HEAD(deferred_free_list);
gpu = uvm_va_space_retain_gpu_by_uuid(va_space, gpu_uuid);
if (!gpu)
return NV_ERR_INVALID_DEVICE;
mm = uvm_va_space_mm_or_current_retain(va_space);
if (!mm) {
status = NV_ERR_PAGE_TABLE_NOT_AVAIL;
goto error_gpu_release;
}
status = create_gpu_va_space(gpu, va_space, user_rm_va_space, &gpu_va_space);
if (status != NV_OK)
goto error_gpu_release;
uvm_gpu_va_space_acquire_mmap_lock(mm);
uvm_va_space_down_write(va_space);
status = check_gpu_va_space(gpu_va_space);
if (status != NV_OK)
goto error_unlock;
status = uvm_ats_register_gpu_va_space(gpu_va_space);
if (status != NV_OK)
goto error_unlock;
uvm_va_space_up_write(va_space);
uvm_gpu_va_space_release_mmap_lock(mm);
status = uvm_gpu_va_space_set_page_dir(gpu_va_space);
if (status != NV_OK)
goto error_destroy;
uvm_gpu_va_space_acquire_mmap_lock(mm);
uvm_va_space_down_write(va_space);
// va_space state might have changed before the lock reacquire for write.
// So, check the state again.
status = check_gpu_va_space(gpu_va_space);
if (status != NV_OK)
goto error_unlock;
add_gpu_va_space(gpu_va_space);
// Tell the VA ranges that they can map this GPU, if they need to.
//
// Ideally we'd downgrade the VA space lock to read mode while adding new
// mappings, but that would complicate error handling since we have to
// remove the GPU VA space if any of these mappings fail.
uvm_for_each_va_range(va_range, va_space) {
status = uvm_va_range_add_gpu_va_space(va_range, gpu_va_space, mm);
if (status != NV_OK)
goto error;
}
uvm_va_space_up_write(va_space);
uvm_gpu_va_space_release_mmap_lock(mm);
uvm_va_space_mm_or_current_release(va_space, mm);
uvm_gpu_release(gpu);
return NV_OK;
error:
UVM_ASSERT(uvm_gpu_va_space_state(gpu_va_space) == UVM_GPU_VA_SPACE_STATE_ACTIVE);
remove_gpu_va_space(gpu_va_space, mm, &deferred_free_list);
// Nothing else could've been attached to this gpu_va_space (channels,
// external allocations) since we're still holding the VA space lock
// since add_gpu_va_space(). Therefore the GPU VA space itself should be
// the only item in the list, and we can just destroy it directly below.
UVM_ASSERT(list_is_singular(&deferred_free_list));
error_unlock:
uvm_va_space_up_write(va_space);
uvm_gpu_va_space_release_mmap_lock(mm);
error_destroy:
destroy_gpu_va_space(gpu_va_space);
error_gpu_release:
uvm_va_space_mm_or_current_release(va_space, mm);
uvm_gpu_release(gpu);
return status;
}
// The caller must have stopped all channels under this gpu_va_space before
// calling this function.
static void remove_gpu_va_space(uvm_gpu_va_space_t *gpu_va_space,
struct mm_struct *mm,
struct list_head *deferred_free_list)
{
uvm_va_space_t *va_space;
uvm_va_range_t *va_range;
uvm_va_range_t *va_range_next;
if (!gpu_va_space || uvm_gpu_va_space_state(gpu_va_space) != UVM_GPU_VA_SPACE_STATE_ACTIVE)
return;
va_space = gpu_va_space->va_space;
UVM_ASSERT(va_space);
uvm_assert_rwsem_locked_write(&va_space->lock);
uvm_gpu_va_space_detach_all_user_channels(gpu_va_space, deferred_free_list);
// Removing all registered channels should've removed all VA ranges used by
// those channels.
UVM_ASSERT(list_empty(&gpu_va_space->channel_va_ranges));
// Unmap all page tables in this VA space on this GPU.
// TODO: Bug 1799173: This will need to add objects to deferred_free_list
uvm_for_each_va_range_safe(va_range, va_range_next, va_space)
uvm_va_range_remove_gpu_va_space(va_range, gpu_va_space, mm, deferred_free_list);
uvm_hmm_remove_gpu_va_space(va_space, gpu_va_space, mm);
uvm_deferred_free_object_add(deferred_free_list,
&gpu_va_space->deferred_free,
UVM_DEFERRED_FREE_OBJECT_GPU_VA_SPACE);
// Let uvm_va_space_mm_shutdown know that it has to wait for this GPU VA
// space to be destroyed.
atomic_inc(&va_space->gpu_va_space_deferred_free.num_pending);
uvm_processor_mask_clear(&va_space->registered_gpu_va_spaces, gpu_va_space->gpu->id);
uvm_processor_mask_clear_atomic(&va_space->needs_fault_buffer_flush, gpu_va_space->gpu->id);
va_space->gpu_va_spaces[uvm_id_gpu_index(gpu_va_space->gpu->id)] = NULL;
gpu_va_space->state = UVM_GPU_VA_SPACE_STATE_DEAD;
}
NV_STATUS uvm_va_space_unregister_gpu_va_space(uvm_va_space_t *va_space, const NvProcessorUuid *gpu_uuid)
{
NV_STATUS status = NV_OK;
uvm_gpu_t *gpu;
uvm_gpu_va_space_t *gpu_va_space;
struct mm_struct *mm;
LIST_HEAD(deferred_free_list);
// Stopping channels requires holding the VA space lock in read mode, so do
// it first. This also takes the serialize_writers_lock, so we'll serialize
// with other threads about to perform channel binds in
// uvm_register_channel since.
uvm_va_space_down_read_rm(va_space);
gpu = uvm_va_space_get_gpu_by_uuid_with_gpu_va_space(va_space, gpu_uuid);
if (!gpu) {
uvm_va_space_up_read_rm(va_space);
return NV_ERR_INVALID_DEVICE;
}
gpu_va_space = uvm_gpu_va_space_get(va_space, gpu);
UVM_ASSERT(gpu_va_space);
gpu_va_space_stop_all_channels(gpu_va_space);
// We need to drop the lock to re-take it in write mode
uvm_gpu_va_space_retain(gpu_va_space);
uvm_gpu_retain(gpu);
uvm_va_space_up_read_rm(va_space);
mm = uvm_va_space_mm_or_current_retain_lock(va_space);
uvm_va_space_down_write(va_space);
// We dropped the lock so we have to re-verify that this gpu_va_space is
// still valid. If so, then the GPU is also still registered under the VA
// space. If not, we raced with another unregister thread, so return an
// an error for double-unregister.
if (uvm_gpu_va_space_state(gpu_va_space) == UVM_GPU_VA_SPACE_STATE_DEAD) {
status = NV_ERR_INVALID_DEVICE;
}
else {
UVM_ASSERT(gpu == uvm_va_space_get_gpu_by_uuid_with_gpu_va_space(va_space, gpu_uuid));
UVM_ASSERT(gpu_va_space == uvm_gpu_va_space_get(va_space, gpu));
remove_gpu_va_space(gpu_va_space, mm, &deferred_free_list);
}
uvm_va_space_up_write(va_space);
// Unlock the mm since the call to uvm_deferred_free_object_list() requires
// that we don't hold any locks. We don't release the mm yet because that
// could call uvm_va_space_mm_shutdown() which waits for the deferred free
// list to be empty which would cause a deadlock.
if (mm)
uvm_up_read_mmap_lock(mm);
uvm_deferred_free_object_list(&deferred_free_list);
uvm_gpu_va_space_release(gpu_va_space);
uvm_gpu_release(gpu);
uvm_va_space_mm_or_current_release(va_space, mm);
return status;
}
bool uvm_va_space_peer_enabled(uvm_va_space_t *va_space, uvm_gpu_t *gpu1, uvm_gpu_t *gpu2)
{
size_t table_index;
UVM_ASSERT(uvm_processor_mask_test(&va_space->registered_gpus, gpu1->id));
UVM_ASSERT(uvm_processor_mask_test(&va_space->registered_gpus, gpu2->id));
table_index = uvm_gpu_peer_table_index(gpu1->id, gpu2->id);
return !!test_bit(table_index, va_space->enabled_peers);
}
uvm_processor_id_t uvm_processor_mask_find_closest_id(uvm_va_space_t *va_space,
const uvm_processor_mask_t *candidates,
uvm_processor_id_t src)
{
uvm_processor_mask_t mask;
uvm_processor_id_t id;
// Highest priority: the local processor itself
if (uvm_processor_mask_test(candidates, src))
return src;
// NvLink peers
if (uvm_processor_mask_and(&mask, candidates, &va_space->has_nvlink[uvm_id_value(src)])) {
uvm_processor_mask_t *indirect_peers;
uvm_processor_mask_t direct_peers;
indirect_peers = &va_space->indirect_peers[uvm_id_value(src)];
// Direct peers, prioritizing GPU peers over CPU
if (uvm_processor_mask_andnot(&direct_peers, &mask, indirect_peers)) {
id = uvm_processor_mask_find_first_gpu_id(&direct_peers);
return UVM_ID_IS_INVALID(id)? UVM_ID_CPU : id;
}
// Indirect peers
UVM_ASSERT(UVM_ID_IS_GPU(src));
UVM_ASSERT(!uvm_processor_mask_test(&mask, UVM_ID_CPU));
return uvm_processor_mask_find_first_gpu_id(&mask);
}
// If source is GPU, prioritize PCIe peers over CPU
if (uvm_processor_mask_and(&mask, candidates, &va_space->can_access[uvm_id_value(src)])) {
// CPUs only have direct access to GPU memory over NVLINK, not PCIe, and
// should have been selected above
UVM_ASSERT(UVM_ID_IS_GPU(src));
id = uvm_processor_mask_find_first_gpu_id(&mask);
return UVM_ID_IS_INVALID(id)? UVM_ID_CPU : id;
}
// No GPUs with direct access are in the mask. Just pick the first
// processor in the mask, if any.
return uvm_processor_mask_find_first_id(candidates);
}
static void uvm_deferred_free_object_channel(uvm_deferred_free_object_t *object, uvm_processor_mask_t *flushed_gpus)
{
uvm_user_channel_t *channel = container_of(object, uvm_user_channel_t, deferred_free);
uvm_gpu_t *gpu = channel->gpu;
// Flush out any faults with this instance pointer still in the buffer. This
// prevents us from re-allocating the same instance pointer for a new
// channel and mis-attributing old faults to it.
if (gpu->parent->replayable_faults_supported && !uvm_processor_mask_test(flushed_gpus, gpu->id)) {
uvm_gpu_fault_buffer_flush(gpu);
uvm_processor_mask_set(flushed_gpus, gpu->id);
}
uvm_user_channel_destroy_detached(channel);
}
void uvm_deferred_free_object_list(struct list_head *deferred_free_list)
{
uvm_deferred_free_object_t *object, *next;
uvm_processor_mask_t flushed_gpus;
// Used if there are any channels in the list
uvm_processor_mask_zero(&flushed_gpus);
list_for_each_entry_safe(object, next, deferred_free_list, list_node) {
list_del(&object->list_node);
switch (object->type) {
case UVM_DEFERRED_FREE_OBJECT_TYPE_CHANNEL:
uvm_deferred_free_object_channel(object, &flushed_gpus);
break;
case UVM_DEFERRED_FREE_OBJECT_GPU_VA_SPACE:
destroy_gpu_va_space(container_of(object, uvm_gpu_va_space_t, deferred_free));
break;
case UVM_DEFERRED_FREE_OBJECT_TYPE_EXTERNAL_ALLOCATION:
uvm_ext_gpu_map_free(container_of(object, uvm_ext_gpu_map_t, deferred_free));
break;
default:
UVM_ASSERT_MSG(0, "Invalid type %d\n", object->type);
}
}
}
uvm_user_channel_t *uvm_gpu_va_space_get_user_channel(uvm_gpu_va_space_t *gpu_va_space,
uvm_gpu_phys_address_t instance_ptr)
{
uvm_user_channel_t *user_channel;
uvm_va_space_t *va_space = gpu_va_space->va_space;
UVM_ASSERT(uvm_gpu_va_space_state(gpu_va_space) == UVM_GPU_VA_SPACE_STATE_ACTIVE);
uvm_assert_rwsem_locked(&va_space->lock);
// TODO: Bug 1880191: This is called on every non-replayable fault service.
// Evaluate the performance impact of this list traversal and potentially
// replace it with something better.
list_for_each_entry(user_channel, &gpu_va_space->registered_channels, list_node) {
if (user_channel->instance_ptr.addr.address == instance_ptr.address &&
user_channel->instance_ptr.addr.aperture == instance_ptr.aperture) {
return user_channel;
}
}
return NULL;
}
NV_STATUS uvm_api_enable_peer_access(UVM_ENABLE_PEER_ACCESS_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
NV_STATUS status = NV_OK;
uvm_gpu_t *gpu0 = NULL;
uvm_gpu_t *gpu1 = NULL;
size_t table_index;
uvm_mutex_lock(&g_uvm_global.global_lock);
status = retain_pcie_peers_from_uuids(va_space, &params->gpuUuidA, &params->gpuUuidB, &gpu0, &gpu1);
uvm_mutex_unlock(&g_uvm_global.global_lock);
if (status != NV_OK)
return status;
uvm_va_space_down_write(va_space);
table_index = uvm_gpu_peer_table_index(gpu0->id, gpu1->id);
if (test_bit(table_index, va_space->enabled_peers))
status = NV_ERR_INVALID_DEVICE;
else
status = enable_peers(va_space, gpu0, gpu1);
uvm_va_space_up_write(va_space);
if (status != NV_OK) {
uvm_mutex_lock(&g_uvm_global.global_lock);
uvm_gpu_release_pcie_peer_access(gpu0, gpu1);
uvm_mutex_unlock(&g_uvm_global.global_lock);
}
return status;
}
NV_STATUS uvm_api_disable_peer_access(UVM_DISABLE_PEER_ACCESS_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
NV_STATUS status = NV_OK;
uvm_gpu_t *gpu0, *gpu1;
LIST_HEAD(deferred_free_list);
uvm_va_space_down_write(va_space);
gpu0 = uvm_va_space_get_gpu_by_uuid(va_space, &params->gpuUuidA);
gpu1 = uvm_va_space_get_gpu_by_uuid(va_space, &params->gpuUuidB);
if (!gpu0 || !gpu1) {
status = NV_ERR_INVALID_DEVICE;
goto error;
}
if (uvm_id_equal(gpu0->id, gpu1->id)) {
status = NV_ERR_INVALID_DEVICE;
goto error;
}
if (!uvm_va_space_pcie_peer_enabled(va_space, gpu0, gpu1)) {
status = NV_ERR_INVALID_DEVICE;
goto error;
}
disable_peers(va_space, gpu0, gpu1, &deferred_free_list);
// disable_peers doesn't release the GPU peer ref count, which means the two
// GPUs will remain retained even if another thread unregisters them from
// this VA space after we drop the lock.
uvm_va_space_up_write(va_space);
uvm_deferred_free_object_list(&deferred_free_list);
uvm_mutex_lock(&g_uvm_global.global_lock);
uvm_gpu_release_pcie_peer_access(gpu0, gpu1);
uvm_mutex_unlock(&g_uvm_global.global_lock);
return NV_OK;
error:
uvm_va_space_up_write(va_space);
return status;
}
bool uvm_va_space_pageable_mem_access_supported(uvm_va_space_t *va_space)
{
// Any pageable memory access requires that we have mm_struct association
// via va_space_mm.
if (!uvm_va_space_mm_enabled(va_space))
return false;
// We might have systems with both ATS and HMM support. ATS gets priority.
if (g_uvm_global.ats.supported)
return g_uvm_global.ats.enabled;
return uvm_hmm_is_enabled(va_space);
}
NV_STATUS uvm_test_get_pageable_mem_access_type(UVM_TEST_GET_PAGEABLE_MEM_ACCESS_TYPE_PARAMS *params,
struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
params->type = UVM_TEST_PAGEABLE_MEM_ACCESS_TYPE_NONE;
if (uvm_va_space_pageable_mem_access_supported(va_space)) {
if (g_uvm_global.ats.enabled) {
if (UVM_ATS_IBM_SUPPORTED_IN_KERNEL())
params->type = UVM_TEST_PAGEABLE_MEM_ACCESS_TYPE_ATS_KERNEL;
else
params->type = UVM_TEST_PAGEABLE_MEM_ACCESS_TYPE_ATS_DRIVER;
}
else {
params->type = UVM_TEST_PAGEABLE_MEM_ACCESS_TYPE_HMM;
}
}
else if (uvm_va_space_mm_enabled(va_space)) {
params->type = UVM_TEST_PAGEABLE_MEM_ACCESS_TYPE_MMU_NOTIFIER;
}
return NV_OK;
}
NV_STATUS uvm_test_flush_deferred_work(UVM_TEST_FLUSH_DEFERRED_WORK_PARAMS *params, struct file *filp)
{
UvmTestDeferredWorkType work_type = params->work_type;
switch (work_type) {
case UvmTestDeferredWorkTypeAcessedByMappings:
nv_kthread_q_flush(&g_uvm_global.global_q);
return NV_OK;
default:
return NV_ERR_INVALID_ARGUMENT;
}
}
NV_STATUS uvm_test_enable_nvlink_peer_access(UVM_TEST_ENABLE_NVLINK_PEER_ACCESS_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
NV_STATUS status = NV_OK;
uvm_gpu_t *gpu0 = NULL;
uvm_gpu_t *gpu1 = NULL;
size_t table_index;
uvm_gpu_peer_t *peer_caps = NULL;
uvm_va_space_down_write(va_space);
gpu0 = uvm_va_space_get_gpu_by_uuid(va_space, &params->gpuUuidA);
gpu1 = uvm_va_space_get_gpu_by_uuid(va_space, &params->gpuUuidB);
if (gpu0 && gpu1 && !uvm_id_equal(gpu0->id, gpu1->id))
peer_caps = uvm_gpu_peer_caps(gpu0, gpu1);
if (!peer_caps || peer_caps->link_type < UVM_GPU_LINK_NVLINK_1) {
uvm_va_space_up_write(va_space);
return NV_ERR_INVALID_DEVICE;
}
table_index = uvm_gpu_peer_table_index(gpu0->id, gpu1->id);
// NVLink peers are automatically enabled in the VA space at VA space
// registration time. In order to avoid tests having to keep track of the
// different initial state for PCIe and NVLink peers, we just return NV_OK
// if NVLink peer were already enabled.
if (test_bit(table_index, va_space->enabled_peers))
status = NV_OK;
else
status = enable_peers(va_space, gpu0, gpu1);
uvm_va_space_up_write(va_space);
return status;
}
NV_STATUS uvm_test_disable_nvlink_peer_access(UVM_TEST_DISABLE_NVLINK_PEER_ACCESS_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
NV_STATUS status = NV_OK;
uvm_gpu_t *gpu0, *gpu1;
LIST_HEAD(deferred_free_list);
uvm_va_space_down_write(va_space);
gpu0 = uvm_va_space_get_gpu_by_uuid(va_space, &params->gpuUuidA);
gpu1 = uvm_va_space_get_gpu_by_uuid(va_space, &params->gpuUuidB);
if (!gpu0 || !gpu1) {
status = NV_ERR_INVALID_DEVICE;
goto error;
}
if (uvm_id_equal(gpu0->id, gpu1->id)) {
status = NV_ERR_INVALID_DEVICE;
goto error;
}
if (!uvm_va_space_nvlink_peer_enabled(va_space, gpu0, gpu1)) {
status = NV_ERR_INVALID_DEVICE;
goto error;
}
disable_peers(va_space, gpu0, gpu1, &deferred_free_list);
uvm_va_space_up_write(va_space);
uvm_deferred_free_object_list(&deferred_free_list);
return NV_OK;
error:
uvm_va_space_up_write(va_space);
return status;
}
NV_STATUS uvm_test_va_space_inject_error(UVM_TEST_VA_SPACE_INJECT_ERROR_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
atomic_set(&va_space->test.migrate_vma_allocation_fail_nth, params->migrate_vma_allocation_fail_nth);
atomic_set(&va_space->test.va_block_allocation_fail_nth, params->va_block_allocation_fail_nth);
return NV_OK;
}
// Add a fixed number of dummy thread contexts to each thread context table.
// The newly added thread contexts are removed by calling
// uvm_test_va_space_remove_dummy_thread_contexts, or during VA space shutdown.
NV_STATUS uvm_test_va_space_add_dummy_thread_contexts(UVM_TEST_VA_SPACE_ADD_DUMMY_THREAD_CONTEXTS_PARAMS *params,
struct file *filp)
{
size_t i;
uvm_va_space_t *va_space;
size_t total_dummy_thread_contexts = params->num_dummy_thread_contexts * UVM_THREAD_CONTEXT_TABLE_SIZE;
NV_STATUS status = NV_OK;
if (params->num_dummy_thread_contexts == 0)
return NV_OK;
va_space = uvm_va_space_get(filp);
uvm_va_space_down_write(va_space);
if (va_space->test.dummy_thread_context_wrappers != NULL) {
status = NV_ERR_INVALID_STATE;
goto out;
}
if (va_space->test.num_dummy_thread_context_wrappers > 0) {
status = NV_ERR_INVALID_STATE;
goto out;
}
if (!uvm_thread_context_wrapper_is_used()) {
status = NV_ERR_INVALID_STATE;
goto out;
}
va_space->test.dummy_thread_context_wrappers = uvm_kvmalloc(sizeof(*va_space->test.dummy_thread_context_wrappers) *
total_dummy_thread_contexts);
if (va_space->test.dummy_thread_context_wrappers == NULL) {
status = NV_ERR_NO_MEMORY;
goto out;
}
va_space->test.num_dummy_thread_context_wrappers = total_dummy_thread_contexts;
for (i = 0; i < total_dummy_thread_contexts; i++) {
uvm_thread_context_t *thread_context = &va_space->test.dummy_thread_context_wrappers[i].context;
// The context pointer is used to fill the task.
thread_context->task = (struct task_struct *) thread_context;
uvm_thread_context_add_at(thread_context, i % UVM_THREAD_CONTEXT_TABLE_SIZE);
}
out:
uvm_va_space_up_write(va_space);
return status;
}
static void va_space_remove_dummy_thread_contexts(uvm_va_space_t *va_space)
{
size_t i;
uvm_assert_rwsem_locked_write(&va_space->lock);
if (va_space->test.dummy_thread_context_wrappers == NULL) {
UVM_ASSERT(va_space->test.num_dummy_thread_context_wrappers == 0);
return;
}
UVM_ASSERT(uvm_thread_context_wrapper_is_used());
UVM_ASSERT(uvm_enable_builtin_tests != 0);
UVM_ASSERT(va_space->test.num_dummy_thread_context_wrappers > 0);
for (i = 0; i < va_space->test.num_dummy_thread_context_wrappers; i++) {
uvm_thread_context_t *thread_context = &va_space->test.dummy_thread_context_wrappers[i].context;
uvm_thread_context_remove_at(thread_context, i % UVM_THREAD_CONTEXT_TABLE_SIZE);
}
uvm_kvfree(va_space->test.dummy_thread_context_wrappers);
va_space->test.dummy_thread_context_wrappers = NULL;
va_space->test.num_dummy_thread_context_wrappers = 0;
}
NV_STATUS uvm_test_va_space_remove_dummy_thread_contexts(UVM_TEST_VA_SPACE_REMOVE_DUMMY_THREAD_CONTEXTS_PARAMS *params,
struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
uvm_va_space_down_write(va_space);
va_space_remove_dummy_thread_contexts(va_space);
uvm_va_space_up_write(va_space);
return NV_OK;
}
NV_STATUS uvm_test_destroy_gpu_va_space_delay(UVM_TEST_DESTROY_GPU_VA_SPACE_DELAY_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
// va_space lock is not needed here.
atomic64_set(&va_space->test.destroy_gpu_va_space_delay_us, params->delay_us);
return NV_OK;
}
// List of fault service contexts for CPU faults
static LIST_HEAD(g_cpu_service_block_context_list);
static uvm_spinlock_t g_cpu_service_block_context_list_lock;
NV_STATUS uvm_service_block_context_init(void)
{
unsigned num_preallocated_contexts = 4;
uvm_spin_lock_init(&g_cpu_service_block_context_list_lock, UVM_LOCK_ORDER_LEAF);
// Pre-allocate some fault service contexts for the CPU and add them to the global list
while (num_preallocated_contexts-- > 0) {
uvm_service_block_context_t *service_context = uvm_kvmalloc(sizeof(*service_context));
if (!service_context)
return NV_ERR_NO_MEMORY;
list_add(&service_context->cpu_fault.service_context_list, &g_cpu_service_block_context_list);
}
return NV_OK;
}
void uvm_service_block_context_exit(void)
{
uvm_service_block_context_t *service_context, *service_context_tmp;
// Free fault service contexts for the CPU and add clear the global list
list_for_each_entry_safe(service_context, service_context_tmp, &g_cpu_service_block_context_list,
cpu_fault.service_context_list) {
uvm_kvfree(service_context);
}
INIT_LIST_HEAD(&g_cpu_service_block_context_list);
}
// Get a fault service context from the global list or allocate a new one if
// there are no available entries.
static uvm_service_block_context_t *service_block_context_cpu_alloc(void)
{
uvm_service_block_context_t *service_context;
uvm_spin_lock(&g_cpu_service_block_context_list_lock);
service_context = list_first_entry_or_null(&g_cpu_service_block_context_list, uvm_service_block_context_t,
cpu_fault.service_context_list);
if (service_context)
list_del(&service_context->cpu_fault.service_context_list);
uvm_spin_unlock(&g_cpu_service_block_context_list_lock);
if (!service_context)
service_context = uvm_kvmalloc(sizeof(*service_context));
return service_context;
}
// Put a fault service context in the global list.
static void service_block_context_cpu_free(uvm_service_block_context_t *service_context)
{
uvm_spin_lock(&g_cpu_service_block_context_list_lock);
list_add(&service_context->cpu_fault.service_context_list, &g_cpu_service_block_context_list);
uvm_spin_unlock(&g_cpu_service_block_context_list_lock);
}
static vm_fault_t uvm_va_space_cpu_fault(uvm_va_space_t *va_space,
struct vm_area_struct *vma,
struct vm_fault *vmf,
bool is_hmm)
{
uvm_va_block_t *va_block;
NvU64 fault_addr = nv_page_fault_va(vmf);
bool is_write = vmf->flags & FAULT_FLAG_WRITE;
NV_STATUS status = uvm_global_get_status();
bool tools_enabled;
bool major_fault = false;
uvm_service_block_context_t *service_context;
uvm_global_processor_mask_t gpus_to_check_for_ecc;
if (status != NV_OK)
goto convert_error;
// TODO: Bug 2583279: Lock tracking is disabled for the power management
// lock in order to suppress reporting of a lock policy violation.
// The violation consists in acquiring the power management lock multiple
// times, and it is manifested as an error during release. The
// re-acquisition of the power management locks happens upon re-entry in the
// UVM module, and it is benign on itself, but when combined with certain
// power management scenarios, it is indicative of a potential deadlock.
// Tracking will be re-enabled once the power management locking strategy is
// modified to avoid deadlocks.
if (!uvm_down_read_trylock_no_tracking(&g_uvm_global.pm.lock)) {
status = NV_ERR_BUSY_RETRY;
goto convert_error;
}
service_context = service_block_context_cpu_alloc();
if (!service_context) {
status = NV_ERR_NO_MEMORY;
goto unlock;
}
service_context->cpu_fault.wakeup_time_stamp = 0;
// There are up to three mm_structs to worry about, and they might all be
// different:
//
// 1) vma->vm_mm
// 2) current->mm
// 3) va_space->va_space_mm.mm (though note that if this is valid, then it
// must match vma->vm_mm).
//
// The kernel guarantees that vma->vm_mm has a reference taken with
// mmap_lock held on the CPU fault path, so tell the fault handler to use
// that one. current->mm might differ if we're on the access_process_vm
// (ptrace) path or if another driver is calling get_user_pages.
service_context->block_context.mm = vma->vm_mm;
// The mmap_lock might be held in write mode, but the mode doesn't matter
// for the purpose of lock ordering and we don't rely on it being in write
// anywhere so just record it as read mode in all cases.
uvm_record_lock_mmap_lock_read(vma->vm_mm);
do {
bool do_sleep = false;
if (status == NV_WARN_MORE_PROCESSING_REQUIRED) {
NvU64 now = NV_GETTIME();
if (now < service_context->cpu_fault.wakeup_time_stamp)
do_sleep = true;
if (do_sleep)
uvm_tools_record_throttling_start(va_space, fault_addr, UVM_ID_CPU);
// Drop the VA space lock while we sleep
uvm_va_space_up_read(va_space);
// usleep_range is preferred because msleep has a 20ms granularity
// and udelay uses a busy-wait loop. usleep_range uses
// high-resolution timers and, by adding a range, the Linux
// scheduler may coalesce our wakeup with others, thus saving some
// interrupts.
if (do_sleep) {
unsigned long nap_us = (service_context->cpu_fault.wakeup_time_stamp - now) / 1000;
usleep_range(nap_us, nap_us + nap_us / 2);
}
}
uvm_va_space_down_read(va_space);
if (do_sleep)
uvm_tools_record_throttling_end(va_space, fault_addr, UVM_ID_CPU);
if (is_hmm) {
// Note that normally we should find a va_block for the faulting
// address because the block had to be created when migrating a
// page to the GPU and a device private PTE inserted into the CPU
// page tables in order for migrate_to_ram() to be called. Not
// finding it means the PTE was remapped to a different virtual
// address with mremap() so create a new va_block if needed.
status = uvm_hmm_va_block_find_create(va_space,
fault_addr,
&service_context->block_context.hmm.vma,
&va_block);
if (status != NV_OK)
break;
UVM_ASSERT(service_context->block_context.hmm.vma == vma);
status = uvm_hmm_migrate_begin(va_block);
if (status != NV_OK)
break;
service_context->cpu_fault.vmf = vmf;
}
else {
status = uvm_va_block_find_create_managed(va_space, fault_addr, &va_block);
if (status != NV_OK) {
UVM_ASSERT_MSG(status == NV_ERR_NO_MEMORY, "status: %s\n", nvstatusToString(status));
break;
}
// Watch out, current->mm might not be vma->vm_mm
UVM_ASSERT(vma == uvm_va_range_vma(va_block->va_range));
}
// Loop until thrashing goes away.
status = uvm_va_block_cpu_fault(va_block, fault_addr, is_write, service_context);
if (is_hmm)
uvm_hmm_migrate_finish(va_block);
} while (status == NV_WARN_MORE_PROCESSING_REQUIRED);
if (status != NV_OK && !(is_hmm && status == NV_ERR_BUSY_RETRY)) {
UvmEventFatalReason reason;
reason = uvm_tools_status_to_fatal_fault_reason(status);
UVM_ASSERT(reason != UvmEventFatalReasonInvalid);
uvm_tools_record_cpu_fatal_fault(va_space, fault_addr, is_write, reason);
}
tools_enabled = va_space->tools.enabled;
if (status == NV_OK) {
uvm_va_space_global_gpus_in_mask(va_space,
&gpus_to_check_for_ecc,
&service_context->cpu_fault.gpus_to_check_for_ecc);
uvm_global_mask_retain(&gpus_to_check_for_ecc);
}
uvm_va_space_up_read(va_space);
uvm_record_unlock_mmap_lock_read(vma->vm_mm);
if (status == NV_OK) {
status = uvm_global_mask_check_ecc_error(&gpus_to_check_for_ecc);
uvm_global_mask_release(&gpus_to_check_for_ecc);
}
if (tools_enabled)
uvm_tools_flush_events();
// Major faults involve I/O in order to resolve the fault.
// If any pages were DMA'ed between the GPU and host memory, that makes it
// a major fault. A process can also get statistics for major and minor
// faults by calling readproc().
major_fault = service_context->cpu_fault.did_migrate;
service_block_context_cpu_free(service_context);
unlock:
// TODO: Bug 2583279: See the comment above the matching lock acquisition
uvm_up_read_no_tracking(&g_uvm_global.pm.lock);
convert_error:
switch (status) {
case NV_OK:
case NV_ERR_BUSY_RETRY:
return VM_FAULT_NOPAGE | (major_fault ? VM_FAULT_MAJOR : 0);
case NV_ERR_NO_MEMORY:
return VM_FAULT_OOM;
default:
return VM_FAULT_SIGBUS;
}
}
vm_fault_t uvm_va_space_cpu_fault_managed(uvm_va_space_t *va_space,
struct vm_area_struct *vma,
struct vm_fault *vmf)
{
UVM_ASSERT(va_space == uvm_va_space_get(vma->vm_file));
return uvm_va_space_cpu_fault(va_space, vma, vmf, false);
}
vm_fault_t uvm_va_space_cpu_fault_hmm(uvm_va_space_t *va_space,
struct vm_area_struct *vma,
struct vm_fault *vmf)
{
return uvm_va_space_cpu_fault(va_space, vma, vmf, true);
}