615.71.09

This commit is contained in:
Maneet Singh
2026-09-09 11:55:37 -07:00
parent e4a5faa256
commit 61dcc93722
1178 changed files with 621297 additions and 542126 deletions
+6 -7
View File
@@ -1,7 +1,7 @@
# NVIDIA Linux Open GPU Kernel Module Source
This is the source release of the NVIDIA Linux open GPU kernel modules,
version 610.57.04.
version 615.71.09.
## How to Build
@@ -17,7 +17,7 @@ as root:
Note that the kernel modules built here must be used with GSP
firmware and user-space NVIDIA GPU driver components from a corresponding
610.57.04 driver release. This can be achieved by installing
615.71.09 driver release. This can be achieved by installing
the NVIDIA GPU driver from the .run file using the `--no-kernel-modules`
option. E.g.,
@@ -41,6 +41,7 @@ E.g.,
# compile on x86_64 for aarch64
make modules -j$(nproc) \
TARGET_ARCH=aarch64 \
ARCH=arm64 \
CC=aarch64-linux-gnu-gcc \
LD=aarch64-linux-gnu-ld \
AR=aarch64-linux-gnu-ar \
@@ -185,7 +186,7 @@ table below).
For details on feature support and limitations, see the NVIDIA GPU driver
end user README here:
https://us.download.nvidia.com/XFree86/Linux-x86_64/610.57.04/README/kernel_open.html
https://us.download.nvidia.com/XFree86/Linux-x86_64/615.71.09/README/kernel_open.html
For vGPU support, please refer to the README.vgpu packaged in the vGPU Host
Package for more details.
@@ -952,6 +953,7 @@ Subsystem Device ID.
| NVIDIA GB200 | 2941 10DE 20D5 |
| NVIDIA GB200 | 2941 10DE 21C9 |
| NVIDIA GB200 | 2941 10DE 21CA |
| NVIDIA GB200 | 2941 10DE 22C4 |
| NVIDIA DRIVE P2021 | 29BB 10DE 207C |
| NVIDIA GeForce RTX 5090 | 2B85 |
| NVIDIA GeForce RTX 5090 D | 2B87 |
@@ -979,6 +981,7 @@ Subsystem Device ID.
| NVIDIA RTX 6000D | 2BB9 10DE 2279 |
| NVIDIA GeForce RTX 5080 | 2C02 |
| NVIDIA GeForce RTX 5070 Ti | 2C05 |
| NVIDIA GeForce RTX 5070 | 2C09 |
| NVIDIA GeForce RTX 5090 Laptop GPU | 2C18 |
| NVIDIA GeForce RTX 5080 Laptop GPU | 2C19 |
| NVIDIA RTX PRO 4500 Blackwell | 2C31 1028 2051 |
@@ -1018,10 +1021,6 @@ Subsystem Device ID.
| NVIDIA RTX PRO 500 Blackwell Generation Laptop GPU | 2DB9 |
| NVIDIA GeForce RTX 5050 Laptop GPU | 2DD8 |
| NVIDIA RTX PRO 500 Blackwell Embedded GPU | 2DF9 |
| NVIDIA RTX Spark N1X (6144-core Blackwell RTX GPU) | 2E03 1043 1484 |
| NVIDIA RTX Spark N1X (6144-core Blackwell RTX GPU) | 2E03 1043 3309 |
| NVIDIA RTX Spark N1X (5120-core Blackwell RTX GPU) | 2E06 1043 1484 |
| NVIDIA RTX Spark N1X (5120-core Blackwell RTX GPU) | 2E06 1043 3309 |
| NVIDIA GB10 | 2E12 10DE 21EC |
| NVIDIA GeForce RTX 5070 | 2F04 |
| NVIDIA GeForce RTX 5060 | 2F06 |
+31 -4
View File
@@ -38,10 +38,37 @@ NV_BUILD_TYPE ?= release
# just the object files.
#
ASSIGN_PER_OBJ_CFLAGS = \
$(foreach _cflags_variable, \
$(notdir $(1)) $(1), \
$(eval $(addprefix CFLAGS_,$(_cflags_variable)) += $(2)))
$(foreach _obj, \
$(sort $(notdir $(1)) $(1)), \
$(eval $(addprefix CFLAGS_,$(_obj)) += $(2)))
#
# Utility macro ASSIGN_PER_OBJ_CFLAGS_REMOVE: to control CFLAGS on a
# per-object basis, Kbuild honors the 'CFLAGS_REMOVE_$(object)' variable.
# E.g., "CFLAGS_REMOVE_nv.o" will remove those CFLAGS from the nv.o compilation
# unit. It has the same workaround for the change in Kbuild handling of
# object-specific CFLAGS as described in ASSIGN_PER_OBJ_CFLAGS.
#
ASSIGN_PER_OBJ_CFLAGS_REMOVE = \
$(foreach _obj, \
$(sort $(notdir $(1)) $(1)), \
$(eval $(addprefix CFLAGS_REMOVE_,$(_obj)) += $(2)))
#
# As ASSIGN_PER_OBJ_CFLAGS, just for per-object LDFLAGS.
#
ASSIGN_PER_OBJ_LDFLAGS = \
$(foreach _obj, \
$(sort $(notdir $(1)) $(1)), \
$(eval $(addprefix LDFLAGS_,$(_obj)) += $(2)))
#
# As ASSIGN_PER_OBJ_CFLAGS, just for per-object OBJCOPYFLAGS.
#
ASSIGN_PER_OBJ_OBJCOPYFLAGS = \
$(foreach _obj, \
$(sort $(notdir $(1)) $(1)), \
$(eval $(addprefix OBJCOPYFLAGS_,$(_obj)) += $(2)))
#
# Include the specifics of the individual NVIDIA kernel modules.
@@ -79,7 +106,7 @@ ccflags-y += -I$(src)/common/inc
ccflags-y += -I$(src)
ccflags-y += -Wall $(DEFINES) $(INCLUDES) -Wno-cast-qual -Wno-format-extra-args
ccflags-y += -D__KERNEL__ -DMODULE -DNVRM
ccflags-y += -DNV_VERSION_STRING=\"610.57.04\"
ccflags-y += -DNV_VERSION_STRING=\"615.71.09\"
# Include and link Tegra out-of-tree modules.
ifneq ($(wildcard /usr/src/nvidia/nvidia-public),)
+6
View File
@@ -178,6 +178,7 @@
#if defined(__riscv) && (__riscv_xlen==64)
# define NVCPU_RISCV64
# define NVCPU_FAMILY_RISCV
# if defined(__nvriscv)
# define NVCPU_NVRISCV64
# endif
@@ -401,6 +402,11 @@
#else
#define NVCPU_IS_FAMILY_ARM 0
#endif
#if defined(NVCPU_FAMILY_RISCV)
#define NVCPU_IS_FAMILY_RISCV 1
#else
#define NVCPU_IS_FAMILY_RISCV 0
#endif
#if defined(NVCPU_SH4)
#define NVCPU_IS_SH4 1
#else
+2
View File
@@ -24,6 +24,8 @@
#ifndef _NV_CAPS_H_
#define _NV_CAPS_H_
#include <nv-kernel-interface-api.h>
/*
+33
View File
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*/
#ifndef _NV_DEV_PRINTF_H_
#define _NV_DEV_PRINTF_H_
#include "nv_stdarg.h"
#include "nv-kernel-interface-api.h"
/*
* Device logging (kernel/system log) levels.
*
* These are distinct from NV_DBG_* which are used for internal driver filtering.
*/
typedef enum NV_LOG_LEVEL
{
NV_LOG_LEVEL_DEBUG = 0,
NV_LOG_LEVEL_INFO = 1,
NV_LOG_LEVEL_NOTICE = 2,
NV_LOG_LEVEL_WARNING = 3,
NV_LOG_LEVEL_ERROR = 4,
NV_LOG_LEVEL_CRIT = 5,
NV_LOG_LEVEL_ALERT = 6,
} NV_LOG_LEVEL;
struct nv_state_t;
int NV_API_CALL nv_dev_printf(struct nv_state_t *, NV_LOG_LEVEL, const char *, ...);
int NV_API_CALL nv_dev_vprintf(struct nv_state_t *, NV_LOG_LEVEL, const char *, va_list);
#endif // _NV_DEV_PRINTF_H_
+7 -1
View File
@@ -44,5 +44,11 @@
#define NV_ESC_SYS_PARAMS (NV_IOCTL_BASE + 14)
#define NV_ESC_EXPORT_TO_DMABUF_FD (NV_IOCTL_BASE + 17)
#define NV_ESC_WAIT_OPEN_COMPLETE (NV_IOCTL_BASE + 18)
#ifdef NV_VMWARE
/*
* NV_ESC_INIT_GPUS: VMware-only ioctl to initialize all probed GPUs from
* nvidia-ucodeoffload after GSP binary is pushed, avoiding rm_init from open.
*/
#define NV_ESC_INIT_GPUS (NV_IOCTL_BASE + 19)
#endif
#endif
+64 -16
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2001-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2001-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -38,6 +38,10 @@
#include "nv-chardev-numbers.h"
#include "nv-platform.h"
#if defined(NV_CXL_CXL_H_PRESENT)
#include <cxl/cxl.h>
#endif
#ifndef AUTOCONF_INCLUDED
#if defined(NV_GENERATED_AUTOCONF_H_PRESENT)
#include <generated/autoconf.h>
@@ -103,6 +107,7 @@
#endif
/* task and signal-related items */
#include <linux/sched/mm.h>
#include <linux/sched/signal.h>
#include <linux/sched/task.h>
#include <linux/sched.h>
@@ -342,12 +347,12 @@ NV_STATUS nvos_forward_error_to_cray(struct pci_dev *, NvU32,
/* keep track of memory usage */
#include "nv-memdbg.h"
static inline void *nv_vmalloc(unsigned long size)
static inline void *nv_vmalloc(unsigned long size, gfp_t flags)
{
#if defined(NV_VMALLOC_HAS_PGPROT_T_ARG)
void *ptr = __vmalloc(size, GFP_KERNEL, PAGE_KERNEL);
void *ptr = __vmalloc(size, flags, PAGE_KERNEL);
#else
void *ptr = __vmalloc(size, GFP_KERNEL);
void *ptr = __vmalloc(size, flags);
#endif
NV_MEMDBG_ADD(ptr, size);
return ptr;
@@ -489,8 +494,8 @@ static inline pgprot_t nv_adjust_pgprot(pgprot_t vm_prot)
#define NV_IS_SUSER() capable(CAP_SYS_ADMIN)
#define NV_MAY_SLEEP() (!irqs_disabled() && !in_interrupt() && !in_atomic())
#define NV_MODULE_PARAMETER(x) module_param(x, int, 0)
#define NV_MODULE_STRING_PARAMETER(x) module_param(x, charp, 0)
#define NV_MODULE_PARAMETER(x) module_param(x, int, 0444)
#define NV_MODULE_STRING_PARAMETER(x) module_param(x, charp, 0444)
#undef MODULE_PARM
#define NV_HAVE_MEMORY_ENCRYPT_DECRYPT 0
@@ -677,15 +682,13 @@ static inline int nv_remap_page_range(struct vm_area_struct *vma,
}
static inline int nv_io_remap_page_range(struct vm_area_struct *vma,
NvU64 phys_addr, NvU64 size, NvU64 start)
NvU64 phys_addr, NvU64 size, NvU64 start, pgprot_t prot)
{
int ret = -1;
#if !defined(NV_XEN_SUPPORT_FULLY_VIRTUALIZED_KERNEL)
ret = nv_remap_page_range(vma, start, phys_addr, size,
nv_adjust_pgprot(vma->vm_page_prot));
ret = nv_remap_page_range(vma, start, phys_addr, size, prot);
#else
ret = io_remap_pfn_range(vma, start, (phys_addr >> PAGE_SHIFT),
size, nv_adjust_pgprot(vma->vm_page_prot));
ret = io_remap_pfn_range(vma, start, (phys_addr >> PAGE_SHIFT), size, prot);
#endif
return ret;
}
@@ -902,6 +905,13 @@ struct nv_dma_buf
};
#endif // CONFIG_DMA_SHARED_BUFFER
typedef struct nv_linux_mm_free_work_s {
NvU64 num_pages;
struct mm_struct *mm;
struct work_struct task;
} nv_linux_mm_free_work_t;
typedef struct nv_alloc_s {
struct nv_alloc_s *next;
struct device *dev;
@@ -932,6 +942,7 @@ typedef struct nv_alloc_s {
void *import_priv;
struct sg_table *import_sgt;
dma_addr_t dma_handle; /* dma handle used by dma_alloc_coherent(), dma_free_coherent() */
nv_linux_mm_free_work_t *accounting_mm_work;
} nv_alloc_t;
/**
@@ -1104,12 +1115,30 @@ typedef struct nv_dma_map_s {
* single-page sg elements on Xen Server.
*/
#if !defined(NV_DOM0_KERNEL_PRESENT)
#if defined(NV_SG_ALLOC_TABLE_FROM_PAGES_SEGMENT_PRESENT)
/*
* See Bug 6419127 for the split-submap GMMU contiguity failure.
* This function and correct max_segment value avoid the overflow
* issue in the kernel.
*/
#define NV_DMA_MAX_SEGMENT_SIZE ((NvU32)(0x80000000)) /* 2G */
#define NV_ALLOC_DMA_SUBMAP_SCATTERLIST(dm, sm, i) \
((sg_alloc_table_from_pages_segment( \
&(sm)->sgt, \
&(dm)->pages[NV_DMA_SUBMAP_IDX_TO_PAGE_IDX(i)], \
(sm)->page_count, \
0, \
(sm)->page_count * PAGE_SIZE, \
NV_DMA_MAX_SEGMENT_SIZE, \
NV_GFP_KERNEL) == 0) ? NV_OK : NV_ERR_OPERATING_SYSTEM)
#else
#define NV_ALLOC_DMA_SUBMAP_SCATTERLIST(dm, sm, i) \
((sg_alloc_table_from_pages(&sm->sgt, \
&dm->pages[NV_DMA_SUBMAP_IDX_TO_PAGE_IDX(i)], \
sm->page_count, 0, \
sm->page_count * PAGE_SIZE, NV_GFP_KERNEL) == 0) ? NV_OK : \
NV_ERR_OPERATING_SYSTEM)
#endif
#else
#define NV_ALLOC_DMA_SUBMAP_SCATTERLIST(dm, sm, i) \
((sg_alloc_table(&sm->sgt, sm->page_count, NV_GFP_KERNEL)) == \
@@ -1235,6 +1264,16 @@ struct nv_pci_tegra_devfreq_dev;
/* linux-specific version of old nv_state_t */
/* this is a general os-specific state structure. the first element *must* be
the general state structure, for the generic unix-based code */
#if defined(NV_CXL_CXL_H_PRESENT)
struct nv_cxl {
struct cxl_dev_state cxlds;
struct cxl_memdev *cxlmd;
struct cxl_endpoint_decoder *cxled;
struct cxl_region *nv_region;
};
#endif
typedef struct nv_linux_state_s {
nv_state_t nv_state;
@@ -1248,6 +1287,11 @@ typedef struct nv_linux_state_s {
/* coherent link information */
coherent_link_info_t coherent_link_info;
#if defined(NV_CXL_CXL_H_PRESENT)
/* CXL device state; NULL for non-CXL GPUs */
struct nv_cxl *cxl;
#endif
/* Dedicated queue to be used for removing FB memory which is onlined
* to kernel as a NUMA node. Refer Bug : 3879845*/
nv_kthread_q_t remove_numa_memory_q;
@@ -1418,11 +1462,10 @@ typedef struct nv_linux_state_s {
struct nv_pci_tegra_devfreq_dev *nvd_devfreq_dev;
struct nv_pci_tegra_devfreq_dev *sys_devfreq_dev;
struct nv_pci_tegra_devfreq_dev *pwr_devfreq_dev;
NvU32 tegra_suspend_freq;
int (*devfreq_suspend)(struct device *dev);
int (*devfreq_resume)(struct device *dev);
int (*devfreq_enable_boost)(struct device *dev, unsigned int duration);
int (*devfreq_enable_boost)(struct device *dev, unsigned int duration, int boost_type);
int (*devfreq_disable_boost)(struct device *dev);
#endif
@@ -1457,6 +1500,9 @@ extern struct semaphore nv_linux_devices_lock;
extern struct rw_semaphore nv_system_pm_lock;
extern NvBool nv_ats_supported;
extern NvBool nv_non_ats_device_present;
void nv_set_max_sysmem_address(void);
/*
* file-private data
@@ -1605,8 +1651,8 @@ extern NvU32 NVreg_EnableNonblockingOpen;
extern NvU32 NVreg_UseKernelSuspendNotifiers;
extern NvU32 NVreg_GpuInitOnProbe;
extern NvU32 num_probed_nv_devices;
extern NvU32 num_nv_devices;
extern atomic_t num_probed_nv_devices;
extern atomic_t num_nv_devices;
#define NV_FILE_INODE(file) (file)->f_inode
@@ -1724,7 +1770,7 @@ typedef enum
#else
#include <linux/gpio/driver.h>
static inline int __to_hwgpio(const struct gpio_device *gdev,
static inline int __to_hwgpio(struct gpio_device *gdev,
const struct of_phandle_args *gpiospec)
{
#if defined(CONFIG_OF_GPIO)
@@ -1797,5 +1843,7 @@ static inline int of_get_named_gpio(const struct device_node *np,
#define NV_EXPORT_SYMBOL(symbol) EXPORT_SYMBOL_GPL(symbol)
#define NV_CHECK_EXPORT_SYMBOL(symbol) NV_IS_EXPORT_SYMBOL_PRESENT_##symbol
// Async PCI probe requires wait_for_device_probe() as a post-registration barrier
#define NV_PCI_ASYNC_PROBE_SUPPORTED NV_IS_EXPORT_SYMBOL_PRESENT_wait_for_device_probe
#endif /* _NV_LINUX_H_ */
+83 -24
View File
@@ -26,6 +26,15 @@
#include <linux/pci.h>
#include "nv-linux.h"
#include <uapi/linux/pci_regs.h>
#if defined(NV_CXL_CXL_H_PRESENT)
#include <cxl/cxl.h>
#endif
#if defined(NV_CXL_PCI_H_PRESENT)
#include <cxl/pci.h>
#endif
#define NV_GPU_BAR1 1
#define NV_GPU_BAR3 3
@@ -33,36 +42,86 @@
int nv_pci_register_driver(void);
void nv_pci_unregister_driver(void);
int nv_pci_count_devices(void);
void nv_pci_wait_for_probe_complete(void);
NvU8 nv_find_pci_capability(struct pci_dev *, NvU8);
int nvidia_dev_get_pci_info(const NvU8 *, struct pci_dev **, NvU64 *, NvU64 *);
nv_linux_state_t * find_pci(NvU32, NvU8, NvU8, NvU8);
NvBool nv_pci_is_valid_topology_for_direct_pci(nv_state_t *, struct pci_dev *);
NvBool nv_pci_has_common_pci_switch(nv_state_t *nv, struct pci_dev *);
void nv_pci_tegra_boost_clocks(struct device *dev);
/* Register Block Identifier (RBI) */
enum cxl_regloc_type {
CXL_REGLOC_RBI_EMPTY = 0,
CXL_REGLOC_RBI_COMPONENT,
CXL_REGLOC_RBI_VIRT,
CXL_REGLOC_RBI_MEMDEV,
CXL_REGLOC_RBI_PMU,
CXL_REGLOC_RBI_TYPES
};
#define NV_PCI_EXT_CAP_START_OFFSET 0x100
#define NV_PCI_EXT_CAP_END_OFFSET 0x1000
/* Maximum number of iterations to traverse the PCI extended capability chain.
Each capability is minimum 8 bytes long. */
#define NV_PCI_EXT_CAP_MAX_ITERATIONS ((NV_PCI_EXT_CAP_END_OFFSET - NV_PCI_EXT_CAP_START_OFFSET) / 8)
#define NV_PCI_EXT_CAP_ID(hdr0) ((hdr0) & 0xffff)
#define NV_PCI_EXT_CAP_VERSION(hdr0) (((hdr0) & 0x000f0000) >> 16)
#define NV_PCI_EXT_CAP_NEXT_OFFSET(hdr0) (((hdr0) & 0xfff00000) >> 20)
#define NV_PCI_EXT_CAP_ID_DVSEC 0x0023
#define NV_PCI_DVSEC_HEADER_1_OFFSET 0x4
#define NV_PCI_DVSEC_VENDOR_ID(hdr1) ((hdr1) & 0xffff)
#define NV_PCI_DVSEC_REVISION(hdr1) (((hdr1) & 0x000f0000) >> 16)
#define NV_PCI_DVSEC_LEN(hdr1) (((hdr1) & 0xfff00000) >> 20)
#define NV_PCI_DVSEC_HEADER_2_OFFSET 0x8
#define NV_PCI_DVSEC_DESIGNATED_ID(hdr2) ((hdr2) & 0xffff)
#define NV_PCI_DVSEC_NVIDIA_VENDOR_ID 0x10de
#define CXL_CM_OFFSET 0x1000
#define CXL_CM_CAP_HDR_OFFSET 0x0
#define CXL_CM_CAP_HDR_ID_MASK GENMASK(15, 0)
#define CM_CAP_HDR_CAP_ID 1
#define CXL_CM_CAP_HDR_ARRAY_SIZE_MASK GENMASK(31, 24)
#define CXL_CM_CAP_PTR_MASK GENMASK(31, 20)
#define CXL_CM_CAP_CAP_ID_HDM 0x5
#define CXL_HDM_DECODER_CAP_OFFSET 0x0
#define CXL_HDM_DECODER0_BASE_LOW_OFFSET(i) (0x20 * (i) + 0x10)
#define CXL_HDM_DECODER0_BASE_HIGH_OFFSET(i) (0x20 * (i) + 0x14)
#define CXL_HDM_DECODER0_SIZE_LOW_OFFSET(i) (0x20 * (i) + 0x18)
#define CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(i) (0x20 * (i) + 0x1c)
#define CXL_HDM_DECODER0_CTRL_OFFSET(i) (0x20 * (i) + 0x20)
#define CXL_HDM_DECODER0_CTRL_COMMITTED BIT(10)
/* DVSEC 3 specific constants */
#define NV_PCI_DVSEC_GPU_PDI_DESIGNATED_ID 0x3
#define NV_PCI_DVSEC_GPU_PDI_CAP_LEN 0x14
#define NV_PCI_DVSEC_GPU_PDI_LOW_OFFSET 0xc
#define NV_PCI_DVSEC_GPU_PDI_HIGH_OFFSET 0x10
#define NV_CXL_REGLOC_RBI_COMPONENT 0x1
#define NV_CXL_CM_OFFSET 0x1000
#define NV_CXL_CM_CAP_HDR_OFFSET 0x0
#define NV_CXL_CM_CAP_HDR_ID_MASK GENMASK(15, 0)
#define NV_CM_CAP_HDR_CAP_ID 1
#define NV_CXL_CM_CAP_HDR_ARRAY_SIZE_MASK GENMASK(31, 24)
#define NV_CXL_CM_CAP_PTR_MASK GENMASK(31, 20)
#define NV_CXL_CM_CAP_CAP_ID_HDM 0x5
#define NV_CXL_HDM_DECODER_CAP_OFFSET 0x0
#define NV_CXL_HDM_DECODER0_BASE_LOW_OFFSET(i) (0x20 * (i) + 0x10)
#define NV_CXL_HDM_DECODER0_BASE_HIGH_OFFSET(i) (0x20 * (i) + 0x14)
#define NV_CXL_HDM_DECODER0_SIZE_LOW_OFFSET(i) (0x20 * (i) + 0x18)
#define NV_CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(i) (0x20 * (i) + 0x1c)
#define NV_CXL_HDM_DECODER0_CTRL_OFFSET(i) (0x20 * (i) + 0x20)
#define NV_CXL_HDM_DECODER0_CTRL_COMMITTED BIT(10)
/* CXL r4.0, 8.1.8: Flex Bus DVSEC */
#ifndef PCI_DVSEC_CXL_FLEXBUS_PORT
#define PCI_DVSEC_CXL_FLEXBUS_PORT 7
#endif
#ifndef PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS
#define PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS 0xE
#endif
#ifndef PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_CACHE
#define PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_CACHE BIT(0)
#endif
#ifndef PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_MEM
#define PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_MEM BIT(2)
#endif
/* CXL r4.0, 8.1.9: Register Locator DVSEC */
#ifndef PCI_DVSEC_CXL_REG_LOCATOR
#define PCI_DVSEC_CXL_REG_LOCATOR 8
#endif
#ifndef PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1
#define PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1 0xC
#endif
#ifndef PCI_DVSEC_CXL_REG_LOCATOR_BIR
#define PCI_DVSEC_CXL_REG_LOCATOR_BIR GENMASK(2, 0)
#endif
#ifndef PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_ID
#define PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_ID GENMASK(15, 8)
#endif
#ifndef PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_OFF_LOW
#define PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_OFF_LOW GENMASK(31, 16)
#endif
#ifndef PCI_VENDOR_ID_CXL
#define PCI_VENDOR_ID_CXL 0x1e98
#endif
#endif /* _NV_PCI_H_ */
+7 -2
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 1999-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 1999-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -49,6 +49,7 @@ void nv_revoke_gpu_mappings_locked(nv_state_t *);
NvUPtr nv_vm_map_pages (struct page **, NvU32, NvBool, NvBool);
void nv_vm_unmap_pages (NvUPtr, NvU32);
NvU64 nv_get_reclaimable_memory_usage(void);
NV_STATUS nv_alloc_contig_pages (nv_state_t *, nv_alloc_t *);
void nv_free_contig_pages (nv_alloc_t *);
@@ -84,7 +85,10 @@ void nvidia_modeset_remove (NvU32 gpuId);
void nvidia_modeset_probe (const nv_linux_state_t *nvl);
NvBool nv_is_uuid_in_gpu_exclusion_list (const char *);
NV_STATUS nv_parse_per_device_option_string(nvidia_stack_t *sp);
NV_STATUS nv_parse_per_device_option_string(nvidia_stack_t *sp,
nv_state_t *current_nv);
NV_STATUS nv_parse_per_device_binary_option_string(nvidia_stack_t *sp,
nv_state_t *current_nv);
void nv_enable_cdmm_mode(nvidia_stack_t *sp);
NvBool nv_is_galaxy_workstation (void);
@@ -103,6 +107,7 @@ NvBool nv_acpi_power_resource_method_present(struct pci_dev *);
int nv_linux_init_open_q(nv_linux_state_t *);
void nv_linux_stop_open_q(nv_linux_state_t *);
void nv_set_init_on_probe(nv_state_t *nv);
int nv_start_device(nv_state_t *nv, nvidia_stack_t *sp);
void nv_stop_device(nv_state_t *nv, nvidia_stack_t *sp);
#endif /* _NV_PROTO_H_ */
+44
View File
@@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* 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.
*/
#ifndef _NV_RETHUNK_H_
#define _NV_RETHUNK_H_
#include "cpuopsys.h"
#if defined(NVCPU_X86_64)
__asm__(".pushsection .text");
__asm__(
".globl __x86_return_thunk;"
".type __x86_return_thunk,@function;"
"__x86_return_thunk:"
" .cfi_startproc;"
" ret;"
" int3;"
" .cfi_endproc;"
".size __x86_return_thunk, .-__x86_return_thunk;");
__asm__(".popsection");
#endif // NVCPU_X86_64
#endif // _NV_RETHUNK_H_
+98
View File
@@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* 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.
*/
#ifndef _NV_TEGRA_CLK_H_
#define _NV_TEGRA_CLK_H_
#include "nv.h"
struct nv_pci_tegra_devfreq_data {
const char *clk_name;
const char *icc_name;
const unsigned int gpc_fuse_field;
const TEGRASOC_DEVFREQ_CLK devfreq_clk;
};
static const struct nv_pci_tegra_devfreq_data tegra_devfreq_tables[][6] = {
{
{
.clk_name = "gpc0clk",
.icc_name = "gpu-write",
.gpc_fuse_field = BIT(0),
.devfreq_clk = TEGRASOC_DEVFREQ_CLK_GPC,
},
{
.clk_name = "gpc1clk",
.icc_name = "gpu-write",
.gpc_fuse_field = BIT(1),
.devfreq_clk = TEGRASOC_DEVFREQ_CLK_GPC,
},
{
.clk_name = "gpc2clk",
.icc_name = "gpu-write",
.gpc_fuse_field = BIT(2),
.devfreq_clk = TEGRASOC_DEVFREQ_CLK_GPC,
},
{
.clk_name = "nvdclk",
.icc_name = "video-write",
.devfreq_clk = TEGRASOC_DEVFREQ_CLK_NVD,
},
{
.clk_name = "sysclk"
},
{
.clk_name = "uprocclk"
},
},
{
{
.clk_name = "gpc0clk",
.icc_name = "gpu-write",
.gpc_fuse_field = BIT(0),
.devfreq_clk = TEGRASOC_DEVFREQ_CLK_GPC,
},
{
.clk_name = "gpc1clk",
.icc_name = "gpu-write",
.gpc_fuse_field = BIT(1),
.devfreq_clk = TEGRASOC_DEVFREQ_CLK_GPC,
},
{
.clk_name = "nvdclk",
.icc_name = "video-write",
.devfreq_clk = TEGRASOC_DEVFREQ_CLK_NVD,
},
{
.clk_name = "sysclk"
},
{
.clk_name = "uprocclk"
},
{
.clk_name = NULL
},
},
};
#endif /* _NV_TEGRA_CLK_H_ */
+32 -11
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 1999-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 1999-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -29,13 +29,7 @@
#include <nvlimits.h>
#if defined(NV_KERNEL_INTERFACE_LAYER) && defined(__FreeBSD__)
#include <sys/stddef.h> // NULL
#elif defined(NV_KERNEL_INTERFACE_LAYER) && defined(NV_LINUX)
#include <linux/stddef.h> // NULL
#else
#include <stddef.h> // NULL
#endif
#include <nv_stddef.h> // NULL
#include <nvstatus.h>
#include "nv_stdarg.h"
@@ -43,6 +37,8 @@
#include <nv-firmware.h>
#include <nv-ioctl.h>
#include <nv-ioctl-numa.h>
#include <nv-dev-printf.h>
#include <nv-kernel-interface-api.h>
#include <nvmisc.h>
#include <os/nv_memory_area.h>
@@ -59,8 +55,6 @@ extern const NvBool nv_is_rm_firmware_supported_os;
#include <nvi2c.h>
#include <nvimpshared.h>
#include <nv-kernel-interface-api.h>
#define GPU_UUID_LEN (16)
/*
@@ -106,6 +100,13 @@ typedef enum _TEGRASOC_DEVFREQ_CLK
TEGRASOC_DEVFREQ_CLK_NVD,
} TEGRASOC_DEVFREQ_CLK;
typedef enum _TEGRASOC_DEVFREQ_CLK_BOOST_TYPE
{
TEGRASOC_DEVFREQ_CLK_BOOST_TYPE_DEFAULT,
TEGRASOC_DEVFREQ_CLK_BOOST_TYPE_FMAX,
TEGRASOC_DEVFREQ_CLK_BOOST_TYPE_RATED_TDP,
} TEGRASOC_DEVFREQ_CLK_BOOST_TYPE;
/*!
* @brief The order of the display clocks in the below defined enum
* should be synced with below mapping array and macro.
@@ -478,6 +479,7 @@ typedef struct nv_state_t
NvBool valid;
NvU8 uuid[GPU_UUID_LEN];
NvBool pci_uuid_read_attempted;
NvBool pci_uuid_from_dvsec_pdi;
NV_STATUS pci_uuid_status;
} nv_uuid_cache;
void *handle;
@@ -514,6 +516,7 @@ typedef struct nv_state_t
NvBool supports_tegra_igpu_rg;
NvBool is_tegra_pci_igpu_rg_enabled;
NvU32 tegra_pci_igpu_pg_mask;
NvU32 gpc_fuse_status_offset;
NvBool primary_vga;
@@ -643,6 +646,15 @@ typedef struct nv_state_t
/* Bool to check if the GPU is a CXL device */
NvBool is_cxl_dev;
/*
* Tracks user-requested persistence mode for backward compatibility
* when init-on-probe is active. Existing scripts and tools expect
* PM0/PM1 state to be reflected in queries even though persistence
* mode has no functional effect under init-on-probe.
* This field will be removed when legacy persistence mode is
* deprecated in the future.
*/
NvBool cached_persistence_mode;
} nv_state_t;
#define NVFP_TYPE_NONE ((NvU32)0x0)
@@ -850,6 +862,7 @@ typedef enum {
// Flags needed by osAllocPagesNode / os_alloc_pages_node
#define NV_ALLOC_PAGES_NODE_NONE 0x0
#define NV_ALLOC_PAGES_NODE_SKIP_RECLAIM 0x1
#define NV_ALLOC_PAGES_NODE_DO_ACCOUNT 0x2
/*
** where we hide our nv_state_t * ...
@@ -990,6 +1003,8 @@ void NV_API_CALL nv_dma_unmap_mmio (nv_dma_device_t *, NvU64, NvU6
void NV_API_CALL nv_dma_cache_invalidate (nv_dma_device_t *, void *);
void* NV_API_CALL nv_dma_get_dev_pagemap (NvU64);
void NV_API_CALL nv_dma_put_dev_pagemap (void *);
NV_STATUS NV_API_CALL nv_dma_init_sysmem_window_for_fabric_access (nv_state_t *, NvU64, NvU64 *, NvU64 *, NvBool *);
void NV_API_CALL nv_dma_destroy_sysmem_window_for_fabric_access (nv_state_t *, NvU64, NvU64);
NvBool NV_API_CALL nv_grdma_pci_topology_supported(nv_state_t *, nv_dma_device_t *);
NvS32 NV_API_CALL nv_start_rc_timer (nv_state_t *);
@@ -1061,6 +1076,7 @@ NvBool NV_API_CALL nv_match_gpu_os_info(nv_state_t *, void *);
void NV_API_CALL nv_get_updated_emu_seg(NvU32 *start, NvU32 *end);
void NV_API_CALL nv_get_screen_info(nv_state_t *, NvU64 *, NvU32 *, NvU32 *, NvU32 *, NvU32 *, NvU64 *);
void NV_API_CALL nv_set_gpu_pg_mask(nv_state_t *);
void NV_API_CALL nv_trigger_gpu_flr(nv_state_t *);
struct dma_buf;
typedef struct nv_dma_buf nv_dma_buf_t;
@@ -1083,6 +1099,7 @@ NvBool NV_API_CALL nv_s2idle_pm_configured (void);
NvBool NV_API_CALL nv_pci_tegra_register_power_domain (nv_state_t *, NvBool);
NvBool NV_API_CALL nv_pci_tegra_pm_init (nv_state_t *);
void NV_API_CALL nv_pci_tegra_pm_deinit (nv_state_t *);
NV_STATUS NV_API_CALL nv_pci_read_gpu_pdi_from_dvsec(nv_state_t *, NvU64 *);
NvBool NV_API_CALL nv_is_chassis_notebook (void);
void NV_API_CALL nv_allow_runtime_suspend (nv_state_t *nv);
@@ -1217,6 +1234,10 @@ NvBool NV_API_CALL rm_is_supported_pci_device(NvU8 pci_class,
NvU16 subsystem_vendor,
NvU16 subsystem_device,
NvBool print_legacy_warning);
NvBool NV_API_CALL rm_is_nvidia_gpu_device(NvU8 pci_class,
NvU8 pci_subclass,
NvU16 vendor,
NvU16 device);
void NV_API_CALL rm_i2c_remove_adapters (nvidia_stack_t *, nv_state_t *);
NvBool NV_API_CALL rm_i2c_is_smbus_capable (nvidia_stack_t *, nv_state_t *, void *);
@@ -1251,7 +1272,7 @@ void NV_API_CALL rm_dma_buf_unmap_mem_handle(nvidia_stack_t *, nv_state_t
NV_STATUS NV_API_CALL rm_dma_buf_get_client_and_device(nvidia_stack_t *,
nv_state_t *, NvHandle, NvHandle,
NvU8, NvHandle *, NvHandle *,
NvHandle *, void **, NvBool *, NvBool *);
NvHandle *, void **, NvBool *);
void NV_API_CALL rm_dma_buf_put_client_and_device(nvidia_stack_t *, nv_state_t *, NvHandle, NvHandle, NvHandle, void *);
void NV_API_CALL rm_kernel_rmapi_op(nvidia_stack_t *sp, void *ops_cmd);
+38
View File
@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* 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.
*/
#ifndef NV_STDDEF_H
#define NV_STDDEF_H
#include "cpuopsys.h"
#if defined(NV_KERNEL_INTERFACE_LAYER) && NVOS_IS_FREEBSD
#include <sys/stddef.h>
#include <sys/types.h>
#elif defined(NV_KERNEL_INTERFACE_LAYER) && NVOS_IS_LINUX
#include <linux/types.h>
#else
#include <stddef.h>
#endif
#endif // NV_STDDEF_H
+26
View File
@@ -563,6 +563,10 @@ typedef struct UvmPlatformInfo_tag
// Out: ATS (Address Translation Services) is supported
NvBool atsSupported;
// Out: Indicates that at least one non-ats device is present in the
// system
NvBool nonAtsDevicePresent;
// Out: True if HW trusted execution, such as AMD's SEV-SNP or Intel's TDX,
// is enabled in the VM, indicating that Confidential Computing must be
// also enabled in the GPU(s); these two security features are either both
@@ -718,6 +722,28 @@ typedef struct UvmGpuInfo_tag
// to direct NVLink peers.
NvBool nvlDirectConnect;
NvU64 nvlDirectConnectMemoryWindowStart;
// Compute GPU has no vidmem, used to detect iGPUs.
NvBool gpuArchIsZeroFb;
struct
{
// Indicates if fabric-based sysmem access is supported on this GPU
NvBool bSupported;
// Start of the FLA window mapped to local CPU sysmem
NvU64 flaStart;
// Size of the FLA window starting from flaWindowStart
NvU64 flaSize;
// DMA/IOVA address corresponding to the start of local CPU sysmem
NvU64 dmaAddrBase;
// Indicates if DMA addresses are identity-mapped (no IOMMU translation).
// If false, physical sysmem ranges need to be linked into the IOVA range.
NvBool bIdentityDmaMap;
} flaWindowForCpuMemNode;
} UvmGpuInfo;
typedef struct UvmGpuFbInfo_tag
+31 -14
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2014-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2014-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -52,6 +52,9 @@
#define NVKMS_OLUT_FP_NORM_SCALE_DEFAULT 0xffffffff
#define NVKMS_HDMI_VSIF_METADATA_MIN_PAYLOAD_SIZE 3
#define NVKMS_HDMI_VSIF_METADATA_MAX_PAYLOAD_SIZE 27
typedef NvU32 NvKmsDeviceHandle;
typedef NvU32 NvKmsDispHandle;
typedef NvU32 NvKmsConnectorHandle;
@@ -186,6 +189,8 @@ enum NvKmsEventType {
NVKMS_EVENT_TYPE_DPY_ATTRIBUTE_CHANGED,
NVKMS_EVENT_TYPE_FRAMELOCK_ATTRIBUTE_CHANGED,
NVKMS_EVENT_TYPE_FLIP_OCCURRED,
NVKMS_EVENT_TYPE_DPY_CONTENT_PROTECTION_CHANGED,
NVKMS_EVENT_TYPE_DPY_CP_TOPOLOGY_CHANGED,
};
enum NvKmsFlipResult {
@@ -497,19 +502,6 @@ struct NvKmsCompositionCapabilities {
};
struct NvKmsLayerCapabilities {
/*!
* Whether Layer supports the window mode. If window mode is supported,
* then clients can set the layer's dimensions so that they're smaller than
* the viewport, and can also change the output position of the layer to a
* non-(0, 0) position.
*
* NOTE: Dimension changes are currently unsupported for the main layer,
* and output position changes for the main layer are currently only
* supported via IOCTL_SET_LAYER_POSITION but not via flips. Support for
* these is coming soon, via changes to flip code.
*/
NvBool supportsWindowMode :1;
/*!
* Whether layer supports ICtCp pipe.
*/
@@ -782,6 +774,14 @@ struct NvKmsVblankSemControlData {
struct NvKmsVblankSemControlDataOneHead head[NV_MAX_HEADS];
};
enum NvKmsContentProtection {
NVKMS_CONTENT_PROTECTION_OFF = 0,
NVKMS_CONTENT_PROTECTION_HDCP1X_ON = 1,
NVKMS_CONTENT_PROTECTION_HDCP2X_TYPE0_ON = 2,
NVKMS_CONTENT_PROTECTION_HDCP2X_TYPE1_ON = 3,
NVKMS_CONTENT_PROTECTION_FAILED = 4,
};
/*
* Dithering control enums, shared between NVKMS API and KAPI.
* These are defined here (instead of nvkms-api.h) so that nvkms-kapi.h
@@ -806,4 +806,21 @@ enum NvKmsDpyAttributeRequestedDitheringModeValue {
typedef void (*NVRgInterruptCallbackProc)(NvU64 clientData,
NvU64 timestamp);
/*! hdcp topology struct defined in drivers/unix/common/inc/nv_hdcp_topology.h */
#define NVKMS_HDCP_TOPOLOGY_SIZE (1296)
/*
* HDMI Vendor Specific InfoFrame (VSIF) metadata.
* The payload is a 27 byte array where the first three bytes are
* the vendor OUI, and the remaining bytes are the infoframe content.
* payloadSize == 0 means disabled (no VSIF override).
* When payloadSize > 0, it must be in the range
* [NVKMS_HDMI_VSIF_METADATA_MIN_PAYLOAD_SIZE,
* NVKMS_HDMI_VSIF_METADATA_MAX_PAYLOAD_SIZE] to enable the VSIF.
*/
struct NvKmsHdmiVsifMetadata {
NvU8 payloadSize;
NvU8 payload[NVKMS_HDMI_VSIF_METADATA_MAX_PAYLOAD_SIZE];
};
#endif /* NVKMS_API_TYPES_H */
+55 -59
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2015-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2015-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -174,7 +174,18 @@ struct NvKmsKapiDeviceResourcesInfo {
NvBool supportsSyncpts;
NvBool supportsColorPassthrough;
NvBool contiguousPhysicalMappings;
/*
* Whether this device is an SoC product whose display is driven by
* the dGPU-style display class and needs the resume connector
* re-probe workaround (bug 6422321). Do not use this for anything
* else: it exists only for that workaround and is tracked for
* removal by bug 6556017.
*/
NvBool isSocDgpuDisplayNeedingWar;
} caps;
NvU64 supportedSurfaceMemoryFormats[NVKMS_KAPI_LAYER_MAX];
@@ -317,25 +328,28 @@ struct NvKmsKapiLayerConfig {
NvBool blendCtm : 1;
} enabled;
} matrixOverrides;
NvBool precompColorPassthrough;
};
struct NvKmsKapiLayerRequestedConfig {
struct NvKmsKapiLayerConfig config;
struct {
NvBool surfaceChanged : 1;
NvBool srcXYChanged : 1;
NvBool srcWHChanged : 1;
NvBool dstXYChanged : 1;
NvBool dstWHChanged : 1;
NvBool cscChanged : 1;
NvBool inputTfChanged : 1;
NvBool outputTfChanged : 1;
NvBool inputColorSpaceChanged : 1;
NvBool inputColorRangeChanged : 1;
NvBool hdrMetadataChanged : 1;
NvBool matrixOverridesChanged : 1;
NvBool ilutChanged : 1;
NvBool tmoChanged : 1;
NvBool surfaceChanged : 1;
NvBool srcXYChanged : 1;
NvBool srcWHChanged : 1;
NvBool dstXYChanged : 1;
NvBool dstWHChanged : 1;
NvBool cscChanged : 1;
NvBool inputTfChanged : 1;
NvBool outputTfChanged : 1;
NvBool inputColorSpaceChanged : 1;
NvBool inputColorRangeChanged : 1;
NvBool hdrMetadataChanged : 1;
NvBool matrixOverridesChanged : 1;
NvBool ilutChanged : 1;
NvBool tmoChanged : 1;
NvBool precompColorPassthroughChanged : 1;
} flags;
};
@@ -386,22 +400,10 @@ struct NvKmsKapiHeadModeSetConfig {
struct NvKmsHDRStaticMetadata staticMetadata;
} hdrInfoFrame;
struct NvKmsHdmiVsifMetadata hdmiVsifMetadata;
enum NvKmsOutputColorimetry colorimetry;
struct {
struct {
NvU32 depth;
NvU32 start;
NvU32 end;
struct NvKmsLutRamps *pRamps;
} input;
struct {
NvBool enabled;
struct NvKmsLutRamps *pRamps;
} output;
} lut;
struct {
NvBool enabled;
struct NvKmsKapiSurface *lutSurface;
@@ -416,21 +418,23 @@ struct NvKmsKapiHeadModeSetConfig {
enum NvKmsDpyAttributeRequestedDitheringValue state;
enum NvKmsDpyAttributeRequestedDitheringModeValue mode;
} dithering;
NvBool postcompColorPassthrough;
};
struct NvKmsKapiHeadRequestedConfig {
struct NvKmsKapiHeadModeSetConfig modeSetConfig;
struct {
NvBool activeChanged : 1;
NvBool displaysChanged : 1;
NvBool modeChanged : 1;
NvBool hdrInfoFrameChanged : 1;
NvBool colorimetryChanged : 1;
NvBool legacyIlutChanged : 1;
NvBool legacyOlutChanged : 1;
NvBool olutChanged : 1;
NvBool olutFpNormScaleChanged : 1;
NvBool ditheringChanged : 1;
NvBool activeChanged : 1;
NvBool displaysChanged : 1;
NvBool modeChanged : 1;
NvBool hdrInfoFrameChanged : 1;
NvBool colorimetryChanged : 1;
NvBool olutChanged : 1;
NvBool olutFpNormScaleChanged : 1;
NvBool ditheringChanged : 1;
NvBool hdmiVsifMetadataChanged : 1;
NvBool postcompColorPassthroughChanged : 1;
} flags;
struct NvKmsKapiCursorRequestedConfig cursorRequestedConfig;
@@ -465,6 +469,16 @@ struct NvKmsKapiEventDisplayChanged {
NvKmsKapiDisplay display;
};
struct NvKmsKapiEventDisplayCpChanged {
NvKmsKapiDisplay display;
enum NvKmsContentProtection cp;
};
struct NvKmsKapiEventDisplayCpTopologyChanged {
NvKmsKapiDisplay display;
const void *topology;
};
struct NvKmsKapiEventDynamicDisplayConnected {
NvKmsKapiDisplay display;
};
@@ -496,6 +510,8 @@ struct NvKmsKapiEvent {
struct NvKmsKapiEventDisplayChanged displayChanged;
struct NvKmsKapiEventDynamicDisplayConnected dynamicDisplayConnected;
struct NvKmsKapiEventFlipOccurred flipOccurred;
struct NvKmsKapiEventDisplayCpChanged displayCpChanged;
struct NvKmsKapiEventDisplayCpTopologyChanged displayCpTopologyChanged;
} u;
};
@@ -1606,26 +1622,6 @@ struct NvKmsKapiFunctionsTable {
NvU32 semaphoreIndex
);
/*!
* Check or wait on a head's LUT notifier.
*
* \param [in] device A device allocated using allocateDevice().
*
* \param [in] head The head to check for LUT completion.
*
* \param [in] waitForCompletion If true, wait for the notifier in NvKms
* before returning.
*
* \param [out] complete Returns whether the notifier has completed.
*/
NvBool
(*checkLutNotifier)
(
struct NvKmsKapiDevice *device,
NvU32 head,
NvBool waitForCompletion
);
/*
* Notify NVKMS that the system's framebuffer console has been disabled and
* the reserved allocation for the old framebuffer console can be unmapped.
+5
View File
@@ -259,8 +259,13 @@ extern "C" {
//
// 64 Bit Versions
//
#if defined(NV_MISRA_COMPLIANCE_REQUIRED)
#define DRF_SHIFT64(drf) ((DRF_BASE(drf)) % 64U)
#define DRF_MASK64(drf) (NV_U64_MAX>>(63U-((DRF_EXTENT(drf)) % 64U)+((DRF_BASE(drf)) % 64U)))
#else
#define DRF_SHIFT64(drf) ((DRF_ISBIT(0,drf)) % 64)
#define DRF_MASK64(drf) (NV_U64_MAX>>(63-((DRF_ISBIT(1,drf)) % 64)+((DRF_ISBIT(0,drf)) % 64)))
#endif
#define DRF_SHIFTMASK64(drf) (DRF_MASK64(drf)<<(DRF_SHIFT64(drf)))
#define DRF_DEF64(d,r,f,c) (((NvU64)(NV ## d ## r ## f ## c))<<DRF_SHIFT64(NV ## d ## r ## f))
+16
View File
@@ -168,6 +168,22 @@ NV_STATUS_CODE(NV_ERR_MC_FLA_OFFSET_TABLE_FULL, 0x0000008A, "Mul
NV_STATUS_CODE(NV_ERR_OPERATION_ABORTED, 0x0000008B, "Operation has aborted")
NV_STATUS_CODE(NV_ERR_DMA_XFER_FAILED, 0x0000008C, "DMA transfer failed")
NV_STATUS_CODE(NV_ERR_RESOURCE_ACCOUNTING_HARD_LIMIT_EXCEEDED, 0x0000008D, "Resource accounting rejected request (hard limit)")
NV_STATUS_CODE(NV_ERR_NVLINK_FW_NOT_LOADED, 0x0000008E, "MSE firmware is not loaded")
NV_STATUS_CODE(NV_ERR_INTR_STUCK, 0x0000008F, "Interrupt is stuck")
NV_STATUS_CODE(NV_ERR_GMCAPI_INVALID_MCTP, 0x00000090, "Invalid MCTP header")
NV_STATUS_CODE(NV_ERR_GMCAPI_INVALID_NVDM_TYPE, 0x00000091, "Invalid NVDM message type")
NV_STATUS_CODE(NV_ERR_GMCAPI_INVALID_COMMAND_ID, 0x00000092, "The given GMCAPI command ID is not valid")
NV_STATUS_CODE(NV_ERR_GMCAPI_REQUEST_SIZE_TOO_LARGE, 0x00000093, "GMCAPI request size is too large")
NV_STATUS_CODE(NV_ERR_GMCAPI_REQUEST_SIZE_TOO_SMALL, 0x00000094, "GMCAPI request size is too small")
NV_STATUS_CODE(NV_ERR_GMCAPI_MAX_RESPONSE_SIZE_TOO_SMALL, 0x00000095, "GMCAPI maximum response size is too small")
// bug 6141566: Unable to add new NV_STATUS values temporarily
//NV_STATUS_CODE(NV_ERR_GMCAPI_RESERVED_FIELD_NOT_ZERO, 0x00000096, "GMCAPI reserved field not set to zero")
//NV_STATUS_CODE(NV_ERR_GMCAPI_INVALID_FLAGS, 0x00000097, "GMCAPI flags for this call are not valid")
#define NV_ERR_GMCAPI_RESERVED_FIELD_NOT_ZERO ((NV_STATUS)0x00000096)
#define NV_ERR_GMCAPI_INVALID_FLAGS ((NV_STATUS)0x00000097)
// Status codes 0x98..0x9D are reserved for GMCAPI
NV_STATUS_CODE(NV_ERR_GMCAPI_INTERNAL_RM_ERROR, 0x0000009E, "RM has hit an internal error while servicing GMCAPI")
NV_STATUS_CODE(NV_ERR_GMCAPI_GENERIC_ERROR, 0x0000009F, "Failed to service GMCAPI: Generic error")
// Warnings:
NV_STATUS_CODE(NV_WARN_HOT_SWITCH, 0x00010001, "WARNING Hot switch")
+14
View File
@@ -524,6 +524,20 @@ typedef struct
#define NV_ALIGNOF(type) __alignof__(type)
#elif defined(_MSC_VER)
#define NV_ALIGNOF(type) __alignof(type)
#endif
//
// This tag can be applied to a type to mark it as ABI-stable and block most
// changes to the internal layout. In all driver builds, this tag is a NOP and
// is there for human benefit only. However, static checker tools may support
// the custom attribute and use it to verify no changes across releases.
// Tag comes between struct/enum keyword and the name, like GCC type attributes.
// e.g. typedef struct NV_ABI_STABLE MyStableStruct { ... } MyStableStruct;
//
#if defined(NV_ABI_STABLE_CHECKED_BUILD)
#define NV_ABI_STABLE __attribute__((nv_abi_stable))
#else
#define NV_ABI_STABLE
#endif
/***************************************************************************\
+27 -6
View File
@@ -191,7 +191,7 @@ NV_STATUS NV_API_CALL os_put_page (NvU64 address);
NvU32 NV_API_CALL os_get_page_refcount (NvU64 address);
NvU32 NV_API_CALL os_count_tail_pages (NvU64 address);
void NV_API_CALL os_free_pages_phys (NvU64, NvU32);
NV_STATUS NV_API_CALL os_open_temporary_file (void **);
NV_STATUS NV_API_CALL os_allocate_temporary_file (void **, NvU64);
void NV_API_CALL os_close_file (void *);
NV_STATUS NV_API_CALL os_write_file (void *, NvU8 *, NvU64, NvU64);
NV_STATUS NV_API_CALL os_read_file (void *, NvU8 *, NvU64, NvU64);
@@ -213,7 +213,7 @@ int NV_API_CALL os_nv_cap_validate_and_dup_fd (const nv_cap_t *, int
void NV_API_CALL os_nv_cap_close_fd (int);
NvS32 NV_API_CALL os_imex_channel_get (NvU64);
NvS32 NV_API_CALL os_imex_channel_count (void);
NV_STATUS NV_API_CALL os_tegra_igpu_perf_boost (void *, NvBool, NvU32);
NV_STATUS NV_API_CALL os_tegra_igpu_perf_boost (void *, NvBool, NvU32, int);
NV_STATUS NV_API_CALL os_get_tegra_platform (NvU32 *);
enum os_pci_req_atomics_type {
@@ -223,6 +223,7 @@ enum os_pci_req_atomics_type {
};
NV_STATUS NV_API_CALL os_enable_pci_req_atomics (void *, enum os_pci_req_atomics_type);
void NV_API_CALL os_pci_trigger_flr(void *handle);
NvU64 NV_API_CALL os_get_reclaimable_memory_usage(void);
NV_STATUS NV_API_CALL os_get_numa_node_memory_usage (NvS32, NvU64 *, NvU64 *);
NV_STATUS NV_API_CALL os_numa_add_gpu_memory (void *, NvU64, NvU64, NvU32 *);
NV_STATUS NV_API_CALL os_numa_remove_gpu_memory (void *, NvU64, NvU64, NvU32);
@@ -264,6 +265,7 @@ extern NvBool os_imex_channel_is_supported;
void NV_API_CALL out_string(const char *str);
int NV_API_CALL nv_printf(NvU32 debuglevel, const char *printf_format, ...);
int NV_API_CALL nv_vprintf(NvU32 debuglevel, const char *printf_format, va_list arglist);
#define NV_DEV_PRINTF(debuglevel, nv, format, ... ) \
nv_printf(debuglevel, "NVRM: GPU " NV_PCI_DEV_FMT ": " format, NV_PCI_DEV_FMT_ARGS(nv), ## __VA_ARGS__)
@@ -289,14 +291,33 @@ int NV_API_CALL nv_printf(NvU32 debuglevel, const char *printf_format, ...);
#define OS_CGROUP_IMPL_NONE 0
#define OS_CGROUP_IMPL_MISC 1
#define OS_CGROUP_IMPL_DMEM 2
void* NV_API_CALL os_cgroup_for_pid(int pid, void *pidInfo, int impl);
#if defined(NV_LINUX)
NvU32 NV_API_CALL os_cgroup_implementation(void);
void* NV_API_CALL os_dmem_cgroup_register_region(NvU64 size, const char *name);
void NV_API_CALL os_dmem_cgroup_unregister_region(void *region);
void* NV_API_CALL os_dmem_cgroup_register_region(const char *name, NvU64 size, NvU64 precharge, void **prechargePool);
void NV_API_CALL os_dmem_cgroup_unregister_region(void *region, void *prechargePool, NvU64 precharge);
NV_STATUS NV_API_CALL os_dmem_cgroup_try_charge(void *region, NvU64 size, void **ret_pool, void **ret_limit_pool);
void NV_API_CALL os_dmem_cgroup_uncharge(void *pool, NvU64 size);
void* NV_API_CALL os_cgroup_for_pid(int pid, void *pidInfo);
void NV_API_CALL os_dmem_cgroup_pool_state_put(void *pool);
NvBool NV_API_CALL os_dmem_cgroup_state_evict_valuable(void *limit_pool, void *test_pool, NvBool ignore_low, NvBool *ret_hit_low);
void* NV_API_CALL os_cgroup_get_from_fd(NvU32 fd);
void NV_API_CALL os_cgroup_put(void *cgroup);
void NV_API_CALL os_cgroup_get(void *cgroup);
void* NV_API_CALL os_cgroup_parent(void *cgroup);
#else // !defined(NV_LINUX)
#define os_cgroup_implementation() OS_CGROUP_IMPL_NONE
#define os_dmem_cgroup_register_region(...) NULL
#define os_dmem_cgroup_unregister_region(...)
#define os_dmem_cgroup_try_charge(...) NV_OK
#define os_dmem_cgroup_uncharge(...)
#define os_dmem_cgroup_pool_state_put(...)
#define os_dmem_cgroup_state_evict_valuable(...) NV_FALSE
#define os_cgroup_get_from_fd(...) NULL
#define os_cgroup_put(...)
#define os_cgroup_get(...)
#define os_cgroup_parent(...) NULL
#endif // defined(NV_LINUX)
#endif /* OS_INTERFACE_H */
+347 -71
View File
@@ -3,8 +3,8 @@
PATH="${PATH}:/bin:/sbin:/usr/bin"
# make sure we are in the directory containing this script
SCRIPTDIR=`dirname $0`
cd $SCRIPTDIR
SCRIPTDIR=$(dirname "$0")
cd "$SCRIPTDIR" || exit 1
CC="$1"
ARCH=$2
@@ -58,8 +58,8 @@ append_conftest() {
# older driver versions
#
while read LINE; do
echo ${LINE}
while read -r LINE; do
echo "${LINE}"
done
}
@@ -80,7 +80,7 @@ test_header_presence() {
TEST_CFLAGS="-E -M -I${NVIDIA_OOT_PATH}/include $CFLAGS"
file="$1"
file_define=NV_`echo $file | tr '/.-' '___' | tr 'a-z' 'A-Z'`_PRESENT
file_define=NV_$(echo "$file" | tr '/.-' '___' | tr 'a-z' 'A-Z')_PRESENT
CODE="#include <$file>"
@@ -101,7 +101,7 @@ test_header_presence() {
}
build_cflags() {
ISYSTEM=`$CC -print-file-name=include 2> /dev/null`
ISYSTEM=$($CC -print-file-name=include 2> /dev/null)
BASE_CFLAGS="-O2 -D__KERNEL__ \
-DKBUILD_BASENAME=\"#conftest$$\" -DKBUILD_MODNAME=\"#conftest$$\" \
-nostdinc -isystem $ISYSTEM \
@@ -130,7 +130,7 @@ build_cflags() {
KERNEL_ARCH="$ARCH"
if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ]; then
if [ "$ARCH" = "i386" ] || [ "$ARCH" = "x86_64" ]; then
if [ -d "$SOURCES/arch/x86" ]; then
KERNEL_ARCH="x86"
fi
@@ -145,12 +145,11 @@ build_cflags() {
# includes if that platform is enabled in the configuration file, which
# may have a definition like this:
# #define CONFIG_ARCH_<MACHUPPERCASE> 1
for _mach_dir in `ls -1d $SOURCES/arch/$KERNEL_ARCH/mach-* 2>/dev/null`; do
_mach=`echo $_mach_dir | \
for _mach_dir in $(ls -1d "$SOURCES/arch/$KERNEL_ARCH"/mach-* 2>/dev/null); do
_mach=$(echo "$_mach_dir" | \
sed -e "s,$SOURCES/arch/$KERNEL_ARCH/mach-,," | \
tr 'a-z' 'A-Z'`
grep "CONFIG_ARCH_$_mach \+1" $AUTOCONF_FILE > /dev/null 2>&1
if [ $? -eq 0 ]; then
tr 'a-z' 'A-Z')
if grep "CONFIG_ARCH_$_mach \+1" "$AUTOCONF_FILE" > /dev/null 2>&1; then
MACH_CFLAGS="$MACH_CFLAGS -I$_mach_dir/include"
fi
done
@@ -189,7 +188,7 @@ build_cflags() {
# Newer versions of gcc-goto.sh don't print anything on success, but
# this is okay, since it's no longer necessary to set CC_HAVE_ASM_GOTO
# based on the output of those versions of gcc-goto.sh.
if [ `/bin/sh "$GCC_GOTO_SH" "$CC"` = "y" ]; then
if [ "$(/bin/sh "$GCC_GOTO_SH" "$CC")" = "y" ]; then
CFLAGS="$CFLAGS -DCC_HAVE_ASM_GOTO"
fi
fi
@@ -202,8 +201,7 @@ build_cflags() {
# fails to get compiled, because conftest.sh runs outside of Kbuild it ends
# up building without -mfentry and CC_USING_FENTRY flags.
#
grep "CONFIG_HAVE_FENTRY \+1" $AUTOCONF_FILE > /dev/null 2>&1
if [ $? -eq 0 ]; then
if grep "CONFIG_HAVE_FENTRY \+1" "$AUTOCONF_FILE" > /dev/null 2>&1; then
echo "" > conftest$$.c
$CC -mfentry -c -x c conftest$$.c > /dev/null 2>&1
@@ -247,7 +245,7 @@ test_configuration_option() {
# Check to see if the given configuration option is defined
#
get_configuration_option $1 >/dev/null 2>&1
get_configuration_option "$1" >/dev/null 2>&1
return $?
@@ -355,7 +353,7 @@ check_symbol_exists() {
# space separated list instead of semicolon separated so we can iterate
# over it.
if [ -z "${CONFTEST_BSD_KMODPATHS}" ] ; then
KMODPATHS=`sysctl -n kern.module_path | sed -e "s/;/ /g"`
KMODPATHS=$(sysctl -n kern.module_path | sed -e "s/;/ /g")
else
KMODPATHS="${CONFTEST_BSD_KMODPATHS}"
fi
@@ -379,7 +377,7 @@ export_symbol_present_conftest() {
SYMBOL="$1"
if check_symbol_exists $SYMBOL; then
if check_symbol_exists "$SYMBOL"; then
echo "#define NV_IS_EXPORT_SYMBOL_PRESENT_$SYMBOL 1" |
append_conftest "symbols"
else
@@ -422,9 +420,9 @@ get_configuration_option() {
NEW_FILE="generated/autoconf.h"
FILE=""
if [ -f $HEADERS/$NEW_FILE -o -f $OUTPUT/include/$NEW_FILE ]; then
if [ -f "$HEADERS/$NEW_FILE" ] || [ -f "$OUTPUT/include/$NEW_FILE" ]; then
FILE=$NEW_FILE
elif [ -f $HEADERS/$OLD_FILE -o -f $OUTPUT/include/$OLD_FILE ]; then
elif [ -f "$HEADERS/$OLD_FILE" ] || [ -f "$OUTPUT/include/$OLD_FILE" ]; then
FILE=$OLD_FILE
fi
@@ -435,9 +433,9 @@ get_configuration_option() {
# via a compile check, and print the option's value.
#
if [ -f $HEADERS/$FILE ]; then
if [ -f "$HEADERS/$FILE" ]; then
INCLUDE_DIRECTORY=$HEADERS
elif [ -f $OUTPUT/include/$FILE ]; then
elif [ -f "$OUTPUT/include/$FILE" ]; then
INCLUDE_DIRECTORY=$OUTPUT/include
else
return 1
@@ -451,7 +449,7 @@ get_configuration_option() {
$OPTION
" > conftest$$.c
$CC -E -P -I$INCLUDE_DIRECTORY -o conftest$$ conftest$$.c > /dev/null 2>&1
$CC -E -P -I"$INCLUDE_DIRECTORY" -o conftest$$ conftest$$.c > /dev/null 2>&1
if [ -e conftest$$ ]; then
tr -d '\r\n\t ' < conftest$$
@@ -461,8 +459,8 @@ get_configuration_option() {
rm -f conftest$$.c conftest$$
else
CONFIG=$OUTPUT/.config
if [ -f $CONFIG ] && grep "^$OPTION=" $CONFIG; then
grep "^$OPTION=" $CONFIG | cut -f 2- -d "="
if [ -f "$CONFIG" ] && grep "^$OPTION=" "$CONFIG"; then
grep "^$OPTION=" "$CONFIG" | cut -f 2- -d "="
RET=$?
fi
fi
@@ -1461,6 +1459,40 @@ compile_test() {
compile_check_conftest "$CODE" "NV_DRM_SYSFS_CONNECTOR_STATUS_EVENT_PRESENT" "" "functions"
;;
drm_connector_attach_content_protection_property)
#
# Determine if drm_connector_attach_content_protection_property() is present.
#
# Commit 6a99099fe1d6c ("drm/display: Move HDCP helpers into display-helper module")
# in Linux v5.19.
#
CODE="
#if defined(NV_DRM_DISPLAY_DRM_HDCP_HELPER_H_PRESENT)
#include <drm/display/drm_hdcp_helper.h>
#endif
void conftest_drm_connector_attach_content_protection_property(void) {
drm_connector_attach_content_protection_property();
}"
compile_check_conftest "$CODE" "NV_DRM_CONNECTOR_ATTACH_CONTENT_PROTECTION_PROPERTY_PRESENT" "" "functions"
;;
drm_hdcp_update_content_protection)
#
# Determine if drm_hdcp_update_content_protection() is present.
#
# Commit 6a99099fe1d6c ("drm/display: Move HDCP helpers into display-helper module")
# in Linux v5.19.
#
CODE="
#if defined(NV_DRM_DISPLAY_DRM_HDCP_HELPER_H_PRESENT)
#include <drm/display/drm_hdcp_helper.h>
#endif
void conftest_drm_hdcp_update_content_protection(void) {
drm_hdcp_update_content_protection();
}"
compile_check_conftest "$CODE" "NV_DRM_HDCP_UPDATE_CONTENT_PROTECTION_PRESENT" "" "functions"
;;
pde_data)
#
# Determine if the pde_data() function is present.
@@ -2086,6 +2118,23 @@ compile_test() {
compile_check_conftest "$CODE" "NV_PCI_DRIVER_HAS_DRIVER_MANAGED_DMA" "" "types"
;;
device_has_tdi_enabled)
#
# Determine if "struct device" has .tdi_enabled member.
#
# tdi_enabled is provided by the downstream RMEDA module which
# supports CCA. This conftest is needed until upstream support
# is available.
#
CODE="
#include <linux/device.h>
int conftest_device_has_tdi_enabled(void) {
return offsetof(struct device, tdi_enabled);
}"
compile_check_conftest "$CODE" "NV_DEVICE_HAS_TDI_ENABLED" "" "types"
;;
drm_file_get_master)
#
# Determine if function drm_file_get_master() is present.
@@ -2202,11 +2251,11 @@ compile_test() {
;;
is_export_symbol_present_*)
export_symbol_present_conftest $(echo $1 | cut -f5- -d_)
export_symbol_present_conftest "$(echo "$1" | cut -f5- -d_)"
;;
is_export_symbol_gpl_*)
export_symbol_gpl_conftest $(echo $1 | cut -f5- -d_)
export_symbol_gpl_conftest "$(echo "$1" | cut -f5- -d_)"
;;
pcie_is_cxl)
@@ -2226,6 +2275,73 @@ compile_test() {
compile_check_conftest "$CODE" "NV_PCIE_IS_CXL_PRESENT" "" "functions"
;;
cxl_init_supported)
#
# Probe CXL bring-up helpers (e.g. devm_cxl_dev_state_create(),
# cxl_pci_setup_regs(), and related calls below).
#
CODE="
#include <linux/pci.h>
#include <cxl/cxl.h>
#include <cxl/pci.h>
struct nv_conftest_cxl_dev_state {
struct cxl_dev_state cxlds;
};
struct nv_conftest_cxl_dev_state *
conftest_devm_cxl_dev_state_create(void)
{
return devm_cxl_dev_state_create(
NULL, CXL_DEVTYPE_DEVMEM, 0, 0,
struct nv_conftest_cxl_dev_state, cxlds, false);
}
typeof(cxl_pci_setup_regs) conftest_cxl_pci_setup_regs;
int conftest_cxl_pci_setup_regs(struct pci_dev *pdev,
enum cxl_regloc_type type,
struct cxl_register_map *map)
{
return 0;
}
typeof(cxl_set_capacity) conftest_cxl_set_capacity;
int conftest_cxl_set_capacity(struct cxl_dev_state *cxlds,
u64 capacity)
{
return 0;
}
typeof(devm_cxl_add_memdev) conftest_devm_cxl_add_memdev;
struct cxl_memdev *conftest_devm_cxl_add_memdev(
struct cxl_dev_state *cxlds,
const struct cxl_memdev_attach *attach)
{
return NULL;
}
typeof(cxl_get_committed_decoder) conftest_cxl_get_committed_decoder;
struct cxl_endpoint_decoder *conftest_cxl_get_committed_decoder(
struct cxl_memdev *cxlmd, struct cxl_region **cxlr)
{
return NULL;
}
typeof(cxl_get_region_range) conftest_cxl_get_region_range;
int conftest_cxl_get_region_range(struct cxl_region *region,
struct range *range)
{
return 0;
}
typeof(cxl_unregister_region) conftest_cxl_unregister_region;
void conftest_cxl_unregister_region(struct cxl_region *cxlr)
{
}"
compile_check_conftest "$CODE" "NV_CXL_INIT_SUPPORTED" "" "types"
;;
get_backlight_device_by_name)
#
# Determine if the get_backlight_device_by_name() function is present
@@ -2367,6 +2483,57 @@ compile_test() {
fi
;;
dma_buf_supports_dynamic_importer)
#
# Determine if a move_notify() callback is present in struct
# dma_buf_attach_ops. The presence of this callback allows importers
# to safely revoke mappings when the underlying resource is moved.
#
# It will be superseded by the invalidate_mappings() callback in
# linux-next and likely to appear in v7.1.
#
# Added by commit: bb42df4662a4
# ("dma-buf: add dynamic DMA-buf handling v15") in v5.10 (2018-07-03)
#
# conftest #1: Check if dma_buf_attach_ops has the move_notify()
# callback
echo "$CONFTEST_PREAMBLE
#include <linux/dma-buf.h>
int conftest_dma_buf_move_notify(void) {
return offsetof(struct dma_buf_attach_ops, move_notify);
}" > conftest$$.c
$CC $CFLAGS -c conftest$$.c > /dev/null 2>&1
rm -f conftest$$.c
if [ -f conftest$$.o ]; then
echo "#define NV_DMA_BUF_ATTACH_OPS_HAS_MOVE_NOTIFY" | append_conftest "types"
rm -f conftest$$.o
return
else
echo "#undef NV_DMA_BUF_ATTACH_OPS_HAS_MOVE_NOTIFY" | append_conftest "types"
fi
# conftest #2: Check if dma_buf_attach_ops has the
# invalidate_mappings() callback
echo "$CONFTEST_PREAMBLE
#include <linux/dma-buf.h>
int conftest_dma_buf_invalidate_mappings(void) {
return offsetof(struct dma_buf_attach_ops, invalidate_mappings);
}" > conftest$$.c
$CC $CFLAGS -c conftest$$.c > /dev/null 2>&1
rm -f conftest$$.c
if [ -f conftest$$.o ]; then
echo "#define NV_DMA_BUF_ATTACH_OPS_HAS_INVALIDATE_MAPPINGS" | append_conftest "types"
rm -f conftest$$.o
else
echo "#undef NV_DMA_BUF_ATTACH_OPS_HAS_INVALIDATE_MAPPINGS" | append_conftest "types"
fi
;;
drm_fb_create_takes_format_info)
#
# Determine if a `struct drm_format_info *` is passed into
@@ -3798,6 +3965,22 @@ compile_test() {
compile_check_conftest "$CODE" "NV_THERMAL_UNBIND_CDEV_FROM_TRIP_PRESENT" "" "functions"
;;
pm_qos_read_value_supported)
#
# Determine if 'dev_pm_qos' structure has member 'freq'
#
# Added by commit 36a8015f89e40
# ("PM / QoS: Restore DEV_PM_QOS_MIN/MAX_FREQUENCY") in v5.5-rc1
#
CODE="
#include <linux/pm_qos.h>
int conftest_pm_qos_read_value_supported(void) {
return offsetof(struct dev_pm_qos, freq);
}
"
compile_check_conftest "$CODE" "NV_PM_QOS_READ_VALUE_SUPPORTED" "" "types"
;;
update_devfreq)
#
# Determine if update_devfreq() function is present
@@ -4218,6 +4401,22 @@ compile_test() {
compile_check_conftest "$CODE" "NV_MEMORY_FAILURE_MF_SW_SIMULATED_DEFINED" "" "types"
;;
dma_fence_chain_alloc)
#
# Determine if the dma_fence_chain_alloc function is present.
#
# Added by commit 440d0f1 ("dma-buf: add dma_fence_chain_alloc/free v3")
# in 5.15.
#
CODE="
#include <linux/dma-fence-chain.h>
void conftest_dma_fence_chain_alloc(void) {
dma_fence_chain_alloc();
}"
compile_check_conftest "$CODE" "NV_DMA_FENCE_CHAIN_ALLOC_PRESENT" "" "functions"
;;
fence_ops_use_64bit_seqno)
#
# Determine if dma_fence_ops has the use_64bit_seqno member
@@ -4577,6 +4776,24 @@ compile_test() {
compile_check_conftest "$CODE" "NV_MMU_INTERVAL_NOTIFIER" "" "types"
;;
drm_connector_attach_broadcast_rgb_property)
#
# Determine if the function
# drm_connector_attach_broadcast_rgb_property() is present.
#
# Added by commit ab52af4ba7c7d ("drm/connector: Introduce
# drm_connector_attach_broadcast_rgb_property()") in v6.8.
#
CODE="
#include <drm/drm_connector.h>
void conftest_drm_connector_attach_broadcast_rgb_property(void) {
drm_connector_attach_broadcast_rgb_property();
}"
compile_check_conftest "$CODE" "NV_DRM_CONNECTOR_ATTACH_BROADCAST_RGB_PROPERTY_PRESENT" "" "functions"
;;
drm_mode_create_dp_colorspace_property_has_supported_colorspaces_arg)
# Determine if drm_mode_create_dp_colorspace_property() takes the
# 'supported_colorspaces' argument.
@@ -5033,8 +5250,9 @@ compile_test() {
CODE="
#include <drm/drm_atomic_helper.h>
static int conftest_drm_connector_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode) {
static enum drm_mode_status
conftest_drm_connector_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode) {
return 0;
}
@@ -5085,6 +5303,25 @@ compile_test() {
compile_check_conftest "$CODE" "NV_SHRINKER_ALLOC_PRESENT" "" "functions"
;;
nr_kernel_misc_reclaimable)
#
# Determine if NR_KERNEL_MISC_RECLAIMABLE is present in
# enum node_stat_item.
#
# Added by commit a4a33ef5657a ("mm: add NR_KERNEL_MISC_RECLAIMABLE
# node stat for misc reclaimable kernel memory") in v5.9.
#
CODE="
#include <linux/mmzone.h>
void conftest_nr_kernel_misc_reclaimable(void) {
enum node_stat_item x = NR_KERNEL_MISC_RECLAIMABLE;
(void)x;
}"
compile_check_conftest "$CODE" "NV_NR_KERNEL_MISC_RECLAIMABLE_PRESENT" "" "types"
;;
memory_device_coherent_present)
#
# Determine if MEMORY_DEVICE_COHERENT support is present or not
@@ -5118,26 +5355,6 @@ compile_test() {
compile_check_conftest "$CODE" "NV_DRM_CRTC_FUNCS_HAS_GET_VBLANK_TIMESTAMP" "" "types"
;;
is_vma_write_locked_has_mm_lock_seq_arg)
#
# Determine if __is_vma_write_locked() takes only a single
# 'struct vm_area_struct *' argument.
#
# Commit 22f7639f2f03 ("mm/vma: improve and document
# __is_vma_write_locked()") removed the 'unsigned int *mm_lock_seq'
# parameter in v7.0-rc1.
#
CODE="
#include <linux/mm.h>
#include <linux/mmap_lock.h>
int conftest_is_vma_write_locked_has_mm_lock_seq_arg(struct vm_area_struct *vma) {
unsigned int mm_lock_seq;
return __is_vma_write_locked(vma, &mm_lock_seq);
}"
compile_check_conftest "$CODE" "NV_IS_VMA_WRITE_LOCKED_HAS_MM_LOCK_SEQ_ARG" "" "types"
;;
pfn_is_map_memory)
#
# Determine if pfn_is_map_memory() function is present.
@@ -5174,7 +5391,7 @@ compile_test() {
#
# Check to see if the misc cgroup type is available
#
# dmem_cgrp_id was added by commit id a72232e ("cgroup: Add misc cgroup
# misc_cgrp_id was added by commit id a72232e ("cgroup: Add misc cgroup
# controller") in 5.13
#
CODE="
@@ -5185,6 +5402,26 @@ compile_test() {
compile_check_conftest "$CODE" "NV_MISC_CGROUP_PRESENT" "" "types"
;;
is_vma_write_locked_has_mm_lock_seq_arg)
#
# Determine if __is_vma_write_locked() takes only a single
# 'struct vm_area_struct *' argument.
#
# Commit ("mm/vma: improve and document
# __is_vma_write_locked()") removed the 'unsigned int *mm_lock_seq'
# parameter in linux-next, expected in v7.0-rc1.
#
CODE="
#include <linux/mm.h>
#include <linux/mmap_lock.h>
int conftest_is_vma_write_locked_has_mm_lock_seq_arg(struct vm_area_struct *vma) {
unsigned int mm_lock_seq;
return __is_vma_write_locked(vma, &mm_lock_seq);
}"
compile_check_conftest "$CODE" "NV_IS_VMA_WRITE_LOCKED_HAS_MM_LOCK_SEQ_ARG" "" "types"
;;
drm_color_lut32_present)
# Determine if struct drm_color_lut32 is present.
#
@@ -5235,6 +5472,21 @@ compile_test() {
compile_check_conftest "$CODE" "NV_USE_DMA_IOMMU_PRESENT" "" "functions"
;;
percpu_mm_counter)
#
# Determine if the mm_counter variable is percpu or an atomic
#
# Added by commit f1a7941 ("mm: convert mm's rss stats into percpu_counter") in 6.2.
#
CODE="
#include <linux/mm.h>
void conftest_percpu_mm_counter(struct mm_struct *mm) {
percpu_counter_add(&mm->rss_stat[0], 0);
}"
compile_check_conftest "$CODE" "NV_PERCPU_MM_COUNTER" "" "types"
;;
drm_atomic_commit_struct_present)
#
# Determine if 'struct drm_atomic_state' has been renamed to
@@ -5251,6 +5503,32 @@ compile_test() {
compile_check_conftest "$CODE" "NV_DRM_ATOMIC_COMMIT_STRUCT_PRESENT" "" "types"
;;
sg_alloc_table_from_pages_segment)
#
# Determine if the sg_alloc_table_from_pages_segment() function is
# present. This variant accepts an explicit max_segment cap and
# internally relies on sg_chain() to handle scatterlist tables
# whose total size exceeds 4 GB, letting the RM DMA path use a
# single submap instead of splitting at NV_DMA_SUBMAP_MAX_PAGES
# (~4 GB) boundaries.
#
# Added by commit 90e7a6de62781c27d6a111fccfb19b807f9b6887
# ("lib/scatterlist: Provide a dedicated function to support table
# append") in v5.15-rc1.
#
# See Bug 6419127 for the split-submap GMMU contiguity failure.
# This function and correct max_segment value avoid the overflow
# issue in the kernel.
#
CODE="
#include <linux/scatterlist.h>
void conftest_sg_alloc_table_from_pages_segment(void) {
sg_alloc_table_from_pages_segment();
}"
compile_check_conftest "$CODE" "NV_SG_ALLOC_TABLE_FROM_PAGES_SEGMENT_PRESENT" "" "functions"
;;
# When adding a new conftest entry, please use the correct format for
# specifying the relevant upstream Linux kernel commit. Please
# avoid specifying -rc kernels, and only use SHAs that actually exist
@@ -5344,7 +5622,7 @@ case "$5" in
kernel_compile_h=$OUTPUT/include/generated/compile.h
if [ ! -f ${kernel_compile_h} ]; then
if [ ! -f "${kernel_compile_h}" ]; then
# The kernel's compile.h file is not present, so there
# isn't a convenient way to identify the compiler version
# used to build the kernel.
@@ -5355,15 +5633,15 @@ case "$5" in
exit 0
fi
kernel_cc_string=`cat ${kernel_compile_h} | \
grep LINUX_COMPILER | cut -f 2 -d '"'`
kernel_cc_string=$(cat "${kernel_compile_h}" | \
grep LINUX_COMPILER | cut -f 2 -d '"')
kernel_cc_version=`echo ${kernel_cc_string} | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -n 1`
kernel_cc_version=$(echo "${kernel_cc_string}" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -n 1)
if [ -z "${kernel_cc_version}" ]; then
kernel_cc_version=`echo ${kernel_cc_string} | grep -o '[0-9]\+\.[0-9]\+' | head -n 1`
kernel_cc_version=$(echo "${kernel_cc_string}" | grep -o '[0-9]\+\.[0-9]\+' | head -n 1)
fi
kernel_cc_major=`echo ${kernel_cc_version} | cut -d '.' -f 1`
kernel_cc_minor=`echo ${kernel_cc_version} | cut -d '.' -f 2`
kernel_cc_major=$(echo "${kernel_cc_version}" | cut -d '.' -f 1)
kernel_cc_minor=$(echo "${kernel_cc_version}" | cut -d '.' -f 2)
echo "
#if (__GNUC__ != ${kernel_cc_major}) || (__GNUC_MINOR__ != ${kernel_cc_minor})
@@ -5408,7 +5686,7 @@ case "$5" in
echo "";
elif [ "$VERBOSE" = "just_msg" ]; then
echo "Warning: The kernel was built with ${kernel_cc_string}, but the" \
"current compiler version is `$CC --version | head -n 1`.";
"current compiler version is $($CC --version | head -n 1).";
fi
exit 0;
fi
@@ -5421,7 +5699,7 @@ case "$5" in
#
VERBOSE=$6
if [ -n "$IGNORE_XEN_PRESENCE" -o -n "$VGX_BUILD" ]; then
if [ -n "$IGNORE_XEN_PRESENCE" ] || [ -n "$VGX_BUILD" ]; then
exit 0
fi
@@ -5491,9 +5769,9 @@ case "$5" in
PATCHES=""
for PATCH in patch-*.h; do
if [ -f $PATCH ]; then
if [ -f "$PATCH" ]; then
echo "#include \"$PATCH\""
PATCHES="$PATCHES "`echo $PATCH | sed -s 's/patch-\(.*\)\.h/\1/'`
PATCHES="$PATCHES "$(echo "$PATCH" | sed -s 's/patch-\(.*\)\.h/\1/')
fi
done
@@ -5519,7 +5797,7 @@ case "$5" in
CFLAGS=$1
shift
for i in $*; do compile_test $i; done
for i in "$@"; do compile_test "$i"; done
exit 0
;;
@@ -5532,7 +5810,7 @@ case "$5" in
if [ -n "$VGX_BUILD" ]; then
if [ -f /proc/xen/capabilities ]; then
if [ "`cat /proc/xen/capabilities`" = "control_d" ]; then
if [ "$(cat /proc/xen/capabilities)" = "control_d" ]; then
exit 0
fi
else
@@ -5583,7 +5861,7 @@ case "$5" in
VFIO_PCI_CORE_PRESENT=1
fi
if ([ "$VFIO_IOMMU_PRESENT" != "0" ] || [ "$VFIO_IOMMUFD_VFIO_CONTAINER_PRESENT" != "0" ])&& [ "$KVM_PRESENT" != "0" ] ; then
if { [ "$VFIO_IOMMU_PRESENT" != "0" ] || [ "$VFIO_IOMMUFD_VFIO_CONTAINER_PRESENT" != "0" ]; } && [ "$KVM_PRESENT" != "0" ] ; then
# vGPU requires either MDEV or vfio-pci-core framework to be present.
if [ "$VFIO_MDEV_PRESENT" != "0" ] || [ "$VFIO_PCI_CORE_PRESENT" != "0" ]; then
exit 0
@@ -5622,7 +5900,7 @@ case "$5" in
#
OPTION=$6
test_configuration_option $OPTION
test_configuration_option "$OPTION"
exit $?
;;
@@ -5632,7 +5910,7 @@ case "$5" in
#
OPTION=$6
get_configuration_option $OPTION
get_configuration_option "$OPTION"
exit $?
;;
@@ -5643,10 +5921,8 @@ case "$5" in
# to the extent that is possible.
#
HASH=$(get_configuration_option CONFIG_MODULE_SIG_HASH)
if [ $? -eq 0 ] && [ -n "$HASH" ]; then
echo $HASH
if HASH=$(get_configuration_option CONFIG_MODULE_SIG_HASH) && [ -n "$HASH" ]; then
echo "$HASH"
exit 0
else
for SHA in 512 384 256 224 1; do
@@ -5679,7 +5955,7 @@ case "$5" in
#
build_cflags
echo $CFLAGS
echo "$CFLAGS"
exit 0
;;
+2
View File
@@ -40,6 +40,8 @@ NV_HEADER_PRESENCE_TESTS = \
linux/iosys-map.h \
linux/vfio_pci_core.h \
linux/nvgrace-egm.h \
cxl/cxl.h \
cxl/pci.h \
linux/cc_platform.h \
linux/slub_def.h \
linux/of_gpio.h \
+41 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2015-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -57,6 +57,8 @@
#define DRM_NVIDIA_UNREGISTER_ROI 0x1a /* Unregister ROI */
#define DRM_NVIDIA_GET_CRTC_ROI_CRCS 0x1b /* Read CRCs for registered ROIs */
#define DRM_NVIDIA_GET_ROI_CAPABILITIES 0x1c /* Get ROI related capabilities */
#define DRM_NVIDIA_SEMSURF_EXPORT_TO_SYNCOBJ_POINT 0x1d
#define DRM_NVIDIA_SYNCOBJ_GET_SYNCFD 0x1e
/* Maximum possible telltale CRCs per plane (used for array size)
@@ -177,6 +179,16 @@
DRM_NVIDIA_GET_DRM_FILE_UNIQUE_ID), \
struct drm_nvidia_get_drm_file_unique_id_params)
#define DRM_IOCTL_NVIDIA_SEMSURF_EXPORT_TO_SYNCOBJ_POINT \
DRM_IOW((DRM_COMMAND_BASE + \
DRM_NVIDIA_SEMSURF_EXPORT_TO_SYNCOBJ_POINT), \
struct drm_nvidia_semsurf_export_to_syncobj_point_params)
#define DRM_IOCTL_NVIDIA_SYNCOBJ_GET_SYNCFD \
DRM_IOWR((DRM_COMMAND_BASE + \
DRM_NVIDIA_SYNCOBJ_GET_SYNCFD), \
struct drm_nvidia_syncobj_get_syncfd_params)
#define DRM_IOCTL_NVIDIA_REGISTER_ROI \
DRM_IOWR((DRM_COMMAND_BASE + DRM_NVIDIA_REGISTER_ROI), \
struct drm_nvidia_register_roi_params)
@@ -193,6 +205,9 @@
DRM_IOWR((DRM_COMMAND_BASE + DRM_NVIDIA_GET_ROI_CAPABILITIES), \
struct drm_nvidia_get_roi_capabilities_params)
#define NV_DRM_HDMI_VSIF_METADATA_MIN_PAYLOAD_SIZE 3
#define NV_DRM_HDMI_VSIF_METADATA_MAX_PAYLOAD_SIZE 27
struct drm_nvidia_gem_import_nvkms_memory_params {
uint64_t mem_size; /* IN */
@@ -426,6 +441,19 @@ struct drm_nvidia_get_drm_file_unique_id_params {
uint64_t id; /* OUT Unique ID of the DRM file */
};
struct drm_nvidia_semsurf_export_to_syncobj_point_params {
uint32_t fence_context_handle; /* IN GEM handle to fence context */
uint32_t syncobj_handle; /* IN DRM syncobj handle */
uint64_t wait_value; /* IN Semsurf timeline point to export */
uint64_t syncobj_point; /* IN DRM syncobj timeline point to export to */
};
struct drm_nvidia_syncobj_get_syncfd_params {
uint32_t syncobj_handle; /* IN DRM syncobj timeline handle */
int32_t fd; /* OUT sync FD for the syncobj timeline point */
uint64_t syncobj_point; /* IN DRM syncobj timeline point */
};
/**
* @brief Parameters for getting ROI capabilities
*
@@ -583,4 +611,16 @@ struct drm_nvidia_read_crc_params {
NV_DRM_TRANSFER_FUNCTION_MAX = NV_DRM_TRANSFER_FUNCTION_SRGB,
};
/**
* @brief Struct for HDMI VSIF blob property
*
* This structure defines the content of the NV_HDMI_VSIF_METADATA blob property.
* The payload is a 27 byte array where the first three bytes are the vendor OUI,
* and the remaining bytes are the infoframe content.
*/
struct drm_nvidia_hdmi_vsif_metadata {
uint8_t payload[NV_DRM_HDMI_VSIF_METADATA_MAX_PAYLOAD_SIZE];
};
#endif /* _NV_DRM_COMMON_IOCTL_H_ */
@@ -62,6 +62,8 @@
#define NV_DRM_AVAILABLE
#endif
#if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION) || defined(__FreeBSD__)
#if defined(NV_DRM_FBDEV_GENERIC_SETUP_PRESENT) && \
defined(NV_DRM_APERTURE_REMOVE_CONFLICTING_PCI_FRAMEBUFFERS_PRESENT)
#define NV_DRM_FBDEV_AVAILABLE
@@ -87,6 +89,7 @@
#endif
#endif
#endif /* IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION) || defined(__FreeBSD__) */
/*
* DRM color pipeline support (drm_colorop) was added by commit cfc27680ee20
@@ -96,6 +99,16 @@
#define NV_DRM_HAS_COLOROP
#endif
#if defined(NV_DRM_CONNECTOR_ATTACH_CONTENT_PROTECTION_PROPERTY_PRESENT) && \
defined(NV_DRM_HDCP_UPDATE_CONTENT_PROTECTION_PRESENT)
#if (IS_ENABLED(CONFIG_DRM_DISPLAY_HDCP_HELPER))
#include <drm/display/drm_hdcp_helper.h>
// Support content protection prop only from K5.19 onwards
#define NV_DRM_SUPPORT_CONTENT_PROTECTION_PROPERTY
#endif
#endif
/*
* Adapt to quirks in FreeBSD's Linux kernel compatibility layer.
*/
+166 -4
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2015-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -30,6 +30,7 @@
#include "nvidia-drm-crtc.h"
#include "nvidia-drm-utils.h"
#include "nvidia-drm-encoder.h"
#include "nv_drm_common_ioctl.h"
/*
* Commit fcd70cd36b9b ("drm: Split out drm_probe_helper.h")
@@ -44,6 +45,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_sysfs.h>
/*
* Dithering support requires connector atomic_check, which we only enable on
@@ -298,6 +300,16 @@ static struct drm_connector_state* nv_drm_connector_atomic_duplicate_state(struc
__drm_atomic_helper_connector_duplicate_state(connector, &nv_drm_new_connector_state->base);
nv_drm_new_connector_state->dithering_mode = nv_drm_old_connector_state->dithering_mode;
nv_drm_new_connector_state->hdcp_topology_blob = nv_drm_old_connector_state->hdcp_topology_blob;
if (nv_drm_new_connector_state->hdcp_topology_blob) {
drm_property_blob_get(nv_drm_new_connector_state->hdcp_topology_blob);
}
nv_drm_new_connector_state->hdmi_vsif_metadata =
nv_drm_old_connector_state->hdmi_vsif_metadata;
if (nv_drm_new_connector_state->hdmi_vsif_metadata) {
drm_property_blob_get(nv_drm_new_connector_state->hdmi_vsif_metadata);
}
return &nv_drm_new_connector_state->base;
}
@@ -310,6 +322,9 @@ static void nv_drm_connector_atomic_destroy_state(
to_nv_drm_connector_state(state);
__drm_atomic_helper_connector_destroy_state(state);
drm_property_blob_put(nv_drm_connector_state->hdcp_topology_blob);
drm_property_blob_put(nv_drm_connector_state->hdmi_vsif_metadata);
nv_drm_free(nv_drm_connector_state);
}
@@ -341,6 +356,19 @@ static int nv_drm_connector_atomic_set_property(
}
nv_connector_state->dithering_mode = val;
return 0;
} else if (property == nv_dev->nv_hdcp_level_property) {
// nv_hdcp_level_property is read only
return -EINVAL;
}
if (property == nv_dev->nv_connector_hdmi_vsif_metadata_property) {
return nv_drm_atomic_replace_property_blob_from_id_size_range(
nv_dev->dev,
&nv_connector_state->hdmi_vsif_metadata,
val,
NV_DRM_HDMI_VSIF_METADATA_MIN_PAYLOAD_SIZE,
NV_DRM_HDMI_VSIF_METADATA_MAX_PAYLOAD_SIZE);
}
/* Unknown property - DRM core handles standard connector properties */
@@ -354,12 +382,22 @@ static int nv_drm_connector_atomic_get_property(
uint64_t *val)
{
struct nv_drm_device *nv_dev = to_nv_device(connector->dev);
struct nv_drm_connector *nv_conn = to_nv_connector(connector);
const struct nv_drm_connector_state *nv_connector_state =
to_nv_drm_connector_state_const(state);
if (property == nv_dev->nv_connector_dithering_mode_property) {
*val = nv_connector_state->dithering_mode;
return 0;
} else if (property == nv_dev->nv_hdcp_level_property) {
*val = nv_conn->cp;
return 0;
}
if (property == nv_dev->nv_connector_hdmi_vsif_metadata_property) {
*val = nv_connector_state->hdmi_vsif_metadata ?
nv_connector_state->hdmi_vsif_metadata->base.id : 0;
return 0;
}
/* Unknown property - DRM core handles standard connector properties */
@@ -451,11 +489,11 @@ static int nv_drm_connector_get_modes(struct drm_connector *connector)
return count;
}
static int nv_drm_connector_mode_valid(struct drm_connector *connector,
static enum drm_mode_status nv_drm_connector_mode_valid(struct drm_connector *connector,
#if defined(NV_DRM_CONNECTOR_HELPER_FUNCS_MODE_VALID_HAS_CONST_MODE_ARG)
const struct drm_display_mode *mode)
const struct drm_display_mode *mode)
#else
struct drm_display_mode *mode)
struct drm_display_mode *mode)
#endif
{
struct drm_device *dev = connector->dev;
@@ -541,6 +579,30 @@ __nv_drm_connector_atomic_check(struct drm_connector *connector,
req_config->modeSetConfig.dithering.mode = mode;
}
/* Handle HDMI VSIF metadata changes */
req_config->flags.hdmiVsifMetadataChanged =
!nv_drm_blobs_equal(nv_old_connector_state->hdmi_vsif_metadata,
nv_new_connector_state->hdmi_vsif_metadata);
if (req_config->flags.hdmiVsifMetadataChanged) {
if (nv_new_connector_state->hdmi_vsif_metadata &&
nv_new_connector_state->hdmi_vsif_metadata->data) {
struct drm_nvidia_hdmi_vsif_metadata* vsif_metadata =
(struct drm_nvidia_hdmi_vsif_metadata*)
nv_new_connector_state->hdmi_vsif_metadata->data;
NvU32 payload_size =
nv_new_connector_state->hdmi_vsif_metadata->length;
WARN_ON(payload_size < NV_DRM_HDMI_VSIF_METADATA_MIN_PAYLOAD_SIZE ||
payload_size > NV_DRM_HDMI_VSIF_METADATA_MAX_PAYLOAD_SIZE);
memcpy(req_config->modeSetConfig.hdmiVsifMetadata.payload,
vsif_metadata->payload,
payload_size);
req_config->modeSetConfig.hdmiVsifMetadata.payloadSize =
payload_size;
} else {
req_config->modeSetConfig.hdmiVsifMetadata.payloadSize = 0;
}
}
/*
* Override metadata for the entire head instead of allowing NVKMS to derive
* it from the layers' metadata.
@@ -668,6 +730,7 @@ nv_drm_connector_new(struct drm_device *dev,
nv_connector->internal = internal;
nv_connector->modeset_permission_filep = NULL;
nv_connector->modeset_permission_crtc = NULL;
nv_connector->cp = NVKMS_CONTENT_PROTECTION_OFF;
strcpy(nv_connector->dpAddress, dpAddress);
@@ -693,6 +756,29 @@ nv_drm_connector_new(struct drm_device *dev,
DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
}
#ifdef NV_DRM_SUPPORT_CONTENT_PROTECTION_PROPERTY
/* attach content protection properties */
if ((nv_connector->type == NVKMS_CONNECTOR_TYPE_DP) ||
(nv_connector->type == NVKMS_CONNECTOR_TYPE_HDMI)) {
ret = drm_connector_attach_content_protection_property(&nv_connector->base, true);
if (ret != 0) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to attach content protection properties to connector created from physical index %u",
nv_connector->physicalIndex);
goto failed_connector_init;
}
/* attach nvidia defined connector properties */
drm_object_attach_property(&nv_connector->base.base,
nv_dev->nv_hdcp_topology_property,
0);
drm_object_attach_property(&nv_connector->base.base,
nv_dev->nv_hdcp_level_property,
0);
}
#endif
#if defined(NV_DRM_CONNECTOR_ATTACH_HDR_OUTPUT_METADATA_PROPERTY_PRESENT)
if (nv_connector->type == NVKMS_CONNECTOR_TYPE_HDMI) {
#if defined(NV_DRM_MODE_CREATE_DP_COLORSPACE_PROPERTY_HAS_SUPPORTED_COLORSPACES_ARG)
@@ -705,6 +791,12 @@ nv_drm_connector_new(struct drm_device *dev,
drm_connector_attach_colorspace_property(&nv_connector->base);
}
drm_connector_attach_hdr_output_metadata_property(&nv_connector->base);
if (nv_dev->nv_connector_hdmi_vsif_metadata_property) {
drm_object_attach_property(&nv_connector->base.base,
nv_dev->nv_connector_hdmi_vsif_metadata_property,
0);
}
} else if (nv_connector->type == NVKMS_CONNECTOR_TYPE_DP) {
#if defined(NV_DRM_MODE_CREATE_DP_COLORSPACE_PROPERTY_HAS_SUPPORTED_COLORSPACES_ARG)
if (drm_mode_create_dp_colorspace_property(
@@ -816,4 +908,74 @@ bool nv_drm_connector_revoke_permissions(struct drm_device *dev,
return ret;
}
void nv_drm_connector_update_content_protection(struct nv_drm_connector *nv_connector)
{
#ifdef NV_DRM_SUPPORT_CONTENT_PROTECTION_PROPERTY
struct drm_connector *connector = &nv_connector->base;
struct drm_connector_state *state = connector->state;
unsigned int content_protection = state->content_protection;
unsigned int hdcp_content_type = state->hdcp_content_type;
bool update_cp = false;
unsigned int cp_val;
if ((content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) &&
(hdcp_content_type == DRM_MODE_HDCP_CONTENT_TYPE0)) {
if ((nv_connector->cp == NVKMS_CONTENT_PROTECTION_HDCP1X_ON) ||
(nv_connector->cp == NVKMS_CONTENT_PROTECTION_HDCP2X_TYPE0_ON) ||
(nv_connector->cp == NVKMS_CONTENT_PROTECTION_HDCP2X_TYPE1_ON)) {
cp_val = DRM_MODE_CONTENT_PROTECTION_ENABLED;
update_cp = true;
}
} else if ((content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) &&
(hdcp_content_type == DRM_MODE_HDCP_CONTENT_TYPE1)) {
if (nv_connector->cp == NVKMS_CONTENT_PROTECTION_HDCP2X_TYPE1_ON) {
cp_val = DRM_MODE_CONTENT_PROTECTION_ENABLED;
update_cp = true;
}
} else if (content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) {
if ((nv_connector->cp == NVKMS_CONTENT_PROTECTION_OFF) ||
(nv_connector->cp == NVKMS_CONTENT_PROTECTION_FAILED)) {
cp_val = DRM_MODE_CONTENT_PROTECTION_DESIRED;
update_cp = true;
}
}
if (update_cp) {
drm_hdcp_update_content_protection(connector, cp_val);
}
#endif
}
int nv_drm_connector_update_topology_property(struct nv_drm_connector *nv_connector,
const void *topology)
{
#ifdef NV_DRM_SUPPORT_CONTENT_PROTECTION_PROPERTY
struct drm_connector *connector = &nv_connector->base;
struct drm_connector_state *state = connector->state;
struct nv_drm_connector_state *nv_state = to_nv_drm_connector_state(state);
struct drm_device *dev = connector->dev;
struct nv_drm_device *nv_dev = to_nv_device(dev);
int ret;
ret = drm_property_replace_global_blob(dev,
&nv_state->hdcp_topology_blob,
NVKMS_HDCP_TOPOLOGY_SIZE,
topology,
&connector->base,
nv_dev->nv_hdcp_topology_property);
// Generate uevent on cp property when topology is updated
#if defined(NV_DRM_SYSFS_CONNECTOR_PROPERTY_EVENT_PRESENT)
drm_sysfs_connector_property_event(connector,
dev->mode_config.content_protection_property);
#elif defined(NV_DRM_SYSFS_CONNECTOR_STATUS_EVENT_PRESENT)
drm_sysfs_connector_status_event(connector,
dev->mode_config.content_protection_property);
#endif
return ret;
#else
return 0;
#endif
}
#endif
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2016-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -47,6 +47,8 @@ struct nv_drm_connector {
struct nv_drm_encoder *nv_detected_encoder;
struct edid *edid;
enum NvKmsContentProtection cp;
atomic_t connection_status_dirty;
/**
@@ -78,6 +80,8 @@ static inline struct nv_drm_connector *to_nv_connector(
struct nv_drm_connector_state {
struct drm_connector_state base;
enum nv_drm_dithering_mode dithering_mode;
struct drm_property_blob *hdcp_topology_blob;
struct drm_property_blob *hdmi_vsif_metadata;
};
static inline struct nv_drm_connector_state *to_nv_drm_connector_state(
@@ -115,6 +119,10 @@ nv_drm_get_connector(struct drm_device *dev,
bool nv_drm_connector_revoke_permissions(struct drm_device *dev,
struct nv_drm_connector *nv_connector);
void nv_drm_connector_update_content_protection(struct nv_drm_connector *nv_connector);
int nv_drm_connector_update_topology_property(struct nv_drm_connector *nv_connector,
const void *topology);
#endif /* NV_DRM_AVAILABLE */
+237 -212
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2015-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -227,6 +227,7 @@ plane_req_config_disable(struct NvKmsKapiLayerRequestedConfig *req_config)
req_config->flags.matrixOverridesChanged = NV_TRUE;
req_config->flags.ilutChanged = NV_TRUE;
req_config->flags.tmoChanged = NV_TRUE;
req_config->flags.precompColorPassthroughChanged = NV_TRUE;
}
static inline void
@@ -484,7 +485,52 @@ static int init_drm_nvkms_surface(struct nv_drm_device *nv_dev,
return 0;
}
static struct nv_drm_lut_surface *alloc_drm_lut_surface(
static struct nv_drm_lut_surface *pool_acquire_drm_lut_surface(
struct nv_drm_device *nv_dev,
NvU32 num_vss_header_entries,
NvU32 num_entries)
{
struct nv_drm_lut_surface *surface, *n;
mutex_lock(&nv_dev->drm_lut_surface_pool_mutex);
list_for_each_entry_safe(surface, n, &nv_dev->drm_lut_surface_pool,
pool_entry) {
if (surface->properties.vssSegmentEntries == num_vss_header_entries &&
surface->properties.lutEntries == num_entries) {
list_del_init(&surface->pool_entry);
kref_init(&surface->base.refcount);
mutex_unlock(&nv_dev->drm_lut_surface_pool_mutex);
return surface;
}
}
mutex_unlock(&nv_dev->drm_lut_surface_pool_mutex);
return NULL;
}
static void pool_release_drm_lut_surface(
struct nv_drm_lut_surface *surface)
{
struct nv_drm_device *nv_dev;
if (surface == NULL) {
return;
}
nv_dev = surface->nv_dev;
// The pool should only hold "freed" lut surfaces
WARN_ON_ONCE(kref_read(&surface->base.refcount) != 0);
mutex_lock(&nv_dev->drm_lut_surface_pool_mutex);
list_add_tail(&surface->pool_entry,
&nv_dev->drm_lut_surface_pool);
mutex_unlock(&nv_dev->drm_lut_surface_pool_mutex);
}
static struct nv_drm_lut_surface *acquire_drm_lut_surface(
struct nv_drm_device *nv_dev,
enum NvKmsLUTFormat entry_format,
enum NvKmsLUTVssType vss_type,
@@ -504,16 +550,25 @@ static struct nv_drm_lut_surface *alloc_drm_lut_surface(
params.height = 1;
params.surface_size = surface_size;
drm_lut_surface = nv_drm_calloc(1, sizeof(struct nv_drm_lut_surface));
drm_lut_surface = pool_acquire_drm_lut_surface(nv_dev,
num_vss_header_entries,
num_entries);
if (drm_lut_surface == NULL) {
return NULL;
}
drm_lut_surface = nv_drm_calloc(1, sizeof(struct nv_drm_lut_surface));
if (drm_lut_surface == NULL) {
return NULL;
}
if (init_drm_nvkms_surface(nv_dev, &drm_lut_surface->base, &params) != 0) {
nv_drm_free(drm_lut_surface);
return NULL;
if (init_drm_nvkms_surface(nv_dev, &drm_lut_surface->base, &params) != 0) {
nv_drm_free(drm_lut_surface);
return NULL;
}
drm_lut_surface->nv_dev = nv_dev;
INIT_LIST_HEAD(&drm_lut_surface->pool_entry);
}
drm_lut_surface->properties.vssSegmentEntries = num_vss_header_entries;
drm_lut_surface->properties.vssSegments = num_vss_header_segments;
drm_lut_surface->properties.vssType = vss_type;
drm_lut_surface->properties.lutEntries = num_entries;
@@ -522,17 +577,30 @@ static struct nv_drm_lut_surface *alloc_drm_lut_surface(
return drm_lut_surface;
}
static void free_drm_lut_surface(struct kref *ref)
static void release_drm_lut_surface(struct kref *ref)
{
struct nv_drm_nvkms_surface *drm_nvkms_surface =
container_of(ref, struct nv_drm_nvkms_surface, refcount);
struct nv_drm_lut_surface *drm_lut_surface =
container_of(drm_nvkms_surface, struct nv_drm_lut_surface, base);
// Clean up base
release_drm_nvkms_surface(drm_nvkms_surface);
pool_release_drm_lut_surface(drm_lut_surface);
}
nv_drm_free(drm_lut_surface);
void nv_free_drm_lut_surface_pool(struct nv_drm_device *nv_dev)
{
struct nv_drm_lut_surface *surface, *n;
mutex_lock(&nv_dev->drm_lut_surface_pool_mutex);
list_for_each_entry_safe(surface, n,
&nv_dev->drm_lut_surface_pool,
pool_entry) {
list_del(&surface->pool_entry);
release_drm_nvkms_surface(&surface->base);
nv_drm_free(surface);
}
mutex_unlock(&nv_dev->drm_lut_surface_pool_mutex);
}
static NvU32 fp32_lut_interp(
@@ -560,7 +628,8 @@ static NvU32 fp32_lut_interp(
static struct nv_drm_lut_surface *create_drm_ilut_surface_vss(
struct nv_drm_device *nv_dev,
struct nv_drm_plane *nv_plane,
struct nv_drm_plane_state *nv_drm_plane_state)
struct nv_drm_plane_state *nv_drm_plane_state,
const struct drm_crtc_state *crtc_state)
{
static const NvU32 fp_norm = 0x42FA0000; // FP32 125.0
static const NvU32 u10_norm = 0x447FC000; // FP32 1023.0
@@ -632,12 +701,12 @@ static struct nv_drm_lut_surface *create_drm_ilut_surface_vss(
* Space for the VSS header must be included even for non-VSS LUTs.
*/
drm_lut_surface =
alloc_drm_lut_surface(nv_dev,
NVKMS_LUT_FORMAT_FP16,
vss_type,
num_vss_header_segments,
NUM_VSS_HEADER_ENTRIES,
num_entries);
acquire_drm_lut_surface(nv_dev,
NVKMS_LUT_FORMAT_FP16,
vss_type,
num_vss_header_segments,
NUM_VSS_HEADER_ENTRIES,
num_entries);
if (!drm_lut_surface) {
return ERR_PTR(-ENOMEM);
}
@@ -750,6 +819,22 @@ static struct nv_drm_lut_surface *create_drm_ilut_surface_vss(
NvU32 fp32_entry = nvKmsKapiF16ToF32(fp16_entry);
fp32_r = fp32_g = fp32_b = nvKmsKapiF32Div(fp32_entry, fp_norm);
} else if (!multiply && (crtc_state->degamma_lut != NULL)) {
/* Use legacy DEGAMMA_LUT property if color pipeline not in use. */
fp32_r = nvKmsKapiUI32ToF32(
READ_LUT_AS_UNORM32(crtc_state->degamma_lut,
NVKMS_LUT_ARRAY_SIZE, entry_idx, red));
fp32_g = nvKmsKapiUI32ToF32(
READ_LUT_AS_UNORM32(crtc_state->degamma_lut,
NVKMS_LUT_ARRAY_SIZE, entry_idx, green));
fp32_b = nvKmsKapiUI32ToF32(
READ_LUT_AS_UNORM32(crtc_state->degamma_lut,
NVKMS_LUT_ARRAY_SIZE, entry_idx, blue));
/* Convert UNORM32 to 1.0-normalized FP32. */
fp32_r = nvKmsKapiF32Div(fp32_r, u32_norm);
fp32_g = nvKmsKapiF32Div(fp32_g, u32_norm);
fp32_b = nvKmsKapiF32Div(fp32_b, u32_norm);
} else {
/* Use implicit identity. */
// TODO: Use LUT table?
@@ -817,12 +902,12 @@ static struct nv_drm_lut_surface *create_drm_tmo_surface(
* The TMO LUT always uses VSS.
*/
drm_lut_surface =
alloc_drm_lut_surface(nv_dev,
NVKMS_LUT_FORMAT_UNORM16,
NVKMS_LUT_VSS_TYPE_LINEAR,
num_vss_header_segments,
NUM_VSS_HEADER_ENTRIES,
NVKMS_LUT_ARRAY_SIZE + 1);
acquire_drm_lut_surface(nv_dev,
NVKMS_LUT_FORMAT_UNORM16,
NVKMS_LUT_VSS_TYPE_LINEAR,
num_vss_header_segments,
NUM_VSS_HEADER_ENTRIES,
NVKMS_LUT_ARRAY_SIZE + 1);
if (drm_lut_surface == NULL) {
return ERR_PTR(-ENOMEM);
}
@@ -898,6 +983,13 @@ static struct nv_drm_lut_surface *create_drm_olut_surface_vss(
const NvU16 *vss_entries = NULL;
enum NvKmsLUTVssType vss_type = NVKMS_LUT_VSS_TYPE_NONE;
/*
* The regamma_divisor will be handled later via a separate NvKms parameter,
* but we don't want to use the legacy GAMMA_LUT if we're supplying a
* non-identity regamma_divisor.
*/
NvBool will_divide = (nv_drm_crtc_state->regamma_divisor > NV_DRM_S31_32_ONE);
WARN_ON(!nv_crtc->olut_caps.supported);
WARN_ON(nv_crtc->olut_caps.entryFormat != NVKMS_LUT_FORMAT_UNORM16);
WARN_ON(nv_crtc->olut_caps.vssType != NVKMS_LUT_VSS_TYPE_LOGARITHMIC);
@@ -927,12 +1019,12 @@ static struct nv_drm_lut_surface *create_drm_olut_surface_vss(
* Space for the VSS header must be included even for non-VSS LUTs.
*/
drm_lut_surface =
alloc_drm_lut_surface(nv_dev,
NVKMS_LUT_FORMAT_UNORM16,
vss_type,
num_vss_header_segments,
NUM_VSS_HEADER_ENTRIES,
num_entries);
acquire_drm_lut_surface(nv_dev,
NVKMS_LUT_FORMAT_UNORM16,
vss_type,
num_vss_header_segments,
NUM_VSS_HEADER_ENTRIES,
num_entries);
if (!drm_lut_surface) {
return NULL;
}
@@ -1007,6 +1099,14 @@ static struct nv_drm_lut_surface *create_drm_olut_surface_vss(
} else if (vss_entries != NULL) {
/* Use VSS LUT directly. */
r = g = b = vss_entries[entry_idx];
} else if (!will_divide && (nv_drm_crtc_state->base.gamma_lut != NULL)) {
/* Use legacy GAMMA_LUT property if color pipeline not in use. */
const struct drm_color_lut *gamma_lut =
(struct drm_color_lut *) nv_drm_crtc_state->base.gamma_lut->data;
r = gamma_lut[entry_idx].red;
g = gamma_lut[entry_idx].green;
b = gamma_lut[entry_idx].blue;
} else {
/* Use implicit identity. */
WARN_ON_ONCE(num_entries != (NVKMS_LUT_ARRAY_SIZE + 1));
@@ -1120,6 +1220,7 @@ static enum NvKmsInputColorRange nv_drm_color_range_to_nvkms_color_range(
static int
plane_req_config_update(struct drm_plane *plane,
struct drm_plane_state *plane_state,
const struct drm_crtc_state *crtc_state,
struct NvKmsKapiLayerRequestedConfig *req_config)
{
struct nv_drm_device *nv_dev = to_nv_device(plane->dev);
@@ -1384,6 +1485,22 @@ plane_req_config_update(struct drm_plane *plane,
req_config->flags.outputTfChanged = (old_config.outputTf != req_config->config.outputTf);
#endif
if (crtc_state->color_mgmt_changed || (plane->state->crtc != plane_state->crtc)) {
/*
* According to the comment in the Linux kernel's
* drivers/gpu/drm/drm_color_mgmt.c, if this property is NULL,
* the CTM needs to be changed to the identity matrix
*/
if (crtc_state->ctm) {
ctm_to_csc(&req_config->config.csc,
(struct drm_color_ctm *)crtc_state->ctm->data);
} else {
req_config->config.csc = NVKMS_IDENTITY_CSC_MATRIX;
}
req_config->config.cscUseMain = NV_FALSE;
req_config->flags.cscChanged = NV_TRUE;
}
req_config->config.matrixOverrides.enabled.fmtCtm =
update_matrix_override(nv_drm_plane_state->fmt_ctm,
&req_config->config.matrixOverrides.fmtCtm,
@@ -1421,20 +1538,22 @@ plane_req_config_update(struct drm_plane *plane,
&matrix_overrides_changed);
req_config->flags.matrixOverridesChanged = matrix_overrides_changed;
if (nv_drm_plane_state->degamma_changed) {
if (nv_drm_plane_state->degamma_changed || crtc_state->color_mgmt_changed) {
if (nv_drm_plane_state->degamma_drm_lut_surface != NULL) {
kref_put(&nv_drm_plane_state->degamma_drm_lut_surface->base.refcount,
free_drm_lut_surface);
release_drm_lut_surface);
nv_drm_plane_state->degamma_drm_lut_surface = NULL;
}
if ((nv_drm_plane_state->degamma_tf != NV_DRM_TRANSFER_FUNCTION_DEFAULT) ||
(nv_drm_plane_state->degamma_lut != NULL) ||
(nv_drm_plane_state->degamma_multiplier != NV_DRM_S31_32_ONE)) {
(nv_drm_plane_state->degamma_multiplier != NV_DRM_S31_32_ONE) ||
(crtc_state->degamma_lut != NULL)) {
nv_drm_plane_state->degamma_drm_lut_surface =
create_drm_ilut_surface_vss(nv_dev, nv_plane,
nv_drm_plane_state);
nv_drm_plane_state,
crtc_state);
if (IS_ERR(nv_drm_plane_state->degamma_drm_lut_surface)) {
int ret = PTR_ERR(nv_drm_plane_state->degamma_drm_lut_surface);
nv_drm_plane_state->degamma_drm_lut_surface = NULL;
@@ -1475,7 +1594,7 @@ plane_req_config_update(struct drm_plane *plane,
if (nv_drm_plane_state->tmo_changed) {
if (nv_drm_plane_state->tmo_drm_lut_surface != NULL) {
kref_put(&nv_drm_plane_state->tmo_drm_lut_surface->base.refcount,
free_drm_lut_surface);
release_drm_lut_surface);
nv_drm_plane_state->tmo_drm_lut_surface = NULL;
}
@@ -1520,6 +1639,12 @@ plane_req_config_update(struct drm_plane *plane,
req_config->flags.tmoChanged = NV_TRUE;
}
if (nv_drm_plane_state->precomp_color_passthrough_changed) {
req_config->config.precompColorPassthrough =
nv_drm_plane_state->precomp_color_passthrough;
req_config->flags.precompColorPassthroughChanged = NV_TRUE;
}
/*
* Unconditionally mark the surface as changed, even if nothing changed,
* so that we always get a flip event: a DRM client may flip with
@@ -1651,27 +1776,12 @@ static int nv_drm_plane_atomic_check(struct drm_plane *plane,
#endif
ret = plane_req_config_update(plane,
plane_state,
crtc_state,
plane_requested_config);
if (ret != 0) {
return ret;
}
if (crtc_state->color_mgmt_changed || (plane->state->crtc != plane_state->crtc)) {
/*
* According to the comment in the Linux kernel's
* drivers/gpu/drm/drm_color_mgmt.c, if this property is NULL,
* the CTM needs to be changed to the identity matrix
*/
if (crtc_state->ctm) {
ctm_to_csc(&plane_requested_config->config.csc,
(struct drm_color_ctm *)crtc_state->ctm->data);
} else {
plane_requested_config->config.csc = NVKMS_IDENTITY_CSC_MATRIX;
}
plane_requested_config->config.cscUseMain = NV_FALSE;
plane_requested_config->flags.cscChanged = NV_TRUE;
}
if (__is_async_flip_requested(plane, crtc_state)) {
/*
* Async flip requests that the flip happen 'as soon as
@@ -1725,6 +1835,9 @@ static int nv_drm_atomic_crtc_get_property(
*/
*val = NVKMS_LUT_ARRAY_SIZE;
return 0;
} else if (property == nv_dev->nv_crtc_color_passthrough_property) {
*val = nv_drm_crtc_state->postcomp_color_passthrough;
return 0;
}
return -EINVAL;
@@ -1765,6 +1878,12 @@ static int nv_drm_atomic_crtc_set_property(
nv_drm_crtc_state->regamma_changed = true;
}
return 0;
} else if (property == nv_dev->nv_crtc_color_passthrough_property) {
if (!!val != nv_drm_crtc_state->postcomp_color_passthrough) {
nv_drm_crtc_state->postcomp_color_passthrough = !!val;
nv_drm_crtc_state->postcomp_color_passthrough_changed = true;
}
return 0;
}
return -EINVAL;
@@ -1879,6 +1998,12 @@ static int nv_drm_plane_set_vendor_color_property(
nv_drm_plane_state->tmo_changed = true;
}
return ret;
} else if (property == nv_dev->nv_plane_color_passthrough_property) {
if (!!val != nv_drm_plane_state->precomp_color_passthrough) {
nv_drm_plane_state->precomp_color_passthrough = !!val;
nv_drm_plane_state->precomp_color_passthrough_changed = true;
}
return 0;
}
/* Not a vendor color property */
@@ -2009,6 +2134,9 @@ static int nv_drm_plane_get_vendor_color_property(
*/
*val = NVKMS_LUT_ARRAY_SIZE;
return 0;
} else if (property == nv_dev->nv_plane_color_passthrough_property) {
*val = nv_drm_plane_state->precomp_color_passthrough;
return 0;
}
/* Not a vendor color property */
@@ -2050,6 +2178,21 @@ static int nv_drm_plane_atomic_get_property(
return -EINVAL;
}
static inline struct nv_drm_plane_state *nv_drm_plane_state_alloc(void)
{
struct nv_drm_plane_state *nv_plane_state =
nv_drm_calloc(1, sizeof(*nv_plane_state));
if (!nv_plane_state) {
return NULL;
}
/* Default to 1 in S31.32 Sign-Magnitude Format */
nv_plane_state->degamma_multiplier = NV_DRM_S31_32_ONE;
return nv_plane_state;
}
/**
* nv_drm_plane_atomic_reset - plane state reset hook
* @plane: DRM plane
@@ -2059,7 +2202,7 @@ static int nv_drm_plane_atomic_get_property(
static void nv_drm_plane_atomic_reset(struct drm_plane *plane)
{
struct nv_drm_plane_state *nv_plane_state =
nv_drm_calloc(1, sizeof(*nv_plane_state));
nv_drm_plane_state_alloc();
if (!nv_plane_state) {
return;
@@ -2153,6 +2296,10 @@ nv_drm_plane_atomic_duplicate_state(struct drm_plane *plane)
nv_plane_state->ctms_default_to_identity =
nv_old_plane_state->ctms_default_to_identity;
nv_plane_state->precomp_color_passthrough =
nv_old_plane_state->precomp_color_passthrough;
nv_plane_state->precomp_color_passthrough_changed = false;
return &nv_plane_state->base;
}
@@ -2174,13 +2321,13 @@ static inline void __nv_drm_plane_atomic_destroy_state(
drm_property_blob_put(nv_drm_plane_state->degamma_lut);
if (nv_drm_plane_state->degamma_drm_lut_surface != NULL) {
kref_put(&nv_drm_plane_state->degamma_drm_lut_surface->base.refcount,
free_drm_lut_surface);
release_drm_lut_surface);
}
drm_property_blob_put(nv_drm_plane_state->tmo_lut);
if (nv_drm_plane_state->tmo_drm_lut_surface != NULL) {
kref_put(&nv_drm_plane_state->tmo_drm_lut_surface->base.refcount,
free_drm_lut_surface);
release_drm_lut_surface);
}
}
@@ -2238,32 +2385,6 @@ static inline bool nv_drm_crtc_duplicate_req_head_modeset_config(
old->layerRequestedConfig[i].config;
}
if (old->modeSetConfig.lut.input.pRamps) {
new->modeSetConfig.lut.input.pRamps =
nv_drm_calloc(1, sizeof(*new->modeSetConfig.lut.input.pRamps));
if (!new->modeSetConfig.lut.input.pRamps) {
return false;
}
*new->modeSetConfig.lut.input.pRamps =
*old->modeSetConfig.lut.input.pRamps;
}
if (old->modeSetConfig.lut.output.pRamps) {
new->modeSetConfig.lut.output.pRamps =
nv_drm_calloc(1, sizeof(*new->modeSetConfig.lut.output.pRamps));
if (!new->modeSetConfig.lut.output.pRamps) {
/*
* new->modeSetConfig.lut.input.pRamps is either NULL or it was
* just allocated
*/
nv_drm_free(new->modeSetConfig.lut.input.pRamps);
new->modeSetConfig.lut.input.pRamps = NULL;
return false;
}
*new->modeSetConfig.lut.output.pRamps =
*old->modeSetConfig.lut.output.pRamps;
}
return true;
}
@@ -2280,6 +2401,10 @@ static inline struct nv_drm_crtc_state *nv_drm_crtc_state_alloc(void)
for (i = 0; i < ARRAY_SIZE(nv_state->req_config.layerRequestedConfig); i++) {
plane_config_clear(&nv_state->req_config.layerRequestedConfig[i].config);
}
/* Default to 1 */
nv_state->regamma_divisor = NV_DRM_S31_32_ONE;
return nv_state;
}
@@ -2369,6 +2494,10 @@ nv_drm_atomic_crtc_duplicate_state(struct drm_crtc *crtc)
}
nv_state->regamma_changed = false;
nv_state->postcomp_color_passthrough =
nv_old_state->postcomp_color_passthrough;
nv_state->postcomp_color_passthrough_changed = false;
return &nv_state->base;
}
@@ -2395,12 +2524,9 @@ static void nv_drm_atomic_crtc_destroy_state(struct drm_crtc *crtc,
drm_property_blob_put(nv_state->regamma_lut);
if (nv_state->regamma_drm_lut_surface != NULL) {
kref_put(&nv_state->regamma_drm_lut_surface->base.refcount,
free_drm_lut_surface);
release_drm_lut_surface);
}
nv_drm_free(nv_state->req_config.modeSetConfig.lut.input.pRamps);
nv_drm_free(nv_state->req_config.modeSetConfig.lut.output.pRamps);
nv_drm_free(nv_state);
}
@@ -2491,118 +2617,6 @@ static int head_modeset_config_attach_connector(
return 0;
}
static int color_mgmt_config_copy_lut(struct NvKmsLutRamps *nvkms_lut,
struct drm_color_lut *drm_lut,
uint64_t lut_len)
{
uint64_t i = 0;
if (lut_len != NVKMS_LUT_ARRAY_SIZE) {
return -EINVAL;
}
/*
* Both NvKms and drm LUT values are 16-bit linear values. NvKms LUT ramps
* are in arrays in a single struct while drm LUT ramps are an array of
* structs.
*/
for (i = 0; i < lut_len; i++) {
nvkms_lut->red[i] = drm_lut[i].red;
nvkms_lut->green[i] = drm_lut[i].green;
nvkms_lut->blue[i] = drm_lut[i].blue;
}
return 0;
}
static int color_mgmt_config_set_luts(struct nv_drm_crtc_state *nv_crtc_state,
struct NvKmsKapiHeadRequestedConfig *req_config)
{
struct NvKmsKapiHeadModeSetConfig *modeset_config =
&req_config->modeSetConfig;
struct drm_crtc_state *crtc_state = &nv_crtc_state->base;
int ret = 0;
/*
* According to the comment in the Linux kernel's
* drivers/gpu/drm/drm_color_mgmt.c, if either property is NULL, that LUT
* needs to be changed to a linear LUT
*
* On failure, any LUT ramps allocated in this function are freed when the
* subsequent atomic state cleanup calls nv_drm_atomic_crtc_destroy_state.
*/
if (crtc_state->degamma_lut) {
struct drm_color_lut *degamma_lut = NULL;
uint64_t degamma_len = 0;
if (!modeset_config->lut.input.pRamps) {
modeset_config->lut.input.pRamps =
nv_drm_calloc(1, sizeof(*modeset_config->lut.input.pRamps));
if (!modeset_config->lut.input.pRamps) {
return -ENOMEM;
}
}
degamma_lut = (struct drm_color_lut *)crtc_state->degamma_lut->data;
degamma_len = crtc_state->degamma_lut->length /
sizeof(struct drm_color_lut);
if ((ret = color_mgmt_config_copy_lut(modeset_config->lut.input.pRamps,
degamma_lut,
degamma_len)) != 0) {
return ret;
}
modeset_config->lut.input.depth = 30; /* specify the full LUT */
modeset_config->lut.input.start = 0;
modeset_config->lut.input.end = degamma_len - 1;
} else {
/* setting input.end to 0 is equivalent to disabling the LUT, which
* should be equivalent to a linear LUT */
modeset_config->lut.input.depth = 30; /* specify the full LUT */
modeset_config->lut.input.start = 0;
modeset_config->lut.input.end = 0;
nv_drm_free(modeset_config->lut.input.pRamps);
modeset_config->lut.input.pRamps = NULL;
}
req_config->flags.legacyIlutChanged = NV_TRUE;
if (crtc_state->gamma_lut) {
struct drm_color_lut *gamma_lut = NULL;
uint64_t gamma_len = 0;
if (!modeset_config->lut.output.pRamps) {
modeset_config->lut.output.pRamps =
nv_drm_calloc(1, sizeof(*modeset_config->lut.output.pRamps));
if (!modeset_config->lut.output.pRamps) {
return -ENOMEM;
}
}
gamma_lut = (struct drm_color_lut *)crtc_state->gamma_lut->data;
gamma_len = crtc_state->gamma_lut->length /
sizeof(struct drm_color_lut);
if ((ret = color_mgmt_config_copy_lut(modeset_config->lut.output.pRamps,
gamma_lut,
gamma_len)) != 0) {
return ret;
}
modeset_config->lut.output.enabled = NV_TRUE;
} else {
/* disabling the output LUT should be equivalent to setting a linear
* LUT */
modeset_config->lut.output.enabled = NV_FALSE;
nv_drm_free(modeset_config->lut.output.pRamps);
modeset_config->lut.output.pRamps = NULL;
}
req_config->flags.legacyOlutChanged = NV_TRUE;
return 0;
}
/**
* nv_drm_crtc_atomic_check() can fail after it has modified
* the 'nv_drm_crtc_state::req_config', that is fine because 'nv_drm_crtc_state'
@@ -2668,21 +2682,17 @@ static int nv_drm_crtc_atomic_check(struct drm_crtc *crtc,
req_config->modeSetConfig.vrrEnabled = crtc_state->vrr_enabled;
#endif
if (crtc_state->color_mgmt_changed) {
if ((ret = color_mgmt_config_set_luts(nv_crtc_state, req_config)) != 0) {
return ret;
}
}
if (nv_crtc_state->regamma_changed) {
if (nv_crtc_state->regamma_changed || crtc_state->color_mgmt_changed) {
if (nv_crtc_state->regamma_drm_lut_surface != NULL) {
kref_put(&nv_crtc_state->regamma_drm_lut_surface->base.refcount,
free_drm_lut_surface);
release_drm_lut_surface);
nv_crtc_state->regamma_drm_lut_surface = NULL;
}
if ((nv_crtc_state->regamma_tf != NV_DRM_TRANSFER_FUNCTION_DEFAULT) ||
(nv_crtc_state->regamma_lut != NULL)) {
(nv_crtc_state->regamma_lut != NULL) ||
((crtc_state->gamma_lut != NULL) &&
(nv_crtc_state->regamma_divisor <= NV_DRM_S31_32_ONE))) {
nv_crtc_state->regamma_drm_lut_surface =
create_drm_olut_surface_vss(nv_dev, nv_crtc,
@@ -2732,6 +2742,12 @@ static int nv_drm_crtc_atomic_check(struct drm_crtc *crtc,
}
}
if (nv_crtc_state->postcomp_color_passthrough_changed) {
req_config->modeSetConfig.postcompColorPassthrough =
nv_crtc_state->postcomp_color_passthrough;
req_config->flags.postcompColorPassthroughChanged = NV_TRUE;
}
return ret;
}
@@ -2762,8 +2778,6 @@ static void nv_drm_crtc_install_properties(
NV_DRM_TRANSFER_FUNCTION_DEFAULT);
}
if (nv_dev->nv_crtc_regamma_divisor_property) {
/* Default to 1 */
nv_crtc_state->regamma_divisor = NV_DRM_S31_32_ONE;
drm_object_attach_property(
&crtc->base, nv_dev->nv_crtc_regamma_divisor_property,
nv_crtc_state->regamma_divisor);
@@ -2778,6 +2792,13 @@ static void nv_drm_crtc_install_properties(
NVKMS_LUT_ARRAY_SIZE);
}
}
if (nv_dev->supportsColorPassthrough) {
if (nv_dev->nv_crtc_color_passthrough_property) {
drm_object_attach_property(
&crtc->base, nv_dev->nv_crtc_color_passthrough_property, 0);
}
}
}
static void nv_drm_plane_install_properties(
@@ -2854,8 +2875,6 @@ static void nv_drm_plane_install_properties(
NV_DRM_TRANSFER_FUNCTION_DEFAULT);
}
if (nv_dev->nv_plane_degamma_multiplier_property) {
/* Default to 1 in S31.32 Sign-Magnitude Format */
nv_plane_state->degamma_multiplier = NV_DRM_S31_32_ONE;
drm_object_attach_property(
&plane->base, nv_dev->nv_plane_degamma_multiplier_property,
nv_plane_state->degamma_multiplier);
@@ -2870,6 +2889,13 @@ static void nv_drm_plane_install_properties(
NVKMS_LUT_ARRAY_SIZE);
}
}
if (nv_dev->supportsColorPassthrough) {
if (nv_dev->nv_plane_color_passthrough_property) {
drm_object_attach_property(
&plane->base, nv_dev->nv_plane_color_passthrough_property, 0);
}
}
}
#if defined(NV_DRM_HAS_COLOROP)
@@ -3047,8 +3073,7 @@ nv_drm_plane_create(struct drm_device *dev,
nv_plane->defaultCompositionMode = defaultCompositionMode;
nv_plane->layer_idx = layer_idx;
if ((nv_plane_state =
nv_drm_calloc(1, sizeof(*nv_plane_state))) == NULL) {
if ((nv_plane_state = nv_drm_plane_state_alloc()) == NULL) {
goto failed_state_alloc;
}
+14 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2016-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -177,6 +177,9 @@ struct nv_drm_crtc_state {
uint64_t regamma_divisor;
struct nv_drm_lut_surface *regamma_drm_lut_surface;
NvBool regamma_changed;
NvBool postcomp_color_passthrough;
NvBool postcomp_color_passthrough_changed;
};
static inline struct nv_drm_crtc_state *to_nv_crtc_state(struct drm_crtc_state *state)
@@ -246,6 +249,8 @@ struct nv_drm_nvkms_surface_params {
struct nv_drm_lut_surface {
struct nv_drm_nvkms_surface base;
struct {
NvU32 vssSegmentEntries;
NvU32 vssSegments;
enum NvKmsLUTVssType vssType;
@@ -253,8 +258,13 @@ struct nv_drm_lut_surface {
enum NvKmsLUTFormat entryFormat;
} properties;
struct nv_drm_device *nv_dev;
struct list_head pool_entry;
};
void nv_free_drm_lut_surface_pool(struct nv_drm_device *nv_dev);
struct nv_drm_plane_state {
struct drm_plane_state base;
s32 __user *fd_user_ptr;
@@ -278,6 +288,9 @@ struct nv_drm_plane_state {
struct drm_property_blob *tmo_lut;
struct nv_drm_lut_surface *tmo_drm_lut_surface;
NvBool tmo_changed;
NvBool precomp_color_passthrough;
NvBool precomp_color_passthrough_changed;
};
static inline struct nv_drm_plane_state *to_nv_drm_plane_state(struct drm_plane_state *state)
+169 -82
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2015-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -104,66 +104,6 @@ static int nv_drm_revoke_sub_ownership(struct drm_device *dev);
static DEFINE_MUTEX(dev_list_mutex);
static struct nv_drm_device *dev_list = NULL;
static const char* nv_get_input_colorspace_name(
enum nv_drm_input_color_space colorSpace)
{
switch (colorSpace) {
case NV_DRM_INPUT_COLOR_SPACE_NONE:
return "None";
case NV_DRM_INPUT_COLOR_SPACE_SCRGB_LINEAR:
return "scRGB Linear FP16";
case NV_DRM_INPUT_COLOR_SPACE_BT2100_PQ:
return "BT.2100 PQ";
default:
/* We shoudn't hit this */
WARN_ON("Unsupported input colorspace");
return "None";
}
};
static char* nv_get_transfer_function_name(
enum nv_drm_transfer_function tf)
{
switch (tf) {
case NV_DRM_TRANSFER_FUNCTION_LINEAR:
return "Linear";
case NV_DRM_TRANSFER_FUNCTION_PQ:
return "PQ (Perceptual Quantizer)";
default:
/* We shoudn't hit this */
WARN_ON("Unsupported transfer function");
#if defined(fallthrough)
fallthrough;
#else
/* Fallthrough */
#endif
case NV_DRM_TRANSFER_FUNCTION_DEFAULT:
return "Default";
}
};
static const char* nv_get_dithering_mode_name(
enum nv_drm_dithering_mode mode)
{
switch (mode) {
case NV_DRM_DITHERING_MODE_AUTO:
return "auto";
case NV_DRM_DITHERING_MODE_OFF:
return "off";
case NV_DRM_DITHERING_MODE_STATIC_2X2:
return "static 2x2";
case NV_DRM_DITHERING_MODE_DYNAMIC_2X2:
return "dynamic 2x2";
case NV_DRM_DITHERING_MODE_TEMPORAL:
return "temporal";
case NV_DRM_DITHERING_MODE_ON:
return "on";
default:
/* We shouldn't hit this, but return a safe default */
return "auto";
}
};
#if defined(NV_DRM_OUTPUT_POLL_CHANGED_PRESENT)
static void nv_drm_output_poll_changed(struct drm_device *dev)
{
@@ -232,6 +172,20 @@ static void nv_drm_event_callback(const struct NvKmsKapiEvent *event)
event->u.displayChanged.display);
break;
case NVKMS_EVENT_TYPE_DPY_CONTENT_PROTECTION_CHANGED:
nv_drm_handle_display_cp_change(
nv_dev,
event->u.displayCpChanged.display,
event->u.displayCpChanged.cp);
break;
case NVKMS_EVENT_TYPE_DPY_CP_TOPOLOGY_CHANGED:
nv_drm_handle_display_cp_topology_change(
nv_dev,
event->u.displayCpTopologyChanged.display,
event->u.displayCpTopologyChanged.topology);
break;
case NVKMS_EVENT_TYPE_DYNAMIC_DPY_CONNECTED:
nv_drm_handle_dynamic_display_connected(
nv_dev,
@@ -522,26 +476,51 @@ static void nv_drm_enumerate_encoders_and_connectors
*/
static int nv_drm_create_properties(struct nv_drm_device *nv_dev)
{
struct drm_prop_enum_list colorspace_enum_list[NV_DRM_INPUT_COLOR_SPACE_MAX] = { };
struct drm_prop_enum_list tf_enum_list[NV_DRM_TRANSFER_FUNCTION_MAX] = { };
struct drm_prop_enum_list dithering_mode_enum_list[NV_DRM_DITHERING_MODE_MAX] = { };
int i, len = 0;
const struct drm_prop_enum_list colorspace_enum_list[] = {
{ .type = NV_DRM_INPUT_COLOR_SPACE_NONE,
.name = "None" },
{ .type = NV_DRM_INPUT_COLOR_SPACE_SCRGB_LINEAR,
.name = "scRGB Linear FP16" },
{ .type = NV_DRM_INPUT_COLOR_SPACE_BT2100_PQ,
.name = "BT.2100 PQ" },
};
for (i = 0; i < NV_DRM_INPUT_COLOR_SPACE_MAX; i++) {
colorspace_enum_list[len].type = i;
colorspace_enum_list[len].name = nv_get_input_colorspace_name(i);
len++;
}
const struct drm_prop_enum_list tf_enum_list[] = {
{ .type = NV_DRM_TRANSFER_FUNCTION_LINEAR,
.name = "Linear" },
{ .type = NV_DRM_TRANSFER_FUNCTION_PQ,
.name = "PQ (Perceptual Quantizer)" },
{ .type = NV_DRM_TRANSFER_FUNCTION_DEFAULT,
.name = "Default" },
};
for (i = 0; i < NV_DRM_TRANSFER_FUNCTION_MAX; i++) {
tf_enum_list[i].type = i;
tf_enum_list[i].name = nv_get_transfer_function_name(i);
}
const struct drm_prop_enum_list dithering_mode_enum_list[] = {
{ .type = NV_DRM_DITHERING_MODE_AUTO,
.name = "auto" },
{ .type = NV_DRM_DITHERING_MODE_OFF,
.name = "off" },
{ .type = NV_DRM_DITHERING_MODE_STATIC_2X2,
.name = "static 2x2" },
{ .type = NV_DRM_DITHERING_MODE_DYNAMIC_2X2,
.name = "dynamic 2x2" },
{ .type = NV_DRM_DITHERING_MODE_TEMPORAL,
.name = "temporal" },
{ .type = NV_DRM_DITHERING_MODE_ON,
.name = "on" },
};
for (i = 0; i < NV_DRM_DITHERING_MODE_MAX; i++) {
dithering_mode_enum_list[i].type = i;
dithering_mode_enum_list[i].name = nv_get_dithering_mode_name(i);
}
const struct drm_prop_enum_list hdcp_level_enum_list[] = {
{ .type = NVKMS_CONTENT_PROTECTION_OFF,
.name = "HDCP_OFF" },
{ .type = NVKMS_CONTENT_PROTECTION_HDCP1X_ON,
.name = "HDCP1X_ON" },
{ .type = NVKMS_CONTENT_PROTECTION_HDCP2X_TYPE0_ON,
.name = "HDCP2X_TYPE0_ON" },
{ .type = NVKMS_CONTENT_PROTECTION_HDCP2X_TYPE1_ON,
.name = "HDCP2X_TYPE1_ON" },
{ .type = NVKMS_CONTENT_PROTECTION_FAILED,
.name = "HDCP_FAILED" },
};
if (nv_dev->supportsSyncpts) {
nv_dev->nv_out_fence_property =
@@ -554,12 +533,22 @@ static int nv_drm_create_properties(struct nv_drm_device *nv_dev)
nv_dev->nv_input_colorspace_property =
drm_property_create_enum(nv_dev->dev, 0, "NV_INPUT_COLORSPACE",
colorspace_enum_list, len);
colorspace_enum_list,
ARRAY_SIZE(colorspace_enum_list));
if (nv_dev->nv_input_colorspace_property == NULL) {
NV_DRM_LOG_ERR("Failed to create NV_INPUT_COLORSPACE property");
return -ENOMEM;
}
nv_dev->nv_hdcp_level_property =
drm_property_create_enum(nv_dev->dev, 0, "NV_HDCP_LEVEL",
hdcp_level_enum_list,
ARRAY_SIZE(hdcp_level_enum_list));
if (nv_dev->nv_hdcp_level_property == NULL) {
NV_DRM_LOG_ERR("Failed to create NV_HDCP_LEVEL property");
return -ENOMEM;
}
#if defined(NV_DRM_HAS_HDR_OUTPUT_METADATA)
nv_dev->nv_hdr_output_metadata_property =
drm_property_create(nv_dev->dev, DRM_MODE_PROP_BLOB,
@@ -602,7 +591,7 @@ static int nv_drm_create_properties(struct nv_drm_device *nv_dev)
nv_dev->nv_plane_degamma_tf_property =
drm_property_create_enum(nv_dev->dev, 0,
"NV_PLANE_DEGAMMA_TF", tf_enum_list,
NV_DRM_TRANSFER_FUNCTION_MAX);
ARRAY_SIZE(tf_enum_list));
if (nv_dev->nv_plane_degamma_tf_property == NULL) {
return -ENOMEM;
}
@@ -646,7 +635,7 @@ static int nv_drm_create_properties(struct nv_drm_device *nv_dev)
nv_dev->nv_crtc_regamma_tf_property =
drm_property_create_enum(nv_dev->dev, 0,
"NV_CRTC_REGAMMA_TF", tf_enum_list,
NV_DRM_TRANSFER_FUNCTION_MAX);
ARRAY_SIZE(tf_enum_list));
if (nv_dev->nv_crtc_regamma_tf_property == NULL) {
return -ENOMEM;
}
@@ -677,12 +666,42 @@ static int nv_drm_create_properties(struct nv_drm_device *nv_dev)
drm_property_create_enum(nv_dev->dev, 0,
"dithering mode",
dithering_mode_enum_list,
NV_DRM_DITHERING_MODE_MAX);
ARRAY_SIZE(dithering_mode_enum_list));
if (nv_dev->nv_connector_dithering_mode_property == NULL) {
NV_DRM_LOG_ERR("Failed to create dithering mode property");
return -ENOMEM;
}
// Color Pipeline Passthrough Properties
nv_dev->nv_plane_color_passthrough_property =
drm_property_create_bool(nv_dev->dev, 0,
"NV_PLANE_COLOR_PASSTHROUGH");
if (nv_dev->nv_plane_color_passthrough_property == NULL) {
return -ENOMEM;
}
nv_dev->nv_crtc_color_passthrough_property =
drm_property_create_bool(nv_dev->dev, 0,
"NV_CRTC_COLOR_PASSTHROUGH");
if (nv_dev->nv_crtc_color_passthrough_property == NULL) {
return -ENOMEM;
}
/* hdcp_topology is immutable by user space */
nv_dev->nv_hdcp_topology_property =
drm_property_create(nv_dev->dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
"NV_HDCP_TOPOLOGY", 0);
if (nv_dev->nv_hdcp_topology_property == NULL) {
return -ENOMEM;
}
// HDMI Vendor Specific InfoFrame Metadata
nv_dev->nv_connector_hdmi_vsif_metadata_property =
drm_property_create(nv_dev->dev, DRM_MODE_PROP_BLOB,
"NV_HDMI_VSIF_METADATA", 0);
if (nv_dev->nv_connector_hdmi_vsif_metadata_property == NULL) {
return -ENOMEM;
}
return 0;
}
@@ -800,6 +819,10 @@ static int nv_drm_dev_load(struct drm_device *dev)
nv_dev->supportsSyncpts = resInfo.caps.supportsSyncpts;
nv_dev->supportsColorPassthrough = resInfo.caps.supportsColorPassthrough;
nv_dev->isSocDgpuDisplayNeedingWar = resInfo.caps.isSocDgpuDisplayNeedingWar;
nv_dev->semsurf_stride = resInfo.caps.semsurf.stride;
nv_dev->semsurf_max_submitted_offset =
@@ -829,6 +852,9 @@ static int nv_drm_dev_load(struct drm_device *dev)
nv_dev->modifiers[i++] = DRM_FORMAT_MOD_LINEAR;
nv_dev->modifiers[i++] = DRM_FORMAT_MOD_INVALID;
mutex_init(&nv_dev->drm_lut_surface_pool_mutex);
INIT_LIST_HEAD(&nv_dev->drm_lut_surface_pool);
/* Initialize drm_device::mode_config */
nv_drm_init_mode_config(nv_dev, &resInfo);
@@ -850,6 +876,8 @@ static int nv_drm_dev_load(struct drm_device *dev)
nv_dev->pDevice,
((1 << NVKMS_EVENT_TYPE_DPY_CHANGED) |
(1 << NVKMS_EVENT_TYPE_DYNAMIC_DPY_CONNECTED) |
(1 << NVKMS_EVENT_TYPE_DPY_CONTENT_PROTECTION_CHANGED) |
(1 << NVKMS_EVENT_TYPE_DPY_CP_TOPOLOGY_CHANGED) |
(1 << NVKMS_EVENT_TYPE_FLIP_OCCURRED)))) {
NV_DRM_DEV_LOG_ERR(nv_dev, "Failed to register event mask");
}
@@ -941,6 +969,8 @@ static void nv_drm_dev_unload(struct drm_device *dev)
NV_DRM_DEV_LOG_ERR(nv_dev, "Failed to stop event listening");
}
nv_free_drm_lut_surface_pool(nv_dev);
/* Unset NvKmsKapiDevice */
pDevice = nv_dev->pDevice;
@@ -1841,6 +1871,12 @@ static const struct drm_ioctl_desc nv_drm_ioctls[] = {
DRM_IOCTL_DEF_DRV(NVIDIA_SEMSURF_FENCE_ATTACH,
nv_drm_semsurf_fence_attach_ioctl,
DRM_RENDER_ALLOW|DRM_UNLOCKED),
DRM_IOCTL_DEF_DRV(NVIDIA_SEMSURF_EXPORT_TO_SYNCOBJ_POINT,
nv_drm_semsurf_export_to_syncobj_point_ioctl,
DRM_RENDER_ALLOW|DRM_UNLOCKED),
DRM_IOCTL_DEF_DRV(NVIDIA_SYNCOBJ_GET_SYNCFD,
nv_drm_syncobj_get_syncfd_ioctl,
DRM_RENDER_ALLOW|DRM_UNLOCKED),
/*
* DRM_UNLOCKED is implicit for all non-legacy DRM driver IOCTLs since Linux
@@ -2261,6 +2297,34 @@ void nv_drm_remove_devices(void)
}
}
/*
* Mark the connection status of all connectors dirty and schedule a
* hotplug event, so that both the fbdev helper and userspace DRM clients
* re-detect connectors and restore modes as appropriate.
*/
static void nv_drm_reprobe_connectors(struct nv_drm_device *nv_dev)
{
struct drm_device *dev = nv_dev->dev;
struct drm_connector *connector = NULL;
struct drm_connector_list_iter conn_iter;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
nv_drm_connector_mark_connection_status_dirty(
to_nv_connector(connector));
}
drm_connector_list_iter_end(&conn_iter);
/*
* Delay the hotplug event: this is called from the resume path while
* other devices are still resuming, and the system workqueue is not
* frozen. An immediate hotplug event could drive a modeset (fbdev
* helper or DRM client) concurrently with the device-resume phase.
*/
schedule_delayed_work(&nv_dev->hotplug_event_work,
msecs_to_jiffies(2000));
}
/*
* Handle system suspend and resume.
*
@@ -2326,6 +2390,29 @@ void nv_drm_suspend_resume(NvBool suspend)
drm_fb_helper_set_suspend_unlocked(dev->fb_helper, 0);
#endif
drm_kms_helper_poll_enable(dev);
/*
* XXX: Workaround for bug 6422321, tracked for removal by
* bug 6556017.
*
* On resume, the affected devices come up with all heads
* shut down and the modeset owner is never notified, and
* display detection can trust a stale deasserted HPD and
* skip DDC (bug 6490670), so a monitor that dropped HPD
* during suspend stays undetected. If a sink stays
* connected across suspend, resume generates no HPD edge,
* and NVKMS only re-detects DisplayPort connectors on
* resume (nvRmResumeDP()). Any connector probe that raced
* with resume may also have latched a stale "disconnected"
* status. Force a re-probe of all connectors and send a
* hotplug event so the DRM client re-detects and restores
* the mode, like nv_drm_dev_load() does at boot. Restrict
* this to SoC products with dGPU-class display so other
* GPUs keep the stock resume behavior.
*/
if (nv_dev->isSocDgpuDisplayNeedingWar) {
nv_drm_reprobe_connectors(nv_dev);
}
}
}
+41 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2015-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -299,6 +299,46 @@ void nv_drm_handle_display_change(struct nv_drm_device *nv_dev,
schedule_delayed_work(&nv_dev->hotplug_event_work, 0);
}
void nv_drm_handle_display_cp_change(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay,
enum NvKmsContentProtection cp)
{
struct drm_device *dev = nv_dev->dev;
struct nv_drm_encoder *nv_encoder = NULL;
nv_encoder = get_nv_encoder_from_nvkms_display(dev, hDisplay);
if (nv_encoder == NULL) {
return;
}
mutex_lock(&dev->mode_config.mutex);
nv_encoder->nv_connector->cp = cp;
nv_drm_connector_update_content_protection(nv_encoder->nv_connector);
mutex_unlock(&dev->mode_config.mutex);
}
void nv_drm_handle_display_cp_topology_change(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay,
const void *topology)
{
struct drm_device *dev = nv_dev->dev;
struct nv_drm_encoder *nv_encoder = NULL;
mutex_lock(&dev->mode_config.mutex);
nv_encoder = get_nv_encoder_from_nvkms_display(dev, hDisplay);
if (nv_encoder == NULL) {
NV_DRM_DEV_LOG_ERR(nv_dev, "Encoder not found for display %d", hDisplay);
goto out;
}
nv_drm_connector_update_topology_property(nv_encoder->nv_connector, topology);
out:
mutex_unlock(&dev->mode_config.mutex);
}
void nv_drm_handle_dynamic_display_connected(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay)
{
+9 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2016-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -56,6 +56,14 @@ nv_drm_add_encoder(struct drm_device *dev, NvKmsKapiDisplay hDisplay);
void nv_drm_handle_display_change(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay);
void nv_drm_handle_display_cp_change(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay,
enum NvKmsContentProtection cp);
void nv_drm_handle_display_cp_topology_change(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay,
const void *topology);
void nv_drm_handle_dynamic_display_connected(struct nv_drm_device *nv_dev,
NvKmsKapiDisplay hDisplay);
+176
View File
@@ -36,6 +36,10 @@
#include <linux/dma-fence.h>
#if defined(NV_DRM_SYNCOBJ_FEATURES_PRESENT)
#include <drm/drm_syncobj.h>
#endif
#define NV_DRM_SEMAPHORE_SURFACE_FENCE_MAX_TIMEOUT_MS 5000
struct nv_drm_fence_context;
@@ -1826,4 +1830,176 @@ done:
return ret;
}
int nv_drm_semsurf_export_to_syncobj_point_ioctl(struct drm_device *dev,
void *data,
struct drm_file *filep)
{
#if defined(NV_DRM_SYNCOBJ_FEATURES_PRESENT)
struct nv_drm_device *nv_dev = to_nv_device(dev);
struct drm_nvidia_semsurf_export_to_syncobj_point_params *p = data;
struct nv_drm_fence_context *nv_fence_context = NULL;
struct dma_fence *fence = NULL;
struct drm_syncobj *syncobj = NULL;
struct dma_fence_chain *chain = NULL;
int ret = -EINVAL;
if (nv_dev->pDevice == NULL) {
ret = -EOPNOTSUPP;
goto done;
}
/* Lookup the fence context */
nv_fence_context = __nv_drm_fence_context_lookup(
filep,
p->fence_context_handle);
if (!nv_fence_context) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to look up nv fence context: 0x%08x",
p->fence_context_handle);
goto done;
}
if (nv_fence_context->ops != &nv_drm_semsurf_fence_ctx_ops) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Wrong fence context type: 0x%08x",
p->fence_context_handle);
goto done;
}
/* Lookup the DRM syncobj */
syncobj = drm_syncobj_find(filep, p->syncobj_handle);
if (!syncobj) {
ret = -ENOENT;
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to find syncobj: 0x%08x",
p->syncobj_handle);
goto done;
}
#if defined(NV_DMA_FENCE_CHAIN_ALLOC_PRESENT)
chain = dma_fence_chain_alloc();
#else
chain = kzalloc(sizeof(struct dma_fence_chain), GFP_KERNEL);
#endif
if (!chain) {
ret = -ENOMEM;
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to allocate dmabuf chain");
goto done;
}
/* Create a fence for the semsurf timeline point */
fence = __nv_drm_semsurf_fence_ctx_create_fence(
nv_dev,
to_semsurf_fence_ctx(nv_fence_context),
p->wait_value,
NV_DRM_SEMAPHORE_SURFACE_FENCE_MAX_TIMEOUT_MS);
if (IS_ERR(fence)) {
ret = PTR_ERR(fence);
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to create fence for wait_value: %llu",
p->wait_value);
goto done;
}
/* Replace the syncobj timeline point with our fence */
drm_syncobj_add_point(syncobj, chain, fence, p->syncobj_point);
ret = 0;
done:
if (syncobj) {
drm_syncobj_put(syncobj);
}
if (fence) {
dma_fence_put(fence);
}
if (nv_fence_context) {
nv_drm_gem_object_unreference_unlocked(&nv_fence_context->base);
}
return ret;
#else
return -EOPNOTSUPP;
#endif /* NV_DRM_SYNCOBJ_FEATURES_PRESENT */
}
int nv_drm_syncobj_get_syncfd_ioctl(struct drm_device *dev,
void *data,
struct drm_file *filep)
{
#if defined(NV_DRM_SYNCOBJ_FEATURES_PRESENT)
struct nv_drm_device *nv_dev = to_nv_device(dev);
struct drm_nvidia_syncobj_get_syncfd_params *p = data;
struct drm_syncobj *syncobj = NULL;
struct dma_fence *fence = NULL;
int ret = -EINVAL;
int fd;
if (nv_dev->pDevice == NULL) {
ret = -EOPNOTSUPP;
goto done;
}
/* Lookup the DRM syncobj */
syncobj = drm_syncobj_find(filep, p->syncobj_handle);
if (!syncobj) {
ret = -ENOENT;
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to find syncobj: 0x%08x",
p->syncobj_handle);
goto done;
}
/* Find the fence at the specified timeline point */
ret = drm_syncobj_find_fence(filep, p->syncobj_handle, p->syncobj_point,
0 /* flags */, &fence);
if (ret) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to find fence for syncobj 0x%08x at point %llu",
p->syncobj_handle,
p->syncobj_point);
goto done;
}
/* Create a sync FD for the fence */
fd = nv_drm_create_sync_file(fence);
if (fd < 0) {
ret = fd;
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Failed to create sync file from fence on syncobj 0x%08x",
p->syncobj_handle);
goto fence_found;
}
p->fd = fd;
ret = 0;
fence_found:
/* Release this function's reference to the fence */
dma_fence_put(fence);
done:
if (syncobj) {
drm_syncobj_put(syncobj);
}
return ret;
#else
return -EOPNOTSUPP;
#endif /* NV_DRM_SYNCOBJ_FEATURES_PRESENT */
}
#endif /* NV_DRM_AVAILABLE */
@@ -55,6 +55,14 @@ int nv_drm_semsurf_fence_attach_ioctl(struct drm_device *dev,
void *data,
struct drm_file *filep);
int nv_drm_semsurf_export_to_syncobj_point_ioctl(struct drm_device *dev,
void *data,
struct drm_file *filep);
int nv_drm_syncobj_get_syncfd_ioctl(struct drm_device *dev,
void *data,
struct drm_file *filep);
#endif /* NV_DRM_AVAILABLE */
#endif /* __NVIDIA_DRM_PRIME_FENCE_H__ */
+43 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2016-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -229,4 +229,46 @@ nv_drm_atomic_replace_property_blob_from_id(struct drm_device *dev,
return 0;
}
int
nv_drm_atomic_replace_property_blob_from_id_size_range(struct drm_device *dev,
struct drm_property_blob **blob,
uint64_t blob_id,
ssize_t min_size,
ssize_t max_size)
{
struct drm_property_blob *new_blob = NULL;
if (blob_id != 0) {
new_blob = drm_property_lookup_blob(dev, blob_id);
if (new_blob == NULL) {
return -EINVAL;
}
if (((min_size > 0) && (new_blob->length < min_size)) ||
((max_size > 0) && (new_blob->length > max_size))) {
drm_property_blob_put(new_blob);
return -EINVAL;
}
}
nv_drm_atomic_replace_property_blob(blob, new_blob, NULL);
drm_property_blob_put(new_blob);
return 0;
}
NvBool nv_drm_blobs_equal(const struct drm_property_blob *old_blob,
const struct drm_property_blob *new_blob)
{
if (!old_blob || !new_blob) {
return old_blob == new_blob;
}
if (old_blob->length != new_blob->length) {
return NV_FALSE;
}
return (memcmp(old_blob->data, new_blob->data, old_blob->length) == 0);
}
#endif /* NV_DRM_AVAILABLE */
@@ -445,6 +445,33 @@ int nv_drm_atomic_replace_property_blob_from_id(struct drm_device *dev,
ssize_t expected_size,
NvBool *replaced);
/**
* nv_drm_atomic_replace_property_blob_from_id_size_range - Look up and replace a property blob (size range)
* @dev: DRM device
* @blob: Pointer to blob pointer to replace
* @blob_id: Blob ID to lookup (0 = NULL)
* @min_size: Minimum allowed blob size (inclusive)
* @max_size: Maximum allowed blob size (inclusive)
*
* Returns: 0 on success, -EINVAL if blob not found or length outside [min_size, max_size]
*/
int nv_drm_atomic_replace_property_blob_from_id_size_range(struct drm_device *dev,
struct drm_property_blob **blob,
uint64_t blob_id,
ssize_t min_size,
ssize_t max_size);
/**
* nv_drm_blobs_equal - Compare two blobs
* @old_blob: Old blob
* @new_blob: New blob
*
* Returns: NV_TRUE if equal, NV_FALSE otherwise
*/
NvBool nv_drm_blobs_equal(const struct drm_property_blob *old_blob,
const struct drm_property_blob *new_blob);
/*
* S31.32 sign-magnitude fixed-point constant for 1.0
*/
+3 -3
View File
@@ -32,19 +32,19 @@
MODULE_PARM_DESC(
modeset,
"Enable atomic kernel modesetting (1 = enable (default), 0 = disable)");
module_param_named(modeset, nv_drm_modeset_module_param, bool, 0400);
module_param_named(modeset, nv_drm_modeset_module_param, bool, 0444);
#if defined(NV_DRM_FBDEV_AVAILABLE)
MODULE_PARM_DESC(
fbdev,
"Create a framebuffer device (1 = enable (default), 0 = disable)");
module_param_named(fbdev, nv_drm_fbdev_module_param, bool, 0400);
module_param_named(fbdev, nv_drm_fbdev_module_param, bool, 0444);
#endif
MODULE_PARM_DESC(
vblank,
"Enable drm vblank notification support (1 = enable, 0 = disable (default))");
module_param_named(vblank, nv_drm_vblank_module_param, bool, 0400);
module_param_named(vblank, nv_drm_vblank_module_param, bool, 0444);
#if defined(NV_DRM_HAS_COLOROP)
MODULE_PARM_DESC(
+96 -78
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2015-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -26,6 +26,7 @@
#include "nvidia-drm-priv.h"
#include "nvidia-drm-modeset.h"
#include "nvidia-drm-connector.h"
#include "nvidia-drm-crtc.h"
#include "nvidia-drm-os-interface.h"
#include "nvidia-drm-helper.h"
@@ -500,10 +501,22 @@ nv_drm_atomic_apply_modeset_config(struct drm_device *dev,
requested_config,
&reply_config,
commit)) {
if (commit || reply_config.flipResult != NV_KMS_FLIP_RESULT_IN_PROGRESS) {
return -EINVAL;
return -EINVAL;
}
#ifdef NV_DRM_SUPPORT_CONTENT_PROTECTION_PROPERTY
if (commit) {
struct drm_connector *connector;
struct drm_connector_state *connector_state;
int j;
for_each_new_connector_in_state(state, connector, connector_state, j) {
if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED) {
nv_drm_connector_update_content_protection(to_nv_connector(connector));
}
}
}
#endif
if (commit && nv_drm_vblank_module_param) {
for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
@@ -543,6 +556,7 @@ int nv_drm_atomic_check(struct drm_device *dev,
bool cursor_surface_changed;
bool cursor_only_commit;
bool infoframe_changed;
for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
/*
@@ -564,11 +578,60 @@ int nv_drm_atomic_check(struct drm_device *dev,
}
/*
* if the color management changed on the crtc, we need to update the
* crtc's plane's CSC matrices, so add the crtc's planes to the commit
* Check if infoframe-related properties changed on any connector
* attached to this CRTC. If so, we need to serialize the flip
* to ensure infoframe updates are properly sequenced.
*/
infoframe_changed = false;
#if defined(NV_DRM_CONNECTOR_ATTACH_HDR_OUTPUT_METADATA_PROPERTY_PRESENT)
{
struct drm_connector *connector;
struct drm_connector_state *connector_state;
struct drm_connector_state *old_connector_state;
struct nv_drm_connector_state *nv_connector_state;
struct nv_drm_connector_state *nv_old_connector_state;
int k;
bool hdr_changed, colorspace_changed, vsif_metadata_changed;
for_each_new_connector_in_state(state, connector, connector_state, k) {
if (connector_state->crtc != crtc) {
continue;
}
old_connector_state = drm_atomic_get_old_connector_state(state, connector);
nv_connector_state = to_nv_drm_connector_state(connector_state);
nv_old_connector_state = to_nv_drm_connector_state(old_connector_state);
hdr_changed = !drm_connector_atomic_hdr_metadata_equal(old_connector_state,
connector_state);
colorspace_changed = (old_connector_state->colorspace != connector_state->colorspace);
vsif_metadata_changed = !nv_drm_blobs_equal(nv_connector_state->hdmi_vsif_metadata,
nv_old_connector_state->hdmi_vsif_metadata);
/* Check if HDR metadata or colorspace changed */
if (hdr_changed || colorspace_changed || vsif_metadata_changed) {
NV_DRM_LOG_INFO("Infoframe changed on CRTC %d (hdr=%d colorspace=%d vsif=%d), adding planes on CRTC to atomic commit\n",
to_nv_crtc(crtc)->head,
hdr_changed,
colorspace_changed,
vsif_metadata_changed);
infoframe_changed = true;
break;
}
}
}
#endif
/*
* If the color management changed on the crtc, we need to update the
* crtc's plane's CSC matrices and ILUTs, so add the crtc's planes to
* the commit. Similarly, if infoframe properties changed, add all
* planes to ensure the flip is serialized via flip_list.
*/
if (crtc_state->color_mgmt_changed ||
(cursor_surface_changed && cursor_only_commit)) {
(cursor_surface_changed && cursor_only_commit) ||
infoframe_changed) {
if ((ret = drm_atomic_add_affected_planes(state, crtc)) != 0) {
goto done;
}
@@ -711,71 +774,37 @@ int nv_drm_atomic_commit(struct drm_device *dev,
* Our system already implements such a queue, but due to
* bug 4054608, it is currently not used.
*/
for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
struct nv_drm_crtc *nv_crtc = to_nv_crtc(crtc);
if (nonblock) {
for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
struct nv_drm_crtc *nv_crtc = to_nv_crtc(crtc);
/*
* Here you aren't required to hold nv_drm_crtc::flip_list_lock
* because:
*
* The core DRM driver acquires lock for all affected crtcs before
* calling into ->commit() hook, therefore it is not possible for
* other threads to call into ->commit() hook affecting same crtcs
* and enqueue flip objects into flip_list -
*
* nv_drm_atomic_commit_internal()
* |-> nv_drm_atomic_apply_modeset_config(commit=true)
* |-> nv_drm_crtc_enqueue_flip()
*
* Only possibility is list_empty check races with code path
* dequeuing flip object -
*
* __nv_drm_handle_flip_event()
* |-> nv_drm_crtc_dequeue_flip()
*
* But this race condition can't lead list_empty() to return
* incorrect result. nv_drm_crtc_dequeue_flip() in the middle of
* updating the list could not trick us into thinking the list is
* empty when it isn't.
*/
if (nonblock) {
/*
* Here you aren't required to hold nv_drm_crtc::flip_list_lock
* because:
*
* The core DRM driver acquires lock for all affected crtcs before
* calling into ->commit() hook, therefore it is not possible for
* other threads to call into ->commit() hook affecting same crtcs
* and enqueue flip objects into flip_list -
*
* nv_drm_atomic_commit_internal()
* |-> nv_drm_atomic_apply_modeset_config(commit=true)
* |-> nv_drm_crtc_enqueue_flip()
*
* Only possibility is list_empty check races with code path
* dequeuing flip object -
*
* __nv_drm_handle_flip_event()
* |-> nv_drm_crtc_dequeue_flip()
*
* But this race condition can't lead list_empty() to return
* incorrect result. nv_drm_crtc_dequeue_flip() in the middle of
* updating the list could not trick us into thinking the list is
* empty when it isn't.
*/
if (!list_empty(&nv_crtc->flip_list)) {
return -EBUSY;
}
} else {
if (wait_event_timeout(
nv_dev->flip_event_wq,
list_empty(&nv_crtc->flip_list),
3 * HZ /* 3 second */) == 0) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"Flip event timeout on head %u", nv_crtc->head);
}
}
/*
* If the legacy LUT needs to be updated, ensure that the previous LUT
* update is complete first.
*/
if (crtc_state->color_mgmt_changed) {
NvBool complete = nvKms->checkLutNotifier(nv_dev->pDevice,
nv_crtc->head,
!nonblock /* waitForCompletion */);
/* If checking the LUT notifier failed, assume no LUT notifier is set. */
if (!complete) {
if (nonblock) {
return -EBUSY;
} else {
/*
* checkLutNotifier should wait on the notifier in this
* case, so we should only get here if the wait timed out.
*/
NV_DRM_DEV_LOG_ERR(
nv_dev,
"LUT notifier timeout on head %u", nv_crtc->head);
}
}
}
}
@@ -905,17 +934,6 @@ int nv_drm_atomic_commit(struct drm_device *dev,
__nv_drm_handle_flip_event(nv_crtc);
}
}
if (crtc_state->color_mgmt_changed) {
NvBool complete = nvKms->checkLutNotifier(nv_dev->pDevice,
nv_crtc->head,
true /* waitForCompletion */);
if (!complete) {
NV_DRM_DEV_LOG_ERR(
nv_dev,
"LUT notifier timeout on head %u", nv_crtc->head);
}
}
}
}
@@ -71,8 +71,6 @@ void nv_drm_free(void *ptr)
#if defined(NVCPU_X86) || defined(NVCPU_X86_64)
#define WRITE_COMBINE_FLUSH() asm volatile("sfence":::"memory")
#elif defined(NVCPU_PPC64LE)
#define WRITE_COMBINE_FLUSH() asm volatile("sync":::"memory")
#else
#define WRITE_COMBINE_FLUSH() mb()
#endif
+14 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2015-2026, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -156,11 +156,14 @@ struct nv_drm_device {
NvBool contiguousPhysicalMappings;
NvBool supportsSyncpts;
NvBool supportsColorPassthrough;
NvBool isSocDgpuDisplayNeedingWar;
NvBool subOwnershipGranted;
NvBool hasFramebufferConsole;
struct drm_property *nv_out_fence_property;
struct drm_property *nv_input_colorspace_property;
struct drm_property *nv_hdcp_level_property;
struct {
NvU32 count;
@@ -170,6 +173,7 @@ struct nv_drm_device {
#if defined(NV_DRM_HAS_HDR_OUTPUT_METADATA)
struct drm_property *nv_hdr_output_metadata_property;
#endif
struct drm_property *nv_hdcp_topology_property;
struct drm_property *nv_plane_lms_ctm_property;
struct drm_property *nv_plane_lms_to_itp_ctm_property;
@@ -184,19 +188,28 @@ struct nv_drm_device {
struct drm_property *nv_plane_tmo_lut_property;
struct drm_property *nv_plane_tmo_lut_size_property;
struct drm_property *nv_plane_color_passthrough_property;
struct drm_property *nv_crtc_regamma_tf_property;
struct drm_property *nv_crtc_regamma_lut_property;
struct drm_property *nv_crtc_regamma_lut_size_property;
struct drm_property *nv_crtc_regamma_divisor_property;
struct drm_property *nv_crtc_color_passthrough_property;
struct drm_property *nv_connector_dithering_mode_property;
struct drm_property *nv_connector_hdmi_vsif_metadata_property;
struct nv_drm_device *next;
NvU64 vtFbBaseAddress;
NvU64 vtFbSize;
nv_drm_workthread vblank_worker;
struct mutex drm_lut_surface_pool_mutex;
struct list_head drm_lut_surface_pool;
};
static inline NvU32 nv_drm_next_display_semaphore(
@@ -65,8 +65,11 @@ NV_CONFTEST_FUNCTION_COMPILE_TESTS += vmf_insert_mixed
NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_gem_prime_mmap
NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_sysfs_connector_property_event
NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_sysfs_connector_status_event
NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_connector_attach_content_protection_property
NV_CONFTEST_FUNCTION_COMPILE_TESTS += drm_hdcp_update_content_protection
NV_CONFTEST_FUNCTION_COMPILE_TESTS += pfn_is_map_memory
NV_CONFTEST_FUNCTION_COMPILE_TESTS += vm_flags_set
NV_CONFTEST_FUNCTION_COMPILE_TESTS += dma_fence_chain_alloc
NV_CONFTEST_TYPE_COMPILE_TESTS += drm_driver_has_legacy_dev_list
NV_CONFTEST_TYPE_COMPILE_TESTS += vm_ops_fault_removed_vma_arg
@@ -65,65 +65,111 @@
#include "nv-retpoline.h"
#endif
/*
* Commit 0911b8c52c4d ("x86/bugs: Rename CONFIG_RETHUNK =>
* CONFIG_MITIGATION_RETHUNK)" in v6.9 renamed CONFIG_RETHUNK.
*/
#if !defined(CONFIG_RETHUNK) && !defined(CONFIG_MITIGATION_RETHUNK)
#include "nv-rethunk.h"
#endif
#include <linux/backlight.h>
#if IS_ENABLED(CONFIG_EXTCON_DISP_CABLE_STATE)
#include <linux/extcon/extcon-disp.h>
#endif
#define NVKMS_LOG_PREFIX "nvidia-modeset: "
static bool output_rounding_fix = true;
module_param_named(output_rounding_fix, output_rounding_fix, bool, 0400);
module_param_named(output_rounding_fix, output_rounding_fix, bool, 0444);
static bool disable_hdmi_frl = false;
module_param_named(disable_hdmi_frl, disable_hdmi_frl, bool, 0400);
module_param_named(disable_hdmi_frl, disable_hdmi_frl, bool, 0444);
static bool disable_vrr_memclk_switch = false;
module_param_named(disable_vrr_memclk_switch, disable_vrr_memclk_switch, bool, 0400);
module_param_named(disable_vrr_memclk_switch, disable_vrr_memclk_switch, bool, 0444);
static bool hdmi_deepcolor = true;
module_param_named(hdmi_deepcolor, hdmi_deepcolor, bool, 0400);
module_param_named(hdmi_deepcolor, hdmi_deepcolor, bool, 0444);
static unsigned int max_output_color_depth = 10;
module_param_named(max_output_color_depth, max_output_color_depth, uint, 0444);
static bool opportunistic_display_sync = true;
module_param_named(opportunistic_display_sync, opportunistic_display_sync, bool, 0400);
module_param_named(opportunistic_display_sync, opportunistic_display_sync, bool, 0444);
static enum NvKmsDebugForceColorSpace debug_force_color_space = NVKMS_DEBUG_FORCE_COLOR_SPACE_NONE;
module_param_named(debug_force_color_space, debug_force_color_space, uint, 0400);
module_param_named(debug_force_color_space, debug_force_color_space, uint, 0444);
static bool enable_overlay_layers = true;
module_param_named(enable_overlay_layers, enable_overlay_layers, bool, 0400);
module_param_named(enable_overlay_layers, enable_overlay_layers, bool, 0444);
/* These parameters are used for fault injection tests. Normally the defaults
* should be used. */
MODULE_PARM_DESC(fail_malloc, "Fail the Nth call to nvkms_alloc");
static int fail_malloc_num = -1;
module_param_named(fail_malloc, fail_malloc_num, int, 0400);
module_param_named(fail_malloc, fail_malloc_num, int, 0444);
MODULE_PARM_DESC(malloc_verbose, "Report information about malloc calls on module unload");
static bool malloc_verbose = false;
module_param_named(malloc_verbose, malloc_verbose, bool, 0400);
module_param_named(malloc_verbose, malloc_verbose, bool, 0444);
MODULE_PARM_DESC(force_frl_rate,
"Override the default FRL rate selection (2 = max FRL rate w/ DSC, 1 = max FRL rate, 0 = default rate)");
static int force_frl_rate = 0;
module_param_named(force_frl_rate, force_frl_rate, int, 0400);
module_param_named(force_frl_rate, force_frl_rate, int, 0444);
MODULE_PARM_DESC(conceal_vrr_caps,
"Conceal all display VRR capabilities");
static bool conceal_vrr_caps = false;
module_param_named(conceal_vrr_caps, conceal_vrr_caps, bool, 0400);
module_param_named(conceal_vrr_caps, conceal_vrr_caps, bool, 0444);
MODULE_PARM_DESC(enhanced_pcon_support,
"Enable enhanced display protocol converter features (e.g. VRR over active DP-HDMI converters)");
static bool enhanced_pcon_support = false;
module_param_named(enhanced_pcon_support, enhanced_pcon_support, bool, 0400);
module_param_named(enhanced_pcon_support, enhanced_pcon_support, bool, 0444);
/* Fail allocating the RM core channel for NVKMS using the i-th method (see
* FailAllocCoreChannelMethod). Failures not using the i-th method are ignored. */
MODULE_PARM_DESC(fail_alloc_core_channel, "Control testing for hardware core channel allocation failure");
static int fail_alloc_core_channel_method = -1;
module_param_named(fail_alloc_core_channel, fail_alloc_core_channel_method, int, 0400);
module_param_named(fail_alloc_core_channel, fail_alloc_core_channel_method, int, 0444);
MODULE_PARM_DESC(debug, "Enable debug logging");
static int debug = 0;
module_param_named(debug, debug, int, 0600);
module_param_named(debug, debug, int, 0644);
#if IS_ENABLED(CONFIG_EXTCON_DISP_CABLE_STATE)
/*
* Extcon reporting for HDMI video and audio state.
*
* The extcon-disp-state driver (CONFIG_EXTCON_DISP_CABLE_STATE) is an Android-specific
* kernel driver that exposes HDMI hotplug and audio state to userspace via the
* extcon subsystem. This is required for AOSP's WiredAccessoryManager (audio routing)
* and WindowManager (display management) to receive HDMI state notifications.
*
* These APIs handle device management and duplicate-state checking internally.
*/
void nvkms_extcon_report_hdmi(NvBool state)
{
disp_state_extcon_hdmi_report(state);
}
void nvkms_extcon_report_hdmi_audio(NvBool state)
{
disp_state_extcon_hdmi_audio_report(state);
}
#else
/* Unsupported STUB for nvkms_extcon APIs */
void nvkms_extcon_report_hdmi(NvBool state)
{
}
void nvkms_extcon_report_hdmi_audio(NvBool state)
{
}
#endif
#if NVKMS_CONFIG_FILE_SUPPORTED
/* This parameter is used to find the dpy override conf file */
@@ -132,7 +178,7 @@ module_param_named(debug, debug, int, 0600);
MODULE_PARM_DESC(config_file,
"Path to the nvidia-modeset configuration file (default: disabled)");
static char *nvkms_conf = NULL;
module_param_named(config_file, nvkms_conf, charp, 0400);
module_param_named(config_file, nvkms_conf, charp, 0444);
#endif
static atomic_t nvkms_alloc_called_count;
@@ -204,6 +250,22 @@ NvBool nvkms_hdmi_deepcolor(void)
return hdmi_deepcolor;
}
NvU32 nvkms_max_output_color_bpc(void)
{
switch (max_output_color_depth) {
case 6:
return 6;
case 8:
return 8;
case 12:
return 12;
case 10:
case 0:
default:
return 10;
}
}
NvBool nvkms_opportunistic_display_sync(void)
{
return opportunistic_display_sync;
@@ -474,20 +536,6 @@ NvBool nvkms_syncpt_op(
#define NVKMS_MAJOR_DEVICE_NUMBER 195
#define NVKMS_MINOR_DEVICE_NUMBER 254
/*
* Convert from microseconds to jiffies. The conversion is:
* ((usec) * HZ / 1000000)
*
* Use do_div() to avoid gcc-generated references to __udivdi3().
* Note that the do_div() macro divides the first argument in place.
*/
static inline unsigned long NVKMS_USECS_TO_JIFFIES(NvU64 usec)
{
unsigned long result = usec * HZ;
do_div(result, 1000000);
return result;
}
/*************************************************************************
* NVKMS uses a global lock, nvkms_lock. The lock is taken in the
@@ -1116,7 +1164,7 @@ nvkms_init_timer(struct nvkms_timer_t *timer, nvkms_timer_proc_t *proc,
timer_setup(&timer->kernel_timer, nvkms_timer_callback_typed_data, 0);
timer->kernel_timer_created = NV_TRUE;
mod_timer(&timer->kernel_timer, jiffies + NVKMS_USECS_TO_JIFFIES(usec));
mod_timer(&timer->kernel_timer, jiffies + usecs_to_jiffies(usec));
}
spin_unlock_irqrestore(&nvkms_timers.lock, flags);
}
@@ -2043,9 +2091,7 @@ static struct file_operations nvkms_fops = {
.owner = THIS_MODULE,
.poll = nvkms_poll,
.unlocked_ioctl = nvkms_unlocked_ioctl,
#if NVCPU_IS_X86_64 || NVCPU_IS_AARCH64
.compat_ioctl = nvkms_unlocked_ioctl,
#endif
.mmap = nvkms_mmap,
.open = nvkms_open,
.release = nvkms_close,
@@ -124,10 +124,13 @@ NvBool nvkms_output_rounding_fix(void);
NvBool nvkms_disable_hdmi_frl(void);
NvBool nvkms_disable_vrr_memclk_switch(void);
NvBool nvkms_hdmi_deepcolor(void);
NvU32 nvkms_max_output_color_bpc(void);
NvBool nvkms_opportunistic_display_sync(void);
enum NvKmsDebugForceColorSpace nvkms_debug_force_color_space(void);
NvBool nvkms_enable_overlay_layers(void);
NvBool nvkms_debug_logging(void);
void nvkms_extcon_report_hdmi(NvBool state);
void nvkms_extcon_report_hdmi_audio(NvBool state);
void nvkms_call_rm (void *ops);
void* nvkms_alloc (size_t size,
@@ -37,21 +37,17 @@ ifeq ($(ARCH), arm64)
OFA_ARCH := aarch64
else ifeq ($(ARCH), powerpc)
OFA_ARCH := ppc64le
else ifeq ($(ARCH), x86)
OFA_ARCH := x86_64
else
OFA_ARCH := $(ARCH)
endif
OFA_DIR := /usr/src/ofa_kernel
OFA_CANDIDATES = $(OFA_DIR)/$(OFA_ARCH)/$(KERNELRELEASE) $(OFA_DIR)/$(KERNELRELEASE) $(OFA_DIR)/default /var/lib/dkms/mlnx-ofed-kernel
MLNX_OFED_KERNEL := $(shell for d in $(OFA_CANDIDATES); do \
if [ -d "$$d" ]; then \
echo "$$d"; \
exit 0; \
fi; \
done; \
echo $(OFA_DIR) \
)
OFA_DKMS_DIR := $(OFA_DIR)-dkms
OFA_CANDIDATES = $(OFA_DKMS_DIR)/$(OFA_ARCH)/$(KERNELRELEASE) $(OFA_DIR)/$(OFA_ARCH)/$(KERNELRELEASE) $(OFA_DIR)/$(KERNELRELEASE) $(OFA_DIR)/default
MLNX_OFED_KERNEL := $(firstword $(wildcard $(OFA_CANDIDATES)))
ifneq ($(shell test -d $(MLNX_OFED_KERNEL) && echo "true" || echo "" ),)
ifneq ($(MLNX_OFED_KERNEL),)
NVIDIA_PEERMEM_CFLAGS += -I$(MLNX_OFED_KERNEL)/include -I$(MLNX_OFED_KERNEL)/include/rdma
KBUILD_EXTRA_SYMBOLS := $(MLNX_OFED_KERNEL)/Module.symvers
endif
+1 -4
View File
@@ -1,5 +1,5 @@
/*******************************************************************************
Copyright (c) 2013-2024 NVIDIA Corporation
Copyright (c) 2013-2026 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
@@ -47,8 +47,5 @@
#define NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB10B (0x0000000B)
#define NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB20B (0x0000000B)
/* valid ARCHITECTURE_GB20x implementation values */
#define NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB206 (0x00000006)
#define NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB207 (0x00000007)
#endif /* _ctrl2080mc_h_ */
@@ -79,6 +79,7 @@ NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_migrate.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_populate_pageable.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_migrate_pageable.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_map_external.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_va_range_dmabuf.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_user_channel.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_hmm.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_perf_heuristics.c
@@ -116,3 +117,4 @@ NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_thread_context_test.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_rb_tree_test.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_sec2_test.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_test_file.c
NVIDIA_UVM_SOURCES += nvidia-uvm/uvm_dmabuf_test.c
+3 -1
View File
@@ -52,10 +52,10 @@ NV_CONFTEST_FUNCTION_COMPILE_TESTS += list_is_first
NV_CONFTEST_FUNCTION_COMPILE_TESTS += ioasid_get
NV_CONFTEST_FUNCTION_COMPILE_TESTS += mm_pasid_drop
NV_CONFTEST_FUNCTION_COMPILE_TESTS += iommu_sva_bind_device_has_drvdata_arg
NV_CONFTEST_FUNCTION_COMPILE_TESTS += iommu_is_dma_domain
NV_CONFTEST_FUNCTION_COMPILE_TESTS += for_each_sgtable_dma_page
NV_CONFTEST_FUNCTION_COMPILE_TESTS += folio_test_swapcache
NV_CONFTEST_FUNCTION_COMPILE_TESTS += page_pgmap
NV_CONFTEST_FUNCTION_COMPILE_TESTS += iommu_is_dma_domain
NV_CONFTEST_TYPE_COMPILE_TESTS += mmu_notifier_ops_arch_invalidate_secondary_tlbs
NV_CONFTEST_TYPE_COMPILE_TESTS += migrate_vma_added_flags
@@ -70,8 +70,10 @@ NV_CONFTEST_TYPE_COMPILE_TESTS += mmu_interval_notifier
NV_CONFTEST_TYPE_COMPILE_TESTS += sg_dma_page_iter
NV_CONFTEST_TYPE_COMPILE_TESTS += struct_page_has_zone_device_data
NV_CONFTEST_TYPE_COMPILE_TESTS += memory_device_coherent_present
NV_CONFTEST_TYPE_COMPILE_TESTS += dma_buf_supports_dynamic_importer
NV_CONFTEST_SYMBOL_COMPILE_TESTS += is_export_symbol_present_int_active_memcg
NV_CONFTEST_SYMBOL_COMPILE_TESTS += is_export_symbol_present_migrate_vma_setup
NV_CONFTEST_SYMBOL_COMPILE_TESTS += is_export_symbol_present___iowrite64_lo_hi
NV_CONFTEST_SYMBOL_COMPILE_TESTS += is_export_symbol_present_make_device_exclusive
NV_CONFTEST_SYMBOL_COMPILE_TESTS += is_export_symbol_gpl_dma_iova_try_alloc
+4
View File
@@ -1013,6 +1013,10 @@ static long uvm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_CREATE_EXTERNAL_RANGE, uvm_api_create_external_range);
UVM_ROUTE_CMD_ALLOC_INIT_CHECK(UVM_MAP_EXTERNAL_ALLOCATION, uvm_api_map_external_allocation);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_MAP_EXTERNAL_SPARSE, uvm_api_map_external_sparse);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_IS_DMA_BUF_IMPORT_SUPPORTED, uvm_api_is_dma_buf_import_supported);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_IMPORT_DMA_BUF, uvm_api_import_dma_buf);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_MAP_DMA_BUF, uvm_api_map_dma_buf);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_UNMAP_DMA_BUF, uvm_api_unmap_dma_buf);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_FREE, uvm_api_free);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_DISCARD, uvm_api_discard);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_SET_PREFERRED_LOCATION, uvm_api_set_preferred_location);
+240 -60
View File
@@ -1,5 +1,5 @@
/*******************************************************************************
Copyright (c) 2013-2025 NVIDIA Corporation
Copyright (c) 2013-2026 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
@@ -233,7 +233,7 @@ NV_STATUS UvmDeinitialize(void);
//
// Arguments:
// flags: (INPUT)
// Must be zero. UVM will be reinitialized with the
// Must be zero. UVM will be reinitialized with the
// Same flags that were passed to UvmInitialize() originally.
//
// Error codes:
@@ -475,17 +475,12 @@ NV_STATUS UvmUnregisterGpu(const NvProcessorUuid *gpuUuid);
// on this GPU with the mapping and caching attributes as specified during that
// call, or with default attributes if none were specified.
//
// Any VA ranges that had a preferred location set to this GPU will be mapped on
// this GPU only if this GPU is not fault-capable and the VA range belongs to a
// non-migratable range group. If such a mapping cannot be established, an error
// is returned.
//
// Any VA ranges which have accessed-by set for this GPU will be mapped on this
// GPU. If that VA range resides in a PCIe peer GPU's memory and P2P support
// between the two GPUs has not been enabled via UvmEnablePeerAccess, then a
// mapping won't be established. Also, if read duplication is enabled for this
// VA range, or its preferred location is set to this GPU, and this GPU is a
// fault-capable GPU, then a mapping will not be established. If this is a
// fault-capable GPU, then a mapping will not be established. If this is a
// non-fault-capable GPU and a mapping cannot be established, then an error is
// returned.
//
@@ -926,9 +921,9 @@ NV_STATUS UvmReleaseVa(void *base,
//
// Creates a new mapping in the virtual address space of the process, populates
// it at the specified preferred location, maps it on the provided list of
// processors if feasible and associates the range with the given range group.
// If the preferredLocationUuid is the UUID of the CPU, preferred location is
// set to all CPU nodes allowed by the global and thread memory policies.
// processors if feasible. If the preferredLocationUuid is the UUID of the CPU,
// preferred location is set to all CPU nodes allowed by the global and thread
// memory policies.
//
// This API is equivalent to the following code sequence:
// UvmMemMap(base, length);
@@ -964,9 +959,6 @@ NV_STATUS UvmReleaseVa(void *base,
// accessedByCount: (INPUT)
// Number of elements in the accessedByUuids array.
//
// rangeGroupId: (INPUT)
// ID of the range group to associate this VA range with.
//
// Errors:
// NV_ERR_UVM_ADDRESS_IN_USE:
// The requested address range overlaps with an existing allocation.
@@ -980,9 +972,6 @@ NV_STATUS UvmReleaseVa(void *base,
// accessedByUuids array was not registered or the UUID represents a GPU
// that has no VA space registered for it.
//
// NV_ERR_OBJECT_NOT_FOUND:
// rangeGroupId was not found.
//
// NV_ERR_NO_MEMORY:
// Internal memory allocation failed.
//
@@ -1092,9 +1081,8 @@ NV_STATUS UvmCleanUpZombieResources(void);
// a GPU VA space is registered in the future for a GPU which is unable to map
// this allocation, that GPU VA space registration will fail.
//
// The pages in this VA range cannot be associated with range groups, cannot be
// the target for read duplication, cannot have a preferred location set, and
// cannot have any accessed-by processors.
// The pages in this VA range cannot be the target for read duplication, cannot
// have a preferred location set, and cannot have any accessed-by processors.
//
// The VA range can be unmapped and freed via a call to UvmFree.
//
@@ -1356,10 +1344,6 @@ NV_STATUS UvmAllocDeviceP2P(NvProcessorUuid gpuUuid,
// Unexpected error. We try hard to avoid returning this error code,
// because it is not very informative.
//
// NV_WARN_MORE_PROCESSING_REQUIRED:
// Fewer than the number of requested pages were migrated because some
// pages were associated with a non-migratable range group.
//
//------------------------------------------------------------------------------
NV_STATUS UvmMigrate(void *base,
NvLength length,
@@ -1444,10 +1428,6 @@ NV_STATUS UvmMigrate(void *base,
// Unexpected error. We try hard to avoid returning this error code,
// because it is not very informative.
//
// NV_WARN_MORE_PROCESSING_REQUIRED:
// Fewer than the number of requested pages were migrated because some
// pages were associated with a non-migratable range group.
//
//------------------------------------------------------------------------------
NV_STATUS UvmMigrateAsync(void *base,
NvLength length,
@@ -1657,10 +1637,9 @@ NV_STATUS UvmMemMap(void *base,
// previously reserved via UvmReserveVa.
//
// Any mappings created within this VA range are considered non-migratable.
// Consequently, pages cannot be associated with range groups, cannot be
// the target for read duplication, cannot have a preferred location set,
// cannot have any accessed-by processors, and any GPU faults within this range
// are fatal.
// Consequently, pages cannot be the target for read duplication, cannot have a
// preferred location set, cannot have any accessed-by processors, and any GPU
// faults within this range are fatal.
//
// Mappings within this range neither create nor modify any CPU mappings, even
// if the mappings came from a region previously reserved via UvmReserveVa.
@@ -1879,7 +1858,6 @@ NV_STATUS UvmMapExternalAllocation(void *base,
// length: (INPUT)
// Length, in bytes, of the range. The length must be 64K aligned.
//
//
// gpuUuid: (INPUT)
// UUID of the physical GPU if the GPU is not SMC capable or SMC
// enabled, or the GPU instance UUID of the partition to map the sparse
@@ -1968,6 +1946,225 @@ NV_STATUS UvmUnmapExternal(void *base,
NvLength length,
const NvProcessorUuid *gpuUuid);
//------------------------------------------------------------------------------
// UvmIsDmaBufImportSupported
//
// Returns true only if the system features support for the Linux kernel
// DMA-BUF interface.
//
// Arguments:
// dmaBufImportSupported: (OUTPUT)
// Returns true (non-zero) if the system has sufficient support for the
// Linux kernel DMA-BUF interface and false (zero) otherwise.
//
// Error codes:
// NV_ERR_INVALID_STATE:
// UVM was not initialized.
//
// NV_ERR_GENERIC:
// Unexpected error. We try hard to avoid returning this error code,
// because it is not very informative.
//
//------------------------------------------------------------------------------
NV_STATUS UvmIsDmaBufImportSupported(NvBool *dmaBufImportSupported);
//------------------------------------------------------------------------------
// UvmImportDmaBuf
//
// Create a VA range within the process's address space reserved for mapping
// memory exposed through the kernel DMA-BUF interface through a handle file
// descriptor. The VA range is not mapped to any physical allocation at the
// time of creation. Once the DMA-BUF VA range has been created using this API,
// the user is free to map any number of gpus to the memory referenced by the
// DMA-BUF (see UvmMapDmaBuf for more details) provided that they are
// actually capable of mapping that memory. Often the DMA-BUF memories are
// expected to be PCIe peers or otherwise on self-hosted systems, the CPU must
// be able to map the memory.
//
// The virtual address range, itself, does not impose any restrictions on the
// alignment of the physical allocations mapped within it. However, both base
// and length must be aligned to the system page size.
//
// The VA range must not overlap with an existing VA range, irrespective of
// whether the existing range corresponds to a UVM allocation or an external
// allocation.
//
// It is allowed (but not required) for the VA range to come from a region
// previously reserved via UvmReserveVa.
//
// Any mappings created within this VA range are considered non-migratable.
// Consequently, pages cannot be the target for read duplication, cannot have a
// preferred location set, cannot have any accessed-by processors, and any GPU
// faults within this range are fatal.
//
// Mappings within this range neither create nor modify any CPU mappings, even
// if the mappings came from a region previously reserved via UvmReserveVa.
// This implies that CPU accesses to any mappings within this range will cause
// a fatal fault if it's not mapped.
//
// The VA range is not reclaimed until UvmFree is called on it even if it is
// fully unmapped from all GPUs either explicitly via UvmUnmapDmaBuf or
// implicitly via APIs such as UvmUnregisterGpu, UvmUnregisterGpuVaSpace,
// UvmDisablePeerAccess, etc.
//
// Arguments:
// base: (INPUT)
// Base address of the virtual address range.
//
// length: (INPUT)
// Length, in bytes, of the range.
//
// Errors:
// NV_ERR_INVALID_ADDRESS:
// base is NULL or length is zero or at least one of base and length is
// not aligned to the system page size, or the length is not equal to
// the reported size of the DMA-BUF.
//
// NV_ERR_UVM_ADDRESS_IN_USE:
// The requested address range overlaps with an existing allocation.
//
// NV_ERR_NO_MEMORY:
// Internal memory allocation failed.
//
//------------------------------------------------------------------------------
NV_STATUS UvmImportDmaBuf(UvmDmaBufFileDescriptor handleFd,
void *base,
NvLength length);
//------------------------------------------------------------------------------
// UvmMapDmaBuf
//
// Maps a buffer exposed by file descriptor through the kernel DMA-BUF interface
// on the specified GPU. The DMA-BUF can be unmapped from a specific GPU using
// UvmUnmapDmaBuf or from all GPUs using UvmFree.
//
// The virtual address range specified by (base, length) must fall within a VA
// range previously created with UvmImportDmaBuf. A GPU VA space must
// have been registered for each GPU in the list. The (base, length) range must
// lie within the largest possible virtual address supported by the specified
// GPUs.
//
// The page size used for the mapping is the system page size, as the DMA-BUF
// interface requires exporters to ensure page size alignment for the DMA
// mapped chunks returned through the dma_buf_map_attachment() call.
//
// The page size used for the mapping is the largest supported page size less
// than or equal to the alignments of base, length, offset, and the allocation
// page size.
//
// If the range specified by (base, length) falls within any existing mappings,
// the behavior is the same as if UvmUnmapDmaBuf with the range specified by
// (base, length) had been called first.
//
// The underlying buffer must not reside in GPU memory.
//
// Arguments:
// base: (INPUT)
// Base address of the virtual address range.
//
// length: (INPUT)
// Length, in bytes, of the range.
//
// gpuUuid: (INPUT)
// UUID of the physical GPU if the GPU is not SMC capable or SMC
// enabled, or the GPU instance UUID of the partition to map the VA
// range.
//
// Errors:
// NV_ERR_INVALID_ADDRESS:
// One of the following occurred:
// - base is NULL.
// - length is zero.
// - either base and length are not aligned to the system page size.
// - The requested address range does not fall entirely within an
// existing external VA range created with a single call to
// UvmCreateExternalRange.
// - The mapping page size allowed by the alignments of base, length,
// and offset is smaller than the minimum supported page size on the
// GPU.
// - base or base + length fall within an existing mapping but are not
// aligned to that mapping's page size.
// - The DMA mappings to the memory backing this DMA-BUF have been
// revoked by the exporter, and so no GPU mapping is possible.
//
// NV_ERR_OUT_OF_RANGE:
// The range specified by (base, length) exceeds the largest virtual
// address supported by one or more of the specified GPUs.
//
// NV_ERR_INVALID_DEVICE:
// One of the following occurred:
// - The gpu UUID to be mapped was either not registered or has no GPU
// VA space registered for it.
// - The specified GPU is unable to DMA map the buffer (for example,
// the exporter rejected the attachment, the backing store is not
// reachable from this GPU, or the requested direction is not
// supported).
//
// NV_ERR_NO_MEMORY:
// Internal memory allocation failed.
//
// NV_ERR_GENERIC:
// Unexpected error. We try hard to avoid returning this error code,
// because it is not very informative.
//
//------------------------------------------------------------------------------
NV_STATUS UvmMapDmaBuf(void *base,
NvLength length,
const NvProcessorUuid *gpuUuid);
//------------------------------------------------------------------------------
// UvmUnmapDmaBuf
//
// Unmaps a virtual address range that was mapped using UvmMapDmaBuf from the
// specified GPU. The range specified by (base, length) must be fully contained
// within a single DMA-BUF VA range created with UvmImportDmaBuf.
//
// If the range specified by (base, length) range partially overlaps existing
// mappings, the overlapping portion of the existing mappings will be unmapped
// provided that the split points are aligned to the mappings' respective page
// sizes. Otherwise, the overlapping portions of the existing mappings will be
// left in an undefined state.
//
// Note that the VA range is not reclaimed until UvmFree is called on it even if
// all mappings in the created range have been unmapped from all GPUs via this
// API.
//
// Arguments:
// base: (INPUT)
// Base address of the virtual address range.
//
// length: (INPUT)
// The length of the virtual address range.
//
// gpuUuid: (INPUT)
// UUID of the physical GPU if the GPU is not SMC capable or SMC
// enabled, or the GPU instance UUID of the partition to unmap the VA
// range from.
//
// Errors:
// NV_ERR_INVALID_ADDRESS:
// One of the following has occurred:
// - base is NULL.
// - The requested address range does not fall entirely within an
// existing external VA range created with a single call to
// UvmImportDmaBuf.
// - base or base + length fall within an existing mapping but are not
// aligned to that mapping's page size.
//
// NV_ERR_INVALID_DEVICE:
// Either gpuUuid does not represent a valid registered GPU or the VA
// range corresponding to the given base address is not mapped on the
// specified GPU.
//
// NV_ERR_GENERIC:
// Unexpected error. We try hard to avoid returning this error code,
// because it is not very informative.
//
//------------------------------------------------------------------------------
NV_STATUS UvmUnmapDmaBuf(void *base,
NvLength length,
const NvProcessorUuid *gpuUuid);
//------------------------------------------------------------------------------
// UvmMapDynamicParallelismRegion
//
@@ -1980,11 +2177,11 @@ NV_STATUS UvmUnmapExternal(void *base,
// mapping for the GPU. A GPU VA space must have been registered for the GPU and
// the GPU must support dynamic parallelism.
//
// The mapping is created immediately and not modified until a call to UvmFree
// Calling UvmFree frees the GPU page table mapping. The range cannot be
// associated with range groups and any GPU faults within this range are fatal.
// Also, the pages cannot be the target for read duplication, cannot have a
// preferred location set, and cannot have any accessed-by processors.
// The mapping is created immediately and not modified until a call to UvmFree.
// Calling UvmFree frees the GPU page table mapping. Any GPU faults within this
// range are fatal. Also, the pages cannot be the target for read duplication,
// cannot have a preferred location set, and cannot have any accessed-by
// processors.
//
// Note that calling UvmUnregisterGpuVaSpace will also unmap all mappings
// created via this API on the GPU that the GPU VA space is associated with.
@@ -2253,16 +2450,8 @@ NV_STATUS UvmDisableReadDuplication(void *base,
// specified processor.
//
// NV_ERR_INVALID_DEVICE:
// One of the following occurred:
// - preferredLocationUuid is neither the UUID of the CPU nor the UUID
// of a GPU that was registered by this process.
// - At least one page in VA range belongs to a non-migratable range
// group and the specified UUID represents a fault-capable GPU.
// - preferredLocationUuid is the UUID of a non-fault-capable GPU and at
// least one page in the VA range belongs to a non-migratable range
// group and another non-fault-capable GPU is in the accessed-by list
// of the same page but P2P support between both GPUs has not been
// enabled.
// preferredLocationUuid is neither the UUID of the CPU nor the UUID of
// a GPU that was registered by this process.
//
// NV_ERR_INVALID_ARGUMENT:
// One of the following occured:
@@ -2359,9 +2548,7 @@ NV_STATUS UvmUnsetPreferredLocation(void *base,
// permit a mapping to be established, then no mapping is created for that page.
// If a page in the VA range migrates to a new location, then the mapping is
// updated to point to the new location if establishing such a mapping is
// possible. If a page in the VA range is associated with a non-migratable range
// group and the specified processor is a non-fault-capable GPU, then an error
// is returned if the mapping cannot be established.
// possible.
//
// If the specified processor is a GPU and no GPU VA space has been registered
// for it or if the registered GPU VA space gets unregistered, then the policies
@@ -2420,11 +2607,7 @@ NV_STATUS UvmUnsetPreferredLocation(void *base,
//
// NV_ERR_INVALID_DEVICE:
// accessedByUuid is neither the UUID of the CPU nor the UUID of a GPU
// that was registered by this process. Or accessedByUuid is the UUID of
// a non-fault-capable GPU and the VA range is associated with a
// non-migratable range group with a preferred location set to another
// non-fault-capable GPU that doesn't have P2P support enabled with this
// GPU.
// that was registered by this process.
//
// NV_ERR_NO_MEMORY:
// accessedByUuid is a non-fault-capable GPU and there was insufficient
@@ -2691,7 +2874,6 @@ NV_STATUS UvmIs8Supported(NvU32 *is8Supported);
// least that you should expect is that all your session related objects will
// become useless once target process closes Uvm file handle.
//
//
// There are security requirements for this call to be successful. Fortunately,
// after validating a file descriptor, one of the following conditions must
// hold:
@@ -2864,7 +3046,6 @@ NV_STATUS UvmToolsCreateEventQueue_V2(UvmToolsSessionHandle session,
UvmToolsEventQueueDescriptor UvmToolsGetEventQueueDescriptor(UvmToolsEventQueueHandle queue);
//------------------------------------------------------------------------------
// UvmToolsSetNotificationThreshold
//
@@ -2981,7 +3162,6 @@ NV_STATUS UvmToolsEventQueueEnableEvents(UvmToolsEventQueueHandle queue,
NV_STATUS UvmToolsEventQueueDisableEvents(UvmToolsEventQueueHandle queue,
NvU64 eventTypeFlags);
//------------------------------------------------------------------------------
// UvmToolsCreateProcessAggregateCounters
//
@@ -3348,7 +3528,7 @@ NV_STATUS UvmToolsGetProcessorUuidTable_V2(UvmToolsSessionHandle session,
// UvmToolsFlushEvents
//
// Some events, like migrations, which have end timestamps are not immediately
// submitted to queues when they are completed. This call enqueues any
// submitted to queues when they are completed. This call enqueues any
// completed but unenqueued events associated with the session.
//
// Arguments:
+1 -1
View File
@@ -63,7 +63,7 @@ void uvm_hal_ada_arch_init_properties(uvm_parent_gpu_t *parent_gpu)
parent_gpu->access_counters_serialize_clear_ops_by_type = false;
parent_gpu->access_bits_supported = false;
parent_gpu->access_bits_supported = true;
parent_gpu->has_clear_faulted_channel_sw_method = true;
+1 -1
View File
@@ -83,7 +83,7 @@ void uvm_hal_ampere_arch_init_properties(uvm_parent_gpu_t *parent_gpu)
parent_gpu->map_remap_larger_page_promotion = false;
// Note that access bits are only supported GA10x onwards
parent_gpu->access_bits_supported = false;
parent_gpu->access_bits_supported = true;
}
parent_gpu->ats.no_ats_range_required = false;
+9 -5
View File
@@ -1,5 +1,5 @@
/*******************************************************************************
Copyright (c) 2015-2025 NVIDIA Corporation
Copyright (c) 2015-2026 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
@@ -34,10 +34,10 @@
#include "uvm_va_space.h"
#include "nv_uvm_user_types.h"
// This weird number comes from UVM_PREVENT_MIGRATION_RANGE_GROUPS_PARAMS. That
// ioctl is called frequently so we don't want to allocate a copy every time.
// It's a little over 256 bytes in size.
#define UVM_MAX_IOCTL_PARAM_STACK_SIZE 288
// This number comes from UVM_TEST_RANGE_TREE_RANDOM_PARAMS. It cover almost all
// driver and test ioctls. Above this limit, ioctls require two orders of
// magnitude more space. In these cases, the UVM_ROUTE_CMD_ALLOC_* is preferred.
#define UVM_MAX_IOCTL_PARAM_STACK_SIZE 176
// The UVM_ROUTE_CMD_* macros are only intended for use in the ioctl routines
@@ -238,6 +238,10 @@ NV_STATUS uvm_api_disable_peer_access(UVM_DISABLE_PEER_ACCESS_PARAMS *params, st
NV_STATUS uvm_api_create_external_range(UVM_CREATE_EXTERNAL_RANGE_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_map_external_allocation(UVM_MAP_EXTERNAL_ALLOCATION_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_map_external_sparse(UVM_MAP_EXTERNAL_SPARSE_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_is_dma_buf_import_supported(UVM_IS_DMA_BUF_IMPORT_SUPPORTED_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_import_dma_buf(UVM_IMPORT_DMA_BUF_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_map_dma_buf(UVM_MAP_DMA_BUF_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_unmap_dma_buf(UVM_UNMAP_DMA_BUF_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_free(UVM_FREE_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_discard(UVM_DISCARD_PARAMS *params, struct file *filp);
NV_STATUS uvm_api_set_preferred_location(const UVM_SET_PREFERRED_LOCATION_PARAMS *params, struct file *filp);
+9 -20
View File
@@ -1,5 +1,5 @@
/*******************************************************************************
Copyright (c) 2022-2025 NVIDIA Corporation
Copyright (c) 2022-2026 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
@@ -79,7 +79,7 @@ void uvm_hal_blackwell_arch_init_properties(uvm_parent_gpu_t *parent_gpu)
parent_gpu->ats.gmmu_pt_depth0_init_required = parent_gpu->ats.non_pasid_ats_enabled;
parent_gpu->access_bits_supported = false;
parent_gpu->access_bits_supported = true;
// Blackwell has a physical translation prefetcher, meaning SW must assume
// that any physical ATS translation can be fetched at any time. The
@@ -121,29 +121,18 @@ void uvm_hal_blackwell_arch_init_properties(uvm_parent_gpu_t *parent_gpu)
parent_gpu->conf_computing.per_channel_key_rotation = true;
// TODO: Bug 5023085: this should be queried from RM instead of determined
// by UVM.
if (parent_gpu->rm_info.gpuArch == NV2080_CTRL_MC_ARCH_INFO_ARCHITECTURE_GB100 &&
parent_gpu->rm_info.gpuImplementation == NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB10B) {
// If the parent GPU has no vidmem, initialise as an iGPU. This is the
// same property that is queried by NV0000_CTRL_GPU_GET_ID_INFO_V2.
if (parent_gpu->rm_info.gpuArchIsZeroFb) {
parent_gpu->is_integrated_gpu = true;
parent_gpu->access_bits_supported = false;
}
if (parent_gpu->rm_info.gpuArch == NV2080_CTRL_MC_ARCH_INFO_ARCHITECTURE_GB100 &&
parent_gpu->rm_info.gpuImplementation == NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB10B) {
// GB10B has sticky L2 coherent cache lines.
// For details, refer to the comments in uvm_gpu.h
// where this field is declared.
parent_gpu->sticky_l2_coherent_cache_lines = true;
}
if (parent_gpu->rm_info.gpuArch == NV2080_CTRL_MC_ARCH_INFO_ARCHITECTURE_GB200 &&
parent_gpu->rm_info.gpuImplementation == NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB20B) {
parent_gpu->is_integrated_gpu = true;
parent_gpu->access_bits_supported = false;
}
if (parent_gpu->rm_info.gpuArch == NV2080_CTRL_MC_ARCH_INFO_ARCHITECTURE_GB200 &&
(parent_gpu->rm_info.gpuImplementation == NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB206 ||
parent_gpu->rm_info.gpuImplementation == NV2080_CTRL_MC_ARCH_INFO_IMPLEMENTATION_GB207)) {
// TODO: Bug 3186788 : As reported in Bug 5309034, GB206
// and GB207 experience a GSP crash with VAB. Depending
// on whether RM fixes it or marks it as cannot fix, the
// below checks can be removed or retained.
parent_gpu->access_bits_supported = false;
}
}
+12 -5
View File
@@ -48,10 +48,12 @@ uvm_va_space_t *uvm_devmem_page_to_va_space(struct page *page)
// uvm_hmm_unregister_gpu() needs to do a racy check here so
// page->zone_device_data might be NULL.
if (!gpu_chunk || !gpu_chunk->va_block)
if (!gpu_chunk || !gpu_chunk->va_space)
return NULL;
return gpu_chunk->va_block->hmm.va_space;
// Use the cached va_space rather than gpu_chunk->va_block->hmm.va_space
// since the va_block may have been freed while the va_space is still valid.
return gpu_chunk->va_space;
}
// Check there are no orphan pages. This should be only called as part of
@@ -113,10 +115,14 @@ static void devmem_page_free_gpu_chunk(uvm_gpu_chunk_t *chunk)
{
uvm_gpu_t *gpu = uvm_gpu_chunk_get_gpu(chunk);
if (chunk->va_block) {
uvm_va_space_t *va_space = chunk->va_block->hmm.va_space;
UVM_ASSERT(chunk->va_space);
// Use the cached va_space rather than chunk->va_block->hmm.va_space. The
// va_block may already have been freed by the time this page_free() callback
// runs, but the va_space is guaranteed to outlive the device page.
if (chunk->va_space) {
uvm_va_space_t *va_space = chunk->va_space;
UVM_ASSERT(va_space);
atomic64_dec(&va_space->hmm.allocated_page_count);
UVM_ASSERT(atomic64_read(&va_space->hmm.allocated_page_count) >= 0);
}
@@ -130,6 +136,7 @@ static void devmem_page_free_gpu_chunk(uvm_gpu_chunk_t *chunk)
UVM_ASSERT(chunk->is_referenced);
chunk->va_block = NULL;
chunk->va_space = NULL;
chunk->is_referenced = false;
if (chunk->state == UVM_PMM_GPU_CHUNK_STATE_ALLOCATED) {
+313
View File
@@ -0,0 +1,313 @@
/*******************************************************************************
Copyright (c) 2015-2026 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_linux.h"
#include "nv-kref.h"
#include "uvm_test.h"
#include "uvm_test_ioctl.h"
#include "uvm_kvmalloc.h"
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
#include <linux/dma-resv.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
// The struct dma_buf doesn't tracking the backing store.
typedef struct {
struct page **pages;
unsigned long num_pages;
size_t size;
} dma_buf_priv_t;
// The dma_buf_attach() and dma_buf_map_attachment() return but don't
// store the scatter gather table of DMA mappings.
typedef struct {
struct sg_table *sg_table;
enum dma_data_direction direction;
} attachment_priv_t;
static int sysmem_attach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attach)
{
attachment_priv_t *attch_priv;
attch_priv = uvm_kvmalloc_zero(sizeof(*attch_priv));
if (!attch_priv)
return -ENOMEM;
attach->priv = attch_priv;
return 0;
}
static void sysmem_detach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attach)
{
uvm_kvfree(attach->priv);
attach->priv = NULL;
}
static struct sg_table *sysmem_map(struct dma_buf_attachment *attach,
enum dma_data_direction direction)
{
dma_buf_priv_t *dmabuf_priv = attach->dmabuf->priv;
attachment_priv_t *attach_priv = attach->priv;
struct sg_table *sg_table;
int ret;
sg_table = uvm_kvmalloc_zero(sizeof(*sg_table));
if (!sg_table)
return ERR_PTR(-ENOMEM);
// Merges adjacent pages into fewer sg entries where possible.
ret = sg_alloc_table_from_pages(sg_table,
dmabuf_priv->pages,
dmabuf_priv->num_pages,
0,
dmabuf_priv->size,
GFP_KERNEL);
if (ret) {
uvm_kvfree(sg_table);
return ERR_PTR(ret);
}
ret = dma_map_sgtable(attach->dev,
sg_table,
direction,
0);
if (ret) {
sg_free_table(sg_table);
uvm_kvfree(sg_table);
return ERR_PTR(ret);
}
attach_priv->sg_table = sg_table;
attach_priv->direction = direction;
return sg_table;
}
static void sysmem_unmap(struct dma_buf_attachment *attach,
struct sg_table *sg_table,
enum dma_data_direction direction)
{
attachment_priv_t *attach_priv = attach->priv;
attach_priv->sg_table = NULL;
dma_unmap_sgtable(attach->dev, sg_table, direction, 0);
sg_free_table(sg_table);
uvm_kvfree(sg_table);
}
static int sysmem_mmap(struct dma_buf *dmabuf,
struct vm_area_struct *vma)
{
dma_buf_priv_t *dmabuf_priv = dmabuf->priv;
int i, ret;
if (vma_pages(vma) > dmabuf_priv->num_pages)
return -EINVAL;
for (i = 0; i < vma_pages(vma); i++) {
ret = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE,
dmabuf_priv->pages[i]);
if (ret)
return ret;
}
return 0;
}
static int sysmem_begin_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct dma_buf_attachment *attach;
dma_resv_lock(dmabuf->resv, NULL);
list_for_each_entry(attach, &dmabuf->attachments, node) {
attachment_priv_t *attach_priv = attach->priv;
if (attach_priv->sg_table)
dma_sync_sgtable_for_cpu(attach->dev, attach_priv->sg_table, direction);
}
dma_resv_unlock(dmabuf->resv);
return 0;
}
static int sysmem_end_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct dma_buf_attachment *attach;
dma_resv_lock(dmabuf->resv, NULL);
list_for_each_entry(attach, &dmabuf->attachments, node) {
attachment_priv_t *attach_priv = attach->priv;
if (attach_priv->sg_table)
dma_sync_sgtable_for_device(attach->dev, attach_priv->sg_table, direction);
}
dma_resv_unlock(dmabuf->resv);
return 0;
}
static void sysmem_release(struct dma_buf *dmabuf)
{
int i;
dma_buf_priv_t *dmabuf_priv = dmabuf->priv;
if (!dmabuf_priv->pages) {
uvm_kvfree(dmabuf_priv);
return;
}
for (i = 0; i < dmabuf_priv->num_pages; i++)
__free_page(dmabuf_priv->pages[i]);
uvm_kvfree(dmabuf_priv->pages);
dmabuf_priv->pages = NULL;
dmabuf_priv->num_pages = 0;
uvm_kvfree(dmabuf_priv);
}
static const struct dma_buf_ops uvm_sysmem_ops = {
.attach = sysmem_attach,
.detach = sysmem_detach,
.map_dma_buf = sysmem_map,
.unmap_dma_buf = sysmem_unmap,
.mmap = sysmem_mmap,
.begin_cpu_access = sysmem_begin_cpu_access,
.end_cpu_access = sysmem_end_cpu_access,
.release = sysmem_release,
};
NV_STATUS uvm_test_export_dma_buf(UVM_TEST_EXPORT_DMA_BUF_PARAMS *params,
struct file *filp)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
dma_buf_priv_t *dmabuf_priv;
struct dma_buf *dmabuf;
unsigned long i;
int fd;
if (!params->size || !IS_ALIGNED(params->size, PAGE_SIZE))
return NV_ERR_INVALID_ARGUMENT;
dmabuf_priv = uvm_kvmalloc_zero(sizeof(*dmabuf_priv));
if (!dmabuf_priv)
return NV_ERR_NO_MEMORY;
dmabuf_priv->size = params->size;
dmabuf_priv->num_pages = params->size >> PAGE_SHIFT;
dmabuf_priv->pages = uvm_kvmalloc_zero(dmabuf_priv->num_pages * sizeof(*dmabuf_priv->pages));
if (!dmabuf_priv->pages) {
uvm_kvfree(dmabuf_priv);
return NV_ERR_NO_MEMORY;
}
for (i = 0; i < dmabuf_priv->num_pages; i++) {
dmabuf_priv->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!dmabuf_priv->pages[i]) {
while (i--)
__free_page(dmabuf_priv->pages[i]);
uvm_kvfree(dmabuf_priv->pages);
uvm_kvfree(dmabuf_priv);
return NV_ERR_NO_MEMORY;
}
}
exp_info.ops = &uvm_sysmem_ops;
exp_info.size = dmabuf_priv->size;
exp_info.flags = O_RDWR | O_CLOEXEC;
exp_info.priv = dmabuf_priv;
dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf)) {
UVM_ERR_PRINT("dma_buf_export failed: %ld\n", PTR_ERR(dmabuf));
for (i = 0; i < dmabuf_priv->num_pages; i++)
__free_page(dmabuf_priv->pages[i]);
uvm_kvfree(dmabuf_priv->pages);
uvm_kvfree(dmabuf_priv);
return NV_ERR_OPERATING_SYSTEM;
}
fd = dma_buf_fd(dmabuf, O_CLOEXEC);
if (fd < 0) {
UVM_ERR_PRINT("dma_buf_fd failed: %d\n", fd);
dma_buf_put(dmabuf);
return NV_ERR_OPERATING_SYSTEM;
}
params->fd = fd;
return NV_OK;
}
NV_STATUS uvm_test_revoke_dma_buf(UVM_TEST_REVOKE_DMA_BUF_PARAMS *params,
struct file *filp)
{
struct dma_buf *dmabuf;
dmabuf = dma_buf_get(params->fd);
if (IS_ERR(dmabuf))
return NV_ERR_INVALID_ARGUMENT;
if (dmabuf->ops != &uvm_sysmem_ops) {
dma_buf_put(dmabuf);
return NV_ERR_INVALID_ARGUMENT;
}
// Notify importers that the DMA-BUF is being revoked. The callback,
// invalidate_mappings() is expected to synchronously provide a new mapping,
// but in UVM's use case all GMMU mappings will be withdrawn and any
// further access should produce a fatal fault.
dma_resv_lock(dmabuf->resv, NULL);
#if defined(NV_DMA_BUF_ATTACH_OPS_HAS_INVALIDATE_MAPPINGS)
dma_buf_invalidate_mappings(dmabuf);
#else
dma_buf_move_notify(dmabuf);
#endif
dma_resv_unlock(dmabuf->resv);
dma_buf_put(dmabuf);
return NV_OK;
}
#else
NV_STATUS uvm_test_export_dma_buf(UVM_TEST_EXPORT_DMA_BUF_PARAMS *params,
struct file *filp)
{
return NV_ERR_NOT_SUPPORTED;
}
NV_STATUS uvm_test_revoke_dma_buf(UVM_TEST_REVOKE_DMA_BUF_PARAMS *params,
struct file *filp)
{
return NV_ERR_NOT_SUPPORTED;
}
#endif // UVM_PROVIDES_DMA_BUF_IMPORTER()
@@ -58,6 +58,7 @@ typedef struct uvm_va_policy_struct uvm_va_policy_t;
typedef struct uvm_va_range_struct uvm_va_range_t;
typedef struct uvm_va_range_managed_struct uvm_va_range_managed_t;
typedef struct uvm_va_range_external_struct uvm_va_range_external_t;
typedef struct uvm_va_range_dma_buf_struct uvm_va_range_dma_buf_t;
typedef struct uvm_va_range_channel_struct uvm_va_range_channel_t;
typedef struct uvm_va_range_sked_reflected_struct uvm_va_range_sked_reflected_t;
typedef struct uvm_va_range_semaphore_pool_struct uvm_va_range_semaphore_pool_t;
+12 -8
View File
@@ -43,7 +43,16 @@
#include "uvm_devmem.h"
uvm_global_t g_uvm_global;
static struct UvmEventsLinux g_exported_uvm_events;
static struct UvmEventsLinux g_exported_uvm_events =
{
.isrTopHalf = uvm_isr_top_half_entry,
.suspend = uvm_suspend_entry,
.resume = uvm_resume_entry,
.drainP2P = uvm_suspend_and_drainP2P_entry,
.resumeP2P = uvm_resumeP2P_entry,
};
static bool g_ops_registered = false;
unsigned uvm_force_conf_computing = 0;
@@ -53,12 +62,6 @@ static NV_STATUS uvm_register_callbacks(void)
{
NV_STATUS status = NV_OK;
g_exported_uvm_events.isrTopHalf = uvm_isr_top_half_entry;
g_exported_uvm_events.suspend = uvm_suspend_entry;
g_exported_uvm_events.resume = uvm_resume_entry;
g_exported_uvm_events.drainP2P = uvm_suspend_and_drainP2P_entry;
g_exported_uvm_events.resumeP2P = uvm_resumeP2P_entry;
// Register the UVM callbacks with the main GPU driver:
status = uvm_rm_locked_call(nvUvmInterfaceRegisterUvmEvents(&g_exported_uvm_events));
if (status != NV_OK)
@@ -117,7 +120,7 @@ NV_STATUS uvm_global_init(void)
status = uvm_rm_locked_call(nvUvmInterfaceSessionCreate(&g_uvm_global.rm_session_handle, &platform_info));
if (status != NV_OK) {
UVM_ERR_PRINT("nvUvmInterfaceSessionCreate() failed: %s\n", nvstatusToString(status));
return status;
goto error;
}
uvm_ats_init(&platform_info);
@@ -129,6 +132,7 @@ NV_STATUS uvm_global_init(void)
g_uvm_global.hw_conf_computing_enabled = platform_info.confComputingEnabled;
g_uvm_global.conf_computing_enabled = g_uvm_global.hw_conf_computing_enabled ||
uvm_force_conf_computing;
g_uvm_global.have_non_ats_devices = platform_info.nonAtsDevicePresent;
status = uvm_processor_mask_cache_init();
if (status != NV_OK) {
+3
View File
@@ -125,6 +125,9 @@ struct uvm_global_struct
bool cdmm_enabled;
// Indicates whether there is a non-ATS GPU in the system.
bool have_non_ats_devices;
// List of all active VA spaces
struct
{
+155 -17
View File
@@ -46,6 +46,9 @@
#include "uvm_linux.h"
#include "uvm_mmu.h"
#include "uvm_kvmalloc.h"
#if UVM_USE_DMA_IOVA_API()
#include <linux/dma-mapping.h>
#endif
#define UVM_PROC_GPUS_PEER_DIR_NAME "peers"
@@ -123,7 +126,13 @@ static void fill_parent_gpu_info(uvm_parent_gpu_t *parent_gpu, const UvmGpuInfo
// 47-bit address space holds the routing information for each peer.
// Currently, this is limited to a 16GB framebuffer window size.
parent_gpu->nvswitch_info.fabric_memory_window_start = gpu_info->nvswitchMemoryWindowStart;
parent_gpu->nvswitch_info.egm_fabric_memory_window_start = gpu_info->nvswitchEgmMemoryWindowStart;
if (gpu_info->flaWindowForCpuMemNode.bSupported) {
parent_gpu->egm.fabric_memory_window_start = gpu_info->flaWindowForCpuMemNode.flaStart;
parent_gpu->egm.window_size = gpu_info->flaWindowForCpuMemNode.flaSize;
}
else {
parent_gpu->egm.fabric_memory_window_start = gpu_info->nvswitchEgmMemoryWindowStart;
}
}
parent_gpu->ats.non_pasid_ats_enabled = gpu_info->nonPasidAtsSupport;
@@ -1470,9 +1479,23 @@ static NV_STATUS init_parent_gpu(uvm_parent_gpu_t *parent_gpu,
parent_gpu->closest_cpu_numa_node = dev_to_node(&parent_gpu->pci_dev->dev);
parent_gpu->dma_addressable_start = gpu_platform_info->dma_addressable_start;
parent_gpu->dma_addressable_limit = gpu_platform_info->dma_addressable_limit;
parent_gpu->egm.enabled = gpu_info->egmEnabled;
// RM considers gpu_info->egmEnabled as "legacy" EGM and FLA EGM as
// EGM over FLA. Therefore, the two settings are mutually exclusive.
// However, it would make it easier for us to gate EGM (both styles)
// using a single variable.
UVM_ASSERT(!(gpu_info->egmEnabled && gpu_info->flaWindowForCpuMemNode.bSupported));
parent_gpu->egm.enabled = gpu_info->egmEnabled || gpu_info->flaWindowForCpuMemNode.bSupported;
parent_gpu->egm.local_peer_id = gpu_info->egmPeerId;
parent_gpu->egm.base_address = gpu_info->egmBaseAddr;
parent_gpu->egm.legacy = !gpu_info->flaWindowForCpuMemNode.bSupported;
if (!parent_gpu->egm.legacy) {
UVM_ASSERT(parent_gpu->rm_info.gpuArch >= NV2080_CTRL_MC_ARCH_INFO_ARCHITECTURE_GR100);
parent_gpu->egm.base_address = gpu_info->flaWindowForCpuMemNode.dmaAddrBase;
parent_gpu->egm.identity_iommu = gpu_info->flaWindowForCpuMemNode.bIdentityDmaMap;
}
else {
parent_gpu->egm.base_address = gpu_info->egmBaseAddr;
}
parent_gpu->access_counters_supported = (gpu_info->accessCntrBufferCount != 0);
status = uvm_rm_locked_call(nvUvmInterfaceGetFbInfo(parent_gpu->rm_device, &fb_info));
@@ -2464,17 +2487,24 @@ static NV_STATUS parent_peers_init(uvm_parent_gpu_t *parent_gpu0,
parent_peer_caps->link_type = link_type;
parent_peer_caps->total_link_line_rate_mbyte_per_s = p2p_caps_params.totalLinkLineRateMBps;
if (parent_gpu0->egm.enabled || parent_gpu1->egm.enabled)
if (uvm_parent_gpu_egm_enabled(parent_gpu0) || uvm_parent_gpu_egm_enabled(parent_gpu1))
UVM_ASSERT(parent_peer_caps->link_type >= UVM_GPU_LINK_NVLINK_2);
// Initialize peer ids and establish peer mappings
// Peer id from min(gpu_id0, gpu_id1) -> max(gpu_id0, gpu_id1)
parent_peer_caps->peer_ids[0] = p2p_caps_params.peerIds[0];
parent_peer_caps->egm_peer_ids[0] = p2p_caps_params.egmPeerIds[0];
// Peer id from max(gpu_id0, gpu_id1) -> min(gpu_id0, gpu_id1)
parent_peer_caps->peer_ids[1] = p2p_caps_params.peerIds[1];
parent_peer_caps->egm_peer_ids[1] = p2p_caps_params.egmPeerIds[1];
// RM does not set egmPeerIds for Rubin+ where the EGM carveout is no
// longer supported. Intead, it uses the normal peer IDs for FLA EGM.
if (!uvm_parent_gpu_egm_is_legacy(parent_gpu0) || !uvm_parent_gpu_egm_is_legacy(parent_gpu1)) {
parent_peer_caps->egm_peer_ids[0] = parent_peer_caps->peer_ids[0];
parent_peer_caps->egm_peer_ids[1] = parent_peer_caps->peer_ids[1];
}
else {
parent_peer_caps->egm_peer_ids[0] = p2p_caps_params.egmPeerIds[0];
parent_peer_caps->egm_peer_ids[1] = p2p_caps_params.egmPeerIds[1];
}
parent_peer_caps->optimalNvlinkWriteCEs[0] = p2p_caps_params.optimalNvlinkWriteCEs[0];
parent_peer_caps->optimalNvlinkWriteCEs[1] = p2p_caps_params.optimalNvlinkWriteCEs[1];
@@ -3286,12 +3316,15 @@ uvm_aperture_t uvm_gpu_egm_peer_aperture(uvm_parent_gpu_t *local_gpu, uvm_parent
uvm_parent_gpu_peer_t *peer_caps;
NvU8 peer_id;
if (local_gpu == remote_gpu) {
UVM_ASSERT(local_gpu->egm.enabled);
if (uvm_parent_id_equal(local_gpu->id, remote_gpu->id)) {
if (!uvm_parent_gpu_egm_is_legacy(local_gpu))
return UVM_APERTURE_SYS;
UVM_ASSERT(uvm_parent_gpu_egm_enabled(local_gpu));
peer_id = local_gpu->egm.local_peer_id;
}
else {
UVM_ASSERT(remote_gpu->egm.enabled);
UVM_ASSERT(uvm_parent_gpu_egm_enabled(remote_gpu));
peer_caps = parent_gpu_peer_caps(local_gpu, remote_gpu);
@@ -3302,7 +3335,22 @@ uvm_aperture_t uvm_gpu_egm_peer_aperture(uvm_parent_gpu_t *local_gpu, uvm_parent
peer_id = peer_caps->egm_peer_ids[1];
}
return UVM_APERTURE_PEER(peer_id);
return UVM_APERTURE_EGM_PEER(peer_id);
}
NV_STATUS uvm_test_query_egm_state(UVM_TEST_QUERY_EGM_STATE_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
uvm_gpu_t *gpu;
gpu = uvm_va_space_retain_gpu_by_uuid(va_space, &params->gpu_uuid);
if (!gpu)
return NV_ERR_INVALID_DEVICE;
params->enabled = uvm_parent_gpu_egm_enabled(gpu->parent);
uvm_gpu_release(gpu);
return NV_OK;
}
NvU64 uvm_gpu_peer_ref_count(const uvm_gpu_t *gpu0, const uvm_gpu_t *gpu1)
@@ -3387,18 +3435,22 @@ bool uvm_gpu_address_is_peer(uvm_gpu_t *gpu, uvm_gpu_address_t address)
}
}
else {
if (uvm_aperture_is_peer(address.aperture)) {
if (uvm_aperture_is_peer(address.aperture))
return true;
if (uvm_aperture_is_egm(address.aperture)) {
uvm_parent_processor_mask_t peer_parent_gpus;
uvm_parent_gpu_t *peer_parent_gpu;
// Local EGM accesses don't go over NVLINK
if (gpu->parent->egm.enabled && address.aperture == gpu->parent->egm.local_peer_id)
if (uvm_parent_gpu_egm_enabled(gpu->parent) && uvm_parent_gpu_egm_is_legacy(gpu->parent) &&
UVM_APERTURE_EGM_PEER_ID(address.aperture) == gpu->parent->egm.local_peer_id)
return false;
uvm_spin_lock(&gpu->peer_info.peer_gpu_lock);
uvm_parent_gpus_from_processor_mask(&peer_parent_gpus, &gpu->peer_info.peer_gpu_mask);
for_each_parent_gpu_in_mask(peer_parent_gpu, &peer_parent_gpus) {
if (!peer_parent_gpu->egm.enabled)
if (!uvm_parent_gpu_egm_enabled(peer_parent_gpu))
continue;
// EGM uses peer IDs but they are different from VIDMEM peer
@@ -3976,12 +4028,13 @@ NV_STATUS uvm_gpu_map_cpu_pages_no_invalidate(uvm_gpu_t *gpu, struct page *page,
UVM_ASSERT(PAGE_ALIGNED(size));
// This is also the path we take when EGM is enabled but the
// IOMMU is OFF or in IDENTITY domain.
dma_addr = dma_map_page(&parent_gpu->pci_dev->dev, page, 0, size, DMA_BIDIRECTIONAL);
if (dma_mapping_error(&parent_gpu->pci_dev->dev, dma_addr))
return NV_ERR_OPERATING_SYSTEM;
if (dma_addr < parent_gpu->dma_addressable_start ||
dma_addr + size - 1 > parent_gpu->dma_addressable_limit) {
if (dma_addr < parent_gpu->dma_addressable_start || dma_addr + size - 1 > parent_gpu->dma_addressable_limit) {
dma_unmap_page(&parent_gpu->pci_dev->dev, dma_addr, size, DMA_BIDIRECTIONAL);
UVM_ERR_PRINT_RL("PCI mapped range [0x%llx, 0x%llx) not in the addressable range [0x%llx, 0x%llx), GPU %s\n",
dma_addr,
@@ -3994,7 +4047,73 @@ NV_STATUS uvm_gpu_map_cpu_pages_no_invalidate(uvm_gpu_t *gpu, struct page *page,
atomic64_add(size, &parent_gpu->mapped_cpu_pages_size);
*dma_address_out = uvm_parent_gpu_dma_addr_to_gpu_addr(parent_gpu, dma_addr);
return NV_OK;
}
NV_STATUS uvm_gpu_map_cpu_pages_for_egm(uvm_parent_gpu_t *parent_gpu,
struct page *page,
size_t size,
NvU64 *dma_address_out)
{
unsigned long node_start = node_start_pfn(page_to_nid(page)) << PAGE_SHIFT;
dma_addr_t dma_addr = 0;
NV_STATUS status = NV_OK;
// This function only actually needs the parent GPU, but it takes in the
// sub GPU for API symmetry with uvm_gpu_map_cpu_pages().
UVM_ASSERT(PAGE_ALIGNED(size));
*dma_address_out = dma_addr;
// If EGM is not enabled, we don't create any mappings. They shouldn't
// be needed in such a case anyway.
// If EGM is enabled but it's legacy EGM, we also don't need a mapping
// because the EGM PA is based on the page's physical address in
// SYSMEM.
if (!uvm_parent_gpu_egm_enabled(parent_gpu) || uvm_parent_gpu_egm_is_legacy(parent_gpu))
return NV_OK;
if (uvm_parent_gpu_egm_iommu_is_identity(parent_gpu)) {
phys_addr_t phys_addr = page_to_phys(page);
dma_addr = phys_addr - node_start;
}
else {
#if UVM_USE_DMA_IOVA_API()
// Note that the check below also implies kernels >= 6.17+, where
// the DMA IOVA API is available. RM will only enable EGM with FLA
// mappings and create DMA IOVA windows only on those kernel.
struct dma_iova_state state = {.addr = uvm_parent_gpu_egm_iova_address(parent_gpu),
.__size = uvm_parent_gpu_egm_window_size(parent_gpu)};
phys_addr_t phys_addr = page_to_phys(page);
NvU64 offset = phys_addr - node_start;
int ret;
UVM_ASSERT(page_to_nid(page) == parent_gpu->closest_cpu_numa_node);
UVM_ASSERT(size <= uvm_parent_gpu_egm_window_size(parent_gpu));
ret = dma_iova_link(&parent_gpu->pci_dev->dev, &state, phys_addr, offset, size, DMA_BIDIRECTIONAL, 0);
if (ret != 0) {
status = errno_to_nv_status(ret);
goto done;
}
ret = dma_iova_sync(&parent_gpu->pci_dev->dev, &state, offset, size);
if (ret != 0) {
dma_iova_unlink(&parent_gpu->pci_dev->dev, &state, offset, size, DMA_BIDIRECTIONAL, 0);
status = errno_to_nv_status(ret);
goto done;
}
dma_addr = offset;
atomic64_add(size, &parent_gpu->mapped_cpu_pages_size);
done:
#else
status = NV_ERR_NOT_SUPPORTED;
#endif
}
*dma_address_out = dma_addr;
return NV_OK;
}
@@ -4018,10 +4137,29 @@ void uvm_parent_gpu_unmap_cpu_pages(uvm_parent_gpu_t *parent_gpu, NvU64 dma_addr
UVM_ASSERT(PAGE_ALIGNED(size));
dma_address = gpu_addr_to_dma_addr(parent_gpu, dma_address);
dma_unmap_page(&parent_gpu->pci_dev->dev, dma_address, size, DMA_BIDIRECTIONAL);
atomic64_sub(size, &parent_gpu->mapped_cpu_pages_size);
}
void uvm_parent_gpu_unmap_cpu_pages_for_egm(uvm_parent_gpu_t *parent_gpu, NvU64 dma_address, size_t size)
{
if (!uvm_parent_gpu_egm_enabled(parent_gpu) || uvm_parent_gpu_egm_is_legacy(parent_gpu))
return;
#if UVM_USE_DMA_IOVA_API()
if (!uvm_parent_gpu_egm_iommu_is_identity(parent_gpu)) {
// See comment in uvm_gpu_map_cpu_pages_no_invalidate.
struct dma_iova_state state = {.addr = uvm_parent_gpu_egm_iova_address(parent_gpu),
.__size = uvm_parent_gpu_egm_window_size(parent_gpu)};
dma_iova_unlink(&parent_gpu->pci_dev->dev, &state, dma_address, size, DMA_BIDIRECTIONAL, 0);
atomic64_sub(size, &parent_gpu->mapped_cpu_pages_size);
}
#endif
return;
}
// This function implements the UvmRegisterGpu API call, as described in uvm.h.
// Notes:
//
+79 -10
View File
@@ -1288,10 +1288,6 @@ struct uvm_parent_gpu_struct
// 47-bit fabric memory physical offset that peer gpus need to access
// to read a peer's memory
NvU64 fabric_memory_window_start;
// 47-bit fabric memory physical offset that peer gpus need to access
// to read remote EGM memory.
NvU64 egm_fabric_memory_window_start;
} nvswitch_info;
struct
@@ -1351,15 +1347,34 @@ struct uvm_parent_gpu_struct
// Is EGM support enabled on this GPU.
bool enabled;
// Legacy EGM uses GPA GMMU mappings while non-legacy uses FLA.
bool legacy;
// Local EGM peer ID. This ID is used to route EGM memory accesses to
// the local CPU socket.
// the local CPU socket. Only, valid/used for legacy EGM.
NvU8 local_peer_id;
// EGM base address of the EGM carveout for remote EGM accesses.
// The base address is used when computing PTE PA address values for
// accesses to the local CPU socket's EGM memory from other peer
// GPUs.
NvU64 base_address;
// For non-legacy EGM, this is the base of the DMA mapping created
// by RM for the SYSMEM window of the NUMA node to which the GPU
// is attached.
dma_addr_t base_address;
// Size of EGM memory window. Effectively, this is the size of the
// DMA mapping created by RM.
size_t window_size;
// True if IOMMU is off or IOMMU_DOMAIN_IDENTITY type.
bool identity_iommu;
// 47-bit fabric memory physical offset that peer gpus need to access
// to read remote EGM memory.
// This field is also used for FLA mappings. It holds the base FLA
// address for each peer GPU.
NvU64 fabric_memory_window_start;
} egm;
// Peer VIDMEM base offset used when creating GPA PTEs for
@@ -1617,14 +1632,16 @@ static bool uvm_parent_gpu_supports_full_coherence(uvm_parent_gpu_t *parent_gpu)
// so pageable memory which uses ZONE_DEVICE can only be migrated to GPUs which
// have neither property.
//
// CDMM GPUs use a separate devmem mechanism that does not rely on
// DEVICE_PRIVATE pages, so UVM_CAN_USE_DEVICE_PRIVATE_MEMREMAP_PAGES() does
// not apply to them — uvm_devmem_init() handles this distinction internally.
// CDMM GPUs use a separate devmem mechanism that does not rely on HMM or
// DEVICE_PRIVATE pages. Other GPUs only support devmem when HMM is enabled
// system-wide and DEVICE_PRIVATE pages can be used.
static bool uvm_gpu_supports_devmem(uvm_gpu_t *gpu)
{
if (gpu->mem_info.numa.enabled || gpu->parent->is_integrated_gpu)
return false;
return gpu->parent->cdmm_enabled || UVM_CAN_USE_DEVICE_PRIVATE_MEMREMAP_PAGES();
return gpu->parent->cdmm_enabled ||
(uvm_hmm_is_enabled_system_wide() && UVM_CAN_USE_DEVICE_PRIVATE_MEMREMAP_PAGES());
}
// Returns a GPU peer pair index in the range [0 .. UVM_MAX_UNIQUE_GPU_PAIRS).
@@ -1788,9 +1805,59 @@ NV_STATUS uvm_gpu_map_cpu_pages(uvm_gpu_t *gpu, struct page *page, size_t size,
// uvm_hal_tlb_invalidate_phys().
NV_STATUS uvm_gpu_map_cpu_pages_no_invalidate(uvm_gpu_t *gpu, struct page *page, size_t size, NvU64 *dma_address_out);
// Create IOVA mappings for EGM accesses.
// Please note that the GPU passed here is not the accessing GPU
// but the routing GPU.
// Note that this function take in a parent GPU unlike other mapping
// functions.
//
// Locking: The needs to have retained the parent GPU. The block
// lock for the black that is being mapped should be held.
NV_STATUS uvm_gpu_map_cpu_pages_for_egm(uvm_parent_gpu_t *parent_gpu,
struct page *page,
size_t size,
NvU64 *dma_address_out);
// Unmap num_pages pages previously mapped with uvm_gpu_map_cpu_pages().
void uvm_parent_gpu_unmap_cpu_pages(uvm_parent_gpu_t *parent_gpu, NvU64 dma_address, size_t size);
static bool uvm_parent_gpu_egm_enabled(uvm_parent_gpu_t *parent_gpu)
{
return parent_gpu->egm.enabled;
}
static bool uvm_parent_gpu_egm_iommu_is_identity(uvm_parent_gpu_t *parent_gpu)
{
return parent_gpu->egm.identity_iommu;
}
static bool uvm_parent_gpu_egm_is_legacy(uvm_parent_gpu_t *parent_gpu)
{
return parent_gpu->egm.legacy;
}
static dma_addr_t uvm_parent_gpu_egm_iova_address(uvm_parent_gpu_t *parent_gpu)
{
return parent_gpu->egm.base_address;
}
static NvU64 uvm_parent_gpu_egm_fabric_address(uvm_parent_gpu_t *parent_gpu)
{
return parent_gpu->egm.fabric_memory_window_start;
}
static size_t uvm_parent_gpu_egm_window_size(uvm_parent_gpu_t *parent_gpu)
{
return parent_gpu->egm.window_size;
}
// Destroy IOVA mappings for EGM.
// Just like with uvm_vapu_map_cpu_pages_for_egm(), the parent GPU
// passed here is the routing GPU.
//
// Locking: Same as uvm_parent_gpu_map_cpu_pages_for_egm()
void uvm_parent_gpu_unmap_cpu_pages_for_egm(uvm_parent_gpu_t *parent_gpu, NvU64 dma_address, size_t size);
static NV_STATUS uvm_gpu_map_cpu_page(uvm_gpu_t *gpu, struct page *page, NvU64 *dma_address_out)
{
return uvm_gpu_map_cpu_pages(gpu, page, PAGE_SIZE, dma_address_out);
@@ -1918,4 +1985,6 @@ typedef enum
// PCIe BAR containing static framebuffer memory mappings for PCIe P2P
int uvm_device_p2p_static_bar(uvm_parent_gpu_t *gpu);
NV_STATUS uvm_test_query_egm_state(UVM_TEST_QUERY_EGM_STATE_PARAMS *params, struct file *filp);
#endif // __UVM_GPU_H__
+135 -69
View File
@@ -27,6 +27,7 @@
#include "uvm_api.h"
#include "uvm_gpu.h"
#include "uvm_hal.h"
#include "uvm_hmm.h"
#include "uvm_kvmalloc.h"
#include "uvm_tools.h"
#include "uvm_va_block.h"
@@ -1126,7 +1127,8 @@ static NV_STATUS service_va_block_locked(uvm_gpu_t *gpu,
uvm_va_block_t *va_block,
uvm_va_block_retry_t *va_block_retry,
uvm_service_block_context_t *service_context,
uvm_page_mask_t *accessed_pages)
uvm_page_mask_t *accessed_pages,
bool *did_migrate)
{
NV_STATUS status = NV_OK;
uvm_page_index_t page_index;
@@ -1139,17 +1141,24 @@ static NV_STATUS service_va_block_locked(uvm_gpu_t *gpu,
uvm_assert_mutex_locked(&va_block->lock);
// GPU VA space could be gone since we received the notification. We handle
// this case by skipping service if processor is not in the mapped mask.
// Using this approach we also filter out notifications for pages that
// moved since they were reported by the GPU. This is fine because:
// Filter out notifications for pages that moved since they were reported by
// the GPU. This is fine because:
// - If the GPU is still accessing them, it should have faulted
// - If the GPU gets remote mappings in the future, we will get new
// notifications and we will act accordingly
// - If the GPU does not access the pages again, we do not want to migrate
// them
if (!uvm_processor_mask_test(&va_block->mapped, processor))
return NV_OK;
//
// Skip this check for coherent GPUs since the coherent GPU would never be
// in the mapped list of the va_block.
//
// gpu_va_space is obtained under va_space lock with uvm_gpu_va_space_get().
// gpu_va_space can't be destroyed from under us.This code is only reached
// iff gpu_va_space is active, valid and non-null.
if (!(uvm_parent_gpu_is_coherent(gpu->parent) && uvm_va_block_is_hmm(va_block)) &&
!uvm_processor_mask_test(&va_block->mapped, processor)) {
return NV_OK;
}
if (uvm_processor_mask_test(&va_block->resident, processor))
residency_mask = uvm_va_block_resident_mask_get(va_block, processor, NUMA_NO_NODE);
@@ -1264,8 +1273,21 @@ static NV_STATUS service_va_block_locked(uvm_gpu_t *gpu,
&policy,
&outer);
}
if (status != NV_OK)
break;
if (status != NV_OK) {
if (uvm_va_block_is_hmm(va_block)) {
// HMM va_blocks may be backed by multiple VMAs with
// different protections.
// uvm_hmm_find_policy_vma_and_outer() can return
// NV_ERR_INVALID_ADDRESS if one of the VMAs doesn't
// even have VM_READ or if there's no backing VMA.
status = NV_OK;
first_page_index++;
continue;
}
else {
break;
}
}
}
service_context->region = uvm_va_block_region(first_page_index, outer);
@@ -1274,6 +1296,11 @@ static NV_STATUS service_va_block_locked(uvm_gpu_t *gpu,
status = uvm_va_block_service_locked(gpu, va_block, va_block_retry, service_context);
if (status != NV_OK)
break;
if (did_migrate &&
!uvm_page_mask_empty(&service_context->block_context->make_resident.pages_changed_residency)) {
*did_migrate = true;
}
}
}
}
@@ -1294,12 +1321,15 @@ static NV_STATUS service_va_block_locked(uvm_gpu_t *gpu,
static NV_STATUS service_notification_va_block_helper(struct mm_struct *mm,
uvm_va_block_t *va_block,
uvm_gpu_t *gpu,
uvm_access_counter_service_batch_context_t *batch_context)
uvm_access_counter_service_batch_context_t *batch_context,
bool *did_migrate)
{
uvm_va_block_retry_t va_block_retry;
uvm_page_mask_t *accessed_pages = &batch_context->accessed_pages;
uvm_service_block_context_t *service_context = &batch_context->block_service_context;
*did_migrate = false;
if (uvm_page_mask_empty(accessed_pages))
return NV_OK;
@@ -1314,10 +1344,12 @@ static NV_STATUS service_notification_va_block_helper(struct mm_struct *mm,
va_block,
&va_block_retry,
service_context,
accessed_pages));
accessed_pages,
did_migrate));
}
static void expand_notification_block(uvm_gpu_va_space_t *gpu_va_space,
struct mm_struct *mm,
uvm_va_block_t *va_block,
const uvm_access_counter_buffer_t *access_counters,
uvm_va_block_context_t *va_block_context,
@@ -1326,10 +1358,10 @@ static void expand_notification_block(uvm_gpu_va_space_t *gpu_va_space,
{
NvU64 addr;
NvU64 granularity = 0;
uvm_gpu_t *resident_gpu = NULL;
uvm_processor_id_t resident_id;
uvm_page_index_t page_index;
uvm_gpu_t *gpu = gpu_va_space->gpu;
const bool is_hmm = uvm_va_block_is_hmm(va_block);
config_granularity_to_bytes(access_counters->current_config.rm.granularity, &granularity);
@@ -1347,17 +1379,29 @@ static void expand_notification_block(uvm_gpu_va_space_t *gpu_va_space,
resident_id = uvm_va_block_page_get_closest_resident(va_block, va_block_context, page_index, gpu->id);
// resident_id might be invalid or might already be the same as the GPU
// which received the notification if the memory was already migrated before
// acquiring the locks either during the servicing of previous notifications
// or during faults or because of explicit migrations or if the VA range was
// freed after receiving the notification. Return NV_OK in such cases.
// For managed memory, resident_id might be invalid or might already be the
// same as the GPU which received the notification if the memory was already
// migrated before acquiring the locks either during the servicing of
// previous notifications or during faults or because of explicit migrations
// or if the VA range was freed after receiving the notification. Return
// NV_OK in such cases.
//
// For HMM/CDMM system memory, resident_id might be invalid if there were no
// associated va_blocks before the access counter notifications were
// triggered. This implies resident_id was CPU.
if (is_hmm) {
if (!UVM_ID_IS_VALID(resident_id)) {
NV_STATUS status = uvm_hmm_va_block_update_residency_info(va_block, mm, addr, false);
if (status != NV_OK)
return;
resident_id = uvm_va_block_page_get_closest_resident(va_block, va_block_context, page_index, gpu->id);
}
}
if (!UVM_ID_IS_VALID(resident_id) || uvm_id_equal(resident_id, gpu->id))
return;
if (UVM_ID_IS_GPU(resident_id))
resident_gpu = uvm_gpu_get(resident_id);
if (uvm_va_block_get_physical_size(va_block, resident_id, page_index) != granularity) {
uvm_page_mask_set(accessed_pages, page_index);
}
@@ -1390,6 +1434,7 @@ static NV_STATUS service_notifications_in_block(uvm_gpu_va_space_t *gpu_va_space
struct mm_struct *mm,
uvm_access_counter_buffer_t *access_counters,
uvm_va_block_t *va_block,
struct vm_area_struct *vma,
NvU32 index,
NvU32 *out_index)
{
@@ -1397,12 +1442,16 @@ static NV_STATUS service_notifications_in_block(uvm_gpu_va_space_t *gpu_va_space
NvU32 flags = 0;
NV_STATUS status = NV_OK;
NV_STATUS flags_status;
bool did_migrate = false;
bool has_accessed_pages;
NvU64 end;
uvm_gpu_t *gpu = gpu_va_space->gpu;
uvm_va_space_t *va_space = gpu_va_space->va_space;
uvm_access_counter_service_batch_context_t *batch_context = &access_counters->batch_service_context;
uvm_page_mask_t *accessed_pages = &batch_context->accessed_pages;
uvm_access_counter_buffer_entry_t **notifications = batch_context->notifications;
uvm_service_block_context_t *service_context = &batch_context->block_service_context;
const bool is_hmm = uvm_va_block_is_hmm(va_block);
UVM_ASSERT(va_block);
UVM_ASSERT(index < batch_context->num_notifications);
@@ -1413,6 +1462,19 @@ static NV_STATUS service_notifications_in_block(uvm_gpu_va_space_t *gpu_va_space
uvm_va_block_context_init(service_context->block_context, mm);
if (is_hmm) {
NvU64 base;
uvm_access_counter_buffer_entry_t *current_entry = notifications[index];
NvU64 address = current_entry->address;
uvm_hmm_migrate_begin_wait(va_block);
UVM_ASSERT(vma);
base = UVM_VA_BLOCK_ALIGN_DOWN(address);
end = min(base + UVM_VA_BLOCK_SIZE, (NvU64)vma->vm_end);
}
uvm_mutex_lock(&va_block->lock);
for (i = index; i < batch_context->num_notifications; i++) {
@@ -1422,7 +1484,11 @@ static NV_STATUS service_notifications_in_block(uvm_gpu_va_space_t *gpu_va_space
if (current_entry->va_space != va_space || current_entry->gpu != gpu || address > va_block->end)
break;
if (is_hmm && (address >= end))
break;
expand_notification_block(gpu_va_space,
mm,
va_block,
access_counters,
batch_context->block_service_context.block_context,
@@ -1437,11 +1503,16 @@ static NV_STATUS service_notifications_in_block(uvm_gpu_va_space_t *gpu_va_space
batch_context->block_service_context.access_counters_buffer_index = access_counters->index;
status = service_notification_va_block_helper(mm, va_block, gpu, batch_context);
has_accessed_pages = !uvm_page_mask_empty(accessed_pages);
status = service_notification_va_block_helper(mm, va_block, gpu, batch_context, &did_migrate);
uvm_mutex_unlock(&va_block->lock);
if (status == NV_OK)
if (is_hmm)
uvm_hmm_migrate_finish(va_block);
if (status == NV_OK && (!is_hmm || !has_accessed_pages || did_migrate))
flags |= UVM_ACCESS_COUNTER_ACTION_BATCH_CLEAR;
flags_status = notify_tools_and_process_flags(va_space,
@@ -1589,12 +1660,7 @@ static NV_STATUS service_notifications_batch(uvm_gpu_va_space_t *gpu_va_space,
}
if (va_block) {
status = service_notifications_in_block(gpu_va_space,
mm,
access_counters,
va_block,
index,
out_index);
status = service_notifications_in_block(gpu_va_space, mm, access_counters, va_block, NULL, index, out_index);
}
else {
status = notify_tools_and_process_flags(va_space,
@@ -1608,56 +1674,56 @@ static NV_STATUS service_notifications_batch(uvm_gpu_va_space_t *gpu_va_space,
*out_index = index + 1;
}
}
else if (uvm_ats_can_service_faults(gpu_va_space, mm)) {
if (!gpu_va_space->gpu->parent->cdmm_enabled) {
status = service_notification_ats(gpu_va_space, mm, access_counters, index, out_index);
}
else {
status = notify_tools_and_process_flags(va_space,
gpu_va_space->gpu,
access_counters,
0,
batch_context->notifications,
1,
0,
NULL);
*out_index = index + 1;
}
}
else {
NvU32 flags;
uvm_va_block_t *va_block = NULL;
status = uvm_hmm_va_block_find(va_space, address, &va_block);
// TODO: Bug 4309292: [UVM][HMM] Re-enable access counter HMM block
// migrations for virtual notifications
//
// - If the va_block is HMM, don't clear the notification since HMM
// migrations are currently disabled.
//
// - If the va_block isn't HMM, the notification belongs to a recently
// freed va_range. Clear the notification entry to continue receiving
// notifications when a new va_range is allocated in this region.
flags = va_block ? 0 : UVM_ACCESS_COUNTER_ACTION_BATCH_CLEAR;
UVM_ASSERT((status == NV_ERR_OBJECT_NOT_FOUND) ||
(status == NV_ERR_INVALID_ADDRESS) ||
uvm_va_block_is_hmm(va_block));
// Clobber status to continue processing the rest of the notifications
// in the batch.
else if (!gpu_va_space->va_space->pageable.migrations_enabled) {
status = notify_tools_and_process_flags(va_space,
gpu_va_space->gpu,
access_counters,
0,
batch_context->notifications,
&batch_context->notifications[index],
1,
flags,
0,
NULL);
*out_index = index + 1;
}
else if (uvm_ats_can_service_faults(gpu_va_space, mm)) {
status = service_notification_ats(gpu_va_space, mm, access_counters, index, out_index);
}
else {
NvU32 flags;
uvm_va_block_t *va_block = NULL;
struct vm_area_struct *vma = NULL;
// HMM and CDMM pageable memory are serviced through HMM VA blocks.
status = uvm_hmm_va_block_find_create(va_space, address, &vma, &va_block);
if (va_block) {
UVM_ASSERT(uvm_va_block_is_hmm(va_block));
status = service_notifications_in_block(gpu_va_space, mm, access_counters, va_block, vma, index, out_index);
}
else {
// If no va_block was found, the notifications was for a recently
// freed memory region. Clear the notification entry to continue
// receiving notifications when a new region is allocated.
UVM_ASSERT(status == NV_ERR_INVALID_ADDRESS);
flags = UVM_ACCESS_COUNTER_ACTION_BATCH_CLEAR;
// Clobber status to continue processing the rest of the
// notifications in the batch.
status = notify_tools_and_process_flags(va_space,
gpu_va_space->gpu,
access_counters,
0,
&batch_context->notifications[index],
1,
flags,
NULL);
*out_index = index + 1;
}
}
return status;
}
@@ -441,15 +441,18 @@ static NV_STATUS service_managed_fault_in_block_locked(uvm_va_block_t *va_block,
// If this is a ATS processor and the page is already resident in the correct location
// then it should already be mapped on the CPU so handle this as a minor fault.
if (uvm_va_block_is_hmm(va_block) && gpu->parent->ats_supported) {
uvm_page_mask_t *resident_pages = uvm_va_block_resident_mask_get(va_block, gpu->id, NUMA_NO_NODE);
if (resident_pages && uvm_page_mask_test(resident_pages, page_index)) {
uvm_va_block_page_resident_processors(va_block, page_index,
&service_context->block_context->scratch_processor_mask);
if (uvm_processor_mask_test(&service_context->block_context->scratch_processor_mask, gpu->id)) {
unsigned int flags = FAULT_FLAG_REMOTE;
if (fault_entry->fault_access_type >= UVM_FAULT_ACCESS_TYPE_WRITE)
flags |= FAULT_FLAG_WRITE;
// handle_mm_fault() may call hmm_invalidate() so we can't call it with the block lock held
uvm_mutex_unlock(&va_block->lock);
UVM_HANDLE_MM_FAULT(service_context->block_context->hmm.vma,
uvm_va_block_cpu_page_address(va_block, page_index), flags);
uvm_mutex_lock(&va_block->lock);
return NV_OK;
}
@@ -1,5 +1,5 @@
/*******************************************************************************
Copyright (c) 2015-2025 NVIDIA Corporation
Copyright (c) 2015-2026 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
@@ -1259,12 +1259,11 @@ static void fault_entry_duplicate_flags(uvm_fault_service_batch_context_t *batch
// need to be cancelled since they disappear on replay.
//
// The UVM driver considers two scenarios for logical permissions violation:
// - All access types are invalid. For example, when faulting from a processor
// that doesn't have access to the preferred location of a range group when it
// is not migratable. In this case all accesses to the page must be cancelled.
// - Access types are invalid, see:
// uvm_va_block.h:uvm_va_block_check_logical_permissions()
// - Write/atomic accesses are invalid. Basically, when trying to modify a
// read-only VA range. In this case we restrict fault cancelling to those types
// of accesses.
// read-only VA range. In this case we restrict fault cancelling to those
// types of accesses.
//
// Return values:
// - service_access_type: highest access type that can be serviced.
@@ -1534,8 +1533,12 @@ static NV_STATUS service_fault_batch_block_locked(uvm_gpu_va_space_t *gpu_va_spa
if (service_access_type >= UVM_FAULT_ACCESS_TYPE_WRITE)
flags |= FAULT_FLAG_WRITE;
// handle_mm_fault() may call hmm_invalidate() so we can't call it with the block lock held
uvm_mutex_unlock(&va_block->lock);
UVM_HANDLE_MM_FAULT(block_context->block_context->hmm.vma,
uvm_va_block_cpu_page_address(va_block, page_index), flags);
uvm_mutex_lock(&va_block->lock);
continue;
}
}
+10 -1
View File
@@ -806,7 +806,7 @@ void uvm_hal_tlb_invalidate_phys(uvm_push_t *push, uvm_dma_map_invalidation_t in
const char *uvm_aperture_string(uvm_aperture_t aperture)
{
BUILD_BUG_ON(UVM_APERTURE_MAX != 13);
BUILD_BUG_ON(UVM_APERTURE_MAX != 22);
switch (aperture) {
UVM_ENUM_STRING_CASE(UVM_APERTURE_PEER_0);
@@ -818,6 +818,15 @@ const char *uvm_aperture_string(uvm_aperture_t aperture)
UVM_ENUM_STRING_CASE(UVM_APERTURE_PEER_6);
UVM_ENUM_STRING_CASE(UVM_APERTURE_PEER_7);
UVM_ENUM_STRING_CASE(UVM_APERTURE_PEER_MAX);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_0);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_1);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_2);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_3);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_4);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_5);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_6);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_7);
UVM_ENUM_STRING_CASE(UVM_APERTURE_EGM_PEER_MAX);
UVM_ENUM_STRING_CASE(UVM_APERTURE_SYS);
UVM_ENUM_STRING_CASE(UVM_APERTURE_SYS_NON_COHERENT);
UVM_ENUM_STRING_CASE(UVM_APERTURE_VID);
+31
View File
@@ -42,6 +42,16 @@ typedef enum
UVM_APERTURE_PEER_7,
UVM_APERTURE_PEER_MAX,
UVM_APERTURE_EGM_PEER_0,
UVM_APERTURE_EGM_PEER_1,
UVM_APERTURE_EGM_PEER_2,
UVM_APERTURE_EGM_PEER_3,
UVM_APERTURE_EGM_PEER_4,
UVM_APERTURE_EGM_PEER_5,
UVM_APERTURE_EGM_PEER_6,
UVM_APERTURE_EGM_PEER_7,
UVM_APERTURE_EGM_PEER_MAX,
// SYS aperture is used for memory addresses in system physical address
// space, or device DMA space. This is currently used for system memory
// and certain types of peer memory.
@@ -123,6 +133,27 @@ static inline uvm_aperture_t UVM_APERTURE_PEER(NvU32 id)
return aperture;
}
static bool uvm_aperture_is_egm(uvm_aperture_t aperture)
{
return (aperture >= UVM_APERTURE_EGM_PEER_0) && (aperture < UVM_APERTURE_EGM_PEER_MAX);
}
static inline NvU32 UVM_APERTURE_EGM_PEER_ID(uvm_aperture_t aperture)
{
UVM_ASSERT(uvm_aperture_is_egm(aperture));
return (NvU32)aperture - (NvU32)UVM_APERTURE_EGM_PEER_0;
}
static inline uvm_aperture_t UVM_APERTURE_EGM_PEER(NvU32 id)
{
uvm_aperture_t aperture = (uvm_aperture_t)(UVM_APERTURE_EGM_PEER_0 + id);
UVM_ASSERT(UVM_APERTURE_EGM_PEER_ID(aperture) == id);
return aperture;
}
// A physical GPU address
typedef struct
{
+18 -2
View File
@@ -1908,6 +1908,7 @@ static NV_STATUS gpu_chunk_add(uvm_va_block_t *va_block,
uvm_page_mask_set(&gpu_state->resident, page_index);
gpu_chunk->va_block = va_block;
gpu_chunk->va_space = va_block->hmm.va_space;
gpu_state->chunks[page_index] = gpu_chunk;
@@ -3160,7 +3161,11 @@ NV_STATUS uvm_hmm_va_block_service_locked(uvm_gpu_t *gpu,
args->fault_page = NULL;
ret = migrate_vma_setup_locked(args, va_block);
UVM_ASSERT(!ret);
if (ret < 0) {
UVM_ASSERT(0);
gpu_chunk_free_preallocated(va_block, va_block_retry);
return errno_to_nv_status(ret);
}
status = uvm_hmm_gpu_fault_alloc_and_copy(&uvm_hmm_gpu_fault_event);
if (status == NV_WARN_MORE_PROCESSING_REQUIRED) {
@@ -3540,6 +3545,13 @@ NV_STATUS uvm_hmm_va_block_evict_chunk_prep(uvm_va_block_t *va_block,
return NV_OK;
}
void uvm_hmm_va_block_evict_chunk_cancel(uvm_va_block_context_t *block_context)
{
migrate_device_finalize(block_context->hmm.src_pfns,
block_context->hmm.dst_pfns,
ARRAY_SIZE(block_context->hmm.src_pfns));
}
// Note that the caller must initialize va_block_context->hmm.src_pfns by
// calling uvm_hmm_va_block_evict_chunk_prep() before calling this.
static NV_STATUS hmm_va_block_evict_chunks(uvm_va_block_t *va_block,
@@ -3660,6 +3672,7 @@ NV_STATUS uvm_hmm_va_block_evict_pages_from_gpu(uvm_va_block_t *va_block,
{
uvm_va_block_context_t *block_context = service_context->block_context;
unsigned long *src_pfns = block_context->hmm.src_pfns;
unsigned long *dst_pfns = block_context->hmm.dst_pfns;
uvm_va_block_gpu_state_t *gpu_state;
uvm_page_index_t page_index;
uvm_gpu_chunk_t *gpu_chunk;
@@ -3673,6 +3686,7 @@ NV_STATUS uvm_hmm_va_block_evict_pages_from_gpu(uvm_va_block_t *va_block,
// Fill in the src_pfns[] with the ZONE_DEVICE private PFNs of the GPU.
memset(src_pfns, 0, sizeof(block_context->hmm.src_pfns));
memset(dst_pfns, 0, sizeof(block_context->hmm.dst_pfns));
// TODO: Bug 3368756: add support for large GPU pages.
for_each_va_block_page_in_region_mask(page_index, pages_to_evict, region) {
@@ -3683,8 +3697,10 @@ NV_STATUS uvm_hmm_va_block_evict_pages_from_gpu(uvm_va_block_t *va_block,
block_context,
gpu_chunk,
uvm_va_block_region_for_page(page_index));
if (status != NV_OK)
if (status != NV_OK) {
uvm_hmm_va_block_evict_chunk_cancel(block_context);
return status;
}
}
return hmm_va_block_evict_chunks(va_block,
+16
View File
@@ -340,6 +340,9 @@ typedef struct
uvm_gpu_chunk_t *gpu_chunk,
uvm_va_block_region_t chunk_region);
// clears out any migration PTEs setup by uvm_hmm_va_block_evict_chunk_prep() above
void uvm_hmm_va_block_evict_chunk_cancel(uvm_va_block_context_t *block_context);
// Migrate pages to system memory for the given page mask. Note that the
// mmap lock is not held and there is no MM retained. This must be called
// after uvm_hmm_va_block_evict_chunk_prep() has initialized
@@ -632,6 +635,11 @@ typedef struct
return NV_OK;
}
static void uvm_hmm_va_block_evict_chunk_cancel(uvm_va_block_context_t *block_context)
{
return;
}
static NV_STATUS uvm_hmm_va_block_evict_chunks(uvm_va_block_t *va_block,
uvm_service_block_context_t *service_context,
const uvm_page_mask_t *pages_to_evict,
@@ -660,6 +668,14 @@ typedef struct
return NV_ERR_INVALID_ADDRESS;
}
static NV_STATUS uvm_hmm_va_block_update_residency_info(uvm_va_block_t *va_block,
struct mm_struct *mm,
NvU64 lookup_address,
bool populate)
{
return NV_ERR_INVALID_ADDRESS;
}
static NV_STATUS uvm_hmm_va_block_update_residency_info_unlocked(uvm_va_block_t *va_block,
struct mm_struct *mm,
NvU64 lookup_address,
+1 -1
View File
@@ -80,7 +80,7 @@ void uvm_hal_hopper_arch_init_properties(uvm_parent_gpu_t *parent_gpu)
parent_gpu->access_counters_serialize_clear_ops_by_type = false;
parent_gpu->access_bits_supported = false;
parent_gpu->access_bits_supported = true;
parent_gpu->has_clear_faulted_channel_sw_method = true;
+12 -4
View File
@@ -60,10 +60,18 @@ static NvU32 ce_aperture(uvm_aperture_t aperture)
return HWCONST(C8B5, SET_SRC_PHYS_MODE, TARGET, LOCAL_FB);
}
else {
UVM_ASSERT(uvm_aperture_is_peer(aperture));
return HWCONST(C8B5, SET_SRC_PHYS_MODE, TARGET, PEERMEM) |
HWVALUE(C8B5, SET_SRC_PHYS_MODE, FLA, 0) |
HWVALUE(C8B5, SET_SRC_PHYS_MODE, PEER_ID, UVM_APERTURE_PEER_ID(aperture));
NvU32 aperture_bits;
UVM_ASSERT(uvm_aperture_is_peer(aperture) || uvm_aperture_is_egm(aperture));
aperture_bits = HWCONST(C8B5, SET_SRC_PHYS_MODE, TARGET, PEERMEM) | HWVALUE(C8B5, SET_SRC_PHYS_MODE, FLA, 0);
if (uvm_aperture_is_egm(aperture))
aperture_bits |= HWVALUE(C8B5, SET_SRC_PHYS_MODE, PEER_ID, UVM_APERTURE_EGM_PEER_ID(aperture));
else
aperture_bits |= HWVALUE(C8B5, SET_SRC_PHYS_MODE, PEER_ID, UVM_APERTURE_PEER_ID(aperture));
return aperture_bits;
}
}
+4 -2
View File
@@ -189,7 +189,7 @@ static NvU64 make_pte_hopper(uvm_aperture_t aperture, NvU64 address, uvm_prot_t
aperture_bits = NV_MMU_VER3_PTE_APERTURE_SYSTEM_NON_COHERENT_MEMORY;
else if (aperture == UVM_APERTURE_VID)
aperture_bits = NV_MMU_VER3_PTE_APERTURE_VIDEO_MEMORY;
else if (aperture >= UVM_APERTURE_PEER_0 && aperture <= UVM_APERTURE_PEER_7)
else if (uvm_aperture_is_peer(aperture) || uvm_aperture_is_egm(aperture))
aperture_bits = NV_MMU_VER3_PTE_APERTURE_PEER_MEMORY;
else
UVM_ASSERT_MSG(0, "Invalid aperture: %d\n", aperture);
@@ -213,8 +213,10 @@ static NvU64 make_pte_hopper(uvm_aperture_t aperture, NvU64 address, uvm_prot_t
pte_bits |= HWVALUE64(_MMU_VER3, PTE, ADDRESS, address);
// peer id 63:61
if (aperture >= UVM_APERTURE_PEER_0 && aperture <= UVM_APERTURE_PEER_7)
if (uvm_aperture_is_peer(aperture))
pte_bits |= HWVALUE64(_MMU_VER3, PTE, PEER_ID, UVM_APERTURE_PEER_ID(aperture));
else if (uvm_aperture_is_egm(aperture))
pte_bits |= HWVALUE64(_MMU_VER3, PTE, PEER_ID, UVM_APERTURE_EGM_PEER_ID(aperture));
}
return pte_bits;
+46
View File
@@ -955,6 +955,52 @@ typedef struct
NV_STATUS rmStatus; // OUT
} UVM_QUERY_RESIDENCY_PARAMS;
//
// UvmIsDmaBufImportSupported
//
#define UVM_IS_DMA_BUF_IMPORT_SUPPORTED UVM_IOCTL_BASE(82)
typedef struct
{
NvBool dmaBufImportSupported; // OUT
NV_STATUS rmStatus; // OUT
} UVM_IS_DMA_BUF_IMPORT_SUPPORTED_PARAMS;
//
// UvmImportDmaBuf
//
#define UVM_IMPORT_DMA_BUF UVM_IOCTL_BASE(83)
typedef struct
{
NvU64 base NV_ALIGN_BYTES(8); // IN
NvU64 length NV_ALIGN_BYTES(8); // IN
NvS32 handleFd; // IN
NV_STATUS rmStatus; // OUT
} UVM_IMPORT_DMA_BUF_PARAMS;
//
// UvmMapDmaBuf
//
#define UVM_MAP_DMA_BUF UVM_IOCTL_BASE(84)
typedef struct
{
NvU64 base NV_ALIGN_BYTES(8); // IN
NvU64 length NV_ALIGN_BYTES(8); // IN
NvProcessorUuid gpuUuid; // IN
NV_STATUS rmStatus; // OUT
} UVM_MAP_DMA_BUF_PARAMS;
//
// UvmUnmapDmaBuf
//
#define UVM_UNMAP_DMA_BUF UVM_IOCTL_BASE(85)
typedef struct
{
NvU64 base NV_ALIGN_BYTES(8); // IN
NvU64 length NV_ALIGN_BYTES(8); // IN
NvProcessorUuid gpuUuid; // IN
NV_STATUS rmStatus; // OUT
} UVM_UNMAP_DMA_BUF_PARAMS;
//
// Temporary ioctls which should be removed before UVM 8 release
// Number backwards from 2047 - highest custom ioctl function number
+18
View File
@@ -124,6 +124,18 @@
#define UVM_CAN_USE_MMU_NOTIFIERS() 0
#endif
// Check that importers can provide an invalidate_mappings() callback to
// determine that DMA-BUF API stability has been reached and whether to provide
// importer support through UVM.
//
// This callback was previously called move_notify().
#if (defined(NV_DMA_BUF_ATTACH_OPS_HAS_INVALIDATE_MAPPINGS) || defined(NV_DMA_BUF_ATTACH_OPS_HAS_MOVE_NOTIFY)) && \
defined(NV_DMA_BUF_ATTACHMENT_HAS_PEER2PEER)
#define UVM_PROVIDES_DMA_BUF_IMPORTER() 1
#else
#define UVM_PROVIDES_DMA_BUF_IMPORTER() 0
#endif
//
// printk.h already defined pr_fmt, so we have to redefine it so the pr_*
// routines pick up our version
@@ -195,6 +207,12 @@ typedef struct
#include <asm/pgtable_types.h>
#endif
#if NV_IS_EXPORT_SYMBOL_GPL_dma_iova_try_alloc
#define UVM_USE_DMA_IOVA_API() 1
#else
#define UVM_USE_DMA_IOVA_API() 0
#endif // NV_IS_EXPORT_SYMBOL_GPL_dma_iova_try_alloc
// PAGE_KERNEL_NOENC is only defined on x86. Define it for all architectures so
// we don't have to wrap uses in #ifdefs.
#if !defined(PAGE_KERNEL_NOENC)
+3 -1
View File
@@ -27,7 +27,7 @@
const char *uvm_lock_order_to_string(uvm_lock_order_t lock_order)
{
BUILD_BUG_ON(UVM_LOCK_ORDER_COUNT != 38);
BUILD_BUG_ON(UVM_LOCK_ORDER_COUNT != 40);
switch (lock_order) {
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_INVALID);
@@ -37,10 +37,12 @@ const char *uvm_lock_order_to_string(uvm_lock_order_t lock_order)
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_ISR);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_MMAP_LOCK);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_VA_SPACES_LIST);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_DMA_BUF_RESV_LOCK);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_VA_SPACE_SERIALIZE_WRITERS);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_VA_SPACE_READ_ACQUIRE_WRITE_RELEASE_LOCK);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_VA_SPACE);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_EXT_RANGE_TREE);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_DMA_BUF_RANGE_TREE);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_GPU_SEMAPHORE_POOL);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_RM_API);
UVM_ENUM_STRING_CASE(UVM_LOCK_ORDER_RM_GPUS);
+46
View File
@@ -139,6 +139,20 @@
// UVM driver calls into RM. In other words, mmap_lock and the RM GPUs lock
// are mutually exclusive.
//
// - DMA-BUF resv lock (dma_resv)
// Order: UVM_LOCK_ORDER_DMA_BUF_RESV_LOCK
// Wound-wait mutex (ww_mutex)
// Exclusive lock per exported DMA-BUF.
//
// Imported DMA-BUF resources by UVM are tracked by VA ranges. Multiple
// such ranges may refer to a single resource obtained from an exporter.
//
// Exporters may issue the invalidate_mappings() callback to any attached
// GPU. The callback holds the dma_resv lock, though the callback must
// first clear all GMMU mappings before it is safe to tear down dma all
// mappings with dma_buf_unmap_attachment under this lock. Thus this lock
// should be taken before va_space or subsequent locks.
//
// - Global VA spaces list lock
// Order: UVM_LOCK_ORDER_VA_SPACES_LIST
// Mutex which protects g_uvm_global.va_spaces state.
@@ -251,6 +265,12 @@
//
// Protects the per-GPU sub-range tree mappings in each external VA range.
//
// - DMA-BUF Allocation Tree lock
// Order: UVM_LOCK_ORDER_DMA_BUF_RANGE_TREE
// Exclusive lock (mutex) per DMA-BUF VA range, per GPU.
//
// Protects the per-GPU sub-range tree mappings in each DMA-BUF VA range.
//
// - GPU semaphore pool lock (semaphore_pool->mutex)
// Order: UVM_LOCK_ORDER_GPU_SEMAPHORE_POOL
// Exclusive lock (mutex) per uvm_gpu_semaphore_pool
@@ -507,10 +527,12 @@ typedef enum
UVM_LOCK_ORDER_ISR,
UVM_LOCK_ORDER_MMAP_LOCK,
UVM_LOCK_ORDER_VA_SPACES_LIST,
UVM_LOCK_ORDER_DMA_BUF_RESV_LOCK,
UVM_LOCK_ORDER_VA_SPACE_SERIALIZE_WRITERS,
UVM_LOCK_ORDER_VA_SPACE_READ_ACQUIRE_WRITE_RELEASE_LOCK,
UVM_LOCK_ORDER_VA_SPACE,
UVM_LOCK_ORDER_EXT_RANGE_TREE,
UVM_LOCK_ORDER_DMA_BUF_RANGE_TREE,
UVM_LOCK_ORDER_GPU_SEMAPHORE_POOL,
UVM_LOCK_ORDER_RM_API,
UVM_LOCK_ORDER_RM_GPUS,
@@ -643,6 +665,14 @@ bool __uvm_locking_initialized(void);
#define uvm_check_locked_mmap_lock(mm, flags) \
__uvm_check_locked(nv_mmap_get_lock(mm), UVM_LOCK_ORDER_MMAP_LOCK, (flags))
// Helpers for recording DMA-BUF lock usage.
#define uvm_record_lock_dma_resv() \
uvm_record_lock_raw((void*)UVM_LOCK_ORDER_DMA_BUF_RESV_LOCK, UVM_LOCK_ORDER_DMA_BUF_RESV_LOCK, \
UVM_LOCK_FLAGS_MODE_EXCLUSIVE)
#define uvm_record_unlock_dma_resv() \
uvm_record_unlock_raw((void*)UVM_LOCK_ORDER_DMA_BUF_RESV_LOCK, UVM_LOCK_ORDER_DMA_BUF_RESV_LOCK, \
UVM_LOCK_FLAGS_MODE_EXCLUSIVE)
// Helpers for recording RM API lock usage around UVM-RM interfaces
#define uvm_record_lock_rm_api() \
uvm_record_lock_raw((void*)UVM_LOCK_ORDER_RM_API, UVM_LOCK_ORDER_RM_API, \
@@ -683,6 +713,9 @@ bool __uvm_locking_initialized(void);
#define uvm_check_locked_mmap_lock uvm_check_locked
#define uvm_record_lock_dma_resv()
#define uvm_record_unlock_dma_resv()
#define uvm_record_lock_rm_api()
#define uvm_record_unlock_rm_api()
@@ -751,6 +784,19 @@ bool __uvm_locking_initialized(void);
uvm_record_unlock_mmap_lock_write(_mm); \
})
// Helpers for locking dma_resv lock and recording its usage.
#define uvm_dma_resv_lock(resv) ({ \
typeof(resv) _resv = (resv); \
uvm_record_lock_dma_resv(); \
dma_resv_lock(_resv, NULL); \
})
#define uvm_dma_resv_unlock(resv) ({ \
typeof(resv) _resv = (resv); \
dma_resv_unlock(_resv); \
uvm_record_unlock_dma_resv(); \
})
// Helper for calling a UVM-RM interface function with lock recording
#define uvm_rm_locked_call(call) ({ \
typeof(call) ret; \
+38 -36
View File
@@ -370,10 +370,10 @@ static uvm_membar_t va_range_downgrade_membar(uvm_va_range_t *va_range, uvm_ext_
return UVM_MEMBAR_GPU;
// EGM uses the same barriers as sysmem.
return uvm_hal_downgrade_membar_type(ext_gpu_map->gpu,
return uvm_hal_downgrade_membar_type(ext_gpu_map->base.gpu,
!ext_gpu_map->is_sysmem &&
!ext_gpu_map->is_fabricmem &&
ext_gpu_map->gpu == ext_gpu_map->owning_gpu);
ext_gpu_map->base.gpu == ext_gpu_map->owning_gpu);
}
NV_STATUS uvm_va_range_map_rm_allocation(uvm_va_range_t *va_range,
@@ -423,8 +423,8 @@ NV_STATUS uvm_va_range_map_rm_allocation(uvm_va_range_t *va_range,
// We should be never called with ext_gpu_map == NULL
// and UVM_VA_RANGE_TYPE_EXTERNAL
UVM_ASSERT(ext_gpu_map != NULL);
node = &ext_gpu_map->node;
pt_range_vec = &ext_gpu_map->pt_range_vec;
node = &ext_gpu_map->base.node;
pt_range_vec = &ext_gpu_map->base.pt_range_vec;
}
else {
node = &va_range->node;
@@ -700,7 +700,7 @@ static uvm_ext_gpu_map_t *uvm_va_range_ext_gpu_map(uvm_va_range_external_t *exte
node = uvm_range_tree_find(&range_tree->tree, addr);
if (node) {
ext_gpu_map = uvm_ext_gpu_map_container(node);
UVM_ASSERT(ext_gpu_map->gpu == mapping_gpu);
UVM_ASSERT(ext_gpu_map->base.gpu == mapping_gpu);
}
}
else {
@@ -719,18 +719,18 @@ static NV_STATUS uvm_ext_gpu_map_split(uvm_range_tree_t *tree,
NV_STATUS status;
NvU64 new_start = new_end + 1;
if (!IS_ALIGNED(new_start, existing_map->pt_range_vec.page_size))
if (!IS_ALIGNED(new_start, existing_map->base.pt_range_vec.page_size))
return NV_ERR_INVALID_ADDRESS;
UVM_ASSERT(new_start >= existing_map->node.start && new_start < existing_map->node.end);
UVM_ASSERT(new_start >= existing_map->base.node.start && new_start < existing_map->base.node.end);
new = uvm_kvmalloc_zero(sizeof(*new));
if (!new)
return NV_ERR_NO_MEMORY;
RB_CLEAR_NODE(&new->node.rb_node);
RB_CLEAR_NODE(&new->base.node.rb_node);
new->mem_handle = existing_map->mem_handle;
new->gpu = existing_map->gpu;
new->base.gpu = existing_map->base.gpu;
new->owning_gpu = existing_map->owning_gpu;
new->is_sysmem = existing_map->is_sysmem;
new->is_egm = existing_map->is_egm;
@@ -745,20 +745,22 @@ static NV_STATUS uvm_ext_gpu_map_split(uvm_range_tree_t *tree,
return status;
}
status = uvm_page_table_range_vec_split_upper(&existing_map->pt_range_vec, new_start - 1, &new->pt_range_vec);
status = uvm_page_table_range_vec_split_upper(&existing_map->base.pt_range_vec,
new_start - 1,
&new->base.pt_range_vec);
if (status != NV_OK) {
uvm_tracker_deinit(&new->tracker);
uvm_kvfree(new);
return status;
}
new->node.start = new_start;
new->base.node.start = new_start;
// Sparse mappings don't have actual allocations.
if (new->mem_handle)
nv_kref_get(&new->mem_handle->ref_count);
uvm_range_tree_split(tree, &existing_map->node, &new->node);
uvm_range_tree_split(tree, &existing_map->base.node, &new->base.node);
if (new_map)
*new_map = new;
@@ -822,13 +824,13 @@ static NV_STATUS uvm_unmap_external_in_range(uvm_va_range_external_t *external_r
// newly created uvm_ext_gpu_map_t.
ext_map = uvm_ext_gpu_map_iter_first(external_range, gpu, start, end);
while (ext_map) {
if (start > ext_map->node.start) {
if (start > ext_map->base.node.start) {
status = uvm_ext_gpu_map_split(&range_tree->tree, ext_map, start - 1, &ext_map_next);
if (status != NV_OK)
break;
}
else {
if (end < ext_map->node.end) {
if (end < ext_map->base.node.end) {
status = uvm_ext_gpu_map_split(&range_tree->tree, ext_map, end, NULL);
if (status != NV_OK)
break;
@@ -886,9 +888,9 @@ static NV_STATUS uvm_map_external_allocation_on_gpu(uvm_va_range_external_t *ext
// Insert the ext_gpu_map into the external range immediately since some of
// the below calls require it to be there.
ext_gpu_map->node.start = base;
ext_gpu_map->node.end = base + length - 1;
RB_CLEAR_NODE(&ext_gpu_map->node.rb_node);
ext_gpu_map->base.node.start = base;
ext_gpu_map->base.node.end = base + length - 1;
RB_CLEAR_NODE(&ext_gpu_map->base.node.rb_node);
uvm_tracker_init(&ext_gpu_map->tracker);
ext_gpu_map->mem_handle = uvm_kvmalloc_zero(sizeof(*ext_gpu_map->mem_handle));
if (!ext_gpu_map->mem_handle) {
@@ -898,11 +900,11 @@ static NV_STATUS uvm_map_external_allocation_on_gpu(uvm_va_range_external_t *ext
// Due to the fact that any overlapping mappings were already unmapped,
// adding the new mapping to the tree cannot fail.
status = uvm_range_tree_add(&range_tree->tree, &ext_gpu_map->node);
status = uvm_range_tree_add(&range_tree->tree, &ext_gpu_map->base.node);
UVM_ASSERT(status == NV_OK);
uvm_processor_mask_set_atomic(&external_range->mapped_gpus, mapping_gpu->id);
ext_gpu_map->gpu = mapping_gpu;
ext_gpu_map->base.gpu = mapping_gpu;
ext_gpu_map->mem_handle->gpu = mapping_gpu;
nv_kref_init(&ext_gpu_map->mem_handle->ref_count);
@@ -952,7 +954,7 @@ static NV_STATUS uvm_map_external_allocation_on_gpu(uvm_va_range_external_t *ext
// allocations, the vMMU segment size may limit the range of page sizes.
biggest_mapping_page_size = uvm_mmu_biggest_page_size_up_to(&gpu_va_space->page_tables,
mapping_gpu->mem_info.max_vidmem_page_size);
if (!ext_gpu_map->is_sysmem && (ext_gpu_map->gpu == ext_gpu_map->owning_gpu) &&
if (!ext_gpu_map->is_sysmem && (ext_gpu_map->base.gpu == ext_gpu_map->owning_gpu) &&
(mapping_page_size > biggest_mapping_page_size))
mapping_page_size = biggest_mapping_page_size;
}
@@ -1135,31 +1137,31 @@ static NV_STATUS uvm_map_external_sparse_on_gpu(uvm_va_range_external_t *externa
goto error;
}
ext_gpu_map->node.start = base;
ext_gpu_map->node.end = base + length - 1;
RB_CLEAR_NODE(&ext_gpu_map->node.rb_node);
ext_gpu_map->base.node.start = base;
ext_gpu_map->base.node.end = base + length - 1;
RB_CLEAR_NODE(&ext_gpu_map->base.node.rb_node);
uvm_tracker_init(&ext_gpu_map->tracker);
// Due to the fact that any overlapping mappings were already unmapped,
// adding the new mapping to the tree cannot fail.
status = uvm_range_tree_add(&range_tree->tree, &ext_gpu_map->node);
status = uvm_range_tree_add(&range_tree->tree, &ext_gpu_map->base.node);
UVM_ASSERT(status == NV_OK);
uvm_processor_mask_set_atomic(&external_range->mapped_gpus, mapping_gpu->id);
ext_gpu_map->gpu = mapping_gpu;
ext_gpu_map->base.gpu = mapping_gpu;
UVM_ASSERT(uvm_va_range_ext_gpu_map(external_range, mapping_gpu, base) == ext_gpu_map);
status = uvm_page_table_range_vec_init(page_tree,
ext_gpu_map->node.start,
uvm_range_tree_node_size(&ext_gpu_map->node),
ext_gpu_map->base.node.start,
uvm_range_tree_node_size(&ext_gpu_map->base.node),
UVM_PAGE_SIZE_64K,
UVM_PMM_ALLOC_FLAGS_EVICT,
&ext_gpu_map->pt_range_vec);
&ext_gpu_map->base.pt_range_vec);
if (status != NV_OK)
goto error;
status = uvm_page_table_range_vec_write_ptes(&ext_gpu_map->pt_range_vec,
status = uvm_page_table_range_vec_write_ptes(&ext_gpu_map->base.pt_range_vec,
UVM_MEMBAR_NONE,
external_sparse_pte_maker,
NULL);
@@ -1233,7 +1235,7 @@ static uvm_gpu_t *uvm_ext_gpu_map_free_internal(uvm_ext_gpu_map_t *ext_gpu_map)
if (!ext_gpu_map)
return NULL;
UVM_ASSERT(!ext_gpu_map->pt_range_vec.ranges);
UVM_ASSERT(!ext_gpu_map->base.pt_range_vec.ranges);
if (ext_gpu_map->mem_handle)
nv_kref_put(&ext_gpu_map->mem_handle->ref_count, uvm_release_rm_handle);
@@ -1267,13 +1269,13 @@ void uvm_ext_gpu_map_destroy(uvm_va_range_external_t *external_range,
// The external map is inserted into the tree prior to the rest of the mapping
// steps. So, if it has not been inserted yet, there is nothing to clean up. Just
// free the memory.
if (RB_EMPTY_NODE(&ext_gpu_map->node.rb_node)) {
if (RB_EMPTY_NODE(&ext_gpu_map->base.node.rb_node)) {
uvm_kvfree(ext_gpu_map->mem_handle);
uvm_kvfree(ext_gpu_map);
return;
}
mapped_gpu = ext_gpu_map->gpu;
mapped_gpu = ext_gpu_map->base.gpu;
range_tree = uvm_ext_gpu_range_tree(external_range, mapped_gpu);
@@ -1297,13 +1299,13 @@ void uvm_ext_gpu_map_destroy(uvm_va_range_external_t *external_range,
uvm_assert_mutex_locked(&range_tree->lock);
UVM_ASSERT(uvm_gpu_va_space_get(external_range->va_range.va_space, mapped_gpu));
uvm_range_tree_remove(&range_tree->tree, &ext_gpu_map->node);
uvm_range_tree_remove(&range_tree->tree, &ext_gpu_map->base.node);
// Unmap the PTEs
if (ext_gpu_map->pt_range_vec.ranges) {
if (ext_gpu_map->base.pt_range_vec.ranges) {
membar = va_range_downgrade_membar(&external_range->va_range, ext_gpu_map);
uvm_page_table_range_vec_clear_ptes(&ext_gpu_map->pt_range_vec, membar);
uvm_page_table_range_vec_deinit(&ext_gpu_map->pt_range_vec);
uvm_page_table_range_vec_clear_ptes(&ext_gpu_map->base.pt_range_vec, membar);
uvm_page_table_range_vec_deinit(&ext_gpu_map->base.pt_range_vec);
}
if (deferred_free_list && ext_gpu_map->mem_handle) {
+25 -29
View File
@@ -40,7 +40,7 @@ typedef struct
UvmGpuCompressionType compression_type;
} uvm_map_rm_params_t;
static uvm_ext_gpu_range_tree_t *uvm_ext_gpu_range_tree(uvm_va_range_external_t *external_range, uvm_gpu_t *gpu)
static uvm_gpu_range_tree_t *uvm_ext_gpu_range_tree(uvm_va_range_external_t *external_range, uvm_gpu_t *gpu)
{
return &external_range->gpu_ranges[uvm_id_gpu_index(gpu->id)];
}
@@ -52,50 +52,46 @@ static uvm_ext_gpu_map_t *uvm_ext_gpu_map_iter_first(uvm_va_range_external_t *ex
NvU64 start,
NvU64 end)
{
uvm_ext_gpu_range_tree_t *range_tree;
uvm_range_tree_node_t *node;
UVM_ASSERT(start >= external_range->va_range.node.start);
UVM_ASSERT(end <= external_range->va_range.node.end);
range_tree = uvm_ext_gpu_range_tree(external_range, gpu);
node = uvm_range_tree_iter_first(&range_tree->tree, start, end);
return uvm_ext_gpu_map_container(node);
return uvm_ext_gpu_map_container(
uvm_gpu_range_tree_iter_first(uvm_ext_gpu_range_tree(external_range, gpu), start, end));
}
// Returns the external map following the provided map (if any) in address order from
// the gpu's range tree.
// The caller must hold the range tree lock.
// Returns the external map following the provided map (if any) in address order
// from the gpu's range tree. The caller must hold the range tree lock.
static uvm_ext_gpu_map_t *uvm_ext_gpu_map_iter_next(uvm_va_range_external_t *external_range,
uvm_ext_gpu_map_t *ext_gpu_map,
NvU64 end)
{
uvm_ext_gpu_range_tree_t *range_tree;
uvm_range_tree_node_t *node;
if (!ext_gpu_map)
return NULL;
UVM_ASSERT(end <= external_range->va_range.node.end);
range_tree = uvm_ext_gpu_range_tree(external_range, ext_gpu_map->gpu);
node = uvm_range_tree_iter_next(&range_tree->tree, &ext_gpu_map->node, end);
return uvm_ext_gpu_map_container(node);
return uvm_ext_gpu_map_container(
uvm_gpu_range_tree_iter_next(uvm_ext_gpu_range_tree(external_range, ext_gpu_map->base.gpu),
&ext_gpu_map->base, end));
}
// The four iterators below require that the caller hold the gpu's range tree
// lock.
#define uvm_ext_gpu_map_for_each_in(ext_gpu_map, external_range, gpu, start, end) \
for ((ext_gpu_map) = uvm_ext_gpu_map_iter_first((external_range), (gpu), (start), (end)); \
(ext_gpu_map); \
(ext_gpu_map) = uvm_ext_gpu_map_iter_next((external_range), (ext_gpu_map), (end)))
#define uvm_ext_gpu_map_for_each_in(ext_gpu_map, external_range, gpu, start, end) \
uvm_gpu_map_for_each_in(ext_gpu_map, \
external_range, \
gpu, \
start, \
end, \
uvm_ext_gpu_map_iter_first, \
uvm_ext_gpu_map_iter_next)
#define uvm_ext_gpu_map_for_each_in_safe(ext_gpu_map, ext_gpu_map_next, external_range, gpu, start, end) \
for ((ext_gpu_map) = uvm_ext_gpu_map_iter_first((external_range), (gpu), (start), (end)), \
(ext_gpu_map_next) = uvm_ext_gpu_map_iter_next((external_range), (ext_gpu_map), (end)); \
(ext_gpu_map); \
(ext_gpu_map) = (ext_gpu_map_next), \
(ext_gpu_map_next) = uvm_ext_gpu_map_iter_next((external_range), (ext_gpu_map), (end)))
#define uvm_ext_gpu_map_for_each_in_safe(ext_gpu_map, ext_gpu_map_next, external_range, gpu, start, end) \
uvm_gpu_map_for_each_in_safe(ext_gpu_map, \
ext_gpu_map_next, \
external_range, \
gpu, \
start, \
end, \
uvm_ext_gpu_map_iter_first, \
uvm_ext_gpu_map_iter_next)
#define uvm_ext_gpu_map_for_each(ext_gpu_map, external_range, gpu) \
uvm_ext_gpu_map_for_each_in(ext_gpu_map, \
+60 -26
View File
@@ -303,7 +303,7 @@ static void uvm_migrate_vma_state_compute_masks(struct vm_area_struct *vma,
}
}
static struct page *uvm_migrate_vma_alloc_page(migrate_vma_state_t *state)
static struct page *uvm_migrate_vma_alloc_dst_page(migrate_vma_state_t *state)
{
struct page *dst_page;
uvm_migrate_args_t *uvm_migrate_args = state->uvm_migrate_args;
@@ -461,7 +461,7 @@ static NV_STATUS uvm_migrate_vma_populate_anon_pages(struct vm_area_struct *vma,
{
uvm_push_t push;
unsigned long i;
uvm_gpu_t *copying_gpu;
uvm_gpu_t *scrubbing_gpu;
NV_STATUS status = NV_OK;
uvm_migrate_args_t *uvm_migrate_args = state->uvm_migrate_args;
uvm_processor_id_t dst_id = uvm_migrate_args->dst_id;
@@ -475,25 +475,12 @@ static NV_STATUS uvm_migrate_vma_populate_anon_pages(struct vm_area_struct *vma,
UVM_ASSERT(state->num_populate_anon_pages == bitmap_weight(page_mask, state->num_pages));
// Try to get a GPU attached to the node being populated. If there
// is none, use any of the GPUs registered in the VA space.
if (UVM_ID_IS_CPU(dst_id)) {
copying_gpu = uvm_va_space_find_first_gpu_attached_to_cpu_node(va_space, uvm_migrate_args->dst_node_id);
if (!copying_gpu)
copying_gpu = uvm_va_space_find_first_gpu(va_space);
}
else {
copying_gpu = uvm_gpu_get(dst_id);
}
UVM_ASSERT(copying_gpu);
// Pre-allocate the dst pages and mark the ones that failed
for_each_set_bit(i, page_mask, state->num_pages) {
struct page *dst_page = NULL;
if (!state->out_of_memory)
dst_page = uvm_migrate_vma_alloc_page(state);
dst_page = uvm_migrate_vma_alloc_dst_page(state);
if (!dst_page) {
__set_bit(i, state->allocation_failed_mask.page_mask);
@@ -504,13 +491,25 @@ static NV_STATUS uvm_migrate_vma_populate_anon_pages(struct vm_area_struct *vma,
dst[i] = migrate_pfn(page_to_pfn(dst_page));
}
if (uvm_dma_mapping_required_on_copying_gpu(va_space, dst_id, copying_gpu)) {
status = dma_map_non_failed_pages_in_mask(copying_gpu, uvm_sgt, dst, page_mask, state);
// Try to get a GPU attached to the node being populated. If there
// is none, use any of the GPUs registered in the VA space.
if (UVM_ID_IS_CPU(dst_id)) {
scrubbing_gpu = uvm_va_space_find_first_gpu_attached_to_cpu_node(va_space, uvm_migrate_args->dst_node_id);
if (!scrubbing_gpu)
scrubbing_gpu = uvm_va_space_find_first_gpu(va_space);
UVM_ASSERT(uvm_dma_mapping_required_on_copying_gpu(va_space, dst_id, scrubbing_gpu));
status = dma_map_non_failed_pages_in_mask(scrubbing_gpu, uvm_sgt, dst, page_mask, state);
if (status != NV_OK)
return status;
}
else {
scrubbing_gpu = uvm_gpu_get(dst_id);
}
status = migrate_vma_zero_begin_push(va_space, dst_id, copying_gpu, start, outer - 1, &push);
UVM_ASSERT(scrubbing_gpu);
status = migrate_vma_zero_begin_push(va_space, dst_id, scrubbing_gpu, start, outer - 1, &push);
if (status != NV_OK)
return status;
@@ -591,15 +590,20 @@ static void copy_dma_mapped_pages(uvm_va_space_t *va_space,
if (src_has_dma_mappings) {
uvm_processor_id_t dst_id = uvm_migrate_args->dst_id;
// Copying from sysmem should always be to local vidmem
UVM_ASSERT(UVM_ID_IS_GPU(dst_id));
UVM_ASSERT(uvm_id_equal(dst_id, gpu->id));
UVM_ASSERT(dst[i] & MIGRATE_PFN_VALID);
page = migrate_pfn_to_page(dst[i]);
gpu_addr = uvm_migrate_vma_page_copy_address(page, i, dst_id, gpu, state);
gpu->parent->ce_hal->memcopy(push, gpu_addr, gpu_dma_addr, PAGE_SIZE);
}
else {
UVM_ASSERT(UVM_ID_IS_GPU(src_id));
// Copying to sysmem should always be from local vidmem
UVM_ASSERT(uvm_id_equal(src_id, gpu->id));
UVM_ASSERT(src[i] & MIGRATE_PFN_VALID);
page = migrate_pfn_to_page(src[i]);
gpu_addr = uvm_migrate_vma_page_copy_address(page, i, src_id, gpu, state);
gpu->parent->ce_hal->memcopy(push, gpu_dma_addr, gpu_addr, PAGE_SIZE);
@@ -679,7 +683,8 @@ static void copy_pages_in_mask(uvm_va_space_t *va_space,
// selection of copying_gpu changes based on the source nid, scatter gather
// table management might need to be changed since a source processor could
// potentially be accessed by multiple GPUs.
static uvm_gpu_t *select_gpu_for_vma_copy_push(uvm_processor_id_t dst_id,
static uvm_gpu_t *select_gpu_for_vma_copy_push(uvm_va_space_t *va_space,
uvm_processor_id_t dst_id,
uvm_processor_id_t src_id,
uvm_channel_type_t *out_channel_type)
{
@@ -695,7 +700,14 @@ static uvm_gpu_t *select_gpu_for_vma_copy_push(uvm_processor_id_t dst_id,
} else {
// Prefer to "push" the data from the source for GPU to GPU copies
gpu = uvm_gpu_get(src_id);
channel_type = UVM_CHANNEL_TYPE_GPU_TO_GPU;
if (uvm_processor_mask_test(&va_space->can_copy_from[uvm_id_value(src_id)], dst_id)) {
// Use GPU_TO_GPU if we can do peer copies
channel_type = UVM_CHANNEL_TYPE_GPU_TO_GPU;
}
else {
// Use GPU_TO_CPU if we need to use cLinks
channel_type = UVM_CHANNEL_TYPE_GPU_TO_CPU;
}
}
if (out_channel_type)
@@ -725,6 +737,7 @@ static NV_STATUS uvm_migrate_vma_copy_pages_from(struct vm_area_struct *vma,
uvm_va_space_t *va_space = uvm_migrate_args->va_space;
unsigned long *page_mask;
uvm_sgt_t *uvm_sgt = uvm_select_sgt(src_id, src_nid, state);
bool use_clinks_for_p2p = false;
uvm_tracker_t zero_tracker = UVM_TRACKER_INIT();
@@ -735,14 +748,14 @@ static NV_STATUS uvm_migrate_vma_copy_pages_from(struct vm_area_struct *vma,
UVM_ASSERT(!bitmap_empty(page_mask, state->num_pages));
copying_gpu = select_gpu_for_vma_copy_push(dst_id, src_id, &channel_type);
copying_gpu = select_gpu_for_vma_copy_push(va_space, dst_id, src_id, &channel_type);
// Pre-allocate the dst pages and mark the ones that failed
for_each_set_bit(i, page_mask, state->num_pages) {
struct page *dst_page = NULL;
if (!state->out_of_memory)
dst_page = uvm_migrate_vma_alloc_page(state);
dst_page = uvm_migrate_vma_alloc_dst_page(state);
if (!dst_page) {
__set_bit(i, state->allocation_failed_mask.page_mask);
@@ -786,8 +799,11 @@ static NV_STATUS uvm_migrate_vma_copy_pages_from(struct vm_area_struct *vma,
if (bitmap_equal(page_mask, state->allocation_failed_mask.page_mask, state->num_pages))
return uvm_tracker_wait_deinit(&zero_tracker);
retry_p2p:
// We don't have a case where both src and dst use the SYS aperture.
// In other word, only one mapping for page index i is allowed.
// If one location is CPU the other location is always local vidmem on
// the copying GPU.
// In other words, only one mapping for page index i is allowed.
// In both cases, we're using the source processor scatterlist to host
// the pages because we cannot reuse the destination scatterlist among
// the different source processors.
@@ -796,7 +812,9 @@ static NV_STATUS uvm_migrate_vma_copy_pages_from(struct vm_area_struct *vma,
src_has_dma_mappings = (uvm_sgt->dma_count != 0);
}
else if (uvm_dma_mapping_required_on_copying_gpu(va_space, dst_id, copying_gpu)) {
// Create destination DMA mapping if we're using cLinks even if the
// destination is peer GPU.
else if (uvm_dma_mapping_required_on_copying_gpu(va_space, dst_id, copying_gpu) || use_clinks_for_p2p) {
status = dma_map_non_failed_pages_in_mask(copying_gpu, uvm_sgt, dst, page_mask, state);
dst_has_dma_mappings = (uvm_sgt->dma_count != 0);
}
@@ -807,6 +825,22 @@ static NV_STATUS uvm_migrate_vma_copy_pages_from(struct vm_area_struct *vma,
}
status = migrate_vma_copy_begin_push(va_space, copying_gpu, channel_type, dst_id, src_id, start, outer - 1, &push);
// If reserving a p2p channel slot failed with NV_ERR_BUSY_RETRY, nvlink
// is currently suspended. Retry the copy using cLinks instead of gLinks.
if (status == NV_ERR_BUSY_RETRY && UVM_ID_IS_GPU(src_id) && UVM_ID_IS_GPU(dst_id) && !use_clinks_for_p2p) {
UVM_ASSERT_MSG(!src_has_dma_mappings, "Copy %d -> %d should not have src dma mappings\n", src_id, dst_id);
UVM_ASSERT_MSG(!dst_has_dma_mappings, "Copy %d -> %d should not have dst dma mappings\n", src_id, dst_id);
UVM_ASSERT(uvm_id_equal(copying_gpu->id, src_id));
UVM_ASSERT(channel_type == UVM_CHANNEL_TYPE_GPU_TO_GPU);
// Using clinks treats peer vidmem as sysmem
channel_type = UVM_CHANNEL_TYPE_GPU_TO_CPU;
use_clinks_for_p2p = true;
goto retry_p2p;
}
if (status != NV_OK) {
uvm_tracker_wait_deinit(&zero_tracker);
return status;
+99 -15
View File
@@ -773,7 +773,8 @@ static bool assert_chunk_mergeable(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
// uniform state. This only merges leaves, so none of the children can be in the
// split state themselves.
//
// The children need to be removed from any lists before the merge.
// Removes all children from any lists they are on under list_lock before
// freeing them.
//
// The merged chunk inherits the former state of its children.
static void merge_gpu_chunk(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
@@ -791,6 +792,15 @@ static void merge_gpu_chunk(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
// the subchunk state.
uvm_spin_lock(&pmm->list_lock);
// A concurrent devmem_page_free_gpu_chunk() may have added a sub-chunk to
// va_block_lazy_free The lazy-free kthread acquires pmm->lock before
// dequeuing, so it is blocked for now. Remove every sub-chunk from any list
// here, under list_lock, so that when the kthread runs after pmm->lock is
// released it finds nothing to dequeue and avoids any potential
// use-after-free.
for (i = 0; i < num_sub; i++)
list_del_init(&chunk->suballoc->subchunks[i]->list);
subchunk = chunk->suballoc->subchunks[0];
child_state = subchunk->state;
@@ -798,12 +808,14 @@ static void merge_gpu_chunk(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
UVM_ASSERT(subchunk->va_block);
chunk->va_block = subchunk->va_block;
chunk->va_space = subchunk->va_space;
chunk->is_referenced = subchunk->is_referenced;
}
else if (child_state == UVM_PMM_GPU_CHUNK_STATE_TEMP_PINNED) {
UVM_ASSERT(root_chunk->chunk.suballoc->pinned_leaf_chunks >= num_sub);
root_chunk->chunk.suballoc->pinned_leaf_chunks += 1 - num_sub;
chunk->va_block = subchunk->va_block;
chunk->va_space = subchunk->va_space;
if (chunk_is_root_chunk(chunk))
++pmm->root_chunks.pinned_count;
}
@@ -824,10 +836,6 @@ static void merge_gpu_chunk(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
for (i = 0; i < num_sub; i++) {
subchunk = suballoc->subchunks[i];
// The subchunks should have been removed from their lists prior to the
// merge.
UVM_ASSERT(list_empty(&subchunk->list));
if ((child_state == UVM_PMM_GPU_CHUNK_STATE_ALLOCATED) && uvm_gpu_chunk_is_user(subchunk))
UVM_ASSERT(subchunk->va_block);
@@ -1199,6 +1207,7 @@ void uvm_pmm_gpu_mark_chunk_evicted(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
UVM_ASSERT(chunk->va_block);
chunk->va_block = NULL;
chunk->va_space = NULL;
chunk_pin(pmm, chunk);
uvm_spin_unlock(&pmm->list_lock);
@@ -2154,10 +2163,12 @@ NV_STATUS split_gpu_chunk(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
uvm_assert_mutex_locked(&chunk->va_block->lock);
subchunk->va_block = chunk->va_block;
subchunk->va_space = chunk->va_space;
subchunk->is_referenced = chunk->is_referenced;
}
else if (chunk->state == UVM_PMM_GPU_CHUNK_STATE_TEMP_PINNED) {
subchunk->va_block = chunk->va_block;
subchunk->va_space = chunk->va_space;
}
}
@@ -2172,6 +2183,7 @@ NV_STATUS split_gpu_chunk(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
if (chunk->state == UVM_PMM_GPU_CHUNK_STATE_ALLOCATED) {
chunk->va_block = NULL;
chunk->va_space = NULL;
chunk->is_referenced = false;
}
else if (chunk->state == UVM_PMM_GPU_CHUNK_STATE_TEMP_PINNED) {
@@ -2187,6 +2199,7 @@ NV_STATUS split_gpu_chunk(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
}
chunk->va_block = NULL;
chunk->va_space = NULL;
chunk->is_referenced = false;
}
@@ -2275,6 +2288,7 @@ static void chunk_free_locked(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
UVM_ASSERT(chunk->va_block);
UVM_ASSERT(list_empty(&chunk->list));
chunk->va_block = NULL;
chunk->va_space = NULL;
chunk->is_zero = false;
chunk_pin(pmm, chunk);
}
@@ -2292,6 +2306,7 @@ static void chunk_free_locked(uvm_pmm_gpu_t *pmm, uvm_gpu_chunk_t *chunk)
}
chunk->va_block = NULL;
chunk->va_space = NULL;
chunk->is_zero = false;
if (chunk->state == UVM_PMM_GPU_CHUNK_STATE_TEMP_PINNED)
@@ -2947,25 +2962,43 @@ cleanup:
void uvm_pmm_gpu_process_lazy_free(uvm_pmm_gpu_t *pmm)
{
uvm_gpu_chunk_t *chunk;
// Process entries one at a time, holding pmm->lock across each dequeue and
// free. This closes the race with every path that can call
// merge_gpu_chunk() on this chunk's parent while we hold a pointer to it.
while (1) {
uvm_gpu_chunk_t *chunk;
uvm_pmm_gpu_memory_type_t type;
bool free_root;
NvU32 test_delay_us = pmm->root_chunks.test_lazy_free_delay_us;
uvm_spin_lock(&pmm->list_lock);
uvm_mutex_lock(&pmm->lock);
uvm_spin_lock(&pmm->list_lock);
if (list_empty(&pmm->root_chunks.va_block_lazy_free)) {
uvm_spin_unlock(&pmm->list_lock);
uvm_mutex_unlock(&pmm->lock);
break;
}
// Note: We can't use list_for_each_safe_entry() because we drop the lock
// in the loop. Instead, just keep removing the first entry until the list
// is empty.
while (!list_empty(&pmm->root_chunks.va_block_lazy_free)) {
chunk = list_first_entry(&pmm->root_chunks.va_block_lazy_free, uvm_gpu_chunk_t, list);
UVM_ASSERT(chunk->state == UVM_PMM_GPU_CHUNK_STATE_TEMP_PINNED);
type = chunk->type;
free_root = chunk_is_root_chunk(chunk) || chunk_is_last_allocated_child(pmm, chunk);
list_del_init(&chunk->list);
uvm_spin_unlock(&pmm->list_lock);
free_chunk(pmm, chunk);
if (unlikely(test_delay_us))
usleep_range(test_delay_us, test_delay_us + 1000);
uvm_spin_lock(&pmm->list_lock);
free_chunk_with_merges(pmm, chunk);
uvm_mutex_unlock(&pmm->lock);
if (free_root)
free_next_available_root_chunk(pmm, type);
}
uvm_spin_unlock(&pmm->list_lock);
}
static void process_lazy_free_entry(void *args)
@@ -3564,3 +3597,54 @@ out:
uvm_va_space_up_read(va_space);
return status;
}
NV_STATUS uvm_test_pmm_lazy_free_set_delay(UVM_TEST_PMM_LAZY_FREE_SET_DELAY_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
uvm_gpu_t *gpu;
gpu = uvm_va_space_retain_gpu_by_uuid(va_space, &params->gpu_uuid);
if (!gpu)
return NV_ERR_INVALID_DEVICE;
gpu->pmm.root_chunks.test_lazy_free_delay_us = params->delay_us;
uvm_gpu_release(gpu);
return NV_OK;
}
NV_STATUS uvm_test_pmm_evict_temp_pinned(UVM_TEST_PMM_EVICT_TEMP_PINNED_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
uvm_gpu_t *gpu;
uvm_pmm_gpu_t *pmm;
size_t i;
gpu = uvm_va_space_retain_gpu_by_uuid(va_space, &params->gpu_uuid);
if (!gpu)
return NV_ERR_INVALID_DEVICE;
pmm = &gpu->pmm;
uvm_mutex_lock(&pmm->lock);
for (i = 0; i < pmm->root_chunks.count; i++) {
uvm_gpu_chunk_t *root = &pmm->root_chunks.array[i].chunk;
NV_STATUS walk_status;
if (root->state != UVM_PMM_GPU_CHUNK_STATE_IS_SPLIT)
continue;
do {
walk_status = chunk_walk_pre_order(pmm, root, free_first_pinned_chunk_func, NULL);
} while (walk_status == NV_ERR_MORE_DATA_AVAILABLE);
UVM_ASSERT(walk_status == NV_OK);
}
uvm_mutex_unlock(&pmm->lock);
uvm_gpu_release(gpu);
return NV_OK;
}
+15
View File
@@ -292,6 +292,19 @@ struct uvm_gpu_chunk_struct
// This field is always NULL in kernel chunks.
uvm_va_block_t *va_block;
// The VA space owning the VA block using the chunk, if any. This caches
// uvm_va_block_get_va_space(va_block) (i.e. va_block->hmm.va_space for HMM
// blocks) and is kept in sync whenever va_block is assigned or cleared.
//
// It is needed because a device private (or device coherent) struct page
// can outlive its va_block: the Linux page_free() callback
// (devmem_page_free_gpu_chunk()) may run after the va_block has already been
// freed, so it must not dereference va_block to find the va_space. The
// va_space is guaranteed to outlive any device page referencing this chunk.
//
// This field is always NULL in kernel chunks.
uvm_va_space_t *va_space;
// If this is subchunk it points to the parent - in other words
// chunk of bigger size which contains this chunk.
uvm_gpu_chunk_t *parent;
@@ -344,6 +357,8 @@ typedef struct uvm_pmm_gpu_struct
struct list_head va_block_lazy_free;
nv_kthread_q_item_t va_block_lazy_free_q_item;
NvU32 test_lazy_free_delay_us;
// Count of the number of root chunks "in_eviction". Incremented for
// each root chunks that starts the eviction process, and decremented
// on eviction success or failure.
+117 -5
View File
@@ -27,6 +27,8 @@
#include "uvm_va_block.h"
#include "uvm_va_space.h"
#define DMA_ADDR_INVALID (~(dma_addr_t)0)
static int uvm_cpu_chunk_allocation_sizes = UVM_CPU_CHUNK_SIZES;
module_param(uvm_cpu_chunk_allocation_sizes, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(uvm_cpu_chunk_allocation_sizes, "OR'ed value of all CPU chunk allocation sizes.");
@@ -164,6 +166,17 @@ static void chunk_inc_gpu_mapping(uvm_cpu_physical_chunk_t *chunk, uvm_parent_gp
mapping->map_count++;
}
static void cpu_chunk_unmap_physical_egm(uvm_cpu_physical_chunk_t *chunk, uvm_parent_gpu_t *gpu)
{
if (chunk->common.egm_dma_addr != DMA_ADDR_INVALID) {
UVM_ASSERT(uvm_parent_id_equal(chunk->egm_parent_id, gpu->id));
UVM_ASSERT(page_to_nid(chunk->common.page) == gpu->closest_cpu_numa_node);
uvm_parent_gpu_unmap_cpu_pages_for_egm(gpu, chunk->common.egm_dma_addr, uvm_cpu_chunk_get_size(&chunk->common));
chunk->common.egm_dma_addr = DMA_ADDR_INVALID;
chunk->egm_parent_id = UVM_PARENT_ID_INVALID;
}
}
static void chunk_dec_gpu_mapping(uvm_cpu_physical_chunk_t *chunk, uvm_parent_gpu_id_t id)
{
uvm_cpu_phys_mapping_t *mapping;
@@ -179,13 +192,20 @@ static void chunk_dec_gpu_mapping(uvm_cpu_physical_chunk_t *chunk, uvm_parent_gp
uvm_parent_gpu_unmap_cpu_pages(parent_gpu, mapping->dma_addr, uvm_cpu_chunk_get_size(&chunk->common));
mapping->dma_addr = 0;
if (UVM_PARENT_ID_IS_VALID(chunk->egm_parent_id)) {
parent_gpu = uvm_parent_gpu_get(chunk->egm_parent_id);
UVM_ASSERT(parent_gpu);
cpu_chunk_unmap_physical_egm(chunk, parent_gpu);
}
if (chunk->gpu_mappings.max_entries > 1) {
NvU32 num_active_entries = uvm_parent_processor_mask_get_gpu_count(&chunk->gpu_mappings.dma_addrs_mask);
NvU32 array_index = compute_gpu_mappings_entry_index(&chunk->gpu_mappings.dma_addrs_mask, id);
// Shift any GPU mappings above this one down in the mappings array.
for (; array_index < num_active_entries - 1; array_index++)
chunk->gpu_mappings.dynamic_entries[array_index] = chunk->gpu_mappings.dynamic_entries[array_index+1];
chunk->gpu_mappings.dynamic_entries[array_index] = chunk->gpu_mappings.dynamic_entries[array_index + 1];
}
uvm_parent_processor_mask_clear(&chunk->gpu_mappings.dma_addrs_mask, id);
@@ -202,6 +222,8 @@ NvU64 uvm_cpu_chunk_get_gpu_phys_addr(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu)
if (uvm_cpu_chunk_is_logical(chunk)) {
uvm_cpu_logical_chunk_t *logical_chunk = uvm_cpu_chunk_to_logical(chunk);
// For EGM, we don't care if the GPU is mapped. We know that the entire
// physical chunk is mapped, so we only need the offset.
if (!uvm_processor_mask_test(&logical_chunk->mapped_gpus, gpu->id))
return 0;
@@ -210,10 +232,38 @@ NvU64 uvm_cpu_chunk_get_gpu_phys_addr(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu)
uvm_mutex_lock(&phys_chunk->lock);
mapping = chunk_phys_mapping_get(phys_chunk, gpu->parent->id);
if (mapping &&
(uvm_cpu_chunk_is_logical(chunk) ||
uvm_sub_processor_mask_test(&mapping->sub_processors, uvm_id_sub_processor_index(gpu->id))))
if (mapping && (uvm_cpu_chunk_is_logical(chunk) ||
uvm_sub_processor_mask_test(&mapping->sub_processors, uvm_id_sub_processor_index(gpu->id)))) {
dma_addr = mapping->dma_addr + (parent_offset * PAGE_SIZE);
}
uvm_mutex_unlock(&phys_chunk->lock);
return dma_addr;
}
NvU64 uvm_cpu_chunk_get_gpu_egm_phys_addr(uvm_cpu_chunk_t *chunk, uvm_parent_gpu_t *routing_gpu)
{
uvm_cpu_physical_chunk_t *phys_chunk = get_physical_parent(chunk);
uvm_page_index_t parent_offset = 0;
uvm_cpu_logical_chunk_t *logical;
uvm_cpu_chunk_t *track_chunk;
NvU64 dma_addr = 0;
track_chunk = chunk;
uvm_mutex_lock(&phys_chunk->lock);
while (track_chunk->egm_dma_addr == DMA_ADDR_INVALID && uvm_cpu_chunk_is_logical(track_chunk)) {
logical = uvm_cpu_chunk_to_logical(track_chunk);
track_chunk = logical->parent;
}
if (track_chunk->egm_dma_addr == DMA_ADDR_INVALID) {
uvm_mutex_unlock(&phys_chunk->lock);
return 0;
}
parent_offset = (uvm_page_index_t)(chunk->page - track_chunk->page);
dma_addr = track_chunk->egm_dma_addr + (parent_offset * PAGE_SIZE);
uvm_mutex_unlock(&phys_chunk->lock);
return dma_addr;
@@ -262,11 +312,12 @@ static NV_STATUS cpu_chunk_map_gpu_phys(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu)
mapping->dma_addr = dma_addr;
mapping->map_count = 1;
uvm_sub_processor_mask_zero(&mapping->sub_processors);
if (!logical_chunk)
uvm_sub_processor_mask_set(&mapping->sub_processors, uvm_id_sub_processor_index(gpu->id));
uvm_parent_processor_mask_set(&phys_chunk->gpu_mappings.dma_addrs_mask, parent_gpu->id);
uvm_parent_processor_mask_set(&phys_chunk->gpu_mappings.dma_addrs_mask, gpu->parent->id);
}
else {
mapping = chunk_phys_mapping_get(phys_chunk, parent_gpu->id);
@@ -335,10 +386,68 @@ NV_STATUS uvm_cpu_chunk_map_gpu(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu)
return status;
}
NV_STATUS uvm_cpu_chunk_map_gpu_egm(uvm_cpu_chunk_t *chunk, uvm_parent_gpu_t *routing_gpu)
{
uvm_cpu_physical_chunk_t *phys_chunk = get_physical_parent(chunk);
uvm_cpu_logical_chunk_t *logical;
NV_STATUS status = NV_OK;
dma_addr_t dma_addr;
UVM_ASSERT(page_to_nid(chunk->page) == routing_gpu->closest_cpu_numa_node);
uvm_mutex_lock(&phys_chunk->lock);
while (chunk->egm_dma_addr == DMA_ADDR_INVALID && uvm_cpu_chunk_is_logical(chunk)) {
logical = uvm_cpu_chunk_to_logical(chunk);
chunk = logical->parent;
}
if (chunk->egm_dma_addr == DMA_ADDR_INVALID) {
status = uvm_gpu_map_cpu_pages_for_egm(routing_gpu, chunk->page, uvm_cpu_chunk_get_size(chunk), &dma_addr);
// NV_ERR_NOT_SUPPORTED is returned if the kernel is
// < 6.17. In this cases, there will be no mappings
// mappings created, which will result in non-EGM accesses.
if (status == NV_ERR_NOT_SUPPORTED) {
status = NV_OK;
goto done;
}
if (status != NV_OK)
goto done;
// We already hold a map count on the chunk. All of the
// EGM mappings will be destroyed when the chunk is freed.
chunk->egm_dma_addr = dma_addr;
phys_chunk->egm_parent_id = routing_gpu->id;
}
done:
uvm_mutex_unlock(&phys_chunk->lock);
return status;
}
void uvm_cpu_chunk_unmap_gpu(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu)
{
cpu_chunk_unmap_gpu_phys(chunk, gpu->id);
}
void uvm_cpu_chunk_unmap_gpu_egm(uvm_cpu_chunk_t *chunk, uvm_parent_gpu_t *routing_gpu)
{
uvm_cpu_physical_chunk_t *phys_chunk = get_physical_parent(chunk);
uvm_cpu_logical_chunk_t *logical;
uvm_mutex_lock(&phys_chunk->lock);
while (chunk->egm_dma_addr == DMA_ADDR_INVALID && uvm_cpu_chunk_is_logical(chunk)) {
logical = uvm_cpu_chunk_to_logical(chunk);
chunk = logical->parent;
}
if (uvm_cpu_chunk_is_physical(chunk))
cpu_chunk_unmap_physical_egm(phys_chunk, routing_gpu);
else
uvm_parent_gpu_unmap_cpu_pages_for_egm(routing_gpu, chunk->egm_dma_addr, uvm_cpu_chunk_get_size(chunk));
uvm_mutex_unlock(&phys_chunk->lock);
// Note: there is no corresponding uvm_mmu_sysmem_unmap() for
// uvm_mmu_sysmem_map().
}
@@ -486,6 +595,8 @@ static uvm_cpu_physical_chunk_t *uvm_cpu_chunk_create(uvm_chunk_size_t alloc_siz
nv_kref_init(&chunk->common.refcount);
uvm_mutex_init(&chunk->lock, UVM_LOCK_ORDER_LEAF);
chunk->gpu_mappings.max_entries = 1;
chunk->egm_parent_id = UVM_PARENT_ID_INVALID;
chunk->common.egm_dma_addr = DMA_ADDR_INVALID;
return chunk;
}
@@ -613,6 +724,7 @@ NV_STATUS uvm_cpu_chunk_split(uvm_cpu_chunk_t *chunk, uvm_cpu_chunk_t **new_chun
new_chunk->common.type = UVM_CPU_CHUNK_TYPE_LOGICAL;
new_chunk->common.page = chunk->page + (i * num_subchunk_pages);
new_chunk->common.egm_dma_addr = DMA_ADDR_INVALID;
uvm_cpu_chunk_set_size(&new_chunk->common, new_size);
nv_kref_init(&new_chunk->common.refcount);
new_chunk->parent = chunk;
+25 -1
View File
@@ -29,6 +29,7 @@
#include "uvm_forward_decl.h"
#include "uvm_lock.h"
#include "uvm_pmm_gpu.h"
#include "uvm_processors.h"
// Global initialization/exit functions, that need to be called during driver
// initialization/tear-down. These are needed to allocate/free global internal
@@ -101,12 +102,21 @@ struct uvm_cpu_chunk_struct
// reference counted, there is no need to take separate references
// to the struct page for logical chunks.
struct page *page;
// This is the IOVA address used for EGM accesses. Each chunk
// holds it's own mapping address. The mapping size is the size
// of the chunk.
// EGM IOVA mappings are created on the parent GPU attached to
// the chunk's page NUMA node.
// This address is property of the common chunk in order to
// correctly handle splitting chunks and overlapping chunks.
dma_addr_t egm_dma_addr;
};
typedef struct
{
// Physical GPU DMA address of the CPU chunk.
NvU64 dma_addr;
dma_addr_t dma_addr;
// Reference count of all sub_processors using this mapping across logical
// and physical chunks.
@@ -153,6 +163,9 @@ typedef struct
uvm_parent_processor_mask_t dma_addrs_mask;
} gpu_mappings;
// ID of the parent GPU holding all of the EGM mappings. This value
// is mostly used for sanity checking.
uvm_parent_gpu_id_t egm_parent_id;
} uvm_cpu_physical_chunk_t;
typedef struct
@@ -282,16 +295,27 @@ void uvm_cpu_chunk_free(uvm_cpu_chunk_t *chunk);
// the operation is issued.
NV_STATUS uvm_cpu_chunk_map_gpu(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu);
// Create EGM mappings for the chunk chunk.
// Mappings are created on the mapping GPU but they are with respect to IOVA
// ranges on the routing GPU.
NV_STATUS uvm_cpu_chunk_map_gpu_egm(uvm_cpu_chunk_t *chunk, uvm_parent_gpu_t *routing_gpu);
// Destroy a CPU chunk's DMA mapping for the given GPU.
// If chunk is a logical chunk, this call may not necessarily destroy the DMA
// mapping of the parent physical chunk since all logical chunks and MIG
// partitions share the parent's DMA mapping.
void uvm_cpu_chunk_unmap_gpu(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu);
// Destroy EGM mappings for the CPU chunk.
void uvm_cpu_chunk_unmap_gpu_egm(uvm_cpu_chunk_t *chunk, uvm_parent_gpu_t *routing_gpu);
// Get the CPU chunk's DMA mapping address for the specified GPU ID.
// If there is no mapping for the GPU, 0 is returned.
NvU64 uvm_cpu_chunk_get_gpu_phys_addr(uvm_cpu_chunk_t *chunk, uvm_gpu_t *gpu);
// Get the EGM mapping for the CPU chunk with respect to the routing GPU.
NvU64 uvm_cpu_chunk_get_gpu_egm_phys_addr(uvm_cpu_chunk_t *chunk, uvm_parent_gpu_t *routing_gpu);
// Split a CPU chunk into a set of CPU chunks of the next size down from the set
// of enabled CPU chunk sizes.
//
+1 -1
View File
@@ -66,7 +66,7 @@ void uvm_hal_rubin_arch_init_properties(uvm_parent_gpu_t *parent_gpu)
parent_gpu->access_counters_serialize_clear_ops_by_type = false;
parent_gpu->access_bits_supported = false;
parent_gpu->access_bits_supported = true;
parent_gpu->has_clear_faulted_channel_sw_method = true;
+111
View File
@@ -28,6 +28,11 @@
#include "hwref/rubin/gr100/dev_fault.h"
#include "hwref/rubin/gr100/dev_mmu.h"
// Field defines which are unmaintained by HW
#define NV_MMU_VER3_PTE_ADDRESS_SYS 51 : 12
#define NV_MMU_VER3_PTE_ADDRESS_PEER 51 : 12
#define NV_MMU_VER3_PTE_ADDRESS_VID 39 : 12
static uvm_mmu_mode_hal_t rubin_mmu_mode_hal;
NvU16 uvm_hal_rubin_mmu_client_id_to_utlb_id(NvU16 client_id)
@@ -153,6 +158,111 @@ static NvU64 poisoned_pte_rubin(uvm_page_tree_t *tree)
return WRITE_HWCONST64(pte_bits, _MMU_VER3, PTE, PCF, PRIVILEGE_RO_NO_ATOMIC_UNCACHED_ACD);
}
// PTE Permission Control Flags
static NvU64 pte_pcf(uvm_prot_t prot, NvU64 flags)
{
bool ac = !(flags & UVM_MMU_PTE_FLAGS_ACCESS_COUNTERS_DISABLED);
bool cached = flags & UVM_MMU_PTE_FLAGS_CACHED;
UVM_ASSERT(prot != UVM_PROT_NONE);
UVM_ASSERT((flags & ~UVM_MMU_PTE_FLAGS_MASK) == 0);
if (ac) {
switch (prot) {
case UVM_PROT_READ_ONLY:
return cached ? NV_MMU_VER3_PTE_PCF_REGULAR_RO_NO_ATOMIC_CACHED_ACE :
NV_MMU_VER3_PTE_PCF_REGULAR_RO_NO_ATOMIC_UNCACHED_ACE;
case UVM_PROT_READ_WRITE:
return cached ? NV_MMU_VER3_PTE_PCF_REGULAR_RW_NO_ATOMIC_CACHED_ACE :
NV_MMU_VER3_PTE_PCF_REGULAR_RW_NO_ATOMIC_UNCACHED_ACE;
case UVM_PROT_READ_WRITE_ATOMIC:
return cached ? NV_MMU_VER3_PTE_PCF_REGULAR_RW_ATOMIC_CACHED_ACE :
NV_MMU_VER3_PTE_PCF_REGULAR_RW_ATOMIC_UNCACHED_ACE;
default:
break;
}
}
else {
switch (prot) {
case UVM_PROT_READ_ONLY:
return cached ? NV_MMU_VER3_PTE_PCF_REGULAR_RO_NO_ATOMIC_CACHED_ACD :
NV_MMU_VER3_PTE_PCF_REGULAR_RO_NO_ATOMIC_UNCACHED_ACD;
case UVM_PROT_READ_WRITE:
return cached ? NV_MMU_VER3_PTE_PCF_REGULAR_RW_NO_ATOMIC_CACHED_ACD :
NV_MMU_VER3_PTE_PCF_REGULAR_RW_NO_ATOMIC_UNCACHED_ACD;
case UVM_PROT_READ_WRITE_ATOMIC:
return cached ? NV_MMU_VER3_PTE_PCF_REGULAR_RW_ATOMIC_CACHED_ACD :
NV_MMU_VER3_PTE_PCF_REGULAR_RW_ATOMIC_UNCACHED_ACD;
default:
break;
}
}
// Unsupported PCF
UVM_ASSERT_MSG(0, "Unsupported PTE PCF: prot: %s, ac: %d, cached: %d\n", uvm_prot_string(prot), ac, cached);
return NV_MMU_VER3_PTE_PCF_INVALID;
}
static NvU64 make_pte_rubin(uvm_aperture_t aperture, NvU64 address, uvm_prot_t prot, NvU64 flags)
{
NvU8 aperture_bits = 0;
NvU64 pte_bits = 0;
UVM_ASSERT(prot != UVM_PROT_NONE);
UVM_ASSERT((flags & ~UVM_MMU_PTE_FLAGS_MASK) == 0);
// valid 0:0
pte_bits |= HWCONST64(_MMU_VER3, PTE, VALID, TRUE);
// aperture 2:1
if (aperture == UVM_APERTURE_SYS)
aperture_bits = NV_MMU_VER3_PTE_APERTURE_SYSTEM_COHERENT_MEMORY;
else if (aperture == UVM_APERTURE_SYS_NON_COHERENT)
aperture_bits = NV_MMU_VER3_PTE_APERTURE_SYSTEM_NON_COHERENT_MEMORY;
else if (aperture == UVM_APERTURE_VID)
aperture_bits = NV_MMU_VER3_PTE_APERTURE_VIDEO_MEMORY;
else if (uvm_aperture_is_peer(aperture) || uvm_aperture_is_egm(aperture))
aperture_bits = NV_MMU_VER3_PTE_APERTURE_PEER_MEMORY;
else
UVM_ASSERT_MSG(0, "Invalid aperture: %d\n", aperture);
pte_bits |= HWVALUE64(_MMU_VER3, PTE, APERTURE, aperture_bits);
// PCF (permission control flags) 7:3
if (uvm_aperture_is_egm(aperture)) {
// FLA Unicast mappings require *_ACD PCF flags.
flags |= UVM_MMU_PTE_FLAGS_ACCESS_COUNTERS_DISABLED;
}
pte_bits |= HWVALUE64(_MMU_VER3, PTE, PCF, pte_pcf(prot, flags));
// kind 11:8
if (uvm_aperture_is_egm(aperture))
pte_bits |= HWVALUE64(_MMU_VER3, PTE, KIND, NV_MMU_PTE_KIND_SMSKED_MESSAGE);
else
pte_bits |= HWVALUE64(_MMU_VER3, PTE, KIND, NV_MMU_PTE_KIND_GENERIC_MEMORY);
address >>= NV_MMU_VER3_PTE_ADDRESS_SHIFT;
if (aperture == UVM_APERTURE_VID) {
// vid address 39:12
pte_bits |= HWVALUE64(_MMU_VER3, PTE, ADDRESS_VID, address);
}
else {
// sys/peer address 51:12
pte_bits |= HWVALUE64(_MMU_VER3, PTE, ADDRESS, address);
// peer id 63:61
if (uvm_aperture_is_peer(aperture))
pte_bits |= HWVALUE64(_MMU_VER3, PTE, PEER_ID, UVM_APERTURE_PEER_ID(aperture));
else if (uvm_aperture_is_egm(aperture))
pte_bits |= HWVALUE64(_MMU_VER3, PTE, PEER_ID, UVM_APERTURE_EGM_PEER_ID(aperture));
}
return pte_bits;
}
uvm_mmu_mode_hal_t *uvm_hal_mmu_mode_rubin(void)
{
static bool initialized = false;
@@ -168,6 +278,7 @@ uvm_mmu_mode_hal_t *uvm_hal_mmu_mode_rubin(void)
rubin_mmu_mode_hal = *blackwell_mmu_mode_hal;
rubin_mmu_mode_hal.poisoned_pte = poisoned_pte_rubin;
rubin_mmu_mode_hal.make_pte = make_pte_rubin;
initialized = true;
}
+6
View File
@@ -42,6 +42,7 @@
#include "uvm_test_file.h"
#include "uvm_user_channel.h"
#include "uvm_gpu_non_replayable_faults.h"
#include "uvm_gpu.h"
static NV_STATUS uvm_test_get_gpu_ref_count(UVM_TEST_GET_GPU_REF_COUNT_PARAMS *params, struct file *filp)
{
@@ -375,6 +376,11 @@ long uvm_test_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_DEAD_CHANNEL, uvm_test_dead_channel);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_SET_NON_REPLAYABLE_DELAY, uvm_test_set_non_replayable_delay);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_QUERY_CDMM_DEVMEM, uvm_test_query_cdmm_devmem);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_EXPORT_DMA_BUF, uvm_test_export_dma_buf);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_REVOKE_DMA_BUF, uvm_test_revoke_dma_buf);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_QUERY_EGM_STATE, uvm_test_query_egm_state);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_PMM_LAZY_FREE_SET_DELAY, uvm_test_pmm_lazy_free_set_delay);
UVM_ROUTE_CMD_STACK_INIT_CHECK(UVM_TEST_PMM_EVICT_TEMP_PINNED, uvm_test_pmm_evict_temp_pinned);
}
return -EINVAL;
+6
View File
@@ -131,6 +131,8 @@ NV_STATUS uvm_test_pma_get_batch_size(UVM_TEST_PMA_GET_BATCH_SIZE_PARAMS *params
NV_STATUS uvm_test_pmm_alloc_free_root(UVM_TEST_PMM_ALLOC_FREE_ROOT_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_pmm_inject_pma_evict_error(UVM_TEST_PMM_INJECT_PMA_EVICT_ERROR_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_pmm_query_pma_stats(UVM_TEST_PMM_QUERY_PMA_STATS_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_pmm_lazy_free_set_delay(UVM_TEST_PMM_LAZY_FREE_SET_DELAY_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_pmm_evict_temp_pinned(UVM_TEST_PMM_EVICT_TEMP_PINNED_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_perf_events_sanity(UVM_TEST_PERF_EVENTS_SANITY_PARAMS *params, struct file *filp);
@@ -185,4 +187,8 @@ NV_STATUS uvm_test_dump_access_bits(UVM_TEST_DUMP_ACCESS_BITS_PARAMS *params, st
NV_STATUS uvm_test_sec2_sanity(UVM_TEST_SEC2_SANITY_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_sec2_cpu_gpu_roundtrip(UVM_TEST_SEC2_CPU_GPU_ROUNDTRIP_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_cpu_chunk_api(UVM_TEST_CPU_CHUNK_API_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_export_dma_buf(UVM_TEST_EXPORT_DMA_BUF_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_revoke_dma_buf(UVM_TEST_REVOKE_DMA_BUF_PARAMS *params, struct file *filp);
NV_STATUS uvm_test_query_egm_state(UVM_TEST_QUERY_EGM_STATE_PARAMS *params, struct file *filp);
#endif
+37
View File
@@ -142,6 +142,7 @@ typedef enum
UVM_TEST_VA_RANGE_TYPE_INVALID = 0,
UVM_TEST_VA_RANGE_TYPE_MANAGED,
UVM_TEST_VA_RANGE_TYPE_EXTERNAL,
UVM_TEST_VA_RANGE_TYPE_DMA_BUF,
UVM_TEST_VA_RANGE_TYPE_CHANNEL,
UVM_TEST_VA_RANGE_TYPE_SKED_REFLECTED,
UVM_TEST_VA_RANGE_TYPE_SEMAPHORE_POOL,
@@ -1624,6 +1625,42 @@ typedef struct
NV_STATUS rmStatus; // Out
} UVM_TEST_QUERY_CDMM_DEVMEM_PARAMS;
#define UVM_TEST_EXPORT_DMA_BUF UVM_TEST_IOCTL_BASE(117)
typedef struct {
NvU64 size; // In
NvS32 fd; // Out
NV_STATUS rmStatus; // Out
} UVM_TEST_EXPORT_DMA_BUF_PARAMS;
#define UVM_TEST_REVOKE_DMA_BUF UVM_TEST_IOCTL_BASE(118)
typedef struct {
NvS32 fd; // In
NV_STATUS rmStatus; // Out
} UVM_TEST_REVOKE_DMA_BUF_PARAMS;
#define UVM_TEST_QUERY_EGM_STATE UVM_TEST_IOCTL_BASE(119)
typedef struct
{
NvProcessorUuid gpu_uuid; // In
NvBool enabled; // Out
NV_STATUS rmStatus; // Out
} UVM_TEST_QUERY_EGM_STATE_PARAMS;
#define UVM_TEST_PMM_LAZY_FREE_SET_DELAY UVM_TEST_IOCTL_BASE(120)
typedef struct
{
NvProcessorUuid gpu_uuid; // In
NvU32 delay_us; // In
NV_STATUS rmStatus; // Out
} UVM_TEST_PMM_LAZY_FREE_SET_DELAY_PARAMS;
#define UVM_TEST_PMM_EVICT_TEMP_PINNED UVM_TEST_IOCTL_BASE(121)
typedef struct
{
NvProcessorUuid gpu_uuid; // In
NV_STATUS rmStatus; // Out
} UVM_TEST_PMM_EVICT_TEMP_PINNED_PARAMS;
#ifdef __cplusplus
}
#endif
+4 -2
View File
@@ -254,7 +254,7 @@ static NvU64 make_pte_turing(uvm_aperture_t aperture, NvU64 address, uvm_prot_t
aperture_bits = NV_MMU_VER2_PTE_APERTURE_SYSTEM_COHERENT_MEMORY;
else if (aperture == UVM_APERTURE_VID)
aperture_bits = NV_MMU_VER2_PTE_APERTURE_VIDEO_MEMORY;
else if (aperture >= UVM_APERTURE_PEER_0 && aperture <= UVM_APERTURE_PEER_7)
else if (uvm_aperture_is_peer(aperture))
aperture_bits = NV_MMU_VER2_PTE_APERTURE_PEER_MEMORY;
else
UVM_ASSERT_MSG(0, "Invalid aperture: %d\n", aperture);
@@ -304,8 +304,10 @@ static NvU64 make_pte_turing(uvm_aperture_t aperture, NvU64 address, uvm_prot_t
pte_bits |= HWVALUE64(_MMU_VER2, PTE, COMPTAGLINE, addr_hi);
// peer id 35:33
if (aperture != UVM_APERTURE_VID)
if (aperture != UVM_APERTURE_VID) {
UVM_ASSERT(uvm_aperture_is_peer(aperture));
pte_bits |= HWVALUE64(_MMU_VER2, PTE, ADDRESS_VID_PEER, UVM_APERTURE_PEER_ID(aperture));
}
}
// kind 63:56
+171 -34
View File
@@ -1336,6 +1336,8 @@ static void cpu_chunk_remove_sysmem_gpu_mapping(uvm_cpu_chunk_t *chunk, uvm_gpu_
static NV_STATUS cpu_chunk_add_sysmem_gpu_mapping(uvm_cpu_chunk_t *chunk, uvm_va_block_t *block, uvm_gpu_t *gpu)
{
NV_STATUS status;
uvm_va_space_t *va_space = uvm_va_block_get_va_space(block);
uvm_parent_gpu_t *routing_gpu;
// When the Confidential Computing feature is enabled the transfers don't
// use the DMA mapping of CPU chunks (since it's protected memory), but
@@ -1347,6 +1349,13 @@ static NV_STATUS cpu_chunk_add_sysmem_gpu_mapping(uvm_cpu_chunk_t *chunk, uvm_va
if (status != NV_OK)
return status;
routing_gpu = uvm_va_space_get_egm_routing_gpu(va_space, gpu, uvm_cpu_chunk_get_numa_node(chunk));
if (routing_gpu) {
status = uvm_cpu_chunk_map_gpu_egm(chunk, routing_gpu);
if (status != NV_OK)
return status;
}
// If this GPU requires physical invalidations for new DMA mappings, tell
// the next relevant operation to issue one before accessing the mapping.
if (gpu->parent->ats.dma_map_invalidation != UVM_DMA_MAP_INVALIDATION_NONE)
@@ -2702,7 +2711,7 @@ static bool block_check_egm_peer(uvm_va_space_t *va_space, uvm_gpu_t *gpu, int n
uvm_egm_numa_node_info_t *remote_node_info;
uvm_parent_gpu_t *parent_gpu;
if (!uvm_aperture_is_peer(phys_addr.aperture))
if (!uvm_aperture_is_egm(phys_addr.aperture))
return true;
remote_node_info = uvm_va_space_get_egm_numa_node_info(va_space, nid);
@@ -2710,10 +2719,10 @@ static bool block_check_egm_peer(uvm_va_space_t *va_space, uvm_gpu_t *gpu, int n
for_each_parent_gpu_in_mask(parent_gpu, &remote_node_info->parent_gpus) {
NvU64 page_addr = phys_addr.address;
UVM_ASSERT(parent_gpu->egm.enabled);
page_addr += parent_gpu->egm.base_address;
UVM_ASSERT(uvm_parent_gpu_egm_enabled(parent_gpu));
page_addr += uvm_parent_gpu_egm_iova_address(parent_gpu);
if (parent_gpu->nvswitch_info.is_nvswitch_connected && gpu->parent != parent_gpu)
page_addr -= parent_gpu->nvswitch_info.egm_fabric_memory_window_start;
page_addr -= uvm_parent_gpu_egm_fabric_address(parent_gpu);
if (page_addr >= remote_node_info->node_start && page_addr < remote_node_info->node_end &&
remote_node_info->routing_table[uvm_parent_id_gpu_index(gpu->parent->id)] == parent_gpu) {
@@ -2912,6 +2921,7 @@ static NV_STATUS block_populate_gpu_chunk(uvm_va_block_t *block,
block_retry_add_used_chunk(retry, chunk);
chunk->va_block = block;
chunk->va_space = uvm_va_block_get_va_space(block);
chunk->va_block_page_index = chunk_region.first;
if (uvm_va_block_is_hmm(block))
UVM_ASSERT(chunk_index == chunk_region.first);
@@ -2985,7 +2995,8 @@ static NV_STATUS block_populate_pages(uvm_va_block_t *block,
uvm_va_block_context_t *block_context,
uvm_processor_id_t dest_id,
uvm_va_block_region_t region,
const uvm_page_mask_t *page_mask)
const uvm_page_mask_t *page_mask,
uvm_va_block_p2p_mode_t p2p_mode)
{
NV_STATUS status;
const uvm_page_mask_t *resident_mask = block_resident_mask_get_alloc(block,
@@ -2995,6 +3006,8 @@ static NV_STATUS block_populate_pages(uvm_va_block_t *block,
uvm_page_mask_t *pages_staged = &block_context->make_resident.pages_staged;
uvm_page_mask_t *cpu_populate_mask;
uvm_memcg_context_t memcg_context;
static const uvm_processor_mask_t empty_mask = {};
const bool is_p2p_enabled = p2p_mode == UVM_VA_BLOCK_P2P_ENABLED;
if (!resident_mask)
return NV_ERR_NO_MEMORY;
@@ -3025,10 +3038,11 @@ static NV_STATUS block_populate_pages(uvm_va_block_t *block,
// Get the mask of all processors that have resident pages from which
// the destination cannot copy directly.
can_copy_from_processors = block_get_can_copy_from_mask(block, dest_id);
// If p2p is suspended, use an empty mask.
can_copy_from_processors = is_p2p_enabled ? block_get_can_copy_from_mask(block, dest_id) : &empty_mask;
if (!uvm_processor_mask_andnot(tmp_processor_mask, &block->resident, can_copy_from_processors)) {
uvm_processor_mask_cache_free(tmp_processor_mask);
return status;
return NV_OK;
}
// Compute the pages that will be staged through the CPU by:
@@ -3103,6 +3117,54 @@ typedef enum {
REMOTE_EGM_NOT_ALLOWED = 1,
} remote_egm_mode_t;
static uvm_gpu_phys_address_t block_phys_egm_address(uvm_va_block_t *block,
uvm_gpu_t *local_gpu,
uvm_parent_gpu_t *routing_gpu,
uvm_cpu_chunk_t *chunk,
uvm_page_index_t page_index)
{
uvm_aperture_t aperture;
uvm_parent_gpu_t *local_parent_gpu = local_gpu->parent;
NvU64 phys_addr;
UVM_ASSERT(uvm_parent_gpu_egm_enabled(routing_gpu));
aperture = uvm_gpu_egm_peer_aperture(local_parent_gpu, routing_gpu);
if (uvm_parent_gpu_egm_is_legacy(local_parent_gpu)) {
struct page *page = uvm_cpu_chunk_get_cpu_page(block, chunk, page_index);
phys_addr = page_to_phys(page);
// Remote EGM routing is based on both the EGM base address and EGM
// fabric memory window.
if (routing_gpu->nvswitch_info.is_nvswitch_connected && routing_gpu != local_parent_gpu)
phys_addr += uvm_parent_gpu_egm_fabric_address(routing_gpu);
phys_addr -= uvm_parent_gpu_egm_iova_address(routing_gpu);
}
else {
uvm_va_block_region_t chunk_region;
phys_addr = uvm_cpu_chunk_get_gpu_egm_phys_addr(chunk, routing_gpu);
// If there is no EGM mapping, the remote IOVA mapping has not been
// created either. In this case, the access will take the C2C path.
if (!phys_addr) {
aperture = UVM_APERTURE_SYS;
phys_addr = uvm_cpu_chunk_get_gpu_phys_addr(chunk, local_gpu);
UVM_ASSERT(phys_addr != 0);
}
else {
phys_addr += uvm_parent_gpu_egm_fabric_address(routing_gpu);
}
chunk_region = uvm_va_block_chunk_region(block, uvm_cpu_chunk_get_size(chunk), page_index);
phys_addr += (page_index - chunk_region.first) * PAGE_SIZE;
}
return uvm_gpu_phys_address(aperture, phys_addr);
}
// Get the physical GPU address of a block's page from the POV of the specified
// GPU. This is the address that should be used for making PTEs for the
// specified GPU.
@@ -3121,26 +3183,21 @@ static uvm_gpu_phys_address_t block_phys_page_address(uvm_va_block_t *block,
if (UVM_ID_IS_CPU(block_page.processor)) {
uvm_cpu_chunk_t *chunk = uvm_cpu_chunk_get_chunk_for_page(block, block_page.nid, block_page.page_index);
uvm_va_block_region_t chunk_region;
NvU64 phys_addr;
// Sysmem uses coherent SYS aperture
uvm_aperture_t aperture = UVM_APERTURE_SYS;
uvm_va_space_t *va_space = uvm_va_block_get_va_space(block);
uvm_parent_gpu_t *routing_gpu = uvm_va_space_get_egm_routing_gpu(va_space, gpu, block_page.nid);
phys_addr_t phys_addr;
if (routing_gpu && (allow_remote_egm || routing_gpu == gpu->parent)) {
struct page *page = uvm_cpu_chunk_get_cpu_page(block, chunk, block_page.page_index);
uvm_gpu_phys_address_t gpu_address;
phys_addr = page_to_phys(page);
aperture = uvm_gpu_egm_peer_aperture(gpu->parent, routing_gpu);
gpu_address = block_phys_egm_address(block, gpu, routing_gpu, chunk, block_page.page_index);
if (uvm_aperture_is_egm(gpu_address.aperture))
uvm_page_mask_set(&accessing_gpu_state->egm_pages, block_page.page_index);
// Remote EGM routing is based on both the EGM base address and EGM
// fabric memory window.
if (routing_gpu->nvswitch_info.is_nvswitch_connected && routing_gpu != gpu->parent)
phys_addr += routing_gpu->nvswitch_info.egm_fabric_memory_window_start;
uvm_page_mask_set(&accessing_gpu_state->egm_pages, block_page.page_index);
return uvm_gpu_phys_address(aperture, phys_addr - routing_gpu->egm.base_address);
return gpu_address;
}
// The page should be mapped for physical access already as we do that
@@ -3149,7 +3206,6 @@ static uvm_gpu_phys_address_t block_phys_page_address(uvm_va_block_t *block,
chunk_region = uvm_va_block_chunk_region(block, uvm_cpu_chunk_get_size(chunk), block_page.page_index);
UVM_ASSERT(phys_addr != 0);
phys_addr += (block_page.page_index - chunk_region.first) * PAGE_SIZE;
return uvm_gpu_phys_address(aperture, phys_addr);
}
@@ -3201,7 +3257,7 @@ static uvm_gpu_address_t block_phys_page_copy_address(uvm_va_block_t *block,
// systems
uvm_gpu_phys_address_t phys_addr = block_phys_page_address(block, block_page, gpu, REMOTE_EGM_NOT_ALLOWED);
if (uvm_aperture_is_peer(phys_addr.aperture)) {
if (uvm_aperture_is_egm(phys_addr.aperture)) {
UVM_ASSERT(block_check_egm_peer(uvm_va_block_get_va_space(block), gpu, block_page.nid, phys_addr));
return uvm_gpu_address_from_phys(phys_addr);
}
@@ -4464,7 +4520,8 @@ static NV_STATUS block_copy_resident_pages(uvm_va_block_t *block,
uvm_va_block_region_t region,
const uvm_page_mask_t *page_mask,
const uvm_page_mask_t *prefetch_page_mask,
uvm_va_block_transfer_mode_t transfer_mode)
uvm_va_block_transfer_mode_t transfer_mode,
uvm_va_block_p2p_mode_t p2p_mode)
{
NV_STATUS status = NV_OK;
NV_STATUS tracker_status;
@@ -4526,11 +4583,14 @@ static NV_STATUS block_copy_resident_pages(uvm_va_block_t *block,
uvm_processor_mask_zero(src_processor_mask);
if (UVM_ID_IS_GPU(dst_id)) {
if (UVM_ID_IS_GPU(dst_id) && (p2p_mode == UVM_VA_BLOCK_P2P_ENABLED)) {
// If the destination is a GPU, first copy everything from processors
// with copy access supported. Notably this will copy pages from the CPU
// as well even if later some extra copies from CPU are required for
// staged copies.
// If p2p is not enabled, keep the mask empty and let all copies be
// staged in sysmem.
uvm_processor_mask_and(src_processor_mask, block_get_can_copy_from_mask(block, dst_id), &block->resident);
uvm_processor_mask_clear(src_processor_mask, dst_id);
}
@@ -4702,6 +4762,7 @@ NV_STATUS uvm_va_block_make_resident_copy(uvm_va_block_t *va_block,
uvm_processor_mask_t *unmap_processor_mask;
uvm_page_mask_t *unmap_page_mask = &va_block_context->make_resident.page_mask;
uvm_page_mask_t *resident_mask;
uvm_va_block_p2p_mode_t p2p_mode = UVM_VA_BLOCK_P2P_ENABLED;
va_block_context->make_resident.dest_id = dest_id;
va_block_context->make_resident.cause = cause;
@@ -4769,7 +4830,9 @@ NV_STATUS uvm_va_block_make_resident_copy(uvm_va_block_t *va_block,
&va_block_context->discard.scratch_page_mask,
&va_block->discarded_pages);
status = block_populate_pages(va_block, va_block_retry, va_block_context, dest_id, region, page_mask);
retry_no_p2p:
status = block_populate_pages(va_block, va_block_retry, va_block_context, dest_id, region, page_mask, p2p_mode);
if (status != NV_OK)
goto out;
@@ -4779,7 +4842,14 @@ NV_STATUS uvm_va_block_make_resident_copy(uvm_va_block_t *va_block,
region,
page_mask,
prefetch_page_mask,
UVM_VA_BLOCK_TRANSFER_MODE_MOVE);
UVM_VA_BLOCK_TRANSFER_MODE_MOVE,
p2p_mode);
if (UVM_ID_IS_GPU(dest_id) && status == NV_ERR_BUSY_RETRY && p2p_mode == UVM_VA_BLOCK_P2P_ENABLED) {
p2p_mode = UVM_VA_BLOCK_P2P_DISABLED;
goto retry_no_p2p;
}
// HMM does its own clean up.
if (status != NV_OK && !uvm_va_block_is_hmm(va_block)) {
@@ -5010,6 +5080,7 @@ NV_STATUS uvm_va_block_make_resident_read_duplicate(uvm_va_block_t *va_block,
uvm_page_mask_t *scratch_residency_mask;
uvm_page_mask_t *resident_mask;
uvm_page_mask_t *preprocess_page_mask = &va_block_context->make_resident.page_mask;
uvm_va_block_p2p_mode_t p2p_mode = UVM_VA_BLOCK_P2P_ENABLED;
// TODO: Bug 3660922: need to implement HMM read duplication support.
UVM_ASSERT(!uvm_va_block_is_hmm(va_block));
@@ -5090,7 +5161,9 @@ NV_STATUS uvm_va_block_make_resident_read_duplicate(uvm_va_block_t *va_block,
goto out;
}
status = block_populate_pages(va_block, va_block_retry, va_block_context, dest_id, region, page_mask);
retry_no_p2p:
status = block_populate_pages(va_block, va_block_retry, va_block_context, dest_id, region, page_mask, p2p_mode);
if (status != NV_OK)
goto out;
@@ -5100,7 +5173,15 @@ NV_STATUS uvm_va_block_make_resident_read_duplicate(uvm_va_block_t *va_block,
region,
page_mask,
prefetch_page_mask,
UVM_VA_BLOCK_TRANSFER_MODE_COPY);
UVM_VA_BLOCK_TRANSFER_MODE_COPY,
p2p_mode);
if (UVM_ID_IS_GPU(dest_id) && status == NV_ERR_BUSY_RETRY && p2p_mode == UVM_VA_BLOCK_P2P_ENABLED) {
p2p_mode = UVM_VA_BLOCK_P2P_DISABLED;
goto retry_no_p2p;
}
if (status != NV_OK)
goto out;
@@ -7820,6 +7901,25 @@ NV_STATUS uvm_va_block_unmap(uvm_va_block_t *va_block,
return block_unmap_gpu(va_block, va_block_context, uvm_gpu_get(id), region_page_mask, out_tracker);
}
void uvm_va_block_unmap_egm(uvm_va_block_t *va_block, uvm_parent_gpu_t *gpu)
{
int nid;
uvm_mutex_lock(&va_block->lock);
for_each_possible_uvm_node (nid) {
uvm_cpu_chunk_t *chunk;
uvm_page_index_t page_index;
for_each_cpu_chunk_in_block (chunk, page_index, va_block, nid) {
if (nid == gpu->closest_cpu_numa_node)
uvm_cpu_chunk_unmap_gpu_egm(chunk, gpu);
}
}
uvm_mutex_unlock(&va_block->lock);
}
// This function essentially works as a wrapper around vm_insert_page (hence
// the similar function prototype). This is needed since vm_insert_page
// doesn't take permissions as input, but uses vma->vm_page_prot instead.
@@ -8470,6 +8570,36 @@ NV_STATUS uvm_va_block_map(uvm_va_block_t *va_block,
return status;
}
NV_STATUS uvm_va_block_map_egm(uvm_va_block_t *va_block, uvm_parent_gpu_t *gpu)
{
int nid;
NV_STATUS status = NV_OK;
uvm_mutex_lock(&va_block->lock);
// A future optimization would be to go through all peer GPUs and check if
// there are any existing mappings to SYSMEM pages on the NUMA node
// connected to this GPU.
// If so, unmap them and remap them as EGM mappings.
// TODO: 6211372: Unmap/remap existing SYSMEM mappings.
for_each_possible_uvm_node (nid) {
uvm_cpu_chunk_t *chunk;
uvm_page_index_t page_index;
for_each_cpu_chunk_in_block (chunk, page_index, va_block, nid) {
if (nid == gpu->closest_cpu_numa_node) {
status = uvm_cpu_chunk_map_gpu_egm(chunk, gpu);
if (status != NV_OK)
goto done;
}
}
}
done:
uvm_mutex_unlock(&va_block->lock);
return status;
}
NV_STATUS uvm_va_block_migrate_map_mapped_pages(uvm_va_block_t *va_block,
uvm_va_block_retry_t *va_block_retry,
uvm_va_block_context_t *va_block_context,
@@ -9122,7 +9252,8 @@ static void block_unmap_gpu_egm_mappings(uvm_va_block_t *va_block,
uvm_va_block_context_t *block_context = uvm_va_space_block_context(va_space, NULL);
uvm_va_block_gpu_state_t *gpu_state0 = uvm_va_block_gpu_state_get(va_block, gpu0->id);
if (gpu1->parent->egm.enabled && uvm_va_space_single_gpu_in_parent(va_space, gpu1->parent) && gpu_state0) {
if (uvm_parent_gpu_egm_enabled(gpu1->parent) && uvm_va_space_single_gpu_in_parent(va_space, gpu1->parent) &&
gpu_state0) {
uvm_page_mask_t *unmap_page_mask = &block_context->caller_page_mask;
const uvm_page_mask_t *resident0 = uvm_va_block_resident_mask_get(va_block, gpu0->id, NUMA_NO_NODE);
const uvm_page_mask_t *resident1 = uvm_va_block_resident_mask_get(va_block,
@@ -9139,6 +9270,11 @@ static void block_unmap_gpu_egm_mappings(uvm_va_block_t *va_block,
nvstatusToString(status),
uvm_gpu_name(gpu0));
}
uvm_page_mask_andnot(&gpu_state0->egm_pages, &gpu_state0->egm_pages, unmap_page_mask);
// Unlike legacy EGM, we have to destory all CPU chunk remote EGM
// mappings since the routing GPU is going away.
}
}
}
@@ -12917,8 +13053,11 @@ NV_STATUS uvm_va_block_evict_chunks(uvm_va_block_t *va_block,
if (uvm_va_block_is_hmm(va_block)) {
status = uvm_hmm_va_block_evict_chunk_prep(va_block, block_context, gpu_state->chunks[i], chunk_region);
if (status != NV_OK)
break;
if (status != NV_OK) {
// remove any migration PTEs we prepped
uvm_hmm_va_block_evict_chunk_cancel(block_context);
goto out;
}
}
uvm_page_mask_region_fill(pages_to_evict, chunk_region);
@@ -12955,7 +13094,7 @@ NV_STATUS uvm_va_block_evict_chunks(uvm_va_block_t *va_block,
// root chunk is in_eviction, chunk_free_locked() treats TEMP_PINNED
// chunks as a no-op, so each chunk ends up TEMP_PINNED with an empty
// list as merge_gpu_chunk() requires.
uvm_pmm_gpu_process_lazy_free(&gpu->pmm);
nv_kthread_q_flush(&gpu->parent->lazy_free_q);
}
else {
const uvm_va_policy_t *policy = &va_block->managed_range->policy;
@@ -13427,17 +13566,15 @@ NV_STATUS uvm_test_va_residency_info(UVM_TEST_VA_RESIDENCY_INFO_PARAMS *params,
if (UVM_ID_IS_CPU(block_page.processor)) {
uvm_parent_gpu_t *egm_routing_gpu = uvm_va_space_get_egm_routing_gpu(va_space, gpu, nid);
if (egm_routing_gpu) {
uvm_gpu_t *egm_gpu = uvm_parent_gpu_find_first_valid_gpu(egm_routing_gpu);
compute_egm_page_mask(block, block_context, gpu, egm_gpu->parent, &block_context->caller_page_mask);
if (uvm_page_mask_test(&block_context->caller_page_mask, block_page.page_index)) {
struct page *page = block_page_get(block, block_page);
phys_addr = page_to_phys(page) - egm_routing_gpu->egm.base_address;
phys_addr = page_to_phys(page) - uvm_parent_gpu_egm_iova_address(egm_routing_gpu);
if (egm_routing_gpu->nvswitch_info.is_nvswitch_connected && egm_routing_gpu != gpu->parent)
phys_addr += egm_routing_gpu->nvswitch_info.egm_fabric_memory_window_start;
phys_addr += uvm_parent_gpu_egm_fabric_address(egm_routing_gpu);
params->is_egm_mapping[count] = true;
}
+8 -3
View File
@@ -1,5 +1,5 @@
/*******************************************************************************
Copyright (c) 2015-2025 NVIDIA Corporation
Copyright (c) 2015-2026 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
@@ -902,6 +902,9 @@ NV_STATUS uvm_va_block_map_mask(uvm_va_block_t *va_block,
uvm_prot_t new_prot,
UvmEventMapRemoteCause cause);
// Map all CPU chunks in the block for EGM access.
NV_STATUS uvm_va_block_map_egm(uvm_va_block_t *va_block, uvm_parent_gpu_t *gpu);
// Map pages not already mapped on the destination processor after a migration.
//
// LOCKING: The VA block lock must be held. If va_block_context->mm !=
@@ -954,6 +957,9 @@ NV_STATUS uvm_va_block_unmap(uvm_va_block_t *va_block,
const uvm_page_mask_t *unmap_page_mask,
uvm_tracker_t *out_tracker);
// Unmap any EGM mappings for CPU chunks.
void uvm_va_block_unmap_egm(uvm_va_block_t *block, uvm_parent_gpu_t *gpu);
// Like uvm_va_block_unmap, except it unmaps all processors in the input mask.
// The VA block tracker contains all map operations on return.
NV_STATUS uvm_va_block_unmap_mask(uvm_va_block_t *va_block,
@@ -1253,8 +1259,7 @@ NV_STATUS uvm_va_block_split_locked(uvm_va_block_t *existing_va_block,
// service_context->block_context.mm is ignored and vma->vm_mm is used instead.
//
// Returns NV_ERR_INVALID_ACCESS_TYPE if a CPU mapping to fault_addr cannot be
// accessed, for example because it's within a range group which is non-
// migratable.
// accessed.
NV_STATUS uvm_va_block_cpu_fault(uvm_va_block_t *va_block,
NvU64 fault_addr,
bool is_write,
@@ -342,6 +342,12 @@ typedef enum
UVM_VA_BLOCK_TRANSFER_MODE_COPY = 2
} uvm_va_block_transfer_mode_t;
typedef enum
{
UVM_VA_BLOCK_P2P_DISABLED = 0,
UVM_VA_BLOCK_P2P_ENABLED = 1,
} uvm_va_block_p2p_mode_t;
typedef enum
{
UVM_SERVICE_OPERATION_REPLAYABLE_FAULTS,
+178
View File
@@ -22,7 +22,9 @@
*******************************************************************************/
#include "uvm_common.h"
#include "uvm_forward_decl.h"
#include "uvm_linux.h"
#include "uvm_lock.h"
#include "uvm_types.h"
#include "uvm_api.h"
#include "uvm_processors.h"
@@ -34,13 +36,22 @@
#include "uvm_va_block.h"
#include "uvm_kvmalloc.h"
#include "uvm_map_external.h"
#include "uvm_va_range_dmabuf.h"
#include "uvm_perf_thrashing.h"
#include "nv_uvm_interface.h"
#include <linux/sched.h>
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
#include <linux/dma-buf.h>
#include <linux/dma-resv.h>
#endif
static struct kmem_cache *g_uvm_va_range_managed_cache __read_mostly;
static struct kmem_cache *g_uvm_va_range_external_cache __read_mostly;
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
static struct kmem_cache *g_uvm_va_range_dma_buf_cache __read_mostly;
#endif
static struct kmem_cache *g_uvm_va_range_channel_cache __read_mostly;
static struct kmem_cache *g_uvm_va_range_sked_reflected_cache __read_mostly;
static struct kmem_cache *g_uvm_va_range_semaphore_pool_cache __read_mostly;
@@ -58,6 +69,12 @@ NV_STATUS uvm_va_range_init(void)
if (!g_uvm_va_range_external_cache)
return NV_ERR_NO_MEMORY;
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
g_uvm_va_range_dma_buf_cache = NV_KMEM_CACHE_CREATE("uvm_va_range_dma_buf_t", uvm_va_range_dma_buf_t);
if (!g_uvm_va_range_dma_buf_cache)
return NV_ERR_NO_MEMORY;
#endif
g_uvm_va_range_channel_cache = NV_KMEM_CACHE_CREATE("uvm_va_range_channel_t", uvm_va_range_channel_t);
if (!g_uvm_va_range_channel_cache)
return NV_ERR_NO_MEMORY;
@@ -88,6 +105,9 @@ void uvm_va_range_exit(void)
uvm_va_block_exit();
kmem_cache_destroy_safe(&g_uvm_va_range_managed_cache);
kmem_cache_destroy_safe(&g_uvm_va_range_external_cache);
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
kmem_cache_destroy_safe(&g_uvm_va_range_dma_buf_cache);
#endif
kmem_cache_destroy_safe(&g_uvm_va_range_channel_cache);
kmem_cache_destroy_safe(&g_uvm_va_range_sked_reflected_cache);
kmem_cache_destroy_safe(&g_uvm_va_range_semaphore_pool_cache);
@@ -299,6 +319,74 @@ error:
return status;
}
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
NV_STATUS uvm_va_range_create_dma_buf(uvm_va_space_t *va_space,
struct mm_struct *mm,
struct dma_buf *dmabuf,
NvU64 start,
NvU64 length,
uvm_va_range_dma_buf_t **out_dmabuf_range)
{
NV_STATUS status;
uvm_va_range_dma_buf_t *dmabuf_range = NULL;
NvU32 i;
// Assigns to zero mapped_gpus, revoked_gpus bitmasks.
dmabuf_range = nv_kmem_cache_zalloc(g_uvm_va_range_dma_buf_cache, NV_UVM_GFP_FLAGS);
if (!dmabuf_range)
return NV_ERR_NO_MEMORY;
get_dma_buf(dmabuf);
dmabuf_range->dmabuf = dmabuf;
status = uvm_va_range_initialize_reclaim(&dmabuf_range->va_range,
mm,
UVM_VA_RANGE_TYPE_DMA_BUF,
va_space,
start,
start + length - 1);
if (status != NV_OK) {
kmem_cache_free(g_uvm_va_range_dma_buf_cache, dmabuf_range);
return status;
}
dmabuf_range->retained_mask = uvm_processor_mask_cache_alloc();
if (!dmabuf_range->retained_mask) {
status = NV_ERR_NO_MEMORY;
goto error;
}
for (i = 0; i < ARRAY_SIZE(dmabuf_range->gpu_ranges); i++) {
uvm_mutex_init(&dmabuf_range->gpu_ranges[i].base.lock, UVM_LOCK_ORDER_DMA_BUF_RANGE_TREE);
uvm_range_tree_init(&dmabuf_range->gpu_ranges[i].base.tree);
}
status = uvm_range_tree_add(&va_space->va_range_tree, &dmabuf_range->va_range.node);
if (status != NV_OK)
goto error;
if (out_dmabuf_range)
*out_dmabuf_range = dmabuf_range;
return NV_OK;
error:
uvm_va_range_destroy(&dmabuf_range->va_range, NULL);
return status;
}
#else
NV_STATUS uvm_va_range_create_dma_buf(uvm_va_space_t *va_space,
struct mm_struct *mm,
struct dma_buf *dmabuf,
NvU64 start,
NvU64 length,
uvm_va_range_dma_buf_t **out_dmabuf_range)
{
return NV_ERR_NOT_SUPPORTED;
}
#endif // UVM_PROVIDES_DMA_BUF_IMPORTER()
NV_STATUS uvm_va_range_create_channel(uvm_va_space_t *va_space,
struct mm_struct *mm,
NvU64 start,
@@ -530,6 +618,26 @@ out:
kmem_cache_free(g_uvm_va_range_external_cache, external_range);
}
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
static void uvm_va_range_destroy_dma_buf(uvm_va_range_dma_buf_t *dmabuf_range,
struct list_head *deferred_free_list)
{
uvm_gpu_t *gpu;
uvm_processor_mask_cache_free(dmabuf_range->retained_mask);
for_each_va_space_gpu(gpu, dmabuf_range->va_range.va_space)
uvm_dma_buf_gpu_range_tree_deinit(dmabuf_range, gpu, deferred_free_list);
UVM_ASSERT(uvm_processor_mask_empty(&dmabuf_range->mapped_gpus));
dma_buf_put(dmabuf_range->dmabuf);
kmem_cache_free(g_uvm_va_range_dma_buf_cache, dmabuf_range);
}
#else
static void uvm_va_range_destroy_dma_buf(uvm_va_range_dma_buf_t *dmabuf_range,
struct list_head *deferred_free_list) {}
#endif // UVM_PROVIDES_DMA_BUF_IMPORTER()
static void uvm_va_range_destroy_channel(uvm_va_range_channel_t *channel_range)
{
uvm_gpu_va_space_t *gpu_va_space = channel_range->gpu_va_space;
@@ -604,6 +712,13 @@ void uvm_va_range_destroy(uvm_va_range_t *va_range, struct list_head *deferred_f
case UVM_VA_RANGE_TYPE_EXTERNAL:
uvm_va_range_destroy_external(uvm_va_range_to_external(va_range), deferred_free_list);
return;
case UVM_VA_RANGE_TYPE_DMA_BUF:
#if !UVM_PROVIDES_DMA_BUF_IMPORTER()
UVM_ASSERT_MSG(0, "[0x%llx, 0x%llx] unexpected DMA_BUF range\n",
va_range->node.start, va_range->node.end);
#endif
uvm_va_range_destroy_dma_buf(uvm_va_range_to_dma_buf(va_range), deferred_free_list);
return;
case UVM_VA_RANGE_TYPE_CHANNEL:
uvm_va_range_destroy_channel(uvm_va_range_to_channel(va_range));
return;
@@ -662,6 +777,30 @@ static uvm_processor_mask_t *uvm_free_external(uvm_va_range_external_t *external
return retained_mask;
}
static uvm_processor_mask_t *uvm_free_dma_buf(uvm_va_range_dma_buf_t *dmabuf_range)
{
uvm_processor_mask_t *retained_mask = dmabuf_range->retained_mask;
uvm_va_space_t *va_space = dmabuf_range->va_range.va_space;
uvm_gpu_t *gpu;
// Set the retained_mask to NULL to prevent uvm_va_range_destroy_dma_buf()
// from freeing the mask, should no attachments require deferred free.
dmabuf_range->retained_mask = NULL;
UVM_ASSERT(retained_mask);
uvm_processor_mask_zero(retained_mask);
// DMA-BUF ranges defer free the destruction of attachments, so GPUs must be
// retained. Construct the mask of all the GPUs that need to be retained.
uvm_assert_rwsem_locked(&va_space->lock);
for_each_va_space_gpu(gpu, va_space) {
if (uvm_dma_buf_gpu_range_tree(dmabuf_range, gpu)->attach)
uvm_processor_mask_set(retained_mask, gpu->id);
}
return retained_mask;
}
// This destroys VA ranges created by ioctl. VA ranges created by mmap, such as
// through UvmMemMap, go through munmap.
static NV_STATUS uvm_free(uvm_va_space_t *va_space, NvU64 base)
@@ -685,6 +824,10 @@ static NV_STATUS uvm_free(uvm_va_space_t *va_space, NvU64 base)
retained_mask = uvm_free_external(uvm_va_range_to_external(va_range));
break;
case UVM_VA_RANGE_TYPE_DMA_BUF:
retained_mask = uvm_free_dma_buf(uvm_va_range_to_dma_buf(va_range));
break;
case UVM_VA_RANGE_TYPE_SEMAPHORE_POOL:
status = uvm_free_semaphore_pool(uvm_va_range_to_semaphore_pool(va_range));
break;
@@ -1339,6 +1482,13 @@ static void va_range_remove_gpu_va_space_external(uvm_va_range_external_t *exter
uvm_mutex_unlock(&range_tree->lock);
}
static void va_range_remove_gpu_va_space_dma_buf(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *gpu,
struct list_head *deferred_free_list)
{
uvm_dma_buf_gpu_range_tree_deinit(dmabuf_range, gpu, deferred_free_list);
}
static void va_range_remove_gpu_va_space_semaphore_pool(uvm_va_range_semaphore_pool_t *semaphore_pool_range,
uvm_gpu_t *gpu)
{
@@ -1363,6 +1513,15 @@ void uvm_va_range_remove_gpu_va_space(uvm_va_range_t *va_range,
gpu_va_space->gpu,
deferred_free_list);
break;
case UVM_VA_RANGE_TYPE_DMA_BUF:
#if !UVM_PROVIDES_DMA_BUF_IMPORTER()
UVM_ASSERT_MSG(0, "[0x%llx, 0x%llx] unexpected DMA_BUF range\n",
va_range->node.start, va_range->node.end);
#endif
va_range_remove_gpu_va_space_dma_buf(uvm_va_range_to_dma_buf(va_range),
gpu_va_space->gpu,
deferred_free_list);
break;
case UVM_VA_RANGE_TYPE_CHANNEL:
// All channels under this GPU VA space should've been removed before
// removing the GPU VA space.
@@ -1431,6 +1590,9 @@ NV_STATUS uvm_va_range_enable_peer(uvm_va_range_t *va_range, uvm_gpu_t *gpu0, uv
case UVM_VA_RANGE_TYPE_EXTERNAL:
// UVM_VA_RANGE_TYPE_EXTERNAL doesn't create new mappings when enabling peer access
return NV_OK;
case UVM_VA_RANGE_TYPE_DMA_BUF:
// UVM_VA_RANGE_TYPE_DMA_BUF should never have peer mappings
return NV_OK;
case UVM_VA_RANGE_TYPE_CHANNEL:
// UVM_VA_RANGE_TYPE_CHANNEL should never have peer mappings
return NV_OK;
@@ -1493,6 +1655,9 @@ void uvm_va_range_disable_peer(uvm_va_range_t *va_range,
// If GPU 1 has a mapping to GPU 0, remove GPU 1's mapping
uvm_va_range_disable_peer_external(uvm_va_range_to_external(va_range), gpu1, gpu0, deferred_free_list);
break;
case UVM_VA_RANGE_TYPE_DMA_BUF:
// UVM_VA_RANGE_TYPE_DMA_BUF should never have peer mappings
break;
case UVM_VA_RANGE_TYPE_CHANNEL:
// UVM_VA_RANGE_TYPE_CHANNEL should never have peer mappings
break;
@@ -1608,6 +1773,18 @@ void uvm_va_range_unregister_gpu(uvm_va_range_t *va_range,
case UVM_VA_RANGE_TYPE_EXTERNAL:
va_range_unregister_gpu_external(uvm_va_range_to_external(va_range), gpu, deferred_free_list);
break;
case UVM_VA_RANGE_TYPE_DMA_BUF:
#if !UVM_PROVIDES_DMA_BUF_IMPORTER()
UVM_ASSERT_MSG(0, "[0x%llx, 0x%llx] unexpected DMA_BUF range\n",
va_range->node.start, va_range->node.end);
#endif
// All ranges for this GPU should have been unmapped by GPU VA space
// unregister (va_range_remove_gpu_va_space_dma_buf), which should
// have already happened.
UVM_ASSERT(!uvm_processor_mask_test(&uvm_va_range_to_dma_buf(va_range)->mapped_gpus, gpu->id));
UVM_ASSERT(uvm_range_tree_empty(
&uvm_dma_buf_gpu_range_tree(uvm_va_range_to_dma_buf(va_range), gpu)->base.tree));
break;
case UVM_VA_RANGE_TYPE_CHANNEL:
// All ranges should have been destroyed by GPU VA space unregister,
// which should have already happened.
@@ -2316,6 +2493,7 @@ NV_STATUS uvm_test_va_range_info(UVM_TEST_VA_RANGE_INFO_PARAMS *params, struct f
BUILD_BUG_ON((int)UVM_TEST_VA_RANGE_TYPE_INVALID != (int)UVM_VA_RANGE_TYPE_INVALID);
BUILD_BUG_ON((int)UVM_TEST_VA_RANGE_TYPE_MANAGED != (int)UVM_VA_RANGE_TYPE_MANAGED);
BUILD_BUG_ON((int)UVM_TEST_VA_RANGE_TYPE_EXTERNAL != (int)UVM_VA_RANGE_TYPE_EXTERNAL);
BUILD_BUG_ON((int)UVM_TEST_VA_RANGE_TYPE_DMA_BUF != (int)UVM_VA_RANGE_TYPE_DMA_BUF);
BUILD_BUG_ON((int)UVM_TEST_VA_RANGE_TYPE_CHANNEL != (int)UVM_VA_RANGE_TYPE_CHANNEL);
BUILD_BUG_ON((int)UVM_TEST_VA_RANGE_TYPE_SKED_REFLECTED != (int)UVM_VA_RANGE_TYPE_SKED_REFLECTED);
BUILD_BUG_ON((int)UVM_TEST_VA_RANGE_TYPE_SEMAPHORE_POOL != (int)UVM_VA_RANGE_TYPE_SEMAPHORE_POOL);
+157 -16
View File
@@ -77,6 +77,16 @@
// These ranges do not have blocks. All state (page tables, mapping handles,
// etc) is maintained within the range.
//
// VA ranges with type == UVM_VA_RANGE_TYPE_DMA_BUF:
// These ranges track DMA-BUF buffers that have been imported. The UVM driver is
// responsible for mapping them to the GPU(s), but not to the CPU. These
// ranges do not support faulting nor migration, and they typically represent
// access to PCIe peer device memories not controlled by UVM or RM, and with no
// backing vma.
//
// These ranges do not have blocks. All state (page tables, mapping handles,
// etc) is maintained within the range.
//
// VA ranges with type == UVM_VA_RANGE_TYPE_CHANNEL:
// These are similar to EXTERNAL ranges, except they represent internal
// allocations required for user channels to operate (context save areas,
@@ -107,6 +117,7 @@ typedef enum
UVM_VA_RANGE_TYPE_INVALID = 0,
UVM_VA_RANGE_TYPE_MANAGED,
UVM_VA_RANGE_TYPE_EXTERNAL,
UVM_VA_RANGE_TYPE_DMA_BUF,
UVM_VA_RANGE_TYPE_CHANNEL,
UVM_VA_RANGE_TYPE_SKED_REFLECTED,
UVM_VA_RANGE_TYPE_SEMAPHORE_POOL,
@@ -141,10 +152,48 @@ typedef struct
nv_kref_t ref_count;
} uvm_ext_gpu_mem_handle;
// Common base for per-GPU range trees of sub-range mappings.
typedef struct
{
// Lock protecting the range tree.
uvm_mutex_t lock;
// Range tree that contains all of the mapped portions of a range which
// permits per-GPU sub-range mappings. The tree holds uvm_gpu_map_t
// instances.
uvm_range_tree_t tree;
} uvm_gpu_range_tree_t;
typedef uvm_gpu_range_tree_t uvm_ext_gpu_range_tree_t;
typedef struct
{
uvm_gpu_range_tree_t base;
// A borrowed reference to the GPU's attachment. The attachment is created
// at time of first mapping on the GPU, but detached after this range is
// destroyed as a deferred-free in UvmFree(), outside of the va_space lock.
// This is due to the locking requirement imposed externally by DMA-BUF.
struct dma_buf_attachment *attach;
} uvm_dma_buf_gpu_range_tree_t;
// Common base for per-GPU sub-range mappings.
typedef struct
{
uvm_range_tree_node_t node;
// GPU on which this allocation is mapped.
uvm_gpu_t *gpu;
uvm_page_table_range_vec_t pt_range_vec;
} uvm_gpu_map_t;
typedef uvm_gpu_map_t uvm_dma_buf_gpu_map_t;
typedef struct
{
uvm_gpu_map_t base;
// Handle to the physical user allocation dup'd into our client. This
// prevents the allocation from being removed until we free it, even if the
// user frees their handle without telling us.
@@ -158,9 +207,6 @@ typedef struct
// tracker before operating on pt_range_vec.
uvm_tracker_t tracker;
// GPU on which this allocation is mapped.
uvm_gpu_t *gpu;
// GPU which owns the allocation. For sysmem, this is the GPU that the
// sysmem was originally allocated under. For the allocation to remain valid
// we need to prevent the GPU from going away, similarly to P2P mapped
@@ -191,8 +237,6 @@ typedef struct
// Fabric memory. If true, the allocation is a fabric allocation.
bool is_fabricmem;
uvm_page_table_range_vec_t pt_range_vec;
// Node for the deferred free list where this allocation is stored upon
// unmapped.
//
@@ -210,16 +254,6 @@ typedef struct
bool need_l2_invalidate_at_unmap;
} uvm_ext_gpu_map_t;
typedef struct
{
// Lock protecting the range tree.
uvm_mutex_t lock;
// Range tree that contains all of the mapped portions of an External VA
// range. The tree holds uvm_ext_gpu_map_t instances.
uvm_range_tree_t tree;
} uvm_ext_gpu_range_tree_t;
struct uvm_va_range_struct
{
// Parent uvm_va_space.
@@ -308,6 +342,43 @@ struct uvm_va_range_external_struct
uvm_processor_mask_t *retained_mask;
};
struct uvm_va_range_dma_buf_struct
{
// Base class
uvm_va_range_t va_range;
struct dma_buf *dmabuf;
// Mask of GPUs which have GMMU mappings to this VA range. If a bit in this
// mask is set, at a minimum there must exist for the relevant GPU a DMA-BUF
// attachment and the attachment must be mapped.
// The bitmap can be safely accessed by following the locking rules:
// * If the VA space lock is held for write, the mask can be read or written
// normally.
// * If the VA space lock is held for read, and one of the range tree locks is
// held, only the bit corresponding to that GPU range tree can be accessed.
// Writes must use uvm_processor_mask_set_atomic and
// uvm_processor_mask_clear_atomic to avoid clobbering other bits in the
// mask. If no range tree lock is held, the mask cannot be accessed.
// * If the VA space lock is not held, the mask cannot be accessed
uvm_processor_mask_t mapped_gpus;
// Mask of GPUs that have received a move_notify() notification, prohibiting
// further mappings from being established on this GPU.
// Members of this bitmap are accessible under the same restricions that
// mapped_gpus members are.
uvm_processor_mask_t revoked_gpus;
// Per-GPU tree of mapped dma-buf allocations. This has to be per-GPU in the VA
// range because each GPU is able to map a completely different set of
// allocations to the same VA range.
uvm_dma_buf_gpu_range_tree_t gpu_ranges[UVM_ID_MAX_GPUS];
// Dynamically allocated page mask allocated in
// uvm_va_range_create_dma_buf() and used and freed in uvm_free().
uvm_processor_mask_t *retained_mask;
};
// Subclass of va_range state for va_range.type == UVM_VA_RANGE_TYPE_CHANNEL.
// This represents a channel buffer resource and mapping.
struct uvm_va_range_channel_struct
@@ -427,6 +498,14 @@ static inline uvm_va_range_external_t *uvm_va_range_to_external(uvm_va_range_t *
return container_of(va_range, uvm_va_range_external_t, va_range);
}
// DMA-BUF Range dynamic cast
static inline uvm_va_range_dma_buf_t *uvm_va_range_to_dma_buf(uvm_va_range_t *va_range)
{
UVM_ASSERT(va_range);
UVM_ASSERT(va_range->type == UVM_VA_RANGE_TYPE_DMA_BUF);
return container_of(va_range, uvm_va_range_dma_buf_t, va_range);
}
// Channel Range dynamic cast
static inline uvm_va_range_channel_t *uvm_va_range_to_channel(uvm_va_range_t *va_range)
{
@@ -515,6 +594,17 @@ NV_STATUS uvm_va_range_create_external(uvm_va_space_t *va_space,
NvU64 length,
uvm_va_range_external_t **out_external_range);
// Create an external range. out_dma_buf_range is optional.
//
// Returns NV_ERR_UVM_ADDRESS_IN_USE if the range overlaps with an existing
// range in the va_space tree.
NV_STATUS uvm_va_range_create_dma_buf(uvm_va_space_t *va_space,
struct mm_struct *mm,
struct dma_buf *dmabuf,
NvU64 start,
NvU64 length,
uvm_va_range_dma_buf_t **out_dmabuf_range);
// Create a channel range. out_channel_range is optional.
//
// Returns NV_ERR_UVM_ADDRESS_IN_USE if the range overlaps with an existing
@@ -654,6 +744,18 @@ static uvm_va_range_external_t *uvm_va_range_external_find(uvm_va_space_t *va_sp
return uvm_va_range_to_external(va_range);
}
static uvm_va_range_dma_buf_t *uvm_va_range_dma_buf_find(uvm_va_space_t *va_space, NvU64 addr)
{
uvm_va_range_t *va_range;
va_range = uvm_va_range_find(va_space, addr);
if (!va_range)
return NULL;
if (va_range->type != UVM_VA_RANGE_TYPE_DMA_BUF)
return NULL;
return uvm_va_range_to_dma_buf(va_range);
}
static uvm_va_range_semaphore_pool_t *uvm_va_range_semaphore_pool_find(uvm_va_space_t *va_space,
NvU64 addr)
{
@@ -693,9 +795,48 @@ static uvm_ext_gpu_map_t *uvm_ext_gpu_map_container(uvm_range_tree_node_t *node)
{
if (!node)
return NULL;
return container_of(node, uvm_ext_gpu_map_t, node);
return container_of(node, uvm_ext_gpu_map_t, base.node);
}
static uvm_dma_buf_gpu_map_t *uvm_dma_buf_gpu_map_container(uvm_range_tree_node_t *node)
{
if (!node)
return NULL;
return container_of(node, uvm_dma_buf_gpu_map_t, node);
}
// Shared range tree iteration primitives. Both the ext and dma_buf variants
// delegate to these; callers get typed pointers back via the container_of
// wrappers above.
static uvm_range_tree_node_t *uvm_gpu_range_tree_iter_first(uvm_gpu_range_tree_t *range_tree,
NvU64 start, NvU64 end)
{
return uvm_range_tree_iter_first(&range_tree->tree, start, end);
}
static uvm_range_tree_node_t *uvm_gpu_range_tree_iter_next(uvm_gpu_range_tree_t *range_tree,
uvm_gpu_map_t *map,
NvU64 end)
{
if (!map)
return NULL;
return uvm_range_tree_iter_next(&range_tree->tree, &map->node, end);
}
// Internal base macros for GPU map iteration; use the typed variants defined
// in uvm_map_external.h and uvm_va_range_dmabuf.h.
#define uvm_gpu_map_for_each_in(map, va_range, gpu, start, end, iter_first, iter_next) \
for ((map) = (iter_first)((va_range), (gpu), (start), (end)); \
(map); \
(map) = (iter_next)((va_range), (map), (end)))
#define uvm_gpu_map_for_each_in_safe(map, map_next, va_range, gpu, start, end, iter_first, iter_next) \
for ((map) = (iter_first)((va_range), (gpu), (start), (end)), \
(map_next) = (iter_next)((va_range), (map), (end)); \
(map); \
(map) = (map_next), \
(map_next) = (iter_next)((va_range), (map), (end)))
// Iterators for all va_ranges
#define uvm_for_each_va_range(va_range, va_space) \
@@ -0,0 +1,764 @@
/*******************************************************************************
Copyright (c) 2026 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_linux.h"
#include "uvm_common.h"
#include "uvm_api.h"
#include "uvm_forward_decl.h"
#include "uvm_va_range_dmabuf.h"
#include "uvm_gpu.h"
#include "uvm_hal.h"
#include "uvm_hal_types.h"
#include "uvm_lock.h"
#include "uvm_processors.h"
#include "uvm_tracker.h"
#include "uvm_va_space.h"
#include "uvm_va_range.h"
#include "uvm_va_space.h"
#include "uvm_ioctl.h"
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
#include <linux/dma-buf.h>
#include <linux/dma-resv.h>
// Mutable iterator data state passed to uvm_dma_buf_pte_maker()
typedef struct {
uvm_gpu_t *mapping_gpu;
struct sg_dma_page_iter dma_iter;
} uvm_dma_buf_pte_maker_data_t;
static void uvm_dma_buf_gpu_map_destroy(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_dma_buf_gpu_map_t *dmabuf_map);
static NV_STATUS uvm_create_dma_buf_range(uvm_va_space_t *va_space,
struct dma_buf *dmabuf,
UVM_IMPORT_DMA_BUF_PARAMS *params)
{
uvm_va_range_dma_buf_t *dmabuf_range = NULL;
struct mm_struct *mm;
NV_STATUS status = NV_OK;
// The DMA-BUF interface provides a sg_table of DMA mapped regions through
// dma_buf_map_attachment(), with base addresses and lengths required to be
// aligned to the system page size. Assert the same here.
if (uvm_api_range_invalid(params->base, params->length))
return NV_ERR_INVALID_ADDRESS;
// Require that the provided length for the va_range matches the full size
// of the DMA-BUF.
if (params->length != dmabuf->size)
return NV_ERR_INVALID_ADDRESS;
// The mm needs to be locked in order to remove stale HMM va_blocks.
mm = uvm_va_space_mm_or_current_retain_lock(va_space);
uvm_va_space_down_write(va_space);
// Create the new DMA-BUF VA range.
// uvm_va_range_create_dma_buf handles any collisions when it attempts to
// insert the new range into the va_space range tree.
status = uvm_va_range_create_dma_buf(va_space, mm, dmabuf, params->base, dmabuf->size, &dmabuf_range);
if (status != NV_OK) {
UVM_DBG_PRINT_RL("Failed to create DMA-BUF VA range [0x%llx, 0x%llx)\n",
params->base,
params->base + dmabuf->size);
}
uvm_va_space_up_write(va_space);
uvm_va_space_mm_or_current_release_unlock(va_space, mm);
return status;
}
NV_STATUS uvm_api_import_dma_buf(UVM_IMPORT_DMA_BUF_PARAMS *params, struct file *filp)
{
uvm_va_space_t *va_space = uvm_va_space_get(filp);
struct dma_buf *dmabuf;
NV_STATUS status;
dmabuf = dma_buf_get(params->handleFd);
if (IS_ERR(dmabuf))
return NV_ERR_INVALID_ARGUMENT;
status = uvm_create_dma_buf_range(va_space, dmabuf, params);
dma_buf_put(dmabuf);
return status;
}
// Retrieve the per-GPU sub-range mapping object spanning a given virtual
// address or NULL if no such mapping exists.
static uvm_dma_buf_gpu_map_t *uvm_va_range_dma_buf_get_gpu_map_or_null(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *mapping_gpu,
NvU64 addr)
{
uvm_dma_buf_gpu_map_t *dmabuf_map = NULL;
uvm_range_tree_node_t *node;
uvm_dma_buf_gpu_range_tree_t *range_tree;
uvm_assert_rwsem_locked(&dmabuf_range->va_range.va_space->lock);
range_tree = uvm_dma_buf_gpu_range_tree(dmabuf_range, mapping_gpu);
if (uvm_processor_mask_test(&dmabuf_range->mapped_gpus, mapping_gpu->id)) {
UVM_ASSERT(!uvm_range_tree_empty(&range_tree->base.tree));
node = uvm_range_tree_find(&range_tree->base.tree, addr);
if (node) {
dmabuf_map = uvm_dma_buf_gpu_map_container(node);
UVM_ASSERT(dmabuf_map->gpu == mapping_gpu);
}
}
else {
UVM_ASSERT(uvm_range_tree_empty(&range_tree->base.tree));
}
return dmabuf_map;
}
// TODO: Bug 6164216 refactor for code reuse with external mappings
static NV_STATUS uvm_dma_buf_gpu_map_split(uvm_range_tree_t *tree,
uvm_dma_buf_gpu_map_t *existing_map,
NvU64 new_end,
uvm_dma_buf_gpu_map_t **new_map)
{
uvm_dma_buf_gpu_map_t *new;
NV_STATUS status;
NvU64 new_start = new_end + 1;
if (!IS_ALIGNED(new_start, existing_map->pt_range_vec.page_size))
return NV_ERR_INVALID_ADDRESS;
UVM_ASSERT(new_start >= existing_map->node.start && new_start < existing_map->node.end);
new = uvm_kvmalloc_zero(sizeof(*new));
if (!new)
return NV_ERR_NO_MEMORY;
RB_CLEAR_NODE(&new->node.rb_node);
new->gpu = existing_map->gpu;
status = uvm_page_table_range_vec_split_upper(&existing_map->pt_range_vec, new_start - 1, &new->pt_range_vec);
if (status != NV_OK) {
uvm_kvfree(new);
return status;
}
new->node.start = new_start;
uvm_range_tree_split(tree, &existing_map->node, &new->node);
if (new_map)
*new_map = new;
return NV_OK;
}
// See comment in uvm_unmap_external_buf_in_range for description of the
// implementation.
static NV_STATUS uvm_unmap_dma_buf_in_range(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *gpu,
NvU64 start,
NvU64 end)
{
uvm_dma_buf_gpu_range_tree_t *range_tree = uvm_dma_buf_gpu_range_tree(dmabuf_range, gpu);
uvm_dma_buf_gpu_map_t *dmabuf_map, *dmabuf_map_next = NULL;
NV_STATUS status = NV_OK;
uvm_assert_mutex_locked(&range_tree->base.lock);
dmabuf_map = uvm_dma_buf_gpu_map_iter_first(dmabuf_range, gpu, start, end);
while (dmabuf_map) {
if (start > dmabuf_map->node.start) {
status = uvm_dma_buf_gpu_map_split(&range_tree->base.tree, dmabuf_map, start - 1, &dmabuf_map_next);
if (status != NV_OK)
break;
}
else {
if (end < dmabuf_map->node.end) {
status = uvm_dma_buf_gpu_map_split(&range_tree->base.tree, dmabuf_map, end, NULL);
if (status != NV_OK)
break;
dmabuf_map_next = NULL;
}
else {
dmabuf_map_next = uvm_dma_buf_gpu_map_iter_next(dmabuf_range, dmabuf_map, end);
}
uvm_dma_buf_gpu_map_destroy(dmabuf_range, dmabuf_map);
}
dmabuf_map = dmabuf_map_next;
}
return status;
}
static inline void uvm_dma_buf_pte_maker_data_init(uvm_gpu_t *mapping_gpu,
struct sg_table *sg_table,
NvU64 start_offset,
uvm_dma_buf_pte_maker_data_t *out)
{
memset(out, 0, sizeof(*out));
out->mapping_gpu = mapping_gpu;
__sg_page_iter_start(&out->dma_iter.base,
sg_table->sgl,
sg_table->nents,
start_offset >> PAGE_SHIFT);
}
static NvU64 uvm_dma_buf_pte_maker(uvm_page_table_range_vec_t *range_vec,
NvU64 offset,
void *caller_data)
{
uvm_dma_buf_pte_maker_data_t *data = caller_data;
dma_addr_t dma_addr;
NvU64 gpu_addr;
if (!__sg_page_iter_dma_next(&data->dma_iter)) {
UVM_ERR_PRINT("DMA-BUF sg_table exhausted before all PTEs were written, GPU %s, offset %llu\n",
uvm_gpu_name(data->mapping_gpu),
offset);
return 0;
}
dma_addr = uvm_sg_page_iter_dma_address(&data->dma_iter);
gpu_addr = uvm_parent_gpu_dma_addr_to_gpu_addr(data->mapping_gpu->parent, dma_addr);
// TODO: Bug 6172651: Revisit unconditional issue of RWA permissions.
return range_vec->tree->hal->make_pte(UVM_APERTURE_SYS,
gpu_addr,
UVM_PROT_READ_WRITE_ATOMIC,
UVM_MMU_PTE_FLAGS_ACCESS_COUNTERS_DISABLED);
}
// Implementation of the .invalidate_mappings() callback. It is required to
// signal that the memory backing the DMA-BUF is going to move, and so DMA
// mappings must be removed. The following handler is invoked per-GPU prior to
// the exporter revoking accesses to the buffer, and so also signals that
// no new mappings may be established. This callback may be safely issued
// multiple times, though subsequent calls will be no-ops.
static void uvm_dma_buf_invalidate_mappings(struct dma_buf_attachment *attach)
{
uvm_dma_buf_importer_priv_t *priv = attach->importer_priv;
uvm_va_space_t *va_space;
uvm_va_range_dma_buf_t *dmabuf_range;
uvm_dma_buf_gpu_range_tree_t *range_tree;
uvm_dma_buf_gpu_map_t *dmabuf_map, *dmabuf_map_next;
uvm_gpu_t *gpu;
// The exporter calls this callback with the dma_resv lock held.
uvm_record_lock_dma_resv();
dma_resv_assert_held(attach->dmabuf->resv);
UVM_ASSERT(priv);
va_space = priv->va_space;
uvm_va_space_down_read(va_space);
dmabuf_range = uvm_va_range_dma_buf_find(va_space, priv->base);
if (!dmabuf_range || dmabuf_range->dmabuf != attach->dmabuf) {
uvm_va_space_up_read(va_space);
goto invalidate_no_range;
}
// All GMMU mappings will have been destroyed if the GPU is no longer
// registered but had previously been attached.
if (!uvm_processor_mask_test(&va_space->registered_gpus, priv->gpu_id)) {
uvm_va_space_up_read(va_space);
goto invalidate_no_range;
}
gpu = uvm_gpu_get(priv->gpu_id);
UVM_ASSERT(gpu);
range_tree = uvm_dma_buf_gpu_range_tree(dmabuf_range, gpu);
uvm_mutex_lock(&range_tree->base.lock);
UVM_ASSERT(range_tree->attach == attach);
if (uvm_processor_mask_test(&dmabuf_range->mapped_gpus, gpu->id)) {
// Revoke GMMU mappings before calling dma_buf_unmap_attachment(), which is
// required of the .invalidate_mappings() callback.
uvm_dma_buf_gpu_map_for_each_safe(dmabuf_map, dmabuf_map_next, dmabuf_range, gpu)
uvm_dma_buf_gpu_map_destroy(dmabuf_range, dmabuf_map);
uvm_processor_mask_clear_atomic(&dmabuf_range->mapped_gpus, gpu->id);
}
uvm_processor_mask_set_atomic(&dmabuf_range->revoked_gpus, gpu->id);
uvm_mutex_unlock(&range_tree->base.lock);
uvm_va_space_up_read(va_space);
invalidate_no_range:
// Since the invalidate_mappings() callback is exporter-initiated, it may
// be called even after UVM has ceased to track or accept mappings for this
// GPU. In such an instance, the importer is still obligated to call
// dma_buf_unmap_attachment().
if (priv->sg_table) {
dma_buf_unmap_attachment(attach, priv->sg_table, DMA_BIDIRECTIONAL);
priv->sg_table = NULL;
}
uvm_record_unlock_dma_resv();
}
static const struct dma_buf_attach_ops uvm_dma_buf_importer_ops = {
.allow_peer2peer = true,
#if defined(NV_DMA_BUF_ATTACH_OPS_HAS_INVALIDATE_MAPPINGS)
.invalidate_mappings = uvm_dma_buf_invalidate_mappings,
#else
.move_notify = uvm_dma_buf_invalidate_mappings,
#endif
};
static NV_STATUS uvm_map_dma_buf_on_gpu(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *mapping_gpu,
NvU64 base,
NvU64 length,
struct dma_buf_attachment *new_attach,
bool *attachment_consumed)
{
uvm_va_space_t *va_space = dmabuf_range->va_range.va_space;
uvm_dma_buf_gpu_map_t *dmabuf_map = NULL;
uvm_dma_buf_gpu_range_tree_t *range_tree = uvm_dma_buf_gpu_range_tree(dmabuf_range, mapping_gpu);
uvm_gpu_va_space_t *gpu_va_space = uvm_gpu_va_space_get(va_space, mapping_gpu);
uvm_dma_buf_importer_priv_t *attach_priv;
uvm_page_tree_t *page_tree;
uvm_page_table_range_vec_t *pt_range_vec = NULL;
uvm_dma_buf_pte_maker_data_t pte_maker_data;
bool previous_mapping;
NV_STATUS status;
uvm_assert_rwsem_locked_read(&va_space->lock);
uvm_mutex_lock(&range_tree->base.lock);
if (uvm_processor_mask_test(&dmabuf_range->revoked_gpus, mapping_gpu->id)) {
uvm_mutex_unlock(&range_tree->base.lock);
return NV_ERR_INVALID_ADDRESS;
}
if (!range_tree->attach) {
range_tree->attach = new_attach;
*attachment_consumed = true;
}
attach_priv = range_tree->attach->importer_priv;
status = uvm_unmap_dma_buf_in_range(dmabuf_range, mapping_gpu, base, base + length - 1);
if (status != NV_OK)
goto error;
// Create the dma mappings if this is the first mapping on this GPU.
UVM_ASSERT(attach_priv->sg_table || !uvm_processor_mask_test(&dmabuf_range->mapped_gpus, mapping_gpu->id));
if (!attach_priv->sg_table) {
struct sg_table *sg_table;
dma_resv_assert_held(dmabuf_range->dmabuf->resv);
sg_table = dma_buf_map_attachment(range_tree->attach,
DMA_BIDIRECTIONAL);
if (IS_ERR(sg_table)) {
status = (PTR_ERR(sg_table) == -ENOMEM) ? NV_ERR_NO_MEMORY
: NV_ERR_INVALID_DEVICE;
goto error;
}
attach_priv->sg_table = sg_table;
}
// Now create the GMMU mappings for the requested sub-region, after all
// per-GPU initialisation steps have completed.
dmabuf_map = uvm_kvmalloc_zero(sizeof(*dmabuf_map));
if (!dmabuf_map) {
status = NV_ERR_NO_MEMORY;
goto error;
}
dmabuf_map->node.start = base;
dmabuf_map->node.end = base + length - 1;
RB_CLEAR_NODE(&dmabuf_map->node.rb_node);
// Due to the fact that any overlapping mappings were already unmapped,
// adding the new mapping to the tree cannot fail.
status = uvm_range_tree_add(&range_tree->base.tree, &dmabuf_map->node);
UVM_ASSERT(status == NV_OK);
previous_mapping = uvm_processor_mask_test(&dmabuf_range->mapped_gpus, mapping_gpu->id);
uvm_processor_mask_set_atomic(&dmabuf_range->mapped_gpus, mapping_gpu->id);
dmabuf_map->gpu = mapping_gpu;
UVM_ASSERT(uvm_va_range_dma_buf_get_gpu_map_or_null(dmabuf_range, mapping_gpu, base) == dmabuf_map);
UVM_ASSERT(gpu_va_space);
page_tree = &gpu_va_space->page_tables;
pt_range_vec = &dmabuf_map->pt_range_vec;
// TODO: Bug 6164231: Currently mappings are of uniform size as the system
// page size. This is due to the fact that the sg_tables returned from
// dma_buf_map_attachment() are only guaranteed to have system page size
// alignment. To better suit larger DMA-BUF memories, larger page sizes
// should be chosen wherever possible.
status = uvm_page_table_range_vec_init(page_tree,
dmabuf_map->node.start,
uvm_range_tree_node_size(&dmabuf_map->node),
PAGE_SIZE,
UVM_PMM_ALLOC_FLAGS_EVICT,
pt_range_vec);
if (status != NV_OK) {
if (!previous_mapping)
uvm_processor_mask_clear_atomic(&dmabuf_range->mapped_gpus, mapping_gpu->id);
goto error;
}
uvm_dma_buf_pte_maker_data_init(mapping_gpu,
attach_priv->sg_table,
base - dmabuf_range->va_range.node.start,
&pte_maker_data);
status = uvm_page_table_range_vec_write_ptes(pt_range_vec,
UVM_MEMBAR_SYS,
uvm_dma_buf_pte_maker,
(void *) &pte_maker_data);
if (status != NV_OK) {
if (!previous_mapping)
uvm_processor_mask_clear_atomic(&dmabuf_range->mapped_gpus, mapping_gpu->id);
goto error;
}
uvm_mutex_unlock(&range_tree->base.lock);
return NV_OK;
error:
uvm_dma_buf_gpu_map_destroy(dmabuf_range, dmabuf_map);
uvm_mutex_unlock(&range_tree->base.lock);
return status;
}
NV_STATUS uvm_api_map_dma_buf(UVM_MAP_DMA_BUF_PARAMS *params, struct file *filp)
{
uvm_gpu_t *mapping_gpu;
uvm_va_space_t *va_space = uvm_va_space_get(filp);
uvm_va_range_dma_buf_t *dmabuf_range;
struct dma_buf *dmabuf;
struct dma_buf_attachment *attach;
uvm_dma_buf_importer_priv_t *importer_priv;
bool attachment_consumed = false;
NV_STATUS status;
if (uvm_api_range_invalid(params->base, params->length))
return NV_ERR_INVALID_ADDRESS;
// Phase 1: This may be the first attempt to map to this GPU, and so it is
// unknown whether this GPU has an attachment. Acquire the read lock on the
// va_space and retain the GPU and dmabuf as they will be needed to attempt
// to attach the GPU.
uvm_va_space_down_read(va_space);
dmabuf_range = uvm_va_range_dma_buf_find(va_space, params->base);
if (!dmabuf_range ||
dmabuf_range->va_range.node.end < params->base + params->length - 1) {
uvm_va_space_up_read(va_space);
return NV_ERR_INVALID_ADDRESS;
}
mapping_gpu = uvm_va_space_get_gpu_by_uuid_with_gpu_va_space(va_space, &params->gpuUuid);
if (!mapping_gpu) {
uvm_va_space_up_read(va_space);
return NV_ERR_INVALID_DEVICE;
}
if (!uvm_gpu_can_address(mapping_gpu, params->base, params->length)) {
uvm_va_space_up_read(va_space);
return NV_ERR_OUT_OF_RANGE;
}
dmabuf = dmabuf_range->dmabuf;
get_dma_buf(dmabuf);
uvm_gpu_retain(mapping_gpu);
uvm_va_space_up_read(va_space);
// Phase 2: Creating the attachment will require taking the DMA-BUF's
// dma_resv lock. This cannot be safely acquired with the va_space lock
// held, as dma_buf_invalidate_mappings() is called by the DMA-BUF interface
// with the dma_resv lock, and must itself acquire the va_space lock for
// read. Eagerly create an attachment, in case this is the first mapping
// on this GPU.
importer_priv = uvm_kvmalloc(sizeof(*importer_priv));
if (!importer_priv) {
status = NV_ERR_NO_MEMORY;
goto release;
}
importer_priv->gpu_id = mapping_gpu->id;
importer_priv->sg_table = NULL;
importer_priv->va_space = va_space;
importer_priv->base = params->base;
attach = uvm_dma_buf_attach(dmabuf,
&mapping_gpu->parent->pci_dev->dev,
&uvm_dma_buf_importer_ops,
importer_priv);
if (IS_ERR(attach)) {
uvm_kvfree(importer_priv);
status = (PTR_ERR(attach) == -ENOMEM) ? NV_ERR_NO_MEMORY
: NV_ERR_INVALID_DEVICE;
goto release;
}
// Phase 3: Re-acquire the va_space with read and revalidate - if the
// DMA-BUF or GPU is no longer in use in this va_space then remove the
// attachment. Acquire DMA-BUF lock first so that we dma mappings can be
// established if necessary with dma_buf_map_attachment().
uvm_dma_resv_lock(dmabuf->resv);
uvm_va_space_down_read(va_space);
dmabuf_range = uvm_va_range_dma_buf_find(va_space, params->base);
if (!dmabuf_range ||
dmabuf_range->va_range.node.end < params->base + params->length - 1 ||
dmabuf_range->dmabuf != dmabuf) {
status = NV_ERR_INVALID_ADDRESS;
goto unlock_detach;
}
if (!uvm_processor_mask_test(&va_space->registered_gpus, mapping_gpu->id)) {
status = NV_ERR_INVALID_DEVICE;
goto unlock_detach;
}
status = uvm_map_dma_buf_on_gpu(dmabuf_range,
mapping_gpu,
params->base,
params->length,
attach,
&attachment_consumed);
unlock_detach:
uvm_va_space_up_read(va_space);
uvm_dma_resv_unlock(dmabuf->resv);
// On failure, or if otherwise the mapping_gpu was attached by a previous
// mapping, drop the new attachment after releasing all locks. Attachments
// are otherwise freed at UvmFree().
if (!attachment_consumed) {
uvm_dma_buf_detach(dmabuf, attach);
uvm_kvfree(importer_priv);
}
release:
uvm_gpu_release(mapping_gpu);
dma_buf_put(dmabuf);
return status;
}
static void uvm_dma_buf_gpu_map_destroy(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_dma_buf_gpu_map_t *dmabuf_map)
{
NV_STATUS status;
uvm_dma_buf_gpu_range_tree_t *range_tree;
if (!dmabuf_map)
return;
UVM_ASSERT(uvm_gpu_va_space_get(dmabuf_range->va_range.va_space, dmabuf_map->gpu));
range_tree = uvm_dma_buf_gpu_range_tree(dmabuf_range, dmabuf_map->gpu);
// The DMA-BUF map is inserted into the tree prior to the rest of the
// mapping steps. So, if it has not been inserted yet, there is nothing to
// clean up. Just free the memory.
if (RB_EMPTY_NODE(&dmabuf_map->node.rb_node)) {
uvm_kvfree(dmabuf_map);
return;
}
uvm_assert_mutex_locked(&range_tree->base.lock);
uvm_range_tree_remove(&range_tree->base.tree, &dmabuf_map->node);
// Unmap the PTEs
if (dmabuf_map->pt_range_vec.ranges) {
status = uvm_page_table_range_vec_clear_ptes(&dmabuf_map->pt_range_vec, UVM_MEMBAR_SYS);
if (status != NV_OK)
UVM_ERR_PRINT("Clearing PTEs failed: %s, GPU %s\n",
nvstatusToString(status),
uvm_gpu_name(dmabuf_map->gpu));
UVM_ASSERT(status == NV_OK);
uvm_page_table_range_vec_deinit(&dmabuf_map->pt_range_vec);
}
UVM_ASSERT(!dmabuf_map->pt_range_vec.ranges);
uvm_kvfree(dmabuf_map);
}
void uvm_dma_buf_gpu_range_tree_deinit(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *gpu,
struct list_head *deferred_free_list)
{
uvm_dma_buf_gpu_range_tree_t *range_tree = uvm_dma_buf_gpu_range_tree(dmabuf_range, gpu);
uvm_dma_buf_gpu_map_t *dmabuf_map, *dmabuf_map_next;
struct dma_buf_attachment *attach = NULL;
uvm_dma_buf_importer_priv_t *importer_priv;
uvm_dma_buf_attach_deferred_t *deferred;
uvm_mutex_lock(&range_tree->base.lock);
if (uvm_processor_mask_test(&dmabuf_range->mapped_gpus, gpu->id)) {
uvm_dma_buf_gpu_map_for_each_safe(dmabuf_map, dmabuf_map_next, dmabuf_range, gpu)
uvm_dma_buf_gpu_map_destroy(dmabuf_range, dmabuf_map);
uvm_processor_mask_clear_atomic(&dmabuf_range->mapped_gpus, gpu->id);
}
// If this GPU ever received mappings, defer detachment to the DMA-BUF until
// after the va_space lock is released.
if (range_tree->attach) {
attach = range_tree->attach;
range_tree->attach = NULL;
UVM_ASSERT(deferred_free_list);
}
uvm_mutex_unlock(&range_tree->base.lock);
if (!attach)
return;
importer_priv = attach->importer_priv;
deferred = &importer_priv->deferred;
// Defer close of the DMA-BUF until the attachment is torn down following
// release of the va_space lock.
get_dma_buf(dmabuf_range->dmabuf);
deferred->dmabuf = dmabuf_range->dmabuf;
deferred->attach = attach;
deferred->gpu = gpu;
uvm_deferred_free_object_add(deferred_free_list,
&deferred->deferred_free,
UVM_DEFERRED_FREE_OBJECT_TYPE_DMA_BUF_ATTACHMENT);
}
void uvm_va_range_dma_buf_attach_deferred_free(uvm_dma_buf_attach_deferred_t *deferred)
{
uvm_dma_buf_importer_priv_t *importer_priv = deferred->attach->importer_priv;
// Under the dma_resv lock, check that this is the first time that
// dma_buf_unmap_attachment() has been called.
uvm_dma_resv_lock(deferred->dmabuf->resv);
if (importer_priv->sg_table) {
dma_buf_unmap_attachment(deferred->attach, importer_priv->sg_table, DMA_BIDIRECTIONAL);
importer_priv->sg_table = NULL;
}
uvm_dma_resv_unlock(deferred->dmabuf->resv);
uvm_dma_buf_detach(deferred->dmabuf, deferred->attach);
dma_buf_put(deferred->dmabuf);
uvm_kvfree(importer_priv);
}
NV_STATUS uvm_api_unmap_dma_buf(UVM_UNMAP_DMA_BUF_PARAMS *params, struct file *filp)
{
uvm_gpu_t *gpu;
uvm_va_space_t *va_space = uvm_va_space_get(filp);
uvm_va_range_dma_buf_t *dmabuf_range;
uvm_dma_buf_gpu_range_tree_t *range_tree;
NV_STATUS status = NV_OK;
// The DMA-BUF interface provides a sg_table of DMA mapped regions through
// dma_buf_map_attachment(), with base addresses and lengths required to be
// aligned to the system page size. Assert the same here.
if (uvm_api_range_invalid(params->base, params->length))
return NV_ERR_INVALID_ADDRESS;
uvm_va_space_down_read(va_space);
dmabuf_range = uvm_va_range_dma_buf_find(va_space, params->base);
if (!dmabuf_range ||
dmabuf_range->va_range.node.end < params->base + params->length - 1) {
uvm_va_space_up_read(va_space);
return NV_ERR_INVALID_ADDRESS;
}
gpu = uvm_va_space_get_gpu_by_uuid(va_space, &params->gpuUuid);
if (!gpu) {
uvm_va_space_up_read(va_space);
return NV_ERR_INVALID_DEVICE;
}
range_tree = uvm_dma_buf_gpu_range_tree(dmabuf_range, gpu);
uvm_mutex_lock(&range_tree->base.lock);
status = uvm_unmap_dma_buf_in_range(dmabuf_range, gpu, params->base, params->base + params->length - 1);
uvm_mutex_unlock(&range_tree->base.lock);
uvm_va_space_up_read(va_space);
return status;
}
#else
NV_STATUS uvm_api_import_dma_buf(UVM_IMPORT_DMA_BUF_PARAMS *params, struct file *filp)
{
return NV_ERR_NOT_SUPPORTED;
}
NV_STATUS uvm_api_map_dma_buf(UVM_MAP_DMA_BUF_PARAMS *params, struct file *filp)
{
return NV_ERR_NOT_SUPPORTED;
}
NV_STATUS uvm_api_unmap_dma_buf(UVM_UNMAP_DMA_BUF_PARAMS *params, struct file *filp)
{
return NV_ERR_NOT_SUPPORTED;
}
void uvm_dma_buf_gpu_range_tree_deinit(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *gpu,
struct list_head *deferred_free_list) {}
void uvm_va_range_dma_buf_attach_deferred_free(uvm_dma_buf_attach_deferred_t *deferred) {}
#endif // UVM_PROVIDES_DMA_BUF_IMPORTER()
NV_STATUS uvm_api_is_dma_buf_import_supported(UVM_IS_DMA_BUF_IMPORT_SUPPORTED_PARAMS *params, struct file *filp)
{
params->dmaBufImportSupported = UVM_PROVIDES_DMA_BUF_IMPORTER();
return NV_OK;
}
/*
* MODULE_IMPORT_NS() is added by commit id 8651ec01daeda
* ("module: add support for symbol namespaces") in 5.4
*/
#if defined(MODULE_IMPORT_NS)
/*
* DMA_BUF namespace is added by commit id 16b0314aa746
* ("dma-buf: move dma-buf symbols into the DMA_BUF module namespace") in 5.16
*/
#if defined(NV_MODULE_IMPORT_NS_TAKES_CONSTANT)
MODULE_IMPORT_NS(DMA_BUF);
#else
MODULE_IMPORT_NS("DMA_BUF");
#endif // defined(NV_MODULE_IMPORT_NS_TAKES_CONSTANT)
#endif // defined(MODULE_IMPORT_NS)
@@ -0,0 +1,170 @@
/*******************************************************************************
Copyright (c) 2026 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.
*******************************************************************************/
//
// uvm_va_range_dmabuf.h
//
// This file contains the UVM implementation of the DMA-BUF kernel interface.
//
#ifndef __UVM_VA_RANGE_DMABUF_H__
#define __UVM_VA_RANGE_DMABUF_H__
#include "uvm_linux.h"
#include "uvm_forward_decl.h"
#include "uvm_va_range.h"
#include "uvm_tracker.h"
#include "nv_uvm_types.h"
#include "uvm_types.h"
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
#include <linux/dma-buf.h>
#include <linux/device.h>
#endif
static uvm_dma_buf_gpu_range_tree_t *uvm_dma_buf_gpu_range_tree(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *gpu)
{
return &dmabuf_range->gpu_ranges[uvm_id_gpu_index(gpu->id)];
}
// The caller must hold the range tree lock.
static uvm_dma_buf_gpu_map_t *uvm_dma_buf_gpu_map_iter_first(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *gpu,
NvU64 start,
NvU64 end)
{
UVM_ASSERT(start >= dmabuf_range->va_range.node.start);
UVM_ASSERT(end <= dmabuf_range->va_range.node.end);
return uvm_dma_buf_gpu_map_container(
uvm_gpu_range_tree_iter_first(&uvm_dma_buf_gpu_range_tree(dmabuf_range, gpu)->base, start, end));
}
// The caller must hold the range tree lock.
static uvm_dma_buf_gpu_map_t *uvm_dma_buf_gpu_map_iter_next(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_dma_buf_gpu_map_t *dmabuf_gpu_map,
NvU64 end)
{
if (!dmabuf_gpu_map)
return NULL;
UVM_ASSERT(end <= dmabuf_range->va_range.node.end);
return uvm_dma_buf_gpu_map_container(
uvm_gpu_range_tree_iter_next(&uvm_dma_buf_gpu_range_tree(dmabuf_range, dmabuf_gpu_map->gpu)->base,
dmabuf_gpu_map, end));
}
// The four iterators below require that the caller hold the gpu's range tree
// lock.
#define uvm_dma_buf_gpu_map_for_each_in(dmabuf_gpu_map, dmabuf_range, gpu, start, end) \
uvm_gpu_map_for_each_in(dmabuf_gpu_map, dmabuf_range, gpu, start, end, \
uvm_dma_buf_gpu_map_iter_first, uvm_dma_buf_gpu_map_iter_next)
#define uvm_dma_buf_gpu_map_for_each_in_safe(dmabuf_gpu_map, dmabuf_gpu_map_next, dmabuf_range, gpu, start, end) \
uvm_gpu_map_for_each_in_safe(dmabuf_gpu_map, dmabuf_gpu_map_next, dmabuf_range, gpu, start, end, \
uvm_dma_buf_gpu_map_iter_first, uvm_dma_buf_gpu_map_iter_next)
#define uvm_dma_buf_gpu_map_for_each(dmabuf_gpu_map, dmabuf_range, gpu) \
uvm_dma_buf_gpu_map_for_each_in(dmabuf_gpu_map, dmabuf_range, gpu, \
(dmabuf_range)->va_range.node.start, \
(dmabuf_range)->va_range.node.end)
#define uvm_dma_buf_gpu_map_for_each_safe(dmabuf_gpu_map, dmabuf_gpu_map_next, dmabuf_range, gpu) \
uvm_dma_buf_gpu_map_for_each_in_safe(dmabuf_gpu_map, dmabuf_gpu_map_next, dmabuf_range, gpu, \
(dmabuf_range)->va_range.node.start, \
(dmabuf_range)->va_range.node.end)
// The release of the attachment must occur after the va_space lock has been
// released at tear-down. This deferred_free object holds the retained gpu as
// well as a reference to the dmabuf which will be put on free.
typedef struct
{
struct dma_buf *dmabuf;
struct dma_buf_attachment *attach;
uvm_gpu_t *gpu;
uvm_deferred_free_object_t deferred_free;
} uvm_dma_buf_attach_deferred_t;
// Per attachment (per-GPU, per DMA-BUF import) state. Permits late unmap of
// DMA-BUF mappings. Available for use through deferred-free through the
// attachment.
typedef struct
{
// Reference to the attachment GPU, which is retained until the attachment
// is destroyed.
uvm_gpu_id_t gpu_id;
// Reference to the dma mappings retrieved through dma_buf_map_attachment(),
// To be revoked either at deferred-free or through the importer
// .invalidate_mappings callback in keeping with the DMA-BUF API.
// If this pointer is observed to not be NULL under the dma_resv lock, it
// can be revoked.
struct sg_table *sg_table;
// The va_space and base address of the first mapping are sufficient to
// identify the DMA-BUF range under the va_space lock. The importer's
// attachments to the DMA-BUF can outlive its va_range, in which case the
// sg_table will be torn down directly, as no GMMU mappings will exist.
uvm_va_space_t *va_space;
NvU64 base;
uvm_dma_buf_attach_deferred_t deferred;
} uvm_dma_buf_importer_priv_t;
// Destroy all GPU mapping associated with the VA range. Defer destruction of
// DMA mappings through call to dma_buf_unmap_attachment(), until after the
// va_space lock is dropped.
// LOCKING: The va_space lock must be held in at least read mode.
void uvm_dma_buf_gpu_range_tree_deinit(uvm_va_range_dma_buf_t *dmabuf_range,
uvm_gpu_t *gpu,
struct list_head *deferred_free_list);
// Run a deferred dma_buf_detach() and free the wrapper. Invoked by the
// deferred-free dispatcher in uvm_va_space.c.
void uvm_va_range_dma_buf_attach_deferred_free(uvm_dma_buf_attach_deferred_t *deferred);
#if UVM_PROVIDES_DMA_BUF_IMPORTER()
static inline struct dma_buf_attachment *uvm_dma_buf_attach(struct dma_buf *dmabuf,
struct device *dev,
const struct dma_buf_attach_ops *ops,
void *importer_priv)
{
struct dma_buf_attachment *ret;
uvm_record_lock_dma_resv();
ret = dma_buf_dynamic_attach(dmabuf, dev, ops, importer_priv);
uvm_record_unlock_dma_resv();
return ret;
}
static inline void uvm_dma_buf_detach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attach)
{
uvm_record_lock_dma_resv();
dma_buf_detach(dmabuf, attach);
uvm_record_unlock_dma_resv();
}
#endif // UVM_PROVIDES_DMA_BUF_IMPORTER()
#endif // __UVM_VA_RANGE_DMABUF_H__
+78 -12
View File
@@ -33,6 +33,7 @@
#include "uvm_thread_context.h"
#include "uvm_hal.h"
#include "uvm_map_external.h"
#include "uvm_va_range_dmabuf.h"
#include "uvm_ats.h"
#include "uvm_gpu_replayable_faults.h"
#include "uvm_gpu_non_replayable_faults.h"
@@ -46,12 +47,10 @@
#include "uvm_hmm.h"
#include <linux/mmzone.h>
static bool uvm_disable_sam_migration = false;
static bool uvm_disable_sam_migration = true;
MODULE_PARM_DESC(uvm_disable_sam_migration,
"Disable migration of system allocated memory for CDMM/HMM "
"Default: false (migration is enabled if possible). "
"However, even with uvm_disable_sam_migration=false, migation "
"will not be enabled if is not supported in this driver build ");
"Default: true (migration is disabled). ");
module_param(uvm_disable_sam_migration, bool, 0444);
static bool processor_mask_array_test(const uvm_processor_mask_t *mask,
@@ -364,13 +363,14 @@ static void va_space_parent_gpu_unregister(uvm_va_space_t *va_space, uvm_parent_
if (!uvm_va_space_single_gpu_in_parent(va_space, parent))
return;
if (parent->egm.enabled && parent->closest_cpu_numa_node != NUMA_NO_NODE) {
if (uvm_parent_gpu_egm_enabled(parent) && parent->closest_cpu_numa_node != NUMA_NO_NODE) {
uvm_egm_numa_node_info_t *node_info = uvm_va_space_get_egm_numa_node_info(va_space,
parent->closest_cpu_numa_node);
uvm_parent_processor_mask_clear(&node_info->parent_gpus, parent->id);
// Clear local EGM routing
node_info->routing_table[uvm_parent_id_gpu_index(parent->id)] = NULL;
if (uvm_parent_gpu_egm_is_legacy(parent))
node_info->routing_table[uvm_parent_id_gpu_index(parent->id)] = NULL;
}
if (uvm_parent_processor_mask_test(&va_space->test.non_replayable_delay_set, parent->id)) {
@@ -752,8 +752,7 @@ uvm_gpu_t *uvm_va_space_get_gpu_by_mem_info(uvm_va_space_t *va_space, const UvmG
uvm_gpu_t *gpu;
uvm_assert_rwsem_locked(&va_space->lock);
for_each_va_space_gpu(gpu, va_space) {
for_each_va_space_gpu (gpu, va_space) {
if (uvm_uuid_eq(&gpu->uuid, &mem_info->uuid) ||
(gpu->parent->smc.enabled && uvm_uuid_eq(&gpu->parent->uuid, &mem_info->uuid)))
return gpu;
@@ -766,8 +765,16 @@ static void va_space_parent_gpu_register(uvm_va_space_t *va_space, uvm_parent_gp
{
uvm_egm_numa_node_info_t *node_info;
// If EGM is enabled and is not "legacy" EGM, we don't exit early.
// The GPU needs to be set in the parents_gpus processor mask
// since it's connected to the local node. Even if EGM is not
// enabled on the GPU, it could access memory on remote nodes
// through GPUs that are EGM-enabled.
// Being in the parent_gpus mask is required in order to
// successfully find the correct routing table if the GPU is
// not EGM-enabled.
if (!uvm_va_space_single_gpu_in_parent(va_space, parent) ||
!parent->egm.enabled ||
(uvm_parent_gpu_egm_is_legacy(parent) && !uvm_parent_gpu_egm_enabled(parent)) ||
parent->closest_cpu_numa_node == -1)
return;
@@ -784,7 +791,8 @@ static void va_space_parent_gpu_register(uvm_va_space_t *va_space, uvm_parent_gp
// This is done here because local EGM routing does need not any peers.
// So, if there are no peers to this GPU, local EGM accesses should
// still be possible.
if (parent->egm.enabled)
// Local EGM accesses are only done on "legacy" EGM.
if (uvm_parent_gpu_egm_enabled(parent) && uvm_parent_gpu_egm_is_legacy(parent))
node_info->routing_table[uvm_parent_id_gpu_index(parent->id)] = parent;
}
@@ -842,8 +850,21 @@ NV_STATUS uvm_va_space_register_gpu(uvm_va_space_t *va_space,
va_space->pageable.cdmm_enabled = g_uvm_global.cdmm_enabled;
}
// HACK: Kernels with the bug UVM_CAN_USE_DEVICE_PRIVATE_MEMREMAP_PAGES
// tests for requries us to disable migration on non-coherent GPUs.
// Ideally, we'd reject registering those GPUs on a VA space with
// migration enabled. Unfortunately, doing this would require a big
// rework our test suite.
//
// Fixing the tests is fairly involved, so the hackfix we're doing here
// is unconditionally disabling migration if there are any non-ATS
// (i.e. non-coherent) devices in the system. This still allows for
// migration on systems that have only coherent devices.
//
// TODO: Bug 5969467
if (va_space->pageable.cdmm_enabled &&
va_space->pageable.migrations_enabled &&
g_uvm_global.have_non_ats_devices &&
!UVM_CAN_USE_DEVICE_PRIVATE_MEMREMAP_PAGES()) {
va_space->pageable.migrations_enabled = NV_FALSE;
}
@@ -1154,8 +1175,33 @@ static void disable_egm_peers(uvm_va_space_t *va_space, uvm_gpu_t *gpu0, uvm_gpu
uvm_parent_processor_mask_t proc_mask;
uvm_parent_gpu_id_t peer_parent_id;
if (!uvm_parent_gpu_egm_is_legacy(gpu0->parent)) {
uvm_va_range_managed_t *va_range;
uvm_for_each_va_range_managed (va_range, va_space) {
uvm_va_block_t *va_block;
for_each_va_block_in_va_range (va_range, va_block)
uvm_va_block_unmap_egm(va_block, gpu0->parent);
}
}
uvm_parent_processor_mask_copy(&proc_mask, &node_info->parent_gpus);
uvm_parent_processor_mask_clear(&proc_mask, gpu0->parent->id);
// EGM requires creating mappings on routing GPUs. This requires
// quiescing all access to remote EGM memory. In turn, this make
// re-assigning new remote GPU as the routing GPU racy.
// "Legacy" EGM, on the other hand, does not need any remapping,
// re-routing is only based on simple address calculation.
// So, for non-legacy EGM, we just remove the routing GPU, even
// though there could be other GPUs connected to the NUMA node.
// TODO: Bug 6196507: Re-assign routing GPU to the next GPU in
// the mask.
if (!uvm_parent_gpu_egm_is_legacy(gpu0->parent)) {
node_info->routing_table[uvm_parent_id_gpu_index(gpu1->parent->id)] = NULL;
continue;
}
peer_parent_id = uvm_parent_processor_mask_find_first_gpu_id(&proc_mask);
if (!UVM_PARENT_ID_IS_VALID(peer_parent_id)) {
node_info->routing_table[uvm_parent_id_gpu_index(gpu1->parent->id)] = NULL;
@@ -1210,7 +1256,7 @@ static void disable_peers(uvm_va_space_t *va_space,
static void enable_egm_peers(uvm_va_space_t *va_space, uvm_gpu_t *gpu0, uvm_gpu_t *gpu1)
{
if (gpu0->parent->egm.enabled) {
if (uvm_parent_gpu_egm_enabled(gpu0->parent)) {
uvm_egm_numa_node_info_t *node_info;
int nid;
@@ -1220,8 +1266,24 @@ static void enable_egm_peers(uvm_va_space_t *va_space, uvm_gpu_t *gpu0, uvm_gpu_
// same NUMA node. Otherwise, we want accesses from it to this CPU NUMA
// node to use gpu1's local EGM accesses.
if (!node_info->routing_table[uvm_parent_id_gpu_index(gpu1->parent->id)] &&
!uvm_parent_processor_mask_test(&node_info->parent_gpus, gpu1->parent->id))
gpu0->parent->closest_cpu_numa_node != gpu1->parent->closest_cpu_numa_node) {
node_info->routing_table[uvm_parent_id_gpu_index(gpu1->parent->id)] = gpu0->parent;
if (!uvm_parent_gpu_egm_is_legacy(gpu0->parent)) {
uvm_va_range_managed_t *va_range;
uvm_for_each_va_range_managed (va_range, va_space) {
uvm_va_block_t *va_block;
for_each_va_block_in_va_range (va_range, va_block) {
// We can ignore the status of uvm_va_block_map_egm().
// It it succeeded, accesses will be over gLinks. If
// not, they will take the cLink path. Either way
// there is no reason to fail the peer setup.
uvm_va_block_map_egm(va_block, gpu0->parent);
}
}
}
}
}
}
}
@@ -2030,6 +2092,10 @@ void uvm_deferred_free_object_list(struct list_head *deferred_free_list)
case UVM_DEFERRED_FREE_OBJECT_TYPE_DEVICE_P2P_MEM:
uvm_va_range_free_device_p2p_mem(container_of(object, uvm_device_p2p_mem_t, deferred_free));
break;
case UVM_DEFERRED_FREE_OBJECT_TYPE_DMA_BUF_ATTACHMENT:
uvm_va_range_dma_buf_attach_deferred_free(
container_of(object, uvm_dma_buf_attach_deferred_t, deferred_free));
break;
default:
UVM_ASSERT_MSG(0, "Invalid type %d\n", object->type);
}
+3 -3
View File
@@ -1,5 +1,5 @@
/*******************************************************************************
Copyright (c) 2015-2025 NVIDIA Corporation
Copyright (c) 2015-2026 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
@@ -56,6 +56,7 @@ typedef enum
UVM_DEFERRED_FREE_OBJECT_GPU_VA_SPACE,
UVM_DEFERRED_FREE_OBJECT_TYPE_EXTERNAL_ALLOCATION,
UVM_DEFERRED_FREE_OBJECT_TYPE_DEVICE_P2P_MEM,
UVM_DEFERRED_FREE_OBJECT_TYPE_DMA_BUF_ATTACHMENT,
UVM_DEFERRED_FREE_OBJECT_TYPE_COUNT
} uvm_deferred_free_object_type_t;
@@ -881,8 +882,7 @@ NV_STATUS uvm_test_va_space_allow_movable_allocations(UVM_TEST_VA_SPACE_ALLOW_MO
// VM_FAULT_NOPAGE: if page was faulted in OK
// (possibly or'ed with VM_FAULT_MAJOR if a migration was needed).
// VM_FAULT_OOM: if system memory wasn't available.
// VM_FAULT_SIGBUS: if a CPU mapping to fault_addr cannot be accessed,
// for example because it's within a range group which is non-migratable.
// VM_FAULT_SIGBUS: if a CPU mapping to fault_addr cannot be accessed.
vm_fault_t uvm_va_space_cpu_fault_managed(uvm_va_space_t *va_space, struct vm_fault *vmf);
// Handle a CPU fault in the given VA space for a HMM allocation,
+34 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2017-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2017-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -105,6 +105,35 @@ typedef struct
NvBool is_compatible;
} NVSWITCH_CHECK_VERSION_PARAMS;
/*
* NVSWITCH_CTL_CHECK_VERSION_V2
*
* V2 version check with explicit mode selection, modeled after RM's
* nv_ioctl_rm_api_version_t (NV_RM_API_VERSION_CMD_STRICT/RELAXED/QUERY).
*
* Parameters:
* cmd[in]
* Version check mode: STRICT, RELAXED, or QUERY.
* user[in]
* Version of the interface that the client is compiled with.
* Ignored when cmd is QUERY.
* kernel[out]
* Version of the interface that the kernel driver is compiled with.
* is_compatible[out]
* Result of version comparison. Always 0 for QUERY mode.
*/
#define NVSWITCH_CHECK_VERSION_CMD_STRICT 0
#define NVSWITCH_CHECK_VERSION_CMD_RELAXED 1
#define NVSWITCH_CHECK_VERSION_CMD_QUERY 2
typedef struct
{
NvU32 cmd;
NVSWITCH_VERSION user;
NVSWITCH_VERSION kernel;
NvBool is_compatible;
} NVSWITCH_CHECK_VERSION_V2_PARAMS;
/*
* Max devices supported by the driver
*
@@ -214,6 +243,7 @@ typedef struct
#define CTRL_NVSWITCH_CHECK_VERSION 0x02
#define CTRL_NVSWITCH_GET_DEVICES_V2 0x03
#define CTRL_NVSWITCH_GET_DEVICE_NODES 0x04
#define CTRL_NVSWITCH_CHECK_VERSION_V2 0x05
/*
* Nvswitchctl (device agnostic) IOCTLs
@@ -231,6 +261,9 @@ typedef struct
#define IOCTL_NVSWITCH_GET_DEVICE_NODES \
NVSWITCH_IOCTL_CODE(NVSWITCH_CTL_IO_TYPE, CTRL_NVSWITCH_GET_DEVICE_NODES, NVSWITCH_GET_DEVICE_NODES_PARAMS, \
NVSWITCH_IO_READ_ONLY)
#define IOCTL_NVSWITCH_CHECK_VERSION_V2 \
NVSWITCH_IOCTL_CODE(NVSWITCH_CTL_IO_TYPE, CTRL_NVSWITCH_CHECK_VERSION_V2, NVSWITCH_CHECK_VERSION_V2_PARAMS, \
NVSWITCH_IO_WRITE_READ)
#ifdef __cplusplus
}
+1 -1
View File
@@ -301,7 +301,7 @@ static bool lkca_ecdsa_verify_akcipher(void *ec_context, size_t hash_nid,
#else // USE_LKCA
struct ecc_ctx *ctx = ec_context;
u8 *pub_key;
int err;
int err = -EINVAL;
DECLARE_CRYPTO_WAIT(wait);
// Roundabout way
+90 -12
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2016-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2016-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -167,14 +167,15 @@ MODULE_PARM_DESC(NvSwitchBlacklist, "NvSwitchBlacklist=uuid[,uuid...]");
// .ioctl, .poll and other background tasks.
//
// The kernel guarantees that .close won't happen while .ioctl and .poll
// are going on and without successful .open one can't execute any file ops.
// This behavior guarantees correctness of the locking model.
// are going on for the same file. It does not serialize .close against
// operations on other fds for the same device, so .close must take
// nvswitch_dev.device_mutex before touching per-device state that is also
// accessed by those paths.
//
// If .close is invoked and holding the lock which is also used by threaded
// tasks such as interrupt, driver will deadlock while trying to stop such
// tasks. For example, when threaded interrupts are enabled, free_irq() calls
// kthread_stop() to flush pending interrupt tasks. The locking model
// makes sure that such deadlock cases don't happen.
// Do not hold nvswitch_dev.device_mutex across operations that stop
// threaded tasks such as interrupt. For example, when threaded interrupts
// are enabled, free_irq() calls kthread_stop() to flush pending interrupt
// tasks. The locking model makes sure that such deadlock cases don't happen.
//
// Lock ordering:
// nvswitch.driver_mutex
@@ -259,7 +260,7 @@ struct file_operations ctl_fops =
static int nvswitch_initialize_device_interrupt(NVSWITCH_DEV *nvswitch_dev);
static void nvswitch_shutdown_device_interrupt(NVSWITCH_DEV *nvswitch_dev);
static void nvswitch_load_bar_info(NVSWITCH_DEV *nvswitch_dev);
static void nvswitch_task_dispatch(NVSWITCH_DEV *nvswitch_dev);
static void nvswitch_task_dispatch(void *nvswitch_dev);
static NvBool
nvswitch_is_device_blacklisted
@@ -313,7 +314,7 @@ nvswitch_init_background_tasks
NV_ATOMIC_SET(nvswitch_dev->task_q_ready, 1);
nv_kthread_q_item_init(&nvswitch_dev->task_item,
(nv_q_func_t) &nvswitch_task_dispatch,
&nvswitch_task_dispatch,
nvswitch_dev);
if (!nv_kthread_q_schedule_q_item(&nvswitch_dev->task_q,
@@ -955,6 +956,75 @@ nvswitch_ctl_check_version(NVSWITCH_CHECK_VERSION_PARAMS *p)
return 0;
}
static int
nvswitch_ctl_check_version_v2(NVSWITCH_CHECK_VERSION_V2_PARAMS *p)
{
NvlStatus retval;
NvU32 i;
NvU32 u_major_len = 0, k_major_len = 0;
p->is_compatible = 0;
p->user.version[NVSWITCH_VERSION_STRING_LENGTH - 1] = '\0';
if (p->cmd != NVSWITCH_CHECK_VERSION_CMD_STRICT &&
p->cmd != NVSWITCH_CHECK_VERSION_CMD_RELAXED &&
p->cmd != NVSWITCH_CHECK_VERSION_CMD_QUERY)
{
return -EINVAL;
}
retval = nvswitch_lib_check_api_version(p->user.version, p->kernel.version,
NVSWITCH_VERSION_STRING_LENGTH);
if (p->cmd == NVSWITCH_CHECK_VERSION_CMD_QUERY)
{
return 0;
}
if (retval == NVL_SUCCESS)
{
p->is_compatible = 1;
return 0;
}
if (retval != -NVL_ERR_NOT_SUPPORTED)
{
return nvswitch_map_status(retval);
}
if (p->cmd == NVSWITCH_CHECK_VERSION_CMD_RELAXED)
{
for (i = 0; i < NVSWITCH_VERSION_STRING_LENGTH &&
p->user.version[i] != '\0' && p->user.version[i] != '.'; i++)
{
u_major_len++;
}
for (i = 0; i < NVSWITCH_VERSION_STRING_LENGTH &&
p->kernel.version[i] != '\0' && p->kernel.version[i] != '.'; i++)
{
k_major_len++;
}
if (u_major_len == k_major_len &&
strncmp(p->user.version, p->kernel.version, u_major_len) == 0)
{
p->is_compatible = 1;
printk(KERN_DEBUG "nvidia-nvswitch: Minor version mismatch "
"tolerated (process: %s, pid: %d), kernel %s user %s\n",
current->comm, task_pid_nr(current),
p->kernel.version, p->user.version);
return 0;
}
}
printk(KERN_ERR "nvidia-nvswitch: Version mismatch (process: %s, pid: %d), "
"kernel version %s user version %s\n",
current->comm, task_pid_nr(current),
p->kernel.version, p->user.version);
return 0;
}
static void
nvswitch_ctl_get_devices(NVSWITCH_GET_DEVICES_PARAMS *p)
{
@@ -1053,6 +1123,14 @@ nvswitch_ctl_cmd_dispatch
nvswitch_ctl_get_devices_v2(params);
}
break;
case CTRL_NVSWITCH_CHECK_VERSION_V2:
rc = NVSWITCH_CTL_CHECK_PARAMS(NVSWITCH_CHECK_VERSION_V2_PARAMS,
param_size);
if (!rc)
{
rc = nvswitch_ctl_check_version_v2(params);
}
break;
default:
rc = -EINVAL;
@@ -1208,9 +1286,10 @@ nvswitch_isr_thread
static void
nvswitch_task_dispatch
(
NVSWITCH_DEV *nvswitch_dev
void *_nvswitch_dev
)
{
NVSWITCH_DEV *nvswitch_dev = _nvswitch_dev;
NvU64 nsec;
NvU64 timeout;
NvS64 rc;
@@ -2609,7 +2688,6 @@ nvswitch_os_is_fabric_manager
{
nvswitch_file_private_t *private_data = (nvswitch_file_private_t *)osPrivate;
/* Make sure that fabric mgmt capbaility fd is valid */
if ((private_data == NULL) ||
(private_data->capability_fds.fabric_mgmt < 0))
{
+3 -3
View File
@@ -66,11 +66,11 @@ static nv_cap_table_entry_t g_nv_cap_mig_table[] =
static nv_cap_table_entry_t g_nv_cap_sys_table[] =
{
{"/driver/nvidia/capabilities/fabric-imex-mgmt"}
,
{"/driver/nvidia/capabilities/fabric-imex-mgmt"},
{"/driver/nvidia/capabilities/profiler-device"},
{"/driver/nvidia/capabilities/profiler-context"},
{"/driver/nvidia/capabilities/trace-device"}
{"/driver/nvidia/capabilities/trace-device"},
{"/driver/nvidia/capabilities/wpps-access"},
};
#define NV_CAP_MIG_CI_ENTRIES(_gi) \
+239
View File
@@ -26,6 +26,7 @@
#include "os-interface.h"
#include "nv-linux.h"
#include "nv-reg.h"
#include <linux/iommu.h>
#if IS_ENABLED(CONFIG_DRM)
#include <drm/drm_device.h>
@@ -156,6 +157,10 @@ NV_STATUS nv_create_dma_map_scatterlist(nv_dma_map_t *dma_map)
NV_STATUS status;
nv_dma_submap_t *submap;
NvU32 i;
#if defined(NV_SG_ALLOC_TABLE_FROM_PAGES_SEGMENT_PRESENT)
dma_map->mapping.discontig.submap_count = 1;
#else
NvU64 allocated_size = 0;
NvU64 num_submaps = dma_map->page_count + NV_DMA_SUBMAP_MAX_PAGES - 1;
NvU64 total_size = dma_map->page_count << PAGE_SHIFT;
@@ -175,6 +180,7 @@ NV_STATUS nv_create_dma_map_scatterlist(nv_dma_map_t *dma_map)
}
dma_map->mapping.discontig.submap_count = NvU64_LO32(num_submaps);
#endif
status = os_alloc_mem((void **)&dma_map->mapping.discontig.submaps,
sizeof(nv_dma_submap_t) * dma_map->mapping.discontig.submap_count);
@@ -198,10 +204,14 @@ NV_STATUS nv_create_dma_map_scatterlist(nv_dma_map_t *dma_map)
NV_FOR_EACH_DMA_SUBMAP(dma_map, submap, i)
{
#if defined(NV_SG_ALLOC_TABLE_FROM_PAGES_SEGMENT_PRESENT)
submap->page_count = (NvU32)dma_map->page_count;
#else
NvU64 submap_size = NV_MIN(NV_DMA_SUBMAP_MAX_PAGES << PAGE_SHIFT,
total_size - allocated_size);
submap->page_count = (NvU32)(submap_size >> PAGE_SHIFT);
#endif
status = NV_ALLOC_DMA_SUBMAP_SCATTERLIST(dma_map, submap, i);
if (status != NV_OK)
@@ -218,10 +228,14 @@ NV_STATUS nv_create_dma_map_scatterlist(nv_dma_map_t *dma_map)
}
#endif
#if !(defined(NV_SG_ALLOC_TABLE_FROM_PAGES_SEGMENT_PRESENT))
allocated_size += submap_size;
#endif
}
#if !(defined(NV_SG_ALLOC_TABLE_FROM_PAGES_SEGMENT_PRESENT))
WARN_ON(allocated_size != total_size);
#endif
if (status != NV_OK)
{
@@ -1034,3 +1048,228 @@ void NV_API_CALL nv_dma_release_sgt
{
}
#endif /* IS_ENABLED(CONFIG_DRM) */
static NvBool nv_is_dma_domain
(
nv_state_t *nv
)
{
nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
struct iommu_domain *domain = iommu_get_domain_for_dev(nvl->dev);
if (domain == NULL)
{
return NV_FALSE;
}
#if defined(NV_IOMMU_IS_DMA_DOMAIN_PRESENT)
return iommu_is_dma_domain(domain);
#else
return (domain->type & __IOMMU_DOMAIN_DMA_API) != 0;
#endif
}
static NV_STATUS _nv_dma_get_sysmem_range
(
nv_state_t *nv,
NvU64 *phys_base,
NvU64 *size
)
{
NvS32 node_id = nv->cpu_numa_node_id;
NvU64 start_pfn = NV_U64_MAX;
NvU64 end_pfn = 0ULL;
struct zone *zone;
struct pglist_data *pgdat;
NvU32 zone_id;
if (node_id < 0 || !node_online(node_id))
{
return NV_ERR_NOT_SUPPORTED;
}
//
// UVM uses ZONE_DEVICE for coherent device pages which adds into the
// available sysmem and the inflated size is included in sysmem accounting
// if node_start_pfn/node_end_pfn are used directly.
// Instead, compute the sysmem range from managed, non-ZONE_DEVICE zones
// so device PFNs don't inflate the result.
//
pgdat = NODE_DATA(node_id);
for (zone_id = 0; zone_id < MAX_NR_ZONES; zone_id++)
{
#ifdef CONFIG_ZONE_DEVICE
if (zone_id == ZONE_DEVICE)
continue;
#endif
zone = &(pgdat->node_zones[zone_id]);
if (!managed_zone(zone))
continue;
start_pfn = min(start_pfn, (NvU64)zone->zone_start_pfn);
end_pfn = max(end_pfn, (NvU64)zone_end_pfn(zone));
}
if (start_pfn >= end_pfn)
{
return NV_ERR_NOT_SUPPORTED;
}
*phys_base = start_pfn << PAGE_SHIFT;
*size = (end_pfn - start_pfn) << PAGE_SHIFT;
return NV_OK;
}
static NV_STATUS nv_dma_map_sysmem_dynamic
(
nv_state_t *nv,
NvU64 phys_size,
NvU64 *dma_addr,
NvU64 *dma_size
)
{
#if NV_IS_EXPORT_SYMBOL_GPL_dma_iova_try_alloc
nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
struct dma_iova_state state;
if (!dma_iova_try_alloc(nvl->dev, &state, 0, phys_size))
{
NV_DEV_PRINTF(NV_DBG_ERRORS, nv,
"Failed to allocate IOVA region for sysmem size 0x%llx\n",
phys_size);
return NV_ERR_OPERATING_SYSTEM;
}
*dma_addr = (NvU64)state.addr;
*dma_size = (NvU64)dma_iova_size(&state);
NV_DEV_PRINTF(NV_DBG_INFO, nv,
"Sysmem mapped in DMA domain, IOVA base: 0x%llx size: 0x%llx\n",
*dma_addr, *dma_size);
return NV_OK;
#else
return NV_ERR_NOT_SUPPORTED;
#endif // NV_IS_EXPORT_SYMBOL_GPL_dma_iova_try_alloc
}
static NV_STATUS nv_dma_map_sysmem_identity
(
nv_state_t *nv,
NvU64 phys_base,
NvU64 phys_size,
NvU64 *dma_addr,
NvU64 *dma_size
)
{
nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
dma_addr_t addr;
addr = dma_map_single(nvl->dev, phys_to_virt(phys_base),
phys_size, DMA_BIDIRECTIONAL);
if (dma_mapping_error(nvl->dev, addr))
{
NV_DEV_PRINTF(NV_DBG_ERRORS, nv,
"Local CPU NUMA sysmem mapping failed for phys: 0x%llx size: 0x%llx\n",
phys_base, phys_size);
return NV_ERR_OPERATING_SYSTEM;
}
*dma_addr = (NvU64)addr;
*dma_size = phys_size;
NV_DEV_PRINTF(NV_DBG_INFO, nv,
"Local CPU NUMA sysmem mapped in identity/off mode, addr: 0x%llx size: 0x%llx\n",
*dma_addr, *dma_size);
return NV_OK;
}
NV_STATUS NV_API_CALL nv_dma_init_sysmem_window_for_fabric_access
(
nv_state_t *nv,
NvU64 alignment,
NvU64 *dma_addr,
NvU64 *dma_size,
NvBool *dma_identity
)
{
NvU64 sysmem_base, sysmem_size;
NV_STATUS status;
if ((dma_addr == NULL) ||
(dma_size == NULL) ||
(dma_identity == NULL) ||
(alignment == 0))
{
return NV_ERR_INVALID_ARGUMENT;
}
status = _nv_dma_get_sysmem_range(nv, &sysmem_base, &sysmem_size);
if (status != NV_OK)
{
return status;
}
else if (sysmem_size == 0)
{
return NV_ERR_INVALID_STATE;
}
NV_DEV_PRINTF(NV_DBG_INFO, nv,
"Local CPU NUMA sysmem phys base: 0x%llx size: 0x%llx\n",
sysmem_base, sysmem_size);
if (nv_is_dma_domain(nv))
{
status = nv_dma_map_sysmem_dynamic(nv, NV_ALIGN_UP64(sysmem_size, alignment),
dma_addr, dma_size);
*dma_identity = NV_FALSE;
}
else
{
status = nv_dma_map_sysmem_identity(nv, sysmem_base, sysmem_size,
dma_addr, dma_size);
*dma_identity = NV_TRUE;
}
return status;
}
void NV_API_CALL nv_dma_destroy_sysmem_window_for_fabric_access
(
nv_state_t *nv,
NvU64 dma_addr,
NvU64 dma_size
)
{
nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
struct device *dev = nvl->dev;
if (!nv_is_dma_domain(nv))
{
NV_DEV_PRINTF(NV_DBG_INFO, nv,
"Unmapping local CPU NUMA sysmem DMA addr: 0x%llx size: 0x%llx\n",
dma_addr, dma_size);
dma_unmap_single(dev, (dma_addr_t)dma_addr,
(size_t)dma_size, DMA_BIDIRECTIONAL);
}
else
{
#if NV_IS_EXPORT_SYMBOL_GPL_dma_iova_try_alloc
struct dma_iova_state state;
state.addr = (dma_addr_t)dma_addr;
state.__size = (u64)dma_size;
NV_DEV_PRINTF(NV_DBG_INFO, nv,
"Freeing local NUMA sysmem IOVA range addr: 0x%llx size: 0x%llx\n",
dma_addr, dma_size);
dma_iova_free(dev, &state);
#endif
}
}

Some files were not shown because too many files have changed in this diff Show More